日期:2014-05-20  浏览次数:20923 次

抛弃框架,如何实现分层架构下JDBC事务的控制
现在很多项目,特别是web项目,为了提高开发效率,基本上都用上了框架,struts1,struts2.,spring,hibernate,springmvc,ibatise等等,在事务处理方面,spring的尤其突出,它对事务做了很好的封装,通过AOP的配置,可以灵活的配置在任何一层。但是很多时候,基于需求和应用方面考虑,直接使用JDBC进行事务控制还是很有必要的。
  事务应该是以业务逻辑为基础的;一个完整的业务应该对应业务层里的一个方法,而不应该是多个方法;如果业务执行过程出现异常,则整个事务应该回滚;所以,应该事务控放在业务层里;然而持久层的设计应该遵循一个很重要的原则:持久层应该保证操作的原子性,就是说持久层里的每个方法都应该是不可以分割的,也就是说数据库的连接自始至终都应该是同一个连接,而不是执行完某个Dao操作完毕并且数据库连接关闭后又重新打开一个新的数据库连接执行另一个Dao操作!
  上面的说法可能有点抽象,举个简单的例子来说:针对班级(clazze)和学生(student)的操作,要想删除某个班级,就需要先把该班级下的所有学生删除,再删除班级,这两个操作是应该放在同一个事务里的,要么同时删除学生和班级成功,要么同时失败,不能学生的数据被删除没了,而班级却没被删除,这就是上面所说的----原子性。
  可能上面的描述还是有点抽象,没关系,我们用代码说话。
先定义两个Dao接口,一个是班级的接口(ClazzeDao),一个是学生的接口(StudentDao),里面只提供删除的功能。
ClazzeDao:
Java code

/**
 * FileName:     ClazzeDao.java
 * CreationTime: 2011-8-14
 * Author:       yjd
 * EMail:        908599713@qq.com
 * Site:         http://hi.csdn.net/tjcyjd
 */
package com.tjcyjd.dao;

/**
 * 班级接口
 * 
 * @author yjd
 */
public interface ClazzeDao {
    /** 根据id删除对应的班级 */
    public void deleteClazzeByClazzeId(int clazzeId) throws DaoException;

}



StudentDao:
Java code

/**
 * FileName:     StudentDao.java
 * CreationTime: 2011-8-14
 * Author:       yjd
 * EMail:        908599713@qq.com
 * Site:         http://hi.csdn.net/tjcyjd
 */
package com.tjcyjd.dao;

/**
 * 学生接口
 * 
 * @author yjd
 */
public interface StudentDao {
    /** 根据班级id删除该班级下的所有学生 */
    public void deleteStudentByClazzeId(int clazzeId) throws DaoException;
}


定义完这两个Dao接口以后,应该是在对应的业务层(ClazzeService)中的删除班级的方法里调用这两个方法的。这样就把它们放在同一个事务中了。在调用前,我们还得做点事,弄个数据库连接工厂类(ConnectionFactory)和事务管理器类(TransactionManager)。
ConnectionFactory:
Java code

/**
 * FileName:     ConnectionFactory.java
 * CreationTime: 2011-8-14
 * Author:       yjd
 * EMail:        908599713@qq.com
 * Site:         http://hi.csdn.net/tjcyjd
 */
package com.tjcyjd.commom;

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;

/**
 * 数据库连接工厂
 * 
 * @author yjd
 */
public class ConnectionFactory {
    private static Properties prop = new Properties();
    // 数据源
    private static DataSource ds = null;
    // 用来把数据库连接绑定到当前线程上的变量
    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();
        }
    }

    /**
     * 获取数据库的Connection对象
     * 
     * @return
     */
    public static synchronized Connection getConnection() {
        Connection conn = tl.get(); // 当前线程取出连接实例
        if (null == conn) {
            try {
                conn = ds.getConnection(); // 从连接池中取出一个连接实例
                tl.set(conn); // 把它绑定到当前线程上
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return conn;
    }

    public static synchronized TransactionManager getTranManager() {
        return new TransactionManager(getConnection());
    }
}