我所接触过的逻辑架构一般分为两种:Model1和Model2。Model1采用JSP+JavaBean+Database的方式开发,JSP负责表现,JavaBean负责业务逻辑,Database负责持久保持数据,这是一种轻量级的架构模式,适合做一些小型项目。Model2是经典的MVC架构模式,利用分层思想将项目根据职责不同进行划分,极大的起到了解耦作用,便于系统后期维护和修改,较适合大型项目(抛开框架不说)。
关于MVC的文章在之前的博客里也写了不下三篇,但是随着每次的学习都会有一番新的认识,尤其这次做完DRP项目,对MVC更有一种醍醐灌顶之势,更加深切的感受到了分层的妙处和好处。本以为在学习.net的时候,已领会了MVC的精髓,但接触Java后,发现之前的认识还不够到位,因此有必要再写一篇。下面内容分析较少,代码较多,还需多手动练习,方可深刻理解分层思想。
下图是MVC时序图,图中标注了每层的职责,调用的次序,以及Model2的优点和缺点,具体就不再文字赘述了。
将上图进一步演变,把业务逻辑层分拆为业务逻辑和持久化逻辑,就变成了经典的三层架构模式,这种模式是我们项目中常用的模式,至于分拆出持久化逻辑的原因,请看下图:
上面两种图表现出,MVC与三层架构划分的原理是相同的,只是划分的视角不同罢了。
以添加流向单为例,下面是持久化逻辑中的代码,也就是常说的Dao层,首先将数据库信息配置到sys-config.xml文件中:
<?xml version="1.0" encoding="UTF-8"?>
<config>
<db-info>
<driver-name>oracle.jdbc.driver.OracleDriver</driver-name>
<url>jdbc:oracle:thin:@localhost:1521:xxxx</url>
<user-name>drp</user-name>
<password>drp</password>
</db-info>
</config>
创建读取sys-config.xml的类XmlConfigReader,利用开源组件DOM4J读取,代码如下:
package com.snail.drp.util;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
public class XmlConfigReader {
//保存dao工厂的名称
//key=名称,value=具体类完整路径
private Map<String,String> daoFactoryMap = new HashMap<String,String>();
//保存jdbc相关配置信息
private JdbcConfig jdbcConfig = new JdbcConfig();
//懒汉式(延迟加载lazy)
private static XmlConfigReader instance = null;
private XmlConfigReader(){
SAXReader reader = new SAXReader();
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("sys-config.xml");
try {
Document doc = reader.read(is);
//取得jdbc相关配置信息
Element driverNameElt = (Element)doc.selectObject("/config/db-info/driver-name");
Element urlElt = (Element)doc.selectObject("/config/db-info/url");
Element userNameElt = (Element)doc.selectObject("/config/db-info/user-name");
Element passwordElt = (Element)doc.selectObject("/config/db-info/password");
//设置jdbc相关的设置
jdbcConfig.setDriverName(driverNameElt.getStringValue());
jdbcConfig.setUrl(urlElt.getStringValue());
jdbcConfig.setUserName(userNameElt.getStringValue());
jdbcConfig.setPassword(passwordElt.getStringValue());
//取得DaoFactory信息
List daoFactoryList = doc.selectNodes("/config/dao-factory/*");
for(int i=0;i<daoFactoryList.size();i++){
Element daoFactoryElt = (Element) daoFactoryList.get(i);
String tagName = daoFactoryElt.getName();
String tagText = daoFactoryElt.getText();
// 放入到Map中
daoFactoryMap.put(tagName, tagText);
}
} catch (DocumentException e) {
e.printStackTrace();
}
}
/**
* 创建实例
* @return
*/
public static synchronized XmlConfigReader getInstance(){
if(instance == null){
instance = new XmlConfigReader();
}
return instance;
}
/**
* 返回jdbc相关配置
* @return
*/
public JdbcConfig getJdbcConfig(){
return jdbcConfig;
}
/**
* 根据标签名称取得DaoFactory的名字
* @param name
* @return daoFactory的完整类路径
*/
public String getDaoFactory(String name){
return daoFactoryMap.get(name);
}
}
为了通用性,抽取出一个类,用来获取连接,代码如下
package com.snail.drp.util;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* 采用ThreadLocal封装Connection
* @author yuanfubiao
*
*/
public class ConnectionManager {
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>();
/**
* 获取Connection
* @return
*/
public static Connection getConnection(){
Connection conn = connectionHolder.get();
//如果在当前线程中没有绑定相应的Connection
if(conn == null){
try{
JdbcConfig jdbcConfig = XmlConfigReader.getInstance().getJdbcConfig();
Class.forName(jdbcConfig.getDriverName());
conn = DriverManager.getConnection(jdbcConfig.getUrl(),jdbcConfig.getUserName(),jdbcConfig.getPassword());
//将Connection设置到ThreadLocal
connectionHolder.set(conn);
}catch(ClassNotFoundException e){
e.printStackTrace();
throw new ApplicationException("系统错误,请联系管理员");
}catch(SQLException e){
e.printStackTrace();
throw new ApplicationException("系统错误,请联系管理员");
}
}
return conn;
}
//关闭当前线程连接
public static void closeConnection() {
Connection conn = connectionHolder.get();
if (conn != null) {
try {
conn.close();
//从ThreadLocal中清除Connection
connectionHolder.remove();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
//关闭连接
public static void close(Connection conn) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
//关闭Statement
public static void close(Statement pstmt) {
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
//关闭ResultSet
public static void close(ResultSet rs ) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
//开启事务
public static void beginTransaction(Connection conn) {
try {
if (conn != null) {
if (conn.getAutoCommit()) {
conn.setAutoCommit(false); //手动提交
}
}
}catch(SQLException e) {}
}
//提交事务
public static void commitTransaction(Connection conn) {
try {
if (conn != null) {
if (!conn.getAutoCommit()) {
conn.commit();
}
}
}catch(SQLException e) {}
}
//回滚事务
public static void rollbackTransaction(Connection conn) {
try {
if (conn != null) {
if (!conn.getAutoCommit()) {
conn.rollback();
}
}
}catch(SQLException e) {}
}
}
Dao层添加流向单代码,为了减小各层之间的耦合,所以各层都依赖于接口:
package com.snail.drp.flowcard.dao;
import java.util.Date;
import java.util.List;
import com.snail.drp.flowcard.domain.FlowCard;
import com.snail.drp.flowcard.domain.FlowCardDetail;
import com.snail.drp.util.DaoException;
/**
* 流向单维护数据访问结构
* @author yuanfubiao
*
*/
public interface FlowCardDao {
/**
* 生成流向单号
* @return
* @throws DaoException
*/
public String generateVouNo() throws DaoException;
/**
* 添加流向单主信息
* @param flowCardVouNo
* @param flowCard
* @throws DaoException
*/
public void addFlowCardMaster(String flowCardVouNo,FlowCard flowCard) throws DaoException;
}
实现类代码:
package com.snail.drp.flowcard.dao.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import com.snail.drp.basedata.domain.Client;
import com.snail.drp.flowcard.dao.FlowCardDao;
import com.snail.drp.flowcard.domain.FlowCard;
import com.snail.drp.flowcard.domain.FlowCardDetail;
import com.snail.drp.sysmgr.domain.User;
import com.snail.drp.util.ConnectionManager;
import com.snail.drp.util.DaoException;
public class FlowCardDaoImpl implements FlowCardDao {
@Override
public void addFlowCardMaster(String flowCardVouNo, FlowCard flowCard)
throws DaoException {
StringBuffer sbSql = new StringBuffer();
sbSql.append("insert into t_flow_card_master (flow_card_no, opt_type, fiscal_year_period_id, ")
.append("client_id, recorder_id, opt_date, vou_sts) ")
.append("values (?, ?, ?, ?, ?, ?, ?) ");
PreparedStatement pstmt = null;
try{
Connection conn = ConnectionManager.getConnection();
pstmt = conn.prepareStatement(sbSql.toString());
pstmt.setString(1, flowCardVouNo);
pstmt.setString(2, flowCard.getOptType());
pstmt.setInt(3, flowCard.getFiscalYearPeriod().getId());
pstmt.setInt(4, flowCard.getClient().getId());
pstmt.setString(5, flowCard.getRecorder().getUserId());
pstmt.setTimestamp(6, new Timestamp(flowCard.getOptDate().getTime()));
pstmt.setString(7, flowCard.getVouSts());
pstmt.executeUpdate();
}catch(SQLException e){
e.printStackTrace();
System.out.println("FlowCardDaoImpl-->>addFlowCardMaster,exception:" + e);
throw new DaoException(e);
}finally{
ConnectionManager.close(pstmt);
}
}
}
下图为Dao层一个大致的调用流程:
(接下篇)