对目前的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工程):
原文 :