什么是事务
1.什么是事务
事务是指一组sql语句的集合, 集合中有多条sql语句 可能是insert , update ,select ,delete, 我们希望这些多个sql语句都能成功, 或者都失败, 这些sql语句的执行是一致的,作为一个整体执行。
2.在什么时候想到使用事务
当我的操作,涉及得到多个表,或者是多个sql语句的insert,update,delete。需要保证这些语句都是成功才能完成我的功能,或者都失败,保证操作是符合要求的。
3.你的业务方法需要什么样的事务,说明需要事务的类型。
说明方法需要的事务:
事务的隔离级别:
有4个值。(在高并发的情况下包装安全性) 这些常量均是以 ISOLATION_开头。即形ISOLATION_XXX。
- DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle默认为 READ_COMMITTED。
- READ_UNCOMMITTED:读未提交。未解决任何并发问题。
- READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
- REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
- SERIALIZABLE:串行化。不存在并发问题。
事务的超时时间
表示一个方法最长的执行时间,如果方法执行时超过了时间,事务就回滚。 单位是秒, 整数值, 默认是 -1.
事务的传播行为
控制业务方法是不是有事务的, 是什么样的事务的。 7个传播行为,表示你的业务方法调用时,事务在方法之间是如果使用的。
-
PROPAGATION_REQUIRED 指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事 务,则创建一个新事务。这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为。 如该传播行为加在 doOther()方法上。若 doSome()方法在调用 doOther()方法时就是在事 务内运行的,则 doOther()方法的执行也加入到该事务内执行。若 doSome()方法在调用 doOther()方法时没有在事务内执行,则 doOther()方法会创建一个事务,并在其中执行。 -
PROPAGATION_REQUIRES_NEW 指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。 -
PROPAGATION_SUPPORTS 总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。
以上三个需要掌握的
- PROPAGATION_MANDATORY
- PROPAGATION_NESTED
- PROPAGATION_NEVER
- PROPAGATION_NOT_SUPPORTED
4.事务提交事务,回滚事务的时机
- 当你的业务方法,执行成功,没有异常抛出,当方法执行完毕,spring在方法执行后提交事务。事务管理器commit
- 当你的业务方法抛出运行时异常或ERROR, spring执行回滚,调用事务管理器的rollback
运行时异常的定义: RuntimeException 和他的子类都是运行时异常, 例如NullPointException , NumberFormatException - 当你的业务方法抛出非运行时异常, 主要是受查异常时,提交事务
受查异常:在你写代码中,必须处理的异常。例如IOException, SQLException
Spring中使用事务处理步骤
举例:
购买商品 trans_sale 项目 本例要实现购买商品,模拟用户下订单,向订单表添加销售记录,从商品表减少库存。
实现步骤:
Step0:创建数据库表
创建两个数据库表 sale , goods 1.sale 销售表
create table sale(id int(11) not null AUTO_INCREMENT, gid int(11) not null, nums int(11), primary key (id));
2. goods 商品表
create table goods(id int(11) not null, name varchar(100), amount int(11), price float(0), primary key(id));
goods 表数据
Step1: maven 依赖 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.changsha</groupId>
<artifactId>springtrans</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.24</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Step2:创建实体类
用来存储数据库表中一行的数据 Goods 类保存goods表一行数据
package com.changsha.domain;
public class Goods {
private Integer id;
private String name;
private Integer amount;
private Float price;
public Goods() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
public Float getPrice() {
return price;
}
public void setPrice(Float price) {
this.price = price;
}
@Override
public String toString() {
return "Goods{" +
"id=" + id +
", name='" + name + '\'' +
", amount=" + amount +
", price=" + price +
'}';
}
}
sale表一行数据
package com.changsha.domain;
public class Sale {
private Integer id;
private Integer gid;
private Integer nums;
public Sale(Integer id, Integer gid, Integer nums) {
this.id = id;
this.gid = gid;
this.nums = nums;
}
public Sale() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getGid() {
return gid;
}
public void setGid(Integer gid) {
this.gid = gid;
}
public Integer getNums() {
return nums;
}
public void setNums(Integer nums) {
this.nums = nums;
}
@Override
public String toString() {
return "Sale{" +
"id=" + id +
", gid=" + gid +
", nums=" + nums +
'}';
}
}
Step3:定义 dao 接口
定义两个 dao 的接口 SaleDao , GoodsDao dao接口,定义对数据库的操作
package com.changsha.dao;
import com.changsha.domain.Goods;
import org.apache.ibatis.annotations.Param;
public interface GoodsDao {
int updateGoods(Goods goods);
Goods selectGoods(@Param("gid") Integer id);
}
sale的dao
package com.changsha.dao;
import com.changsha.domain.Sale;
public interface SaleDao {
int insertSale(Sale sale);
}
Step4:定义 dao 接口对应的 sql 映射文件
mapper配置文件(需要和dao接口同名且在同一目录之下) GoodsDao.xml
<?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.changsha.dao.GoodsDao">
<update id="updateGoods">
update goods set amount = amount - #{amount} where id = #{id}
</update>
<select id="selectGoods" resultType="com.changsha.domain.Goods">
select * from goods where id = #{gid}
</select>
</mapper>
SaleDao.xml
<?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.changsha.dao.SaleDao">
<insert id="insertSale">
insert into sale(gid,nums) values (#{gid},#{nums})
</insert>
</mapper>
定义异常类(不是必须的)
定义 service 层可能会抛出的异常类 NotEnoughException
package com.changsha.excep;
public class NotEnoughException extends RuntimeException{
public NotEnoughException() {
super();
}
public NotEnoughException(String message) {
super(message);
}
}
Step5:创建mybatis主配置文件
因为使用别的连接池,所以数据库信息不用指定在这个文件中
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<typeAliases>
<package name="com.changsha.domain"/>
</typeAliases>
<mappers>
<package name="com.changsha.dao"/>
</mappers>
</configuration>
Step6:定义 Service 接口
定义 Service 接口 BuyGoodsService
package com.changsha.service;
public interface BuyGoodsService {
public void buy(Integer goodsId, Integer nums);
}
Step7:定义 service 的实现类
定义 service 层接口的实现类 BuyGoodsServiceImpl
package com.changsha.service.Impl;
import com.changsha.dao.GoodsDao;
import com.changsha.dao.SaleDao;
import com.changsha.domain.Goods;
import com.changsha.domain.Sale;
import com.changsha.excep.NotEnoughException;
import com.changsha.service.BuyGoodsService;
public class BuyGoodsServiceImpl implements BuyGoodsService {
private SaleDao saleDao;
private GoodsDao goodsDao;
@Override
public void buy(Integer goodsId, Integer nums) {
System.out.println("=======buy方法的开始=======");
Sale sale = new Sale();
sale.setGid(goodsId);
sale.setNums(nums);
saleDao.insertSale(sale);
Goods goods = goodsDao.selectGoods(goodsId);
if(goods == null){
throw new NullPointerException("编号是" + goodsId+"商品不存在");
} else if(goods.getAmount() < nums){
throw new NotEnoughException("编号是" + goodsId+"商品库存不足");
}
Goods buyGoods = new Goods();
buyGoods.setId(goodsId);
buyGoods.setAmount(nums);
goodsDao.updateGoods(buyGoods);
System.out.println("=======buy方法的完成=======");
}
public void setSaleDao(SaleDao saleDao) {
this.saleDao = saleDao;
}
public void setGoodsDao(GoodsDao goodsDao) {
this.goodsDao = goodsDao;
}
}
Step8:创建 Spring 配置文件内容
1. 使用@Transactional的步骤(中小项目适用):
1).需要声明事务管理器对象
<bean id="tansactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSource"/>
</bean>
2).开启事务注解驱动, 告诉spring框架,我要使用注解的方式管理事务。
spring使用aop机制,创建@Transactional所在的类代理对象,给方法加入事务的功能。 spring给业务方法加入事务: 在你的业务方法执行之前,先开启事务,在业务方法之后提交或回滚事务,使用aop的环绕通知
@Around("你要增加的事务功能的业务方法名称")
Object myAround(){
开启事务,spring给你开启
try{
buy(1001,10);
spring的事务管理器.commit();
}catch(Exception e){
spring的事务管理器.rollback();
}
}
开启事务的spring设置 annotation-dridven包中一定要以tx结尾
<tx:annotation-dridven transaction-manager="tansactionManager"/>
3).在你需要做事务的方法的上面加入@Trancational注解
代码如下
package com.changsha.service.Impl;
import com.changsha.dao.GoodsDao;
import com.changsha.dao.SaleDao;
import com.changsha.domain.Goods;
import com.changsha.domain.Sale;
import com.changsha.excep.NotEnoughException;
import com.changsha.service.BuyGoodsService;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
public class BuyGoodsServiceImpl implements BuyGoodsService {
private SaleDao saleDao;
private GoodsDao goodsDao;
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
readOnly = false,
rollbackFor = {
NullPointerException.class,
NotEnoughException.class
}
)
@Override
public void buy(Integer goodsId, Integer nums) {
System.out.println("=======buy方法的开始=======");
Sale sale = new Sale();
sale.setGid(goodsId);
sale.setNums(nums);
saleDao.insertSale(sale);
Goods goods = goodsDao.selectGoods(goodsId);
if(goods == null){
throw new NullPointerException("编号是" + goodsId+"商品不存在");
} else if(goods.getAmount() < nums){
throw new NotEnoughException("编号是" + goodsId+"商品库存不足");
}
Goods buyGoods = new Goods();
buyGoods.setId(goodsId);
buyGoods.setAmount(nums);
goodsDao.updateGoods(buyGoods);
System.out.println("=======buy方法的完成=======");
}
public void setSaleDao(SaleDao saleDao) {
this.saleDao = saleDao;
}
public void setGoodsDao(GoodsDao goodsDao) {
this.goodsDao = goodsDao;
}
}
因为注解大多数情况下都是用的默认值,所以可以直接省略赋值
package com.changsha.service.Impl;
import com.changsha.dao.GoodsDao;
import com.changsha.dao.SaleDao;
import com.changsha.domain.Goods;
import com.changsha.domain.Sale;
import com.changsha.excep.NotEnoughException;
import com.changsha.service.BuyGoodsService;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
public class BuyGoodsServiceImpl implements BuyGoodsService {
private SaleDao saleDao;
private GoodsDao goodsDao;
@Transactional
@Override
public void buy(Integer goodsId, Integer nums) {
System.out.println("=======buy方法的开始=======");
Sale sale = new Sale();
sale.setGid(goodsId);
sale.setNums(nums);
saleDao.insertSale(sale);
Goods goods = goodsDao.selectGoods(goodsId);
if(goods == null){
throw new NullPointerException("编号是" + goodsId+"商品不存在");
} else if(goods.getAmount() < nums){
throw new NotEnoughException("编号是" + goodsId+"商品库存不足");
}
Goods buyGoods = new Goods();
buyGoods.setId(goodsId);
buyGoods.setAmount(nums);
goodsDao.updateGoods(buyGoods);
System.out.println("=======buy方法的完成=======");
}
public void setSaleDao(SaleDao saleDao) {
this.saleDao = saleDao;
}
public void setGoodsDao(GoodsDao goodsDao) {
this.goodsDao = goodsDao;
}
}
注解处理事务方式的完整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: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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="maxActive" value="${jdbc.maxActive}"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="myDataSource"/>
<property name="configLocation" value="springtrans.xml"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.changsha.dao"/>
</bean>
<bean id="buyService" class="com.changsha.service.Impl.BuyGoodsServiceImpl">
<property name="goodsDao" ref="goodsDao"/>
<property name="saleDao" ref="saleDao"/>
</bean>
<bean id="tansactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSource"/>
</bean>
<tx:annotation-driven transaction-manager="tansactionManager"/>
</beans>
2.使用aspectj框架 (大型项目使用)
1)maven 依赖 pom.xml
新加入 aspectj 的依赖坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
2)在容器中添加事务管理器
<bean id="tansactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSource"/>
</bean>
3)配置事务通知
为事务通知设置相关属性。用于指定要将事务以什么方式织入给哪些方法。 例如,应用到 buy 方法上的事务要求是必须的,且当 buy 方法发生异常后要回滚业务。
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
rollback-for="java.lang.NullPointerException,com.bjpowernode.excep.NotEnoughException"/>
<tx:method name="add*" propagation="REQUIRES_NEW" />
<tx:method name="modify*" />
<tx:method name="remove*" />
<tx:method name="*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice>
method标签中,首先会找到范围精确的(指定方法名)事务方法,再找半通配的,最后是*
4)配置增强器
指定将配置好的事务通知,织入给谁。
<aop:config>
<aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>
<aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt" />
</aop:config>
切入点表达式在aspectj框架中有介绍
通过aspectj框架实现事务处理的完整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:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="maxActive" value="${jdbc.maxActive}"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="myDataSource"/>
<property name="configLocation" value="springtrans.xml"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.changsha.dao"/>
</bean>
<bean id="buyService" class="com.changsha.service.Impl.BuyGoodsServiceImpl">
<property name="goodsDao" ref="goodsDao"/>
<property name="saleDao" ref="saleDao"/>
</bean>
<bean id="tansactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSource"/>
</bean>
<tx:advice id="myAdvice" transaction-manager="tansactionManager">
<tx:attributes>
<tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
rollback-for="java.lang.NullPointerException,com.bjpowernode.excep.NotEnoughException"/>
<tx:method name="add*" propagation="REQUIRES_NEW" />
<tx:method name="modify*" />
<tx:method name="remove*" />
<tx:method name="*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>
<aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt" />
</aop:config>
</beans>
Step9:方法测试
package com.changsha;
import com.changsha.service.BuyGoodsService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Mytest {
@Test
public void test(){
String config = "applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
BuyGoodsService buyGoodsService = (BuyGoodsService) ctx.getBean("buyService");
System.out.println(buyGoodsService.getClass().getName());
buyGoodsService.buy(1001,10);
}
}
|