Spring 模拟转账开发实战

一、转账业务场景分析

转账是金融类应用的核心场景之一,涉及付款方扣减金额收款方增加金额两个关键操作。在开发中需解决以下问题:

  • 业务层与数据层解耦:通过分层架构(Service 层调用 Dao 层)实现逻辑分离。
  • 数据库事务管理:确保两个操作要么同时成功,要么同时失败,避免资金不一致。
  • 代码复用与简化:利用 Spring 框架的模板类和依赖注入机制,减少样板代码。

二、基于 Spring 的转账业务开发(第一种方式)

1. 分层架构设计

(1)Service 层:定义业务逻辑

java 复制代码
// 接口:声明转账方法
public interface AccountService {
    void pay(String out, String in, double money); // 付款人、收款人、金额
}

// 实现类:调用Dao完成转账
public class AccountServiceImpl implements AccountService {
    private AccountDao accountDao; // 注入Dao层对象

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Override
    public void pay(String out, String in, double money) {
        accountDao.outMoney(out, money); // 付款方扣款
        accountDao.inMoney(in, money); // 收款方加款
    }
}
(2)Dao 层:操作数据库
java 复制代码
// 接口:定义数据库操作
public interface AccountDao {
    void outMoney(String out, double money); // 扣款
    void inMoney(String in, double money); // 加款
}

// 实现类:使用JdbcTemplate操作数据库
public class AccountDaoImpl implements AccountDao {
    private JdbcTemplate jdbcTemplate; // 注入JdbcTemplate

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public void outMoney(String out, double money) {
        jdbcTemplate.update("update account set money=money-? where name=?", money, out);
    }

    @Override
    public void inMoney(String in, double money) {
        jdbcTemplate.update("update account set money=money+? where name=?", money, in);
    }
}

2. Spring 配置文件(applicationContext_dao1.xml)

XML 复制代码
<beans ...>
    <!-- 加载数据库配置 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    
    <!-- 配置Druid连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    
    <!-- 配置JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <!-- 配置Service和Dao,注入依赖 -->
    <bean id="accountService" class="com.qcbyjy.demo2.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"/>
    </bean>
    
    <bean id="accountDao" class="com.qcbyjy.demo2.AccountDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
</beans>

3. 测试代码

java 复制代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext_dao1.xml")
public class Demo2 {
    @Autowired
    private AccountService accountService;

    @Test
    public void testPay() {
        accountService.pay("熊大", "熊二", 100); // 模拟熊大给熊二转账100元
    }
}

三、Dao 层的简化写法(第二种方式)

1. 继承 JdbcDaoSupport

Spring 提供JdbcDaoSupport抽象类,内置JdbcTemplate管理,可简化 Dao 层代码:

java 复制代码
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
    @Override
    public void outMoney(String out, double money) {
        // 通过getJdbcTemplate()获取模板对象
        this.getJdbcTemplate().update("update account set money=money-? where name=?", money, out);
    }

    @Override
    public void inMoney(String in, double money) {
        this.getJdbcTemplate().update("update account set money=money+? where name=?", money, in);
    }
}

2. Spring 配置优化

无需显式配置JdbcTemplate,直接注入数据源(dataSource)即可:

java 复制代码
<bean id="accountDao" class="com.qcbyjy.demo3.AccountDaoImpl">
    <property name="dataSource" ref="dataSource"/> <!-- 注入数据源 -->
</bean>

原理JdbcDaoSupport会自动根据dataSource创建JdbcTemplate,避免手动绑定模板对象。

四、转账业务的事务管理(关键!)

为什么需要事务?

假设转账过程中,付款方扣款成功后系统崩溃,收款方未加款,会导致资金丢失。事务确保两个操作要么都成功,要么都回滚

1. 未加事务的问题演示

  • 场景:付款方余额不足时,扣款操作抛出异常,但收款方已加款。
  • 代码问题:默认情况下,Spring 的JdbcTemplate操作是自动提交的,异常不会回滚。

2. 添加事务控制

(1)在 Spring 配置中启用事务管理

XML 复制代码
<beans ...>
    <!-- 配置事务管理器 -->
    <bean id="transactionManager" 
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/> <!-- 绑定数据源 -->
    </bean>

    <!-- 启用注解事务 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

(2)在 Service 方法上添加@Transactional注解

java 复制代码
@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    // 声明该方法需要事务支持
    @Transactional
    @Override
    public void pay(String out, String in, double money) {
        accountDao.outMoney(out, money); // 若此处抛出异常,两个操作都会回滚
        accountDao.inMoney(in, money);
    }
}

3. 事务关键特性

  • 原子性:两个更新操作视为一个不可分割的整体。
  • 隔离性:避免其他事务看到中间状态(通过数据库隔离级别实现)。
  • 传播行为 :默认REQUIRED(当前有事务则加入,无则创建新事务)。

五、两种 Dao 实现方式对比

六、总结与最佳实践

核心流程总结

  1. 分层设计:Service 层处理业务逻辑,Dao 层封装数据库操作。
  2. 依赖注入:通过 Spring 配置文件管理对象依赖,避免硬编码。
  3. 事务控制 :使用@Transactional注解确保数据一致性,必须配置事务管理器。
  4. 连接池优化:优先使用 Druid 等开源连接池,提升数据库性能。

常见问题与解决方案

  • 事务不生效:检查是否配置事务管理器、注解是否添加在 public 方法上、是否使用了代理对象。
  • SQL 注入风险 :使用JdbcTemplate的参数化查询(?占位符),避免拼接 SQL。
  • 异常处理 :在 Service 层捕获异常并按需回滚(通过TransactionAspectSupport.currentTransactionStatus().setRollbackOnly())。
相关推荐
旷野说2 分钟前
为什么 MyBatis 原生二级缓存“难以修复”?
java·java-ee·mybatis
8***23555 分钟前
【wiki知识库】07.用户管理后端SpringBoot部分
java
bcbnb6 分钟前
iOS 性能测试的工程化方法,构建从底层诊断到真机监控的多工具测试体系
后端
开心就好20259 分钟前
iOS 上架 TestFlight 的真实流程复盘 从构建、上传到审核的团队协作方式
后端
小周在成长17 分钟前
Java 泛型支持的类型
后端
aiopencode18 分钟前
Charles 抓不到包怎么办?HTTPS 抓包失败、TCP 数据流异常与底层补抓方案全解析
后端
阿蔹21 分钟前
JavaWeb-Selenium 配置以及Selenim classnotfound问题解决
java·软件测试·python·selenium·测试工具·自动化
稚辉君.MCA_P8_Java22 分钟前
Gemini永久会员 C++返回最长有效子串长度
开发语言·数据结构·c++·后端·算法
Penge66641 分钟前
Redis-bgsave浅析
redis·后端
阿白的白日梦1 小时前
Windows下c/c++编译器MinGW-w64下载和安装
c语言·后端