10.转账案例-引入事务
需求:使用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章节。