对于用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 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.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();
//注意以上操作是在同一个线程中的
}
}