先来看我们数据库中的表:
我们要做的事情,就是熟悉的转账操作,不过这次是用Spring容器中进行的。
栗子:未开启事务的转账
1、首先提供一个账户的实体类 Account.java(具体参考上一篇博文,在此不做展示)
2、给出账户的持久层接口 IAccountDao.java
public interface IAccountDao {
/**
* 更新
* @param account
*/
void updateAccount(Account account);
/**
* 根据id查询账户
* @return
*/
Account findAccountById(Integer accountId);
}
3、给出账户的持久层实现类 AccountDaoImpl.java
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void updateAccount(Account account) {
try{
jdbcTemplate.update("update account set name = ?, money = ? where id = ?",account.getName(),account.getMoney(),account.getId());
}catch (Exception e){
throw new RuntimeException(e);
}
}
@Override
public Account findAccountById(Integer accountId) {
try{
return jdbcTemplate.queryForObject("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
4、给出账户的业务层接口 IAccountService.java
public interface IAccountService {
/**
* 执行转账操作
* @param inId 转入的id
* @param outId 转出的id
* @param money 转入转出的金额
*/
void transfer(Integer inId, Integer outId, Float money);
}
5、给出账户的业务层实现类 AccountServiceImpl.java
@Service("accountService")
public class AccountServiceImpl implements IAccountService{
@Autowired
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer(Integer inId, Integer outId, Float money) {
Account in = accountDao.findAccountById(inId);
Account out = accountDao.findAccountById(outId);
in.setMoney(in.getMoney() + money);
out.setMoney(out.getMoney() - money);
accountDao.updateAccount(in);
int d = 1/0; //模拟异常
accountDao.updateAccount(out);
}
}
6、给出bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 告知spring在创建容器时要扫描的包 -->
<context:component-scan base-package="dao"></context:component-scan>
<context:component-scan base-package="service"></context:component-scan>
<!--配置jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/spring?useSSL=false&serverTimezone=Asia/Shanghai"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
</beans>
7、测试类AccountServiceTest
public class AccountServiceTest {
public static void main(String[] args) {
//1.获取容易
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.得到业务层对象
IAccountService as = ac.getBean("accountService",IAccountService.class);
//3.执行方法
as.transfer(1,2,100f);
}
}
- 当注释掉异常时
可以看到,转账操作成功,id为1的多了100,id为2的多了900.
- 当开启异常时
开启异常时,会发生转账失败,也就是id为1的账户增加了100(转入成功),而id为1的账户却没有减少(转出失败),这并不是我们想要的结果!!!
这时候就需要引入事务
开启转账的事务操作
项目所需的jar包:
1、首先提供一个账户的实体类 Account.java
2、给出账户的持久层接口 IAccountDao.java
public interface IAccountDao {
void updateAccount(Account account);
Account findAccountByName(String accountName);
}
3、给出账户的持久层实现类 AccountDaoImpl.java
public class AccountDaoImpl implements IAccountDao {
private QueryRunner runner;
private ConnectionUtils connectionUtils;
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
@Override
public void updateAccount(Account account) {
try{
runner.update(connectionUtils.getThreadConnection(),"update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Account findAccountByName(String accountName) {
try{
List<Account> accounts = runner.query(connectionUtils.getThreadConnection(),"select * from account where name = ? ",new BeanListHandler<Account>(Account.class),accountName);
if(accounts == null || accounts.size() == 0){
return null;
}
if(accounts.size() > 1){
throw new RuntimeException("结果集不唯一,数据有问题");
}
return accounts.get(0);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
4、给出账户的业务层接口 IAccountService.java
public interface IAccountService {
void transfer(String sourceName,String targetName,Float money);
}
5、给出账户的业务层实现类 AccountServiceImpl.java
public class AccountServiceImpl implements IAccountService{
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(String sourceName, String targetName, Float money) {
System.out.println("transfer....");
//2.1根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//2.3转出账户减钱
source.setMoney(source.getMoney()-money);
//2.4转入账户加钱
target.setMoney(target.getMoney()+money);
//2.5更新转出账户
accountDao.updateAccount(source);
// int i=1/0;
//2.6更新转入账户
accountDao.updateAccount(target);
}
}
6、给出bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置Service -->
<bean id="accountService" class="service.AccountServiceImpl">
<!-- 注入dao -->
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置Dao对象-->
<bean id="accountDao" class="dao.AccountDaoImpl">
<!-- 注入QueryRunner -->
<property name="runner" ref="runner"></property>
<!-- 注入ConnectionUtils -->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/spring?useSSL=false&serverTimezone=Asia/Shanghai"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!-- 配置Connection的工具类 ConnectionUtils -->
<bean id="connectionUtils" class="utils.ConnectionUtils">
<!-- 注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理器-->
<bean id="txManager" class="utils.TransactionManager">
<!-- 注入ConnectionUtils -->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置aop-->
<aop:config>
<!--配置通用切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* service.*.*(..))"></aop:pointcut>
<aop:aspect id="txAdvice" ref="txManager">
<!--配置前置通知:开启事务-->
<aop:before method="beginTransaction" pointcut-ref="pt1"></aop:before>
<!--配置后置通知:提交事务-->
<aop:after-returning method="commit" pointcut-ref="pt1"></aop:after-returning>
<!--配置异常通知:回滚事务-->
<aop:after-throwing method="rollback" pointcut-ref="pt1"></aop:after-throwing>
<!--配置最终通知:释放连接-->
<aop:after method="release" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
</beans>
7、两个工具类:
ConnectionUtils.java
/**
* 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
*/
public class ConnectionUtils {
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 获取当前线程上的连接
* @return
*/
public Connection getThreadConnection() {
try{
//1.先从ThreadLocal上获取
Connection conn = tl.get();
//2.判断当前线程上是否有连接
if (conn == null) {
//3.从数据源中获取一个连接,并且存入ThreadLocal中
conn = dataSource.getConnection();
tl.set(conn);
}
//4.返回当前线程上的连接
return conn;
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 把连接和线程解绑
*/
public void removeConnection(){
tl.remove();
}
}
TransactionManager.java
package utils;
/**
* 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
*/
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* 开启事务
*/
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 提交事务
*/
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 释放连接
*/
public void release(){
try {
connectionUtils.getThreadConnection().close();//还回连接池中
connectionUtils.removeConnection();
}catch (Exception e){
e.printStackTrace();
}
}
}
7、测试类AccountServiceTest
当未开启异常时:
当开启异常时: