Spring入门篇7 --- spring事务
目录
spring事务的作用:保证在数据层或业务层的一系列数据库操作同成功或同失败。
1.案例:模拟银行账户间的转账业务
需求:A账户减钱,B账户加钱
分析:
①数据层提供基础操作,指定账户减钱、加钱
②业务层提供转账操作(transfer),调用减钱与加钱的操作
③提供2个账号和操作金额执行转账操作
④基于Spring整合MyBatis环境搭建上述操作
(1)创建数据库和配置文件
用户名:root
密码:123456
数据库名:spring_db
表名:tbl_account
id | name | money |
1 | Jack | 1000 |
2 | Rose | 1000 |
/src/main/resources/jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false
jdbc.userName=root
jdbc.password=123456
(2)导入依赖和其它配置
pom.xml中导入的依赖有:spring-context、spring-jdbc、mybatis、mybatis-spring、druid、mysql-connector-java、spring-test、junit(spring-context、spring-jdbc、spring-test要版本一致,JDK版本最好要最新的)
/src/main/java/com/itheima/config/MybatisConfig.java
package com.itheima.config;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
public class MybatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
ssfb.setTypeAliasesPackage("com.itheima.domain");
ssfb.setDataSource(dataSource);
return ssfb;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.itheima.dao");
return msc;
}
}
/src/main/java/com/itheima/config/JdbcConfig.java
package com.itheima.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
public class JdbcConfig {
// 读取配置文件jdbc.properties的信息
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.userName}")
private String userName;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource() {
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
// 配置事物管理器,mybatis使用的是jdbc事务
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
/src/main/java/com/itheima/config/SpringConfig.java
package com.itheima.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class, MybatisConfig.class})
// 开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}
(3)数据层和业务层代码
/src/main/java/com/itheima/dao/AccountDao.java
package com.itheima.dao;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
public interface AccountDao {
@Update("update tbl_account set money = money + #{money} where name = #{name}")
void inMoney(@Param("name") String name, @Param("money") Double money);
@Update("update tbl_account set money = money - #{money} where name = #{name}")
void outMoney(@Param("name") String name, @Param("money") Double money);
}
/src/main/java/com/itheima/service/AccountService.java
package com.itheima.service;
import org.springframework.transaction.annotation.Transactional;
public interface AccountService {
/**
* 转账操作
* @param out 传出方
* @param in 转入方
* @param money 金额
*/
//配置当前接口方法具有事务
@Transactional
public void transfer(String out,String in ,Double money) ;
}
/src/main/java/com/itheima/service/impl/AccountServiceImpl.java
package com.itheima.service.impl;
import com.itheima.dao.AccountDao;
import com.itheima.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public void transfer(String out,String in ,Double money) {
accountDao.outMoney(out,money);
// int i = 1/0;
accountDao.inMoney(in,money);
}
}
(4)测试
/src/test/java/com/itheima/service/AccountServiceTest.java
package com.itheima.service;
import com.itheima.config.SpringConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.io.IOException;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testTransfer() throws IOException {
accountService.transfer("Jack","Rose",100D);
}
}
如果不用事务,在AccountServiceImpl.java的转账操作中,A账户减钱 ,B账户加钱,中间如果抛异常,减钱操作成功,加钱操作失败。这显然是不合适的。
2.Spring事务角色
事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法。
3. 进阶版案例
需求:A账户减钱,B账户加钱,数据库记录转账日志(无论转账成功与否)。
分析:
①基于上方案例增加日志模块,实现在数据库中记录日志
②业务层转账操作:减钱、加钱、记录日志
③默认记录日志操作和转账操作同属一个事务,为了保证转账失败还能记录日志,需要设置记录日志的业务层接口上事务的传播行为REQUIRES_NEW(需要新事务)
在上述案例的基础上修改,其它代码不变
(1)建表tbl_log(id为主键,设置自动递增,可以为null)
id | info | createDate |
0 |
(2)数据层和业务层代码
/src/test/java/com/itheima/dao/LogDao.java
package com.itheima.dao;
import org.apache.ibatis.annotations.Insert;
public interface LogDao {
@Insert("insert into tbl_log(info,createDate) values(#{info},now())" )
void log(String info);
}
/src/main/java/com/itheima/service/LogService.java
package com.itheima.service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
public interface LogService {
//propagation设置事务属性:传播行为设置为当前操作需要新事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
void log(String out, String in, Double money);
}
/src/main/java/com/itheima/service/impl/LogServiceImpl.java
package com.itheima.service.impl;
import com.itheima.dao.LogDao;
import com.itheima.service.LogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class LogServiceImp implements LogService {
@Autowired
private LogDao logDao;
public void log(String out, String in, Double money) {
logDao.log("转账操作由"+out+"到"+in+",金额:"+money);
}
}
(3)转账操作略作修改
/src/main/java/com/itheima/service/impl/AccountServiceImpl.java
package com.itheima.service.impl;
import com.itheima.dao.AccountDao;
import com.itheima.service.AccountService;
import com.itheima.service.LogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
// 该接口类只有一个实现类,这里自动装配的其实是实现类
@Autowired
private LogService logService;
public void transfer(String out,String in ,Double money) {
try {
accountDao.outMoney(out,money);
// int i = 1/0;
accountDao.inMoney(in,money);
} finally {
logService.log(out, in, money);
}
}
}
(4)测试代码不变