07.spring实战—转账案例

169 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第17天,点击查看活动详情

  • 📝 个人主页:程序员阿红🔥
  • 🎉 支持我:点赞👍收藏⭐️留言📝
  • 🍓欢迎大家关注哦,互相学习🍓
  • 🍋欢迎大家访问哦,互相学习🍋
  • 🍑欢迎大家收藏哦,互相学习🍑

I.转账案例-引入事务

需求:使用Spring框架整合DBUtils技术,实现转账功能。

步骤分析:

  1. 创建java项目,导入坐标
  2. 编写Account实体类
  3. 编写AccountDao接口和实现类
  4. 编写AccountService接口和实现类
  5. 编写spring核心配置文件
  6. 编写测试代码

1.创建项目,导入依赖

 <dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.15</version>
    </dependency>

    <dependency>
        <groupId>commons-dbutils</groupId>
        <artifactId>commons-dbutils</artifactId>
        <version>1.6</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.13</version>
    </dependency>
    </dependencies>

2.编写Account实体类

public class Account {
  private Integer id;
  private String name;
  private Double money;
 
  // setter getter....
}

3.编写AccountDao接口和实现类

public interface AccountDao {
//    转出操作
     void out(String  outUser,Double money);

//    转入操作
    void in(String outUser , Double money);
}
@Repository("accountDao") //使用在dao层实例化bean对象;()里不写默认是该类的名字,首字母小写accountDaoImpl
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 e) {
            e.printStackTrace();
        }
    }

    @Override
    public void in(String outUser, Double money) {
        String sql = " update account set money = money + ? where name = ? ";
        try {
            queryRunner.update(sql,money,outUser);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

4.编写AccountService接口和实现类

public interface AccountService {

//转账方法
    public void transfer(String name1 , String name2 , Double money);
}

@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Override
    public void transfer(String name1, String name2,Double money) {
        //一个账号转出资金
        accountDao.out(name1,money);
        //另一个账号资金就增加
        accountDao.in(name2 ,money );
    }
}

5.编写Spring核心配置文件

<!--    开启注解扫描-->
    <context:component-scan base-package="com.lagou"></context:component-scan>

<!--    加载jdbc配置文件 -->
    <context:property-placeholder location="classpath:jdbc.properties" ></context:property-placeholder>

<!--    配置数据源-->
    <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>
    <!--配置QueryRunner,它是jar包封装好的,实例化时需要传入数据源作为参数-->
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>

6.编写测试代码

@RunWith(SpringJUnit4ClassRunner.class)//指定junit运行环境为spring
@ContextConfiguration({"classpath:applicationContext.xml"})//加载spring和核心配置文件
public class AccountServiceTest {

    @Autowired
    private AccountService accountService;

    @Test
    public void test1(){
        accountService.transfer("tom","jery",100d);
    }

}

7.测试结果

初始值:

image-20220304092620295

程序运行后:tom转给jery100元image-20220304092737987

8.出现异常

  • 当一个账户实现转账功能后,程序突然出现异常,情况如下所示,tom的账户的钱已经被转出了,但是jery的账户却没到账。
    @Override
    public void transfer(String name1, String name2,Double money) {
        //一个账号转出资金
        accountDao.out(name1,money);
        int num = 100;
        num = num / 0;
        //另一个账号资金就增加
        accountDao.in(name2 ,money );
    }

image-20220304093336903

image-20220304093245146

9.问题分析

  • 上面的代码事务都在dao层,转出转入操作都是一个独立的事务。
  • 其次在dao层获取数据库连接,它的属性Connection就不是一个线程安全的变量。当多并发场景下,每个线程都需要使用Connection,并且个字使用各自的,很容易在转账后,另一个线程又把该对象的余额修改了。

10.解决办法

引入事务,由于篇幅问题,具体解决办法请看第11章节。

完整代码:----

II.使用Spring整合Junit

普通Junit测试问题:

在普通的测试类中,需要开发者手动加载配置文件并创建Spring容器,然后通过Spring相关API获得Bean实例;如果不这么做,那么无法从容器中获得对象。

ApplicationContext applicationContext =
 new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accountService =
applicationContext.getBean(AccountService.class);

我们可以让SpringJunit负责创建Spring容器来简化这个操作,开发者可以直接在测试类注入Bean实 例;但是需要将配置文件的名称告诉它。

注意:目的就是为了解除上述代码。

  1. 导入spring集成Junit的坐标
  2. 使用@Runwith注解替换原来的运行器
  3. 使用@ContextConfiguration指定配置文件或配置类
  4. 使用@Autowired注入需要测试的对象
  5. 创建测试方法进行测试

(1)导入spring集成Junit的坐标

<!--此处需要注意的是,spring5 及以上版本要求 junit 的版本必须是 4.12 及以上-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-test</artifactId>
  <version>5.1.5.RELEASE</version>
</dependency>
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.12</version>
  <scope>test</scope>
</dependency>

(2)使用@Runwith注解替换原来的运行器

@RunWith(SpringJUnit4ClassRunner.class)
public class SpringJunitTest {
 
}

(3)使用@ContextConfiguration指定配置文件或配置类

@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration(value = {"classpath:applicationContext.xml"}) 加载spring核心配置文件
@ContextConfiguration(classes = {SpringConfig.class}) // 加载spring核心配置类
public class SpringJunitTest {
 
}

(4)使用@Autowired注入需要测试的对象

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfig.class})
public class SpringJunitTest {
 
 @Autowired
  private AccountService accountService;
 
  //测试查询
  @Test
  public void testFindById() {
    Account account = accountService.findById(3);
    System.out.println(account);
 }
}

💖💖💖 完结撒花

💖💖💖 路漫漫其修远兮,吾将上下而求索

💖💖💖 写作不易,如果您觉得写的不错,欢迎给博主点赞、收藏、评论、收藏来一波~让博主更有动力吧

✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨

最后,还不收藏进你的收藏夹吃灰😎😎😎