一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第17天,点击查看活动详情。
- 📝 个人主页:程序员阿红🔥
- 🎉 支持我:点赞👍收藏⭐️留言📝
- 🍓欢迎大家关注哦,互相学习🍓
- 🍋欢迎大家访问哦,互相学习🍋
- 🍑欢迎大家收藏哦,互相学习🍑
I.转账案例-引入事务
需求:使用Spring框架整合DBUtils技术,实现转账功能。
步骤分析:
- 创建java项目,导入坐标
- 编写Account实体类
- 编写AccountDao接口和实现类
- 编写AccountService接口和实现类
- 编写spring核心配置文件
- 编写测试代码
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.测试结果
初始值:
程序运行后:tom转给jery100元
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 );
}
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实 例;但是需要将配置文件的名称告诉它。
注意:目的就是为了解除上述代码。
- 导入spring集成Junit的坐标
- 使用@Runwith注解替换原来的运行器
- 使用@ContextConfiguration指定配置文件或配置类
- 使用@Autowired注入需要测试的对象
- 创建测试方法进行测试
(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);
}
}
💖💖💖 完结撒花
💖💖💖 路漫漫其修远兮,吾将上下而求索
💖💖💖 写作不易,如果您觉得写的不错,欢迎给博主点赞、收藏、评论、收藏来一波~让博主更有动力吧
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
最后,还不收藏进你的收藏夹吃灰😎😎😎