对于用JDBC操作数据库,使用Connection类的setAutoCommit(false)方法可以开启事务,用commit()方法可以提交事务。
由于三层架构的设计模型,事务的逻辑必须在service层,而dao层只是提供简单的CRUD操作,所以必须由service获取Connection并开启事务,将这个Connection传给dao层进行操作后,在service层将事务提交。
将Connection传给dao层可以在调用dao层方法(或构造函数)时以参数形式传入,但是这样不但会污染dao层的方法签名,并且对于多层的参数传递会使方法调用看起来又长又臭,而下面用ThreadLocal容器进行参数传递的方法就显得优雅得多了:
①对于Tomcat服务器,每个http请求分配到一个线程去处理,处理的线程可以是新建线程,也可以是从线程池中获取。而单个的请求是在同一个线程里完成的。
②ThreadLocal可以以当前线程为key存储一个对象,并以当前线程为key取出对应的对象。
在同一个线程中我们在service层将一个Connection开启事务并存到ThreadLocal中,代码运行到dao层的update方法时将ThreadLocal中的Connection获取到(其引用),用这个Connection操作数据库,方法返回到service层后我们从ThreadLocal中获取到Connection,提交事务,并清除ThreadLocal容器。
代码如下:
1.dao层代码:
package com.hao.utils; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; public class DaoUtils { private static DataSource ds; private static ThreadLocal<Connection> connectionThreadLocal = new ThreadLocal<>(); static { try { Context initCtx = new InitialContext(); Context envCtx = (Context) initCtx.lookup("java:comp/env"); ds = (DataSource) envCtx.lookup("jdbc/EmployeeDB"); } catch (NamingException e) { throw new ExceptionInInitializerError(e); } } //开启事务,将一个开启事务的链接绑定到线程上,这是给service层调用的 public static void startTransaction() { try { Connection conn = ds.getConnection(); conn.setAutoCommit(false); connectionThreadLocal.set(conn); } catch (SQLException e) { throw new RuntimeException(e); } } //获取事务链接,从线程上取出链接,这是给dao层用的 public static Connection getTransactionConnection() { return connectionThreadLocal.get(); } //提交事务,将Connection与线程解除绑定,这是给service层用的 public static void commitTransaction() { try { Connection conn = connectionThreadLocal.get(); conn.commit(); } catch (SQLException e) { throw new RuntimeException(e); } finally { //由于connectionThreadLocal是静态的,在服务器内加载后会一直存在,其中装载的东西一定要自己清空 connectionThreadLocal.remove(); } } }
package com.hao.dao; import com.hao.domain.Account; import com.hao.utils.DaoUtils; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; import java.sql.SQLException; public class AccountDao { private QueryRunner qr = new QueryRunner(); public void add(Account account) { String sql = "insert into account(id,name,money) values(?,?,?)"; try { qr.update(sql, account.getId(), account.getName(), account.getMoney()); } catch (SQLException e) { throw new RuntimeException(e); } } public void delete(String id) { String sql = "delete from account where id = ?"; try { qr.update(sql, id); } catch (SQLException e) { throw new RuntimeException(e); } } public void update(Account account) { String sql = "update account set name=?,money=? where id=?"; try { //更新时使用开启事务的链接 qr.update(DaoUtils.getTransactionConnection(), sql, account.getName(), account.getMoney(), account.getId()); } catch (SQLException e) { throw new RuntimeException(e); } } public Account find(String id) { String sql = "select name,money from account where id=?"; try { Account account = qr.query(sql, new BeanHandler<Account>(Account.class), id); account.setId(id); return account; } catch (SQLException e) { throw new RuntimeException(e); } } }
2.service层代码:
package com.hao.service; import com.hao.dao.AccountDao; import com.hao.domain.Account; import com.hao.utils.DaoUtils; public class BusinessService { public void transfer(String sourceId, String targetId, double money) { //开启事务 DaoUtils.startTransaction(); //开始转账 AccountDao dao = new AccountDao(); Account sourceAccount = dao.find(sourceId); sourceAccount.setMoney(sourceAccount.getMoney() - money); dao.update(sourceAccount); Account targetAccount = dao.find(targetId); targetAccount.setMoney(targetAccount.getMoney() + money); dao.update(targetAccount); //提交事务 DaoUtils.commitTransaction(); //注意以上操作是在同一个线程中的 } }