SpringMVC项目实践的逻辑层次
完整的以Spring框架搭建的WEB程序中,通常有4个层次,分别对应不同的功能。
这4个层次从底(数据库层次)到顶(前端显示层次)分别是:
- pojo(数据库实体层)
- dao(数据持久层)
- service (业务逻辑层)
- controller (控制层)
是不是感觉很复杂? 其实一点也不复杂,接下来我来一个一个层次来讲。
我们举一个银行业务的例子,包括存款,取款,汇款的业务功能。
每个层次下面我会附上代码,就很简单易懂啦。
1. pojo层
pojo层也叫做model层,entity层,是数据库实体层,数据库实体层是什么意思呢?
在我自己做的demo里面,因为是银行业务,所以我们在pojo层里面,有两个类,分别是Account类,User类。
下图Account类很简单的告诉你:
package com.example.demospringmvc.pojo;
public class Account {
private int id;
private User user;
private float balance;
public User getUser() {
return user;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public void setUser(User user) {
this.user = user;
}
public Account(){
super();
}
public float getBalance() {
return balance;
}
public void setBalance(float balance) {
this.balance = balance;
}
@Override
public String toString(){
return "Account(name="+user.getName()+",age="+user.getAge()+",balance="+balance+")";
}
}
再举个User类的例子:
package com.example.demospringmvc.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
@Component()
public class User {
private int id;
private String name;
private int age;
public User(){
super();
}
public User(String name,int age){
super();
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
@Value("Tom")
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
@Value("12")
public void setAge(int age) {
this.age = age;
}
@Override
public String toString(){
return "User(name="+name+",age="+age+")";
}
}
上述代码应该很清晰明了了吧,所谓的数据库实体层,就是在pojo里面的类,就是用来把数据库里面的数据进行实例化或者可以说对象化的一些类。这下是不是完全清楚了,所谓的pojo类也没有那么难理解嘛。
2. dao层
dao层也叫做mapper层,是数据持久层。数据持久层是什么意思呢?
其实说白了,就是来实现pojo实体对象和数据库之间的数据操作(增删改查),也就是说,sql的语句是放在dao层里面的。
同样,用demo说话:
package com.example.demospringmvc.dao;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import com.example.demospringmvc.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import com.example.demospringmvc.pojo.Account;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
public class AccountDao implements IAccountDao {
// 声明JdbcTemplate属性及其setter方法
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Resource
private UserDao userDao;
// 添加账户
public int addAccount(Account account) {
int userid=userDao.addUser(account.getUser());
account.getUser().setId(userid);
// 定义SQL
String sql = "insert into tb_account(userid,balance) values(?,?)";
// 定义数组来存放SQL语句中的参数
Object[] obj = new Object[] {
account.getUser().getId(),
account.getBalance()
};
// 执行添加操作,返回的是受SQL语句影响的记录条数
int num = this.jdbcTemplate.update(sql, obj);
return num;
}
// 更新账户
public int updateAccount(Account account) {
// 定义SQL
String sql = "update tb_account set userid=?,balance=? where id = ?";
// 定义数组来存放SQL语句中的参数
Object[] params = new Object[] {
account.getUser().getId(),
account.getBalance(),
account.getId()
};
// 执行添加操作,返回的是受SQL语句影响的记录条数
int num = this.jdbcTemplate.update(sql, params);
return num;
}
// 删除账户
public int deleteAccount(int id) {
// 定义SQL
String sql = "delete from tb_account where id = ? ";
// 执行添加操作,返回的是受SQL语句影响的记录条数
int num = this.jdbcTemplate.update(sql, id);
return num;
}
// 通过id查询账户数据信息
public Account findAccountById(int id) {
//定义SQL语句
String sql = "select * from tb_account,tb_user where TB_ACCOUNT.id = ? and TB_USER.ID=TB_ACCOUNT.USERID";
// 创建一个新的BeanPropertyRowMapper对象
// RowMapper<Account> rowMapper =
// new BeanPropertyRowMapper<Account>(Account.class);
// 将id绑定到SQL语句中,并通过RowMapper返回一个Object类型的单行记录
return this.jdbcTemplate.queryForObject(sql, new AccountRowMapper(), id);
}
// 查询所有账户信息
public List<Account> findAllAccount() {
System.out.println("JDBC Template");
// 定义SQL语句
String sql = "select * from tb_account,tb_user where TB_USER.ID=TB_ACCOUNT.USERID";
// 创建一个新的BeanPropertyRowMapper对象
// RowMapper<Account> rowMapper =
// new BeanPropertyRowMapper<Account>(Account.class);
// 执行静态的SQL查询,并通过RowMapper返回结果
return this.jdbcTemplate.query(sql, new AccountRowMapper());
}
// 查询所有账户信息
public List<Account> findAccountByName(String name) {
// 定义SQL语句
String sql = "select * from tb_account,tb_user where TB_USER.name=? AND TB_USER.ID=TB_ACCOUNT.USERID";
// 创建一个新的BeanPropertyRowMapper对象
// RowMapper<Account> rowMapper =
// new BeanPropertyRowMapper<Account>(Account.class);
// 执行静态的SQL查询,并通过RowMapper返回结果
return this.jdbcTemplate.query(sql, new AccountRowMapper(),name);
}
// 更新余额
public int updateBalance(int id,float money,boolean add){
int num=0;
if (add)
num=this.jdbcTemplate.update("update tb_account set balance = balance +? "
+ "where id = ?",money, id);
else
num=this.jdbcTemplate.update("update tb_account set balance = balance -? "
+ "where id = ?",money, id);
return num;
}
public int getAccountIdByUserId(int userId){
String sql = "select id from tb_account where userId = ?";
RowMapper<Account> rowMapper =
new BeanPropertyRowMapper<Account>(Account.class);
Account account=this.jdbcTemplate.queryForObject(sql, rowMapper, userId);
return account.getId();
}
private class AccountRowMapper implements RowMapper<Account> {
private ResultSet rs;
private int rowNum;
@Override
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
this.rs = rs;
this.rowNum = rowNum;
User user = new User();
user.setId(rs.getInt("tb_user.id"));
user.setName(rs.getString("name"));
user.setAge(rs.getInt("age"));
Account account=new Account();
account.setId(rs.getInt("tb_account.id"));
account.setBalance(rs.getFloat("balance"));
account.setUser(user);
return account;
}
}
}
上面的代码就是AccountDao类的具体实现。
如代码中所表示的,对于Account类来说从应用层面上来说,我们应该可以对数据库中的账户表(tb_account)进行有相关的操作,例如增加账户,查询账户,删除账户之类的操作,那么着一些方法的具体实现就应该放在dao层。所谓的具体实现,就是指例如如果我们要增加账户,那么其实就是在数据库里面增加一条我们需要增加的账户的信息,所以我们在AccountDao里面就需要有一个增加账户的方法,方法里面的语句就是对数据库进行操作。在上述的代码里面,我们是使用一个来自Spring框架中包含的类叫做JdbcTemplate加SQL的语句来实现这个功能。
注意:我在这个AccountDao类里面使用的是Spring自带的JdbcTemplate类,当然,我们也可以把mybatis框架嵌套到这个dao层里面,通过mybatis来操作数据库也是完全没问题的。我们也可以extends JdbcDaoSupport类来实现。也就是说,具体的实现的方法有很多种,但是具体的目的就是为了操作数据库内容。
3. service层
service层也叫做业务逻辑层,这一层就非常好理解了。同样,demo说明一切:
package com.example.demospringmvc.service;
import com.example.demospringmvc.dao.AccountDao;
import com.example.demospringmvc.dao.IAccountDao;
import com.example.demospringmvc.pojo.Account;
import com.example.demospringmvc.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
@Qualifier("accountDao2")
private IAccountDao accountDao;
//开户销户
public boolean openAccount(User user){
if(user.getName()=="" ||user.getAge()<18) return false;
Account account=new Account();
account.setUser(user);
accountDao.addAccount(account);
return true;
}
public boolean closeAccount(User user){
if(user.getName()=="" ||user.getAge()<18) return false;
List<Account> accounts=accountDao.findAccountByName(user.getName());
for(Account account:accounts)
accountDao.deleteAccount(account.getId());
return true;
}
// 存取钱
public boolean saveMoney(User user,float money){
if(user.getName()=="" ||user.getAge()<18 || money<=0) return false;
List<Account> accounts= accountDao.findAccountByName(user.getName());
if (accounts.size()==0) return false;
int acctID=accounts.get(0).getId();
accountDao.updateBalance(acctID, money,true);
return true;
}
public boolean withdrawMoney(User user,float money){
if(user.getName()=="" ||user.getAge()<18 || money<=0) return false;
List<Account> accounts= accountDao.findAccountByName(user.getName());
if (accounts.size()==0 ) return false;
if(accounts.get(0).getBalance()<money) return false;
int acctID=accounts.get(0).getId();
accountDao.updateBalance(acctID, money,false);
return true;
}
//转账
public void transfer(User outUser, User inUser, float money) {
withdrawMoney(outUser,money);
saveMoney(inUser,money);
}
@Transactional(propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT, readOnly = false)
public void transferWithTransaction(User outUser, User inUser, float money) {
withdrawMoney(outUser,money);
// 模拟系统运行时的突发性问题
int i = 1/0;
saveMoney(inUser,money);
}
public List<Account> queryAllAccounts(){
List<Account> accounts= accountDao.findAllAccount();
return accounts;
}
}
上面的代码就是AccountServiceImpl类,这个类也就是在所谓的service层。
所以说,所谓的service层就是我们对于银行业务可以干什么,对于AccountDao类来说,这些就是关于Account账户的一些业务。这时候我们不需要去管具体的实现,也就是dao层里面具体的sql语句是如何写的,具体是使用mybatis啊,还是JDBC啊,我们都不用管,我们只需要在service层调用之前dao层写好的底层实现就可以了。例如在这个类里面,我们可以openAccount,也就是开户,可以closeAccount,也就是消户,也可以savemoney,也就是存钱,也可以withdrawmoney,也就是取钱,在这个service层,我们是需要去进行具体业务相关的操作,也就是例如银行能有些什么功能,而具体的和数据库打交道的具体实现,都一定是在dao层,而不是在service层,那么“存钱”这个概念,就是一个业务,所以我们也管service层叫做业务逻辑层。
4. controller层
controller层也叫做控制层,专业点来说:controller层的功能为请求和响应控制,负责前后端交互,接受前端请求,分发给不同的方法,在方法中调用service层的方法,接收service层返回的数据,最后返回具体数据到客户端页面。
也就是说这一个层次要干的事情就是:把我们之前在service里面如果有得到的数据,返回到前端,例如浏览器上面。
老规矩,demo说明一切:
package com.example.demospringmvc.controller;
import com.alibaba.fastjson.JSON;
import com.example.demospringmvc.pojo.Account;
import com.example.demospringmvc.pojo.User;
import com.example.demospringmvc.pojo.UserList;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@Controller
@RequestMapping("/controller") //类上的注解,表示其中的所有方法的路径都在该路径下
@SessionAttributes("xuser") //session保存xuser信息
public class ControllerParam implements ApplicationContextAware {
private ApplicationContext context;
@Override //这个是ApplicationContextAware 要实现的接口
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
//---------DataBinding ViewToController-----------
//绑定默认数据类型:HttpServletRequest、HttpServletResponse、HttpSession、Model/ModelMap
//E2
@RequestMapping("paratype")
public String myRequest(HttpServletRequest request) throws Exception {
String name = request.getParameter("name");
int age = Integer.parseInt(request.getParameter("age"));
System.out.println("@Request" + "-----------" + name + "---" + age);
request.setAttribute("paramtype", "Default Request Method Param Type");
request.setAttribute("name", name);
request.setAttribute("age", age);
return "path";
}
controller层相对于其他层次的实现,是相对而言复杂一些的。这篇文章就不详细说明具体实现,只挑其中的一点说一下就好。
看下图:
@RequestMapping("paratype")
public String myRequest(HttpServletRequest request) throws Exception {
String name = request.getParameter("name");
int age = Integer.parseInt(request.getParameter("age"));
System.out.println("@Request" + "-----------" + name + "---" + age);
request.setAttribute("paramtype", "Default Request Method Param Type");
request.setAttribute("name", name);
request.setAttribute("age", age);
return "path";
}
下面是path.jsp,这两个必须合起来看:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>Spring Controller Demo<</title>
</head>
<body>
<h1>Spring Controller Demo</h1>
<h3>${
paramtype}</h3>
<br/>
<p>name:${
name}</p>
<p>age:${
age}</p>
</body>
</html>
看到这个@RequestMapping(“String”)了吗,这个的作用就是当浏览器url结尾出现这个双引号里面出现的字符串的时候,程序将跳转到当前方法,并执行当前方法。如果当前方法返回值为String类型的时候,例如上图,这个“path” 其实是一个jsp文件的文件名,有一个jsp文件叫做path,这时候浏览器会跳转到这个path.jsp页面,并且带上当前方法里面的数据。
例如上述方法,浏览器的url的最后会显示paratype/name=xxx&&age=yyy,这个时候,走过String name = request.getParameter(“name”);之后,name就会被赋值为xxx,走过int age = Integer.parseInt(request.getParameter(“age”));之后,age就会被赋值为yyy,当进行了request.setAttribute()方法之后,由于path.jsp里面有如下代码:
<p>name:${
name}</p>
<p>age:${
age}</p>
所以我们在ControllerParam类里面的myRequest方法,就会返回path,并且把path里面的参数填上并最终返回到客户端,也就是浏览器上。
总结:
完整的Spring程序框架一般有4层,从底到顶分别是pojo,dao,service,controller。其实就是把一个可以写在一起,但是如果写在一起会非常杂乱且耦合度极高的程序,分层次的,一步步实现。
注意 :controller层的具体实现在这篇文章里面没有详细描述,以后会讲。