对目前的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事务中把数据库连接绑定到线程,以保证在同一个线程下获得的都是同一个连接。这样就达到目的了。
如下数据库工具类:
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 conn 要关闭数据库连接实例
* @throws DaoException
*/
protected static void close(Connection conn) throws DaoException{
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
throw new DaoException("关闭连接时出现异常",e);
} finally {
tl.remove(); //卸装线程绑定
}
}
}
}
如下事务管理器类:
package com.tjitcast.common;
import java.sql.Connection;
import java.sql.SQLException;
import com.tjitcast.dao.DaoException;
/**
* 事务管理器
* @author qiujy
* @version 0.9Beta
*/
public class TransactionManager {
private Connection conn;
protected TransactionManager(Connection conn) {
this.conn = conn;
}
/** 开启事务 */
public void beginTransaction() throws DaoException{
try {
conn.setAutoCommit(false); //把事务提交方式改为手工提交
} catch (SQLException e) {
throw new DaoException("开户事务时出现异常",e);
}
}
/** 提交事务并关闭连接 */
public void commitAndClose() throws DaoException{
try {
conn.commit(); //提交事务
} catch (SQLException e) {
throw new DaoException("提交事务时出现异常",e);
}finally{
DbUtils.close(conn);
}
}
/** 回滚并关闭连接 */
public void rollbackAndClose()throws DaoException{
try {
conn.rollback();
} catch (SQLException e) {
throw new DaoException("回滚事务时出现异常",e);
}finally{
DbUtils.close(conn);
}
}
}
package com.tjitcast.service;
import java.util.List;
import com.tjitcast.common.DbUtils;
import com.tjitcast.common.TransactionManager;
import com.tjitcast.dao.DaoException;
import com.tjitcast.dao.DaoFactory;
import com.tjitcast.dao.DeptDao;
import com.tjitcast.dao.EmployeeDao;
import com.tjitcast.entity.Dept;
import com.tjitcast.entity.Employee;
import com.tjitcast.entity.PageModel;
/**
* 业务层门面 --> 添加事务控制
* @author qiujy
*/
public class ServiceFacade {
private DeptDao deptDao = DaoFactory.getInstance("deptDao", DeptDao.class);
private EmployeeDao empDao = DaoFactory.getInstance("empDao", EmployeeDao.class);
/**
* 新增部门
* @param dept
*/
public void insertDept(Dept dept){
TransactionManager tx = DbUtils.getTranManager();
try{
tx.beginTransaction();
deptDao.insert(dept);
tx.commitAndClose();
}catch (DaoException e) {
tx.rollbackAndClose();
}
}
/**
* 新增员工
* @param emp 员工
*/
public void insertEmp(Employee emp){
TransactionManager tx = DbUtils.getTranManager();
try{
tx.beginTransaction();
empDao.insert(emp);
tx.commitAndClose();
}catch (DaoException e) {
tx.rollbackAndClose();
}
}
/**
* 获取所有部门的列表
* @return 部门列表
*/
public List<Dept> getDeptList(){
List<Dept> list = null;
TransactionManager tx = DbUtils.getTranManager();
try{
tx.beginTransaction();
list = deptDao.getDeptList();
tx.commitAndClose();
}catch (DaoException e) {
e.printStackTrace();
tx.rollbackAndClose();
}
return list;
}
/**
* 获取指定部门下的员工分页列表
* @param deptId
* @param pageNo
* @param pageSize
* @return 符合条件的PageModel
*/
public PageModel<Employee> getEmpListByDeptId(int deptId, int pageNo, int pageSize){
PageModel<Employee> pm = null;
TransactionManager tx = DbUtils.getTranManager();
try{
tx.beginTransaction();
pm = empDao.getEmpListByDeptId(deptId, pageNo, pageSize);
tx.commitAndClose();
}catch (DaoException e) {
tx.rollbackAndClose();
}
return pm;
}
/**
* 删除指定ID的部门
* @param id 部门ID
*/
public void deleteDept(int id){
TransactionManager tx = DbUtils.getTranManager();
try{
tx.beginTransaction();
empDao.deleteByDeptId(id); //先删除指定ID部门下的所有员工
deptDao.delete(id); //再删除该部门
tx.commitAndClose();
}catch (DaoException e) {
tx.rollbackAndClose();
}
}
}
具体的示例代码结构如下(Eclipse工程):
原文 :
分层架构下的纯JDBC事务控制简单解决方案