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

http://blog.csdn.net/qjyong/article/details/5464835

对目前的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事务中把数据库连接绑定到线程,以保证在同一个线程下获得的都是同一个连接。这样就达到目的了。

 

如下数据库工具类:

 

[java] view plaincopy
 
  1. package com.tjitcast.common;  
  2. import java.io.IOException;  
  3. import java.sql.Connection;  
  4. import java.sql.SQLException;  
  5. import java.util.Properties;  
  6. import javax.sql.DataSource;  
  7. import com.mchange.v2.c3p0.DataSources;  
  8. import com.tjitcast.dao.DaoException;  
  9. /** 
  10.  *  数据库工具类 
  11.  *  可以根据classpath下配置文件jdbc.properties中配置的参数来获取数据库连接并绑定到当前线程上 
  12.  *  可以获取JDBC的事务管理器 
  13.  * @author qiujy 
  14.  * @version 0.9Beta 
  15.  */  
  16. public class DbUtils {  
  17.     private static Properties prop = new Properties();  
  18.     /** 数据源 */  
  19.     private static DataSource ds = null;   
  20.       
  21.     //用来把Connection绑定到当前线程上的变量  
  22.     private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();  
  23.     static{  
  24.         try {  
  25.             prop.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("jdbc.properties"));  
  26.         } catch (IOException e) {  
  27.             e.printStackTrace();  
  28.             System.out.println("在classpath下没有找到jdbc.properties文件");  
  29.         }  
  30.           
  31.         //使用C3P0连接池技术  
  32.         try {  
  33.             Class.forName("com.mysql.jdbc.Driver");  
  34.               
  35.             DataSource unpooled = DataSources.unpooledDataSource(  
  36.                     prop.getProperty("url"),  
  37.                     prop.getProperty("user"),  
  38.                     prop.getProperty("password"));  
  39.             ds = DataSources.pooledDataSource(unpooled);  
  40.               
  41.         } catch (ClassNotFoundException e) {  
  42.             e.printStackTrace();  
  43.         } catch (SQLException e) {  
  44.             e.printStackTrace();  
  45.         }  
  46.     }  
  47.       
  48.     private DbUtils(){}  
  49.       
  50.     /** 
  51.      * 根据数据库的默认连接参数获取数据库的Connection对象,并绑定到当前线程上 
  52.      * @return 成功,返回Connection对象,否则返回null 
  53.      */  
  54.     public static synchronized Connection getConnection(){  
  55.         Connection conn = tl.get(); //先从当前线程上取出连接实例  
  56.           
  57.         if(null == conn){ //如果当前线程上没有Connection的实例   
  58.             try {  
  59.                 conn = ds.getConnection(); // 从连接池中取出一个连接实例   
  60.                 tl.set(conn);  //把它绑定到当前线程上  
  61.             } catch (SQLException e) {  
  62.                 e.printStackTrace();  
  63.             }  
  64.         }  
  65.         return conn;  
  66.     }  
  67.     /** 
  68.      * 获取事务管理器 
  69.      * @return 事务管理实例 
  70.      */  
  71.     public static synchronized TransactionManager getTranManager(){  
  72.         return new TransactionManager(getConnection());  
  73.     }  
  74.       
  75.     /** 
  76.      * 关闭数据库连接,并卸装线程绑定 
  77.      * @param conn 要关闭数据库连接实例 
  78.      * @throws DaoException  
  79.      */  
  80.     protected static void close(Connection conn) throws DaoException{  
  81.         if(conn != null){  
  82.             try {  
  83.                 conn.close();  
  84.             } catch (SQLException e) {  
  85.                 throw new DaoException("关闭连接时出现异常",e);  
  86.             } finally {  
  87.                     tl.remove(); //卸装线程绑定  
  88.             }  
  89.         }  
  90.     }  
  91.       
  92. }  

 

 

如下事务管理器类:

 

[java] view plaincopy
 
  1. package com.tjitcast.common;  
  2. import java.sql.Connection;  
  3. import java.sql.SQLException;  
  4. import com.tjitcast.dao.DaoException;  
  5. /** 
  6.  * 事务管理器 
  7.  * @author qiujy 
  8.  * @version 0.9Beta 
  9.  */  
  10. public class TransactionManager {  
  11.     private Connection conn;  
  12.       
  13.     protected TransactionManager(Connection conn) {  
  14.         this.conn = conn;  
  15.     }  
  16.       
  17.     /** 开启事务 */  
  18.     public void beginTransaction() throws DaoException{  
  19.         try {  
  20.             conn.setAutoCommit(false);  //把事务提交方式改为手工提交  
  21.         } catch (SQLException e) {  
  22.             throw new DaoException("开户事务时出现异常",e);  
  23.         }  
  24.     }  
  25.       
  26.     /** 提交事务并关闭连接 */  
  27.     public void commitAndClose() throws DaoException{  
  28.         try {  
  29.             conn.commit(); //提交事务  
  30.         } catch (SQLException e) {  
  31.             throw new DaoException("提交事务时出现异常",e);  
  32.         }finally{  
  33.             DbUtils.close(conn);  
  34.         }  
  35.     }  
  36.       
  37.     /** 回滚并关闭连接 */  
  38.     public void rollbackAndClose()throws DaoException{  
  39.         try {  
  40.             conn.rollback();  
  41.         } catch (SQLException e) {  
  42.             throw new DaoException("回滚事务时出现异常",e);  
  43.         }finally{  
  44.             DbUtils.close(conn);  
  45.         }  
  46.     }  
  47. }  

 

 

如下业务层类:

 

[java] view plaincopy
 
  1. package com.tjitcast.service;  
  2. import java.util.List;  
  3. import com.tjitcast.common.DbUtils;  
  4. import com.tjitcast.common.TransactionManager;  
  5. import com.tjitcast.dao.DaoException;  
  6. import com.tjitcast.dao.DaoFactory;  
  7. import com.tjitcast.dao.DeptDao;  
  8. import com.tjitcast.dao.EmployeeDao;  
  9. import com.tjitcast.entity.Dept;  
  10. import com.tjitcast.entity.Employee;  
  11. import com.tjitcast.entity.PageModel;  
  12. /** 
  13.  * 业务层门面  --> 添加事务控制 
  14.  * @author qiujy 
  15.  */  
  16. public class ServiceFacade {  
  17.     private DeptDao deptDao = DaoFactory.getInstance("deptDao", DeptDao.class);  
  18.     private EmployeeDao empDao  = DaoFactory.getInstance("empDao", EmployeeDao.class);  
  19.       
  20.     /** 
  21.      * 新增部门 
  22.      * @param dept 
  23.      */  
  24.     public void insertDept(Dept dept){  
  25.         TransactionManager tx = DbUtils.getTranManager();  
  26.         try{  
  27.             tx.beginTransaction();  
  28.               
  29.             deptDao.insert(dept);  
  30.               
  31.             tx.commitAndClose();  
  32.         }catch (DaoException e) {  
  33.             tx.rollbackAndClose();  
  34.         }  
  35.     }  
  36.       
  37.     /** 
  38.      * 新增员工 
  39.      * @param emp 员工 
  40.      */  
  41.     public void insertEmp(Employee emp){  
  42.         TransactionManager tx = DbUtils.getTranManager();  
  43.         try{  
  44.             tx.beginTransaction();  
  45.               
  46.             empDao.insert(emp);  
  47.               
  48.             tx.commitAndClose();  
  49.         }catch (DaoException e) {  
  50.             tx.rollbackAndClose();  
  51.         }  
  52.     }  
  53.       
  54.     /** 
  55.      * 获取所有部门的列表 
  56.      * @return 部门列表 
  57.      */  
  58.     public List<Dept> getDeptList(){  
  59.         List<Dept> list = null;  
  60.           
  61.         TransactionManager tx = DbUtils.getTranManager();  
  62.         try{  
  63.             tx.beginTransaction();  
  64.               
  65.             list = deptDao.getDeptList();  
  66.               
  67.             tx.commitAndClose();  
  68.         }catch (DaoException e) {  
  69.             e.printStackTrace();  
  70.             tx.rollbackAndClose();  
  71.         }  
  72.           
  73.         return list;  
  74.     }  
  75.       
  76.     /** 
  77.      * 获取指定部门下的员工分页列表 
  78.      * @param deptId 
  79.      * @param pageNo 
  80.      * @param pageSize 
  81.      * @return 符合条件的PageModel 
  82.      */  
  83.     public PageModel<Employee> getEmpListByDeptId(int deptId, int pageNo, int pageSize){  
  84.         PageModel<Employee> pm = null;   
  85.         TransactionManager tx = DbUtils.getTranManager();  
  86.         try{  
  87.             tx.beginTransaction();  
  88.               
  89.             pm = empDao.getEmpListByDeptId(deptId, pageNo, pageSize);  
  90.               
  91.             tx.commitAndClose();  
  92.         }catch (DaoException e) {  
  93.             tx.rollbackAndClose();  
  94.         }  
  95.         return pm;  
  96.     }  
  97.       
  98.     /** 
  99.      * 删除指定ID的部门 
  100.      * @param id 部门ID 
  101.      */  
  102.     public void deleteDept(int id){  
  103.           
  104.         TransactionManager tx = DbUtils.getTranManager();  
  105.         try{  
  106.             tx.beginTransaction();  
  107.               
  108.             empDao.deleteByDeptId(id); //先删除指定ID部门下的所有员工  
  109.             deptDao.delete(id);  //再删除该部门  
  110.               
  111.             tx.commitAndClose();  
  112.         }catch (DaoException e) {  
  113.             tx.rollbackAndClose();  
  114.         }  
  115.     }  
  116. }  

 

 

具体的示例代码结构如下(Eclipse工程):

posted @ 2015-06-01 13:41  长风傲天  阅读(290)  评论(0编辑  收藏  举报
[h1[ [B]天涯雪[/B] [/h1]