Java Spring MVC分层设计(转)
原文:https://blog.csdn.net/chris_mao/article/details/48694243
第一次尝试着用Java做Web开发,使用了Java Spring框架,顺便说一句,如果使用Spring开发,建议使用STS(Spring Tool Suite) IDE,它很好的集成了Spring、Maven等框架,使用起来特别方便,尤其是第一次使用Spring框架进行开发,它极大的方便了开发人员,通过快捷菜单及可很简单的配置好Spring开发环境,自动下载、更新Maven依赖包。话不多讲,回到文章的正题。
Spring是一个在Java业界很流行的MVC框架,所谓MVC即模型-视图-控制器,将应用程序的逻辑层与展现层进行分离的一种设计模式。
- 模型(Model)代表数据控制器。数据的读取,插入,更新都是由模型来负责。
- 视图(View)是展示给用户的最终页面。视图负责将数据以用户友好的形式展现出来。
- 控制器(Controller)是模型,视图以及其他任何处理 HTTP 请求所必须的资源之前的中介
概述
一个典型的页面浏览行为在程序端的流程是这样的:
- 控制器最先被调用,并被赋予外部输入
- 控制器根据外部输入向模型请求数据
- 模型从数据库获取数据并发送数据到控制器
- 控制器处理该数据并发送封装好的数据到视图
- 视图根据接到的数据最终展示页面给用户浏览
使用Java进行MVC模式开发时,往往将数据模型分为两部分,即DAO(Data Access Object,数据访问对象)和Service(业务逻辑模型)。在第2步中,控制器向模型请求数据时,并不是直接向DAO请求数据,而是通过Service向DAO请求数据。这样做的好处是,可以将业务逻辑与数据库访问独立开,为将来系统更换数据保存介质(如目前系统使用文件系统存储数据,将来可以更换为使用数据库存储,又或者是现在使用了MSSQL存储数据,将来更换为Oracle或是Mysql等)提供了很大的灵活性。
下图给出了分层设计模型。控制器只需要调用Service接口中的方法获取或是处理数据,Service层对控制器传入的数据进行业务逻辑处理封装后,传给DAO层,由DAO层负责将处理后的数据写入数据库中。
在Service层使用了抽象工厂模式来实现Service层与DAO层的低耦合,Service层并不知道DAO层是如何实现的,实际上也不需要知道系统使用了哪种数据库或是文件系统。
在DAO层使用工厂模式来创建数据模型的实体对象。
Service层设计
接口代码,这里使用了泛型技巧,确保每个Service只处理一种数据类型。
1 package com.emerson.etao.service; 2 3 import java.sql.SQLException; 4 import java.util.List; 5 6 /** 7 * 业务实现层接口 8 * 9 * @author Chris Mao(Zibing) 10 * 11 * @param <T> 12 */ 13 public interface IBaseService<T> { 14 15 /** 16 * 将实体类对象持久化,写入到数据表中 17 * 18 * @param T 19 * @return 返回新写入记录的自增ID 20 * @throws SQLException 21 */ 22 public long insert(T entity); 23 24 /** 25 * 根据Id值,将实体类数据回写到数据库 26 * 27 * @param id 28 * @param T 29 * @return 返回更新的记录笔数 30 * @throws SQLException 31 */ 32 public int update(long id, T entity); 33 34 /** 35 * 根据Id值从数据库中删除实体类数据 36 * 37 * @param id 38 * @return 返回删除的记录笔数 39 * @throws SQLException 40 */ 41 public int delete(long id); 42 43 /** 44 * 根据Id查询具体的实体类信息,并返回实体类对象 45 * 若查询不到数据则返回null 46 * 47 * @param id 48 * @return T 49 */ 50 public T getById(long id); 51 52 /** 53 * 获取列表 54 * 55 * @return List 56 * @throws SQLException 57 */ 58 public List<T> getAll(); 59 }
下面是具体的Service层接口代码,
1 package com.emerson.etao.service.base; 2 3 import java.util.List; 4 5 import com.emerson.etao.entity.base.BusinessApp; 6 import com.emerson.etao.entity.base.Communicator; 7 import com.emerson.etao.entity.base.Customer; 8 import com.emerson.etao.service.IBaseService; 9 10 /** 11 * 客服类操作接口 12 * 13 * @author Chris Mao(Zibing) 14 * @param <T> 15 * 16 * @param <T> 17 */ 18 public interface ICommunicatorService extends IBaseService<Communicator> { 19 20 public List<Communicator> getAll(Customer customer); 21 22 /** 23 * 为客服分配商业应用 24 * 25 * @param c 26 * @param appList 27 * @see BusinessApp 28 */ 29 public void assignBusinessApp(Communicator communicator, List<BusinessApp> appList, boolean deleteExists); 30 31 /** 32 * 为客服分配客户 33 * 34 * @param c 35 * @param customerList 36 * @see Customer 37 */ 38 public void assingCustomer(Communicator communicator, List<Customer> customerList, boolean deleteExists); 39 }
实现接口。
1 /** 2 * 3 */ 4 package com.emerson.etao.service.imp; 5 6 import java.sql.SQLException; 7 import java.util.List; 8 9 import com.emerson.etao.dao.IBaseDao; 10 import com.emerson.etao.dao.IDaoFactory; 11 import com.emerson.etao.service.IBaseService; 12 13 /** 14 * 业务层实现类基类 15 * 16 * 为了降低与数据访问层的耦合,在构造函数中传入DaoFactory接口用于创建具体的数据访问对象实例 17 * 18 * @author Chris Mao(Zibing) 19 * 20 */ 21 public abstract class BaseServiceImp<T> implements IBaseService<T> { 22 23 private IBaseDao<T> dao = null; 24 25 protected IBaseDao<T> getDao() { 26 return this.dao; 27 } 28 29 /** 30 * 31 * @param factory 降低耦合,传入DaoFactory接口 32 * @see IDaoFactory 33 */ 34 public BaseServiceImp(IDaoFactory<T> factory) { 35 super(); 36 this.dao = factory.getDao(); 37 } 38 39 @Override 40 public long insert(T entity) { 41 try { 42 return this.getDao().insert(entity); 43 } catch (SQLException e) { 44 e.printStackTrace(); 45 } 46 return 0; 47 } 48 49 @Override 50 public int update(long id, T entity) { 51 try { 52 return this.getDao().update(id, entity); 53 } catch (SQLException e) { 54 e.printStackTrace(); 55 } 56 return 0; 57 } 58 59 @Override 60 public int delete(long id) { 61 try { 62 return this.getDao().delete(id); 63 } catch (SQLException e) { 64 e.printStackTrace(); 65 } 66 return 0; 67 } 68 69 @Override 70 public List<T> getAll() { 71 try { 72 return this.getDao().getAll(); 73 } catch (SQLException e) { 74 e.printStackTrace(); 75 } 76 return null; 77 } 78 79 @Override 80 public T getById(long id) { 81 try { 82 return this.getDao().getById(id); 83 } catch (SQLException e1) { 84 e1.printStackTrace(); 85 } 86 return null; 87 } 88 }
1 package com.emerson.etao.service.base.imp; 2 3 import java.sql.PreparedStatement; 4 import java.sql.SQLException; 5 import java.text.SimpleDateFormat; 6 import java.util.ArrayList; 7 import java.util.Date; 8 import java.util.Iterator; 9 import java.util.List; 10 11 import com.emerson.etao.dao.IDaoFactory; 12 import com.emerson.etao.entity.base.BusinessApp; 13 import com.emerson.etao.entity.base.Communicator; 14 import com.emerson.etao.entity.base.Customer; 15 import com.emerson.etao.service.base.ICommunicatorService; 16 import com.emerson.etao.service.imp.BaseServiceImp; 17 18 /** 19 * 20 * @author Chris Mao(Zibing) 21 * 22 */ 23 public class CommunicatorServiceImp extends BaseServiceImp<Communicator>implements ICommunicatorService { 24 25 @Override 26 public List<Communicator> getAll(Customer customer) { 27 List<Communicator> result = new ArrayList<Communicator>(); 28 try { 29 result = this.getDao() 30 .getAll("SELECT a.* FROM communicator AS a INNER JOIN customer_communicator cc USING(communicator_id) WHERE cc.customer_id = " 31 + customer.getCustomerId()); 32 } catch (SQLException e) { 33 e.printStackTrace(); 34 } 35 return result; 36 } 37 38 public CommunicatorServiceImp(IDaoFactory<Communicator> factory) { 39 super(factory); 40 } 41 42 @Override 43 public void assignBusinessApp(Communicator communicator, List<BusinessApp> appList, boolean deleteExists) { 44 try { 45 if (true == deleteExists) { 46 this.getDao().getStatement().executeUpdate("DELETE FROM communicator_application WHERE communicator_id = " + communicator.getCommunicatorId()); 47 } 48 49 if (null == appList || appList.isEmpty()) { 50 return; 51 } 52 53 PreparedStatement pstmt = this.getDao().getConnection().prepareStatement("INSERT IGNORE INTO communicator_application(communicator_id, application_id, created_time) VALUES(?, ?, ?)"); 54 BusinessApp app = null; 55 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 设置日期格式 56 Iterator<BusinessApp> ite = appList.iterator(); 57 while (ite.hasNext()) { 58 app = ite.next(); 59 pstmt.setLong(1, communicator.getCommunicatorId()); 60 pstmt.setLong(2, app.getApplicationId()); 61 pstmt.setString(3, sdf.format(new Date())); 62 pstmt.executeUpdate(); 63 } 64 pstmt.close(); 65 } catch (SQLException e) { 66 e.printStackTrace(); 67 } 68 } 69 70 /** 71 * 为客服人员分配客户 72 * 73 * 如果需要删除客服人员名下所有客户,只需将customers设为null或是空列表 74 * 75 * @param communicator 76 * @param apps 77 * @param deleteExists 78 */ 79 @Override 80 public void assingCustomer(Communicator communicator, List<Customer> customerList, boolean deleteExists) { 81 try { 82 if (true == deleteExists) { 83 this.getDao().getStatement().executeQuery("DELETE FROM customer_communicator WHERE communicator_id = " + communicator.getCommunicatorId()); 84 } 85 86 if (null == customerList || customerList.isEmpty()) { 87 return; 88 } 89 90 PreparedStatement pstmt = this.getDao().getConnection().prepareStatement("INSERT IGNORE INTO customer_communicator(communicator_id, customer_id, created_time) VALUES(?, ?, ?)"); 91 Customer customer = null; 92 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 设置日期格式 93 Iterator<Customer> ite = customerList.iterator(); 94 while (ite.hasNext()) { 95 customer = ite.next(); 96 pstmt.setLong(1, communicator.getCommunicatorId()); 97 pstmt.setLong(2, customer.getCustomerId()); 98 pstmt.setString(3, sdf.format(new Date())); 99 pstmt.executeUpdate(); 100 } 101 pstmt.close(); 102 } catch (SQLException e) { 103 e.printStackTrace(); 104 } 105 } 106 }
DAO层的设计
这里需在为DAO层定义一个通个的基础接口IBaseDao,这里包含了对数据的增、删、改、查基础操作。抽象类BaseDao实现接口IBaseDao,并添加了访问限制为protected的数据库连接对象,方便子类使用。
DAO接口代码。
1 /** 2 * 3 */ 4 package com.emerson.etao.dao; 5 6 import java.sql.Connection; 7 import java.sql.SQLException; 8 import java.sql.Statement; 9 import java.util.List; 10 11 /** 12 * 13 * 数据访问层接口 14 * 15 * @author Chris Mao(Zibing) 16 * 17 */ 18 public interface IBaseDao<T> { 19 20 /** 21 * 22 * @return Connection 23 */ 24 public Connection getConnection(); 25 26 /** 27 * 28 * @return Statement 29 */ 30 public Statement getStatement(); 31 32 /** 33 * 将值对象写入到数据表中,并返回其自增ID值 34 * 35 * @param entity 36 * @return 返回新写入记录的自增ID 37 * @throws SQLException 38 */ 39 public long insert(T entity) throws SQLException; 40 41 /** 42 * 将值对象修改后的内容写入到数据表中,并返回其影响的记录笔数 43 * 44 * @param id 45 * @param entity 46 * @return 返回更新的记录笔数 47 * @throws SQLException 48 */ 49 public int update(long id, T entity) throws SQLException; 50 51 /** 52 * 删除ID值,并返回其删除的记录笔数 53 * 54 * @param id 55 * @return 返回删除的记录笔数 56 * @throws SQLException 57 */ 58 public int delete(long id) throws SQLException; 59 60 /** 61 * 依据Id值到数据表中查询数据,并返回值对象 62 * 63 * @param id 64 * @return 65 */ 66 public T getById(long id) throws SQLException; 67 68 /** 69 * 返回数据表中所有记录 70 * 71 * @return List<T> 72 * @throws SQLException 73 */ 74 public List<T> getAll() throws SQLException; 75 76 /** 77 * 返回符合条件的所有记录 78 * 79 * @param queryStr 80 * @return List<T> 81 * @throws SQLException 82 */ 83 public List<T> getAll(String queryStr) throws SQLException; 84 }
抽象工厂接口。
1 package com.emerson.etao.dao; 2 3 /** 4 * 数据访问类工厂接口 5 * 6 * 负责创建具体的数据访问对象实例 7 * 8 * @author Chris Mao(Zibing) 9 * 10 * @param <T> 11 */ 12 public interface IDaoFactory<T> { 13 /** 14 * 创建数据访问对象实例 15 * 16 * @return 17 * @see IBaseDao 18 */ 19 public IBaseDao<T> getDao(); 20 }
抽象类BaseDao实现接口IBaseDao。
1 package com.emerson.etao.dao; 2 3 import java.sql.Connection; 4 import java.sql.ResultSet; 5 import java.sql.SQLException; 6 import java.sql.Statement; 7 8 import org.slf4j.Logger; 9 import org.slf4j.LoggerFactory; 10 11 import com.emerson.etao.db.DBUtils; 12 13 /** 14 * 15 * 数据访问层基类 16 * 17 * 所有数据访问对象都需要继承此类 18 * 19 * @author Chris Mao(Zibing) 20 * 21 */ 22 public abstract class BaseDao<T> implements IBaseDao<T> { 23 24 private static final Logger logger = LoggerFactory.getLogger(BaseDao.class); 25 26 public Connection getConnection() { 27 return DBUtils.getConnection(); 28 } 29 30 public Statement getStatement() { 31 Statement stmt = null; 32 try { 33 Connection conn = DBUtils.getConnection(); 34 stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); 35 } catch (SQLException e) { 36 logger.error("创建 Statement 对象发生错误!!"); 37 e.printStackTrace(); 38 } 39 return stmt; 40 } 41 }
实体类创建工厂接口。
1 package com.emerson.etao.entity; 2 3 import java.sql.ResultSet; 4 5 /** 6 * 7 * 实体类工厂接口 8 * 9 * 所有实体类对象实例需要通过此工厂接口创建 10 * 11 * @author Chris Mao(Zibing) 12 * 13 */ 14 public interface IEntityFactory<T> { 15 16 /** 17 * 创建空的实体类 18 * 19 * @return 20 */ 21 public T createEntity(); 22 23 /** 24 * 创建实体类,并将参数rs中的内容赋值到实体类属性当中 25 * 26 * @param rs 27 * @return 28 */ 29 public T createEntity(ResultSet rs); 30 31 }
具体的DAO对象,继承BaseDao,并实现实体类创建工厂接口。这里使用了内部匿名类实现DAO的抽象工厂接口。
1 package com.emerson.etao.dao.base; 2 3 import java.sql.PreparedStatement; 4 5 import java.sql.ResultSet; 6 import java.sql.SQLException; 7 import java.sql.Statement; 8 import java.text.SimpleDateFormat; 9 import java.util.ArrayList; 10 import java.util.Date; 11 import java.util.List; 12 13 import com.emerson.etao.dao.BaseDao; 14 import com.emerson.etao.dao.IBaseDao; 15 import com.emerson.etao.dao.IDaoFactory; 16 import com.emerson.etao.entity.IEntityFactory; 17 import com.emerson.etao.entity.base.Communicator; 18 19 /** 20 * 客服人员数据访问对象 21 * 22 * @author Chris Mao(Zibing) 23 * 24 */ 25 public class CommunicatorDao extends BaseDao<Communicator>implements IEntityFactory<Communicator> { 26 27 public static IDaoFactory<Communicator> factory = new IDaoFactory<Communicator>() { 28 29 @Override 30 public IBaseDao<Communicator> getDao() { 31 return new CommunicatorDao(); 32 } 33 34 }; 35 36 @Override 37 public Communicator createEntity() { 38 return new Communicator(); 39 } 40 41 @Override 42 public Communicator createEntity(ResultSet rs) { 43 Communicator c = this.createEntity(); 44 try { 45 c.setCommunicatorId(rs.getInt("communicator_id")); 46 c.setCommunicatorName(rs.getString("communicator_name")); 47 c.setPhone(rs.getString("phone")); 48 c.setFax(rs.getString("fax")); 49 c.setEmail(rs.getString("email")); 50 c.setReportTo(rs.getInt("report_to")); 51 c.setReportToName(rs.getString("report_to_name")); 52 c.setValid(rs.getByte("is_valid")); 53 c.setCreatedTime(rs.getTimestamp("created_time")); 54 c.setUpdatedTime(rs.getTimestamp("update_time")); 55 } catch (SQLException e) { 56 e.printStackTrace(); 57 } 58 return c; 59 } 60 61 @Override 62 public Communicator getById(long id) throws SQLException { 63 Communicator result = null; 64 ResultSet rs = this.getStatement().executeQuery("SELECT * FROM vw_communicator WHERE communicator_id = " + id); 65 while (rs.next()) { 66 result = this.createEntity(rs); 67 } 68 rs.close(); 69 return result; 70 } 71 72 @Override 73 public long insert(Communicator entity) throws SQLException { 74 Long newId = (long) 0; 75 76 StringBuilder sql = new StringBuilder(); 77 sql.append("INSERT IGNORE INTO communicator"); 78 sql.append("(communicator_name, phone, fax, email, report_to, created_time) "); 79 sql.append("VALUES(?, ? ,? ,?, ?, ?)"); 80 81 PreparedStatement pstmt = this.getConnection().prepareStatement(sql.toString(), Statement.RETURN_GENERATED_KEYS); 82 pstmt.setString(1, entity.getCommunicatorName()); 83 pstmt.setString(2, entity.getPhone()); 84 pstmt.setString(3, entity.getFax()); 85 pstmt.setString(3, entity.getFax()); 86 pstmt.setString(4, entity.getEmail()); 87 pstmt.setInt(5, entity.getReportTo()); 88 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 设置日期格式 89 pstmt.setString(6, df.format(new Date())); 90 pstmt.executeUpdate(); 91 ResultSet rs = pstmt.getGeneratedKeys(); 92 if (rs.next()) { 93 newId = rs.getLong(1); 94 entity.setCommunicatorId(rs.getInt(1)); 95 // System.out.println("新增客服记录ID为:" + newId); 96 } 97 rs.close(); 98 pstmt.close(); 99 return newId; 100 } 101 102 @Override 103 public int update(long id, Communicator entiry) throws SQLException { 104 int result = 0; 105 StringBuffer sql = new StringBuffer(); 106 Communicator c = (Communicator) entiry; 107 // System.out.println(c); 108 sql.append("UPDATE communicator"); 109 sql.append(" SET communicator_name = ?, phone = ?, fax = ?, email = ?, report_to = ?, is_valid = ?"); 110 sql.append(" WHERE communicator_id = ?"); 111 112 PreparedStatement pstmt = this.getConnection().prepareStatement(sql.toString()); 113 pstmt.setString(1, c.getCommunicatorName()); 114 pstmt.setString(2, c.getPhone()); 115 pstmt.setString(3, c.getFax()); 116 pstmt.setString(3, c.getFax()); 117 pstmt.setString(4, c.getEmail()); 118 pstmt.setInt(5, c.getReportTo()); 119 pstmt.setInt(6, c.getIsValid()); 120 pstmt.setLong(7, c.getCommunicatorId()); 121 result = pstmt.executeUpdate(); 122 // System.out.println("更新客服记录数为:" + result); 123 pstmt.close(); 124 return result; 125 } 126 127 @Override 128 public int delete(long id) throws SQLException { 129 int result = 0; 130 String sql = "DELETE FROM communicator WHERE communicator_id = ?"; 131 132 PreparedStatement pstmt; 133 134 pstmt = this.getConnection().prepareStatement(sql); 135 pstmt.setLong(1, id); 136 result = pstmt.executeUpdate(); 137 // System.out.println("删除客服记录数为:" + result); 138 pstmt.close(); 139 return result; 140 } 141 142 @Override 143 public List<Communicator> getAll() throws SQLException { 144 List<Communicator> result = null; 145 ResultSet rs = this.getStatement().executeQuery("SELECT * FROM vw_communicator"); 146 result = new ArrayList<Communicator>(); 147 while (rs.next()) { 148 result.add(this.createEntity(rs)); 149 } 150 rs.close(); 151 return result; 152 } 153 154 @Override 155 public List<Communicator> getAll(String queryStr) throws SQLException { 156 List<Communicator> result = new ArrayList<Communicator>(); 157 158 ResultSet rs = this.getStatement().executeQuery(queryStr); 159 while (rs.next()) { 160 result.add(this.createEntity(rs)); 161 } 162 rs.close(); 163 return result; 164 } 165 }