文章目录
源码见文章结尾
1. Spring中的事务
1.1事务回顾
事务是逻辑上的一组操作,要么全都成功,要么全部失败
事务的特性:ACID原子性:事务不可分割
一致性:事务执行前后,数据完整新保持一致
隔离性:一个事务执行的时候,不应该受到其他事务的打扰
持久性:一旦结束,数据就会永久的保存到数据库
如果不考虑隔离性:
- 脏读:一个事务读取到另一个事务未提交的数据
- 不可重复读:一个事务读取到另一个事务已近提交的数据(update),导致没有刷新的情况下导致查询结果不一致
- 虚读:一个事务读取到另一个事务已经提交的数据(insert)导致另一个事务多次查询结果不一致
事务的隔离级别:
- 未提交读:以上情况都有可能发生。
- 已提交读:避免脏读,但不可重复读,虚读是有可能发生。
- 可重复读:避免脏读,不可重复读,但是虚读有可能发生。
串行的:避免以上所有情况.
1.2 Spring通过配置XML方式进行事务管理
1.2.1 建表
CREATE TABLE `t_account` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`money` double DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
INSERT INTO `t_account` VALUES ('1', 'Tom', '1000');
INSERT INTO `t_account` VALUES ('2', 'Jerry', '1000');
1.2.2 pom中导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.22</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.22</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.22</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
<!-- mybatis整合spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.9</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.22</version>
</dependency>
1.2.3 AccountController
package com.dzh.controller;
import com.dzh.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
/**
* 控制层
*/
@Controller
public class AccountController {
private AccountService accountService;
/**
* 采用set方法注入依赖 自动注入
* @param accountService 依赖项
*/
@Autowired
public void setAccountService(AccountService accountService) {
this.accountService = accountService;
}
/**
* 转账 fromAccount 给 toAccount 转账 money
* @param fromAccount 转出
* @param toAccount 转入
* @param money 金额
*/
public void transfer(Integer fromAccount, Integer toAccount, Double money) {
accountService.transfer(fromAccount,toAccount,money);
}
}
1.2.4 AccountService和AccountServiceImpl
package com.dzh.service;
/**
* 转账业务层接口
*/
public interface AccountService {
void transfer(Integer fromAccount, Integer toAccount, Double money);
}
package com.dzh.service.impl;
import com.dzh.mapper.AccountMapper;
import com.dzh.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* 业务实现
*/
@Service
@Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED)
public class AccountServiceImpl implements AccountService {
private AccountMapper accountMapper;
/**
* 采用set方法自动注入依赖
* @param accountMapper 依赖项
*/
@Autowired
public void setAccountMapper(AccountMapper accountMapper) {
this.accountMapper = accountMapper;
}
@Override
public void transfer(Integer fromAccount, Integer toAccount, Double money) {
/*
转出操作
*/
accountMapper.transferOut(fromAccount,money);
//设置异常测试
int i = 1/0;
/*
转入操作
*/
accountMapper.transferIn(toAccount,money);
}
}
1.2.5 AccountMapper和accountMapper.xml
package com.dzh.mapper;
import org.apache.ibatis.annotations.Param;
/**
* dao层接口
*/
public interface AccountMapper {
/**
* 转出操作
* @param fromAccount 转出id
* @param money 转账金额
*/
void transferOut(@Param("fromAccountId") Integer fromAccount, @Param("money") Double money);
void transferIn(@Param("toAccountId") Integer toAccount, @Param("money") Double money);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dzh.mapper.AccountMapper">
<!-- 转出账户指定金额 -->
<update id="transferOut">
update t_account set money = money - #{money} where id = #{fromAccountId}
</update>
<!-- 转入账户指定金额 -->
<update id="transferIn">
update t_account set money = money + #{money} where id = #{toAccountId}
</update>
</mapper>
1.2.6 spring配置文件applicationContext.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"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx" 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
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- bean definitions here -->
<context:component-scan base-package="com.dzh"/>
<!-- 引入jdbc配置文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
</bean>
<!-- 配置SqlSessionFactory -->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 引入数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- <property name="configLocation" value="classpath:mybatis-config.xml"/>-->
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
<!-- 扫描mapper接口,生成代理对象 -->
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.dzh.mapper"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="accountAdvice" class="com.dzh.advice.AccountAdvice"/>
<aop:aspectj-autoproxy/>
<!-- 配置事务 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transfer" isolation="DEFAULT" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="txPc" expression="execution(* com.dzh.service.impl.*ServiceImpl.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPc"/>
<aop:aspect ref="accountAdvice">
<aop:after-throwing method="after_throwing" pointcut-ref="txPc"/>
<aop:around method="around" pointcut-ref="txPc"/>
</aop:aspect>
</aop:config>
</beans>
5.6.7 注解方式的spring配置文件
<?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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx" 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
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- bean definitions here -->
<context:component-scan base-package="com.dzh"/>
<!-- 引入jdbc配置文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
</bean>
<!-- 配置SqlSessionFactory -->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 引入数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- <property name="configLocation" value="classpath:mybatis-config.xml"/>-->
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
<!-- 扫描mapper接口,生成代理对象 -->
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.dzh.mapper"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务 -->
<!-- 事务平台管理器,封装了所有的事务操作,依赖数据源 -->
<bean id="accountAdvice" class="com.dzh.advice.AccountAdvice"/>
<!-- <aop:aspectj-autoproxy/>-->
<!-- 开启注解驱动事务支持 -->
<tx:annotation-driven/>
</beans>
1.2 测试类
import com.dzh.controller.AccountController;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AccountTest {
/**
* 事务测试
*/
@Test
public void accountTransfer() {
ClassPathXmlApplicationContext ac =
new ClassPathXmlApplicationContext("applicationContext.xml");
AccountController accountController = (AccountController) ac.getBean("accountController");
accountController.transfer(1,2,1000d);
}
/**
* 注解方式的配置文件测试
*/
@Test
public void accountTransferBeanXml() {
ClassPathXmlApplicationContext ac =
new ClassPathXmlApplicationContext("bean.xml");
AccountController accountController = (AccountController) ac.getBean("accountController");
accountController.transfer(2,1,1000d);
}
}
1.3 配置AOP
package com.dzh.advice;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
@Aspect
public class AccountAdvice {
/**
* 自己设置一个切点,管理重复代码
*/
@Pointcut("execution(public void com.dzh.service.impl.AccountServiceImpl.transfer())")
public void pc(){}
/**
* 前置通知 多个切点用 || 连接
*/
@Before("AccountAdvice.pc()")
public void before() {
System.out.println("--开始--");
}
/**
* 最终的后置通知,异常与否都会执行
* 可直接写 也可写切入点 @Pointcut
*/
// @After("execution(public void com.dzh.service.impl.UserServiceImpl.addUser())")
// @After("AccountAdvice.pc()")
public void after() {
System.out.println("----");
}
/**
* 后置通知,发生异常不会被执行
*/
// @AfterReturning("AccountAdvice.pc()")
public void after_returning() {
System.out.println("转账成功!");
}
/**
* 异常通知
*/
// @AfterThrowing("AccountAdvice.pc()")
public void after_throwing() {
System.out.println("转账失败");
}
/**
*环绕通知
*/
// @Around("AccountAdvice.pc()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("开始转账");
System.out.println("......");
proceedingJoinPoint.proceed();
System.out.println("转账成功");
}
}
源码仓库
https://github.com/dongzihaoa/spring_tx.git