事务概述:保证数据库操作同时成功或者同时失败
Spring 事务的概述:在数据层保证数据库操作同时成功或者同时失败
提供规范接口
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition var1) throws TransactionException;
void commit(TransactionStatus var1) throws TransactionException;
void rollback(TransactionStatus var1) throws TransactionException;
}
方法
具体实现:DataSourceTransactionManager 来实现的,通过DataSource dataSource 以 JDBC 事务的方式来控制事务
业务分析
提供service 方法
public interface IAccountService {
/**
* 实现转账操作
* @param srcId 转账人
* @param deskId 接收人
* @param money 转账金额
*/
public void transfer(Long srcId,Long deskId,int money);
}
下载插件的官方地址 https://plugins.jetbrains.com/
提供service 实现方法
@Service
public class AccountServiceImpl implements IAccountService {
//会使用到 mapper
@Autowired
private AccountMapper mapper;
public void transfer(Long srcId, Long deskId, int money) {
mapper.outAccount(srcId,money);//转账扣钱
mapper.inAccount(deskId,money);//接收转账钱
}
}
提供 mapper 接口
public interface AccountMapper {
void outAccount(@Param("id") Long srcId, @Param("money") int money);
void inAccount(@Param("id")Long deskId,@Param("money") int money);
}
提供 mapper.xml
update>
<update id="outAccount">
update account
set money = money-#{money,jdbcType=INTEGER}
where id = #{id,jdbcType=BIGINT}
update>
<update id="inAccount">
update account
set money = money+#{money,jdbcType=INTEGER}
where id = #{id,jdbcType=BIGINT}
update>
测试
@Test
public void testMybatis(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
IAccountService bean = context.getBean(IAccountService.class);
bean.transfer(1L,2L,500);
}
@EnableTransactionManagement:用于开启事务支持的,直接添加到spring 配置类
说明
名称 | @EnableTransactionManagement |
---|---|
位置 | 配置类上方 |
作用 | 设置当前spring环境支持事务 |
修改配置类
@Configuration
@ComponentScan("cn.sycoder")
@PropertySource("db.properties")
@Import({JdbcConfig.class,MyBatisConfig.class})
//开启事务支持
@EnableTransactionManagement
public class SpringConfig {
}
PlatformTransactionManager
代码
public class JdbcConfig {
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Value("${jdbc.driverClassName}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
//配置连接池
@Bean
public DataSource dataSource(){
DruidDataSource source = new DruidDataSource();
source.setUsername(username);
source.setPassword(password);
source.setDriverClassName(driverClassName);
source.setUrl(url);
return source;
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager manager = new DataSourceTransactionManager();
manager.setDataSource(dataSource);
return manager;
}
}
@Transactional:为业务添加事务的
说明
名称 | @Transactional |
---|---|
位置 | 业务层接口上方,或者实现类上方,或者具体业务方法上方 |
作用 | 为当前的业务方法添加事务支持 |
修改业务层
业务方法上添加
@Transactional
public void transfer(Long srcId, Long deskId, int money) {
mapper.outAccount(srcId,money);//转账扣钱
System.out.println(1/0);
mapper.inAccount(deskId,money);//接收转账钱
}
业务类上添加
@Service
@Transactional
public class AccountServiceImpl implements IAccountService {
//会使用到 mapper
@Autowired
private AccountMapper mapper;
public void transfer(Long srcId, Long deskId, int money) {
mapper.outAccount(srcId,money);//转账扣钱
System.out.println(1/0);
mapper.inAccount(deskId,money);//接收转账钱
}
}
接口层添加
@Transactional
public interface IAccountService {
/**
* 实现转账操作
* @param srcId 转账人
* @param deskId 接收人
* @param money 转账金额
*/
public void transfer(Long srcId,Long deskId,int money);
}
在没有开启Spring事务之前:两条语句分别开启两个事务 T1 和 T2
public void transfer(Long srcId, Long deskId, int money) {
mapper.outAccount(srcId,money);//转账扣钱
System.out.println(1/0);
mapper.inAccount(deskId,money);//接收转账钱
}
开启Spring 事务管理之后
概述:表示只读,没有写操作。可以通过这个属性告诉数据库我们没有写操作,从而数据库可以针对只读sql做优化操作
使用
@Transactional(readOnly = true)
public Account selectById(Long id){
return mapper.selectByPrimaryKey(id);
}
如果对于有写操作的使用这个属性,会报如下错误
超时概述:事务再执行的时候,由于某些原因卡住,长时间占用数据库资源。此时很可能程序sql有问题,希望撤销事务,能够让事务结束,释放资源,即超时回滚。
默认值是-1.-1表示用不回滚,单位是秒
int timeout() default -1;
使用
@Transactional(readOnly = true,timeout = 1)
public Account selectById(Long id){
try {
Thread.sleep(10000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return mapper.selectByPrimaryKey(id);
}
回滚概述:回滚策略,希望对于什么样的异常回顾
注意:并不是所有的异常 Spring 都会回滚,Spring 只对 Error 异常和 RuntimeException 异常回滚
使用
@Transactional(rollbackFor = IOException.class)
public void transfer(Long srcId, Long deskId, int money) throws IOException {
mapper.outAccount(srcId,money);//转账扣钱
if(true){
throw new IOException("");
}
mapper.inAccount(deskId,money);//接收转账钱
}
@Transactional(rollbackForClassName = "IOException")
public void transfer(Long srcId, Long deskId, int money) throws IOException {
mapper.outAccount(srcId,money);//转账扣钱
if(true){
throw new IOException("");
}
mapper.inAccount(deskId,money);//接收转账钱
}
不会滚概述:出现这个异常不回滚
使用
@Transactional(noRollbackFor = ArithmeticException.class)
public void transfer(Long srcId, Long deskId, int money) throws IOException {
mapper.outAccount(srcId,money);//转账扣钱
System.out.println(1/0);
mapper.inAccount(deskId,money);//接收转账钱
}
@Transactional(noRollbackForClassName = "ArithmeticException")
public void transfer(Long srcId, Long deskId, int money) throws IOException {
mapper.outAccount(srcId,money);//转账扣钱
System.out.println(1/0);
mapper.inAccount(deskId,money);//接收转账钱
}
概述:设置事务隔离级别;
如果不记得事务隔离级别,回去复习一下我讲的MySql
使用
@Transactional(isolation = Isolation.REPEATABLE_READ)
public Account selectById(Long id) throws IOException {
return mapper.selectByPrimaryKey(id);
}
事务传播行为:事务协调员对事务管理员所携带的事务的处理态度
说明
传播属性 | 说明 |
---|---|
REQUIRED | 外围方法会开启新事务,内部方法会加入到外部方法的事务中 |
SUPPORTS | 外围方法没有事务,则内部方法不执行事务 |
MANDATORY | 使用当前事务,如果当前没有事务就抛异常 |
REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起 |
NOT_SUPPORTED | 不支持事务 |
NEVER | 不支持事务,如果存在事务还会抛异常 |
NESTED | 如果当前存在事务,则在嵌套事务内执行,如果不存在,执行REQUIRED类似操作 |
实操
REQUIRED:T1和T2会加入T中
@Transactional(propagation = Propagation.REQUIRED)//事务T
public void transfer(Long srcId, Long deskId, int money) throws IOException {
mapper.outAccount(srcId,money);//转账扣钱 //事务T1
System.out.println(1/0);
mapper.inAccount(deskId,money);//接收转账钱 //事务T2
}
SUPPORTS:外围没事务,所以内部只执行自己的事务,T1 和 T2 单独执行
public void transfer(Long srcId, Long deskId, int money) throws IOException {
mapper.outAccount(srcId,money);//转账扣钱//事务T1
System.out.println(1/0);
mapper.inAccount(deskId,money);//接收转账钱//事务T2
}
REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void outAccount(Long id, int money){
mapper.outAccount(id,money);//转账扣钱
}
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void inAccount(Long id, int money){
mapper.inAccount(id,money);//转账扣钱
}
public void transfer(Long srcId, Long deskId, int money) throws IOException {
outAccount(srcId,money);
inAccount(deskId,money);
throw new RuntimeException();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void outAccount(Long id, int money) {
mapper.outAccount(id, money);//转账扣钱
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void inAccount(Long id, int money) {
if (true)
throw new RuntimeException();
mapper.inAccount(id, money);//转账扣钱
}
导入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.17.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.17.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<!-- <scope>test</scope>-->
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<!-- spring 整合 mybatis 的包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<!-- mybatis 包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!-- spring 操作 jdbc 包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.29.RELEASE</version>
</dependency>
</dependencies>
配置文件
<?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"
xmlns:tx="http://www.springframework.org/schema/tx" 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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="accountService" class="cn.sycoder.service.impl.AccountServiceImpl">
<!-- <property name="mapper" ref="mapper"/>-->
</bean>
<!-- <bean id="mapper" class="cn.sycoder.mapper.AccountMapper"></bean>-->
<aop:config>
<aop:advisor advice-ref="tx" pointcut="execution(* cn.sycoder.service.impl.*.*(..))"></aop:advisor>
</aop:config>
<tx:advice id="tx" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
</tx:attributes>
</tx:advice>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- (this dependency is defined somewhere else) -->
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<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>
<context:property-placeholder location="db.properties"/>
</beans>
配置详解
引入db.properties
<context:property-placeholder location="db.properties"/>
配置连接池
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<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>
配置事务管理器
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
配置 aop 事务增强
<aop:config>
<aop:advisor advice-ref="tx" pointcut="execution(* cn.sycoder.service.impl.*.*(..))"/>
</aop:config>
<tx:advice id="tx" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
</tx:attributes>
</tx:advice>
注意:如果你还想通过 xml 配置 mybatis ,那么你还需要把 mybatis 配置文件搞一份过来,通过xml 配置好 mybatis 之后,然后再获取 sqlSessionFactory 去获取 mapper 文件
注意:spring 是面试重头戏,所以,你需要花时间认真巩固和复习,ioc 和 di 特别是对于常用注解,以及事务机制,aop 等都很爱问。