日期:2014-05-16  浏览次数:20373 次

分层架构下的纯JDBC事务控制简单解决方案

对目前的JavaEE企业应用开发来说,基本都会采用分层的架构, 这样可以分散关注、松散耦合、逻辑复用、标准定义。例如,目前使用SSH组合时典型的四层架构:表示层、业务层、持久层和数据层;那么,在四层架构中,事务的控制应该放在哪一层呢?


如果使用Spring框架,它对事务做了很好的封装,通过它的AOP配置,可以灵活的配置在任何一层;但是在很多的需求和应用,直接使用JDBC事务控制还是有其优势的。所以,本文来讨论纯JDBC事务的控制问题。

其实,事务是以业务逻辑为基础的;一个完整的业务应该对应业务层里的一个方法;如果业务操作失败,则整个事务回滚;所以,事务控制是绝对应该放在业务层的;但是,持久层的设计应该遵循一个很重要的原则:持久层应该保证操作的原子性,就是说持久层里的每个方法都应该是不可以分割的。

例如针对一个部门和员工的CRUD操作。如果要删除某个部门,就应该在DeptDao中有一个删除部门的方法:
public class DeptDao {
??? public void deleteDept(int id) ;???????? //删除指定ID的部门
}
在EmpDao中有一个删除指定部门下的所有员工的方法
public interface EmpDao{
??? public void deleteEmpByDeptId(int id);??? //删除指定部门下的所有员工
}
这样,就应该在业务层DeptService中的删除部门方法中组合这两个方法,即把它们放置在同一个事务中:
public class DeptService{
??? public void deleteDept(int id){
??????? try{
???????????? //启动JDBC事务
???????????? //调用EmpDao中的deleteEmpByDeptId(id)方法
???????????? //调用DeptDao中的deleteDept(id)方法
???????????? //操作正常,提交事务
??????? }catch(Exception e){
???????????? //异常,回滚事务
??????? }
??? }
}

要让这两个Dao操作在同一个事务,最主要的一点就是:启动JDBC事务中使用的数据库连接和两个Dao操作方法里获得的数据库连接要是同一个连接。参照Spring的JDBC事务原理,可以使用ThreadLocal类, 在启动JDBC事务中把数据库连接绑定到线程,以保证在同一个线程下获得的都是同一个连接。这样就达到目的了。

如下数据库工具类:

view plaincopy to clipboardprint?
package com.tjitcast.common;??
import java.io.IOException;??
import java.sql.Connection;??
import java.sql.SQLException;??
import java.util.Properties;??
import javax.sql.DataSource;??
import com.mchange.v2.c3p0.DataSources;??
import com.tjitcast.dao.DaoException;??
/**?
?*? 数据库工具类?
?*? 可以根据classpath下配置文件jdbc.properties中配置的参数来获取数据库连接并绑定到当前线程上?
?*? 可以获取JDBC的事务管理器?
?* @author qiujy?
?* @version 0.9Beta?
?*/?
public class DbUtils {??
??? private static Properties prop = new Properties();??
??? /** 数据源 */?
??? private static DataSource ds = null;???
??????
??? //用来把Connection绑定到当前线程上的变量??
??? private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();??
??? static{??
??????? try {??
??????????? prop.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("jdbc.properties"));??
??????? } catch (IOException e) {??
??????????? e.printStackTrace();??
??????????? System.out.println("在classpath下没有找到jdbc.properties文件");??
??????? }??
??????????
??????? //使用C3P0连接池技术??
??????? try {??
??????????? Class.forName("com.mysql.jdbc.Driver");??
??????????????
??????????? DataSource unpooled = DataSources.unpooledDataSource(??
??????????????????? prop.getProperty("url"),??
??????????????????? prop.getProperty("user"),??
??????????????????? prop.getProperty("password"));??
??????????? ds = DataSources.pooledDataSource(unpooled);??
??????????????
??????? } catch (ClassNotFoundException e) {??
??????????? e.printStackTrace();??
??????? } catch (SQLException e) {??
??????????? e.printStackTrace();??
??????? }??
??? }??
??????
??? private DbUtils(){}??
??????
??? /**?
???? * 根据数据库的默认连接参数获取数据库的Connection对象,并绑定到当前线程上?
???? * @return 成功,返回Connection对象,否则返回null?
???? */?
??? public static synchronized Connection getConnection(){??
??????? Connection conn = tl.get(); //先从当前线程上取出连接实例??
??????????
??????? if(null == conn){ //如果当前线程上没有Connection的实例???
??????????? try {??
??????????????? conn = ds.getConnection(); // 从连接池中取出一个连接实例???
??????????????? tl.set(conn);? //把它绑定到当前线程上??
??????????? } catch (SQLException e) {??
??????????????? e.printStackTrace();??
??????????? }??
??????? }??
??????? return conn;??
??? }??
??? /**?
???? * 获取事务管理器?
???? * @return 事务管理实例?
???? */?
??? public static synchronized TransactionManager getTranManager(){??
??????? return new TransactionManager(getConnection());??
??? }??
??????
??? /**?
???? * 关闭数据库连接,并卸装线程绑定?
???? * @param