在项目框架中,如需要在MVC设计模式基础上进一步地将业务进行分离,,Dao层只能是对数据库进行操作,不能涉及任何的业务上的处理,所以事务不应该出现在Dao层中,只能在Service层进行业务处理,但是事务的结束和开始都需要Connection对象,而Connection对象又只能出现在Dao层,为了线程安全,这里在JDBCUtils工具类要对事务进行处理。使用ThreadLocal对象,根据业务逻辑进行判断,使在一个事务中保证是同一个连接。
ThreadLocal:线程本地变量,也就是ThreadLocal为变量在每个线程都创建了一个副本,这样每个线程都可以访问自己内部的副本变量。
ThreadLocal类常用的方法
public T get(){}:获取值
public void set(T value):保存值
public void remove();移除值
c3p0配置文件(使用c3p0连接池连接数据库) 详细配置可前往Java使用c3p0连接数据库
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="user">root</property>
<property name="password">12345</property>
<!—连接池没有空闲连接数时,一次性创建的新连接数 默认值 3 -->
<property name="acquireIncrement">3</property>
<!—连接池初始化时创建的连接数 -->
<property name="initialPoolSize">50</property>
<!-- 连接池保持最少的连接数 -->
<property name="minPoolSize">2</property>
<!—连接池拥有的最大连接数 -->
<property name="maxPoolSize">60</property>
</default-config>
</c3p0-config>
<!—使用命名配置-->
<!—省略了部分代码 -->
JDBCUtils工具类
public class JDBCUtils {
//使用配置文件的默认配置 所以必须给出c3p0-config.xml
private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
//事务Connection
private static ThreadLocal<Connection> tlConn = new ThreadLocal<Connection>();
//使用连接池返回一个连接对象
public static Connection getConnection() throws SQLException {
Connection conn= tlConn.get();//获取当前线程的连接
if(conn!=null) return conn;//conn(事务connection)不为空,说明调用过beginTransaction方法,返回conn
return dataSource.getConnection();
}
public static DataSource getDataSource() {//返回连接池对象
return dataSource;
}
/*开启事务 */
public static void beginTransaction() throws SQLException {
Connection conn = tlConn.get();
if(conn!=null) throw new SQLException("事务已开启,不要重复开启事务");
conn= getConnection();
conn.setAutoCommit(false);
tlConn.set(conn);//把当前线程的连接保存起来
}
/*提交事务*/
public static void commitTransaction() throws SQLException {
Connection conn = tlConn.get();
if(conn==null) throw new SQLException("还没有开启事务,不能提交事务");
conn.commit();//提交事务
conn.close();//稀放连接
/*把conn设置为null,表示事务已经结束,下次再使用getConnection方法获取的Connection时
* 如果不调用beginTransaction方法(开启事务方法),那获取的就是连接池的Connection*/
tlConn.remove();//从TlConn移除连接
}
/*回滚
* 获取beginTransaction提供的Connection 调用rollback方法
* */
public static void rollbackTranscation() throws SQLException {
Connection conn = tlConn.get();
if(conn==null) throw new SQLException("还没有开启事务,不能回滚事务");
conn.rollback();
conn.close();
tlConn.remove();
}
/*释放链接 **/
public static void releaseConnection(Connection connection) throws SQLException {
Connection conn= tlConn.get();
//如果conn等于null,表示没有事务,那么Connection那不是事务链接
if(conn==null) connection.close();
//如果conn不为空,表示有事务,判断参数链接是否与事务链接相等,不等则释放
if(conn!=connection) connection.close();
}}
上面的方法对事务的连接以及不是事务的连接都进行相应处理,为了并发安全,事务的Connection使用ThreadLocal对象。
编写beginTransaction方法(开启事务方法):在该方法里面获取当前线程的Connection,设置该Connection的setAutoCommit为false,表示手动提交,再把当前线程保存起来。而commitTranscation()(提交事务方法):该方法获取从beginTransaction()提供的Connection,调用commit方法后再将该连接从TlConn中移除。
rollbackTransction() 回滚 获取从beginTransaction()提供的Connection 调用rollback方法进行回滚
releaseConnection() 释放连接 判断该连接释放是事务连接,如果是就不稀放,如果不是就稀放,判断参数连接和当前连接是否相等,不等就稀放。
例:转账业务:如下图,小红、小明各有1000元,小明向小红转账100元。小明的余额需要减少100,而小红的余额在原来的基础增加100,这两个操作要保证在同一个事务中,这样才不会有安全问题,如在转账过程中出现任何异常将进行事务回滚。
Dao层 数据库操作,使用到了增强的QueryRunner,该类是封装了自己提供的连接以及对连接的关闭操作,减少了代码的臃肿。
public static void updata(String name,double money) throws SQLException {
QueryRunner qr = new TxQueryRunner();
String sql= "update tab_account set account_money=account_money+? where account_name=?";
Object[] params = {money,name};
qr.update( sql,params);}
业务层(转账业务操作):调用JDBCUtils工具类开启事务,进行转账操作,提交事务,若抛异常则回滚事务。
public void transferAccounts() {
try {
JDBCUtils.beginTransaction();
AccountDao.updata("小明", -100);
AccountDao.updata("小红", 100);
JDBCUtils.commitTransaction();
} catch (SQLException e) {
try {
JDBCUtils.rollbackTranscation();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
}
效果:若不抛异常,转账成功,余额该减少的减少,该增加的增加
若转账过程中抛出异常,则回滚事务,回滚到原来的修改之前的余额。
public void transferAccounts() {
try {
JDBCUtils.beginTransaction();
AccountDao.updata("小明", -100);
if(true) throw new RuntimeException();//手动抛出异常,
AccountDao.updata("小红", 100);
JDBCUtils.commitTransaction();
} catch (SQLException e) {
try {
JDBCUtils.rollbackTranscation();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
}