日期:2014-05-16 浏览次数:20483 次
对目前的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