首页 > 代码库 > Java事务(三) - 使用ThreadLocal

Java事务(三) - 使用ThreadLocal

一. 为什么使用ThreadLocal:

在上一篇博文中, 我们通过传递Connection的方式来控制事务, 这种方法可以达到目的, 但让人看的不爽, 

如果涉及到调用多个service, 那我是不是还得从controller层传递Connection?


ThreadLocal的用法见上一篇博客, 该类保证一个类的实例变量在各个线程中都有一份单独的拷贝, 

从而不会影响其他线程中的实例变量


二. 如何使用ThreadLocal:

1. 写一个TransactionManager类:

/**
 * 管理事务
 */
public class TransactionManager {
	private static ThreadLocal<Connection> local = new ThreadLocal<Connection>();

	// 开启事务
	public static void beginTransaction() throws SQLException {
		Connection conn = JDBCUtils.getConnection();
		conn.setAutoCommit(false); 
		// 将连接存入threadLocal
		local.set(conn);
	}

	// 回滚事务
	public static void rollback() throws SQLException {
		Connection conn = local.get();
		if (conn != null) {
			conn.rollback();
			conn.close();
			// 清空threadLocal
			local.remove();
		}
	}

	// 提交事务
	public static void commitAndClose() throws SQLException {
		Connection conn = local.get();
		if (conn != null) {
			conn.commit();
			conn.close();
			// 清空threadLocal
			local.remove();
		}
	}

	// 获取数据库连接
	public static Connection getConnection() {
		return local.get();
	}
}
使用ThreadLocal, 确保相同的线程获取到的是同一个连接.


2.修改业务处理类

/**
 * 业务逻辑层
 */
public class AccountService {
	
	public void transfer(Account outAccount, Account inAccount, int money) throws SQLException {
		// 开启 事务 
		TransactionManager.beginTransaction();

		// 查询两个账户
		AccountDAO accountDAO = new AccountDAO();
		outAccount = accountDAO.findAccountById(outAccount.getId());
		inAccount = accountDAO.findAccountById(inAccount.getId());

		// 转账 - 修改原账户金额 
		outAccount.setMoney(outAccount.getMoney() - money);
		inAccount.setMoney(inAccount.getMoney() + money);

		try {
			// 更新账户金额
			accountDAO.update(outAccount);
			accountDAO.update(inAccount);
			
			// 转账成功, 提交事务
			TransactionManager.commitAndClose();
		} catch (Exception e) {
			// 转账失败, 回滚事务
			TransactionManager.rollback();
			e.printStackTrace();
		}
	}
}

使用TransactionManager来管理事务, 代码变得更加简洁.


 3. 修改Dao类

/**
 * DAO层: CRUD
 */
public class AccountDAO {
	// 查询账户
	public Account findAccountById(int id) throws SQLException {
		String sql = "select * from account where id = ?";
		Object[] params = {id};
		QueryRunner queryRunner = new QueryRunner(JDBCUtils.getDataSource());
		return queryRunner.query(sql, new BeanHandler<Account>(Account.class), params);
	}
	
	// 更新账户
	public void update(Account account) throws SQLException {
		String sql = "update account set name = ?, money = ? where id = ?";
		Object[] params = {account.getName(), account.getMoney(), account.getId()};
		
		// 从threadLocal中获取连接, 同一个线程拿到的是同一个连接
		Connection conn = TransactionManager.getConnection();
		QueryRunner queryRunner = new QueryRunner();
		queryRunner.update(conn, sql, params);
	}
}

不需要传递Connection, 直接从TransactionManager中获取连接.


三. 总结:

service和dao都是通过TransactionManager来获取Connection, 同一个线程中, 它们在整个事务处理过程中使用了相同的Connection对象, 所以事务会处理成功, dao中没有接受和业务无关的对象, 消除了api污染, 另外使用TransactionManager来管理事务, 使service层的代码变简洁了.


Java事务(三) - 使用ThreadLocal