透过DRP进一步认识MVC架构(一)

我所接触过的逻辑架构一般分为两种: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层一个大致的调用流程:

(接下篇)

评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值