转账案例
需求:使用 spring 框架整合 DBUtils 技术,实现用户转账功能
基础功能
步骤分析:
- 创建 java 项目,导入坐标
- 编写 Account 实体类
- 编写 AccountDao 接口和实现类
- 编写 AccountService 接口和实现类
- 编写 spring 核心配置文件
- 编写测试代码
创建 java 项目,导入坐标
mysql mysql-connector-java 5.1.47com.alibaba druid 1.1.15 commons-dbutils commons-dbutils 1.6 org.springframework spring-context 5.1.5.RELEASE org.springframework spring-test 5.1.5.RELEASE junit junit 4.12 org.aspectj aspectjweaver 1.8.13
编写 AccountDao 接口和实现类
public interface AccountDao { /** * 转出操作 * @param outUser * @param money */ void out(String outUser,Double money); /** * 转入操作 * @param inUser * @param money */ void in(String inUser,Double money);}@Repository("AccountDao")public class AccountDaoImpl implements AccountDao { @Autowired private QueryRunner queryRunner; @Override public void out(String outUser, Double money) { String sql = "update account set money = money - ? where name = ?"; try { queryRunner.update(sql, money, outUser); } catch (SQLException throwables) { throwables.printStackTrace(); } } @Override public void in(String inUser, Double money) { String sql = "update account set money = money + ? where name = ?"; try { queryRunner.update(sql, money, inUser); } catch (SQLException throwables) { throwables.printStackTrace(); } }}
编写 AccountService 接口和实现类
public interface AccountService { /** * 转账方法 */ void transfer(String outUser,String inUser,Double money);}@Service("accountService")public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; /** * 转账方法 */ @Override public void transfer(String outUser, String inUser, Double money) { // 编写了事务相关代码 // 调用了减钱方法 accountDao.out(outUser,money); // 模拟出错 // int i= 1/0; // 调用了加钱方法 accountDao.in(inUser,money); }}
编写 spring 核心配置文件
<?xml version="1.0" encoding="UTF-8"?>
编写测试代码
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration({"classpath:applicationContext.xml"})public class AccountServiceTest { @Autowired private AccountService accountService; @Test public void testTransfer() { accountService.transfer("tom", "jerry", 100d); }}
问题分析
上面的代码事务在 Dao 层,转出转入操作都是一个独立的事务,但实际开发,应该把业务逻辑控制在一个事务中,所以应该将事务挪到 Service 层。
传统事务
步骤分析:
- 编写线程绑定工具类
- 编写事务管理器
- 修改 service 层代码
- 修改 dao 层代码
编写线程绑定工具类
@Componentpublic class ConnectionUtils { @Autowired private DataSource dataSource; private ThreadLocal threadLocal = new ThreadLocal<>(); /** * 获取当前线程上绑定连接:如果获取到的连接为空,那么就要从数据源中获取连接,并且放到 ThreadLocal 中(绑定到当前线程) */ public Connection getThreadConnection() { // 1.先从 ThreadLocal 上获取连接 Connection connection = threadLocal.get(); // 2.判断当前线程中是否是有 Connection if(connection == null){ // 3.从数据源中获取一个连接,并且存入 ThreadLocal 中 try { // 不为 null connection = dataSource.getConnection(); threadLocal.set(connection); } catch (SQLException e) { e.printStackTrace(); } } return connection; } /** * 解除当前线程的连接绑定 */ public void removeThreadConnection(){ threadLocal.remove(); }}
编写事务管理器
@Component("transactionManager")public class TransactionManager { @Autowired private ConnectionUtils connectionUtils; /** * 开启事务 */ public void beginTransaction(){ // 获取 connection 对象 Connection connection = connectionUtils.getThreadConnection(); try { // 开启了一个手动事务 connection.setAutoCommit(false); } catch (SQLException e) { e.printStackTrace(); } } /** * 提交事务 */ public void commit(){ Connection connection = connectionUtils.getThreadConnection(); try { connection.commit(); } catch (SQLException e) { e.printStackTrace(); } } /** * 回滚事务 */ public void rollback(){ Connection connection = connectionUtils.getThreadConnection(); try { connection.rollback(); } catch (SQLException e) { e.printStackTrace(); } } /** * 释放资源 */ public void release(){ // 将手动事务改回成自动提交事务 Connection connection = connectionUtils.getThreadConnection(); try { connection.setAutoCommit(true); // 将连接归还到连接池 connectionUtils.getThreadConnection().close(); // 解除线程绑定 connectionUtils.removeThreadConnection();