Spring事务管理
(1)PlatformTransactionManager(平台事务管理器)
(2)TransactionDefinition(事务定义对象)
事务
什么是事务?
事务指数据库中多个操作合并在一起形成的操作序列
要么同时成功,要么同时失败!具有原子性。
事务的作用
(1)当数据库操作序列中个别操作失败时,提供一种方式使数据库状态恢复到正常状态(A),保障数据库即使在异常状态下仍能保持数据一致性(C)(要么操作前状态,要么操作后状态)。
(2)当出现并发访问数据库时,在多个访问间进行相互隔离,防止并发访问操作结果互相干扰(I)。
-
事务特征(ACID)
-
原子性(Atomicity)指事务是一个不可分割的整体,其中的操作要么全执行或全不执行
-
一致性(Consistency)事务前后数据的完整性必须保持一致
-
隔离性(Isolation)事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离
-
持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
-
事务的隔离级
(1)脏读:允许读取未提交的信息
原因:隔离级别设置的是Read uncommitted(读取未提交的数据)
解决方案: Read committed(表级读锁,即将整个表的读取加一个锁,就可以避免脏读!)
(2)不可重复读:读取过程中单个数据发生了变化(同一个数据读取两次,但是结果不一样)
解决方案: Repeatable read (行级写锁,即在读的过程中,对数据上锁,此时数据就不允许修改了!就可以避免不可重复读)
(3)幻读:读取过程中数据条目发生了变化(原来有的数据突然没了;原来只有一个数据刷新后又突然变出来一堆数据)
解决方案: Serializable(表级写锁,即读表的过程中,整个表都不能写入数据!)
Spring的事务管理
JavaEE开发使用分层设计的思想进行,对于简单的业务层转调数据层的单一操作,事务开启在业务层或者数据层并无太大差别(都可以!),当业务中包含多个数据层的调用时,需要在业务层开启事务,对数据层中多个操作进行组合并归属于同一个事务进行处理。
故Spring中控制的事务我们最好直接在业务层中!!!
Spring为业务层提供了整套的事务解决方案,定义了相应的API,三者协同作用,保障了事务的执行!
-
PlatformTransactionManager(平台事务管理器)
-
TransactionDefinition(事务定义对象)
-
TransactionStatus(事务状态)
Spring事务核心对象
(1)PlatformTransactionManager(平台事务管理器)
PlatformTransactionManager是一个接口,最终想要使用它,必须使用其实现类对象中的方法,而它的实现类又很多。
平台事务管理器实现类(PlatformTransactionManager)
什么是JPA、JDO、JTA?
-
JPA(Java Persistence API)Java EE 标准之一,为POJO提供持久化标准规范,并规范了持久化开发的统一API,符合JPA规范的开发可以在不同的JPA框架下运行。简单的来说,假如我们使用了JPA规范,持久层是用Mybatis做,想换框架使用Hibernate,两者移植可以“无缝衔接”
-
JDO(Java Data Object )是Java对象持久化规范,用于存取某种数据库中的对象,并提供标准化API。与JDBC相比,JDBC仅针对关系数据库进行操作,JDO可以扩展到关系数据库、文件、XML、对象数据库(ODBMS)等,可移植性更强
-
JTA(Java Transaction API)Java EE 标准之一,允许应用程序执行分布式事务处理。与JDBC相比,JDBC事务则被限定在一个单一的数据库连接,而一个JTA事务可以有多个参与者,比如JDBC连接、JDO 都可以参与到一个JTA事务中
PlatformTransactionManager定义的事务基本操作
获取事务 :
TransactionStatus getTransaction(TransactionDefinition definition)
提交事务 :
void commit(TransactionStatus status)
回滚事务 :
void rollback(TransactionStatus status)
显然,我们可以看到获取事务的参数是TransactionDefinition(事务定义对象),提交事务、回滚事务的参数是TransactionStatus(事务状态)
(2)TransactionDefinition(事务定义对象)
(3)TransactionStatus(事务状态)
Spring关于事务的案例
我们用一个转账的案例来讲解事务:
银行转账操作中,涉及从A账户到B账户的资金转移操作。数据层仅提供单条数据的基础操作,未设计多账户间的业务操作。
我们使用Spring整合Mybatis,在Maven工程的条件下来描述:
首先在pom.xml中导入架包
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.16</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
</dependencies>
其中,尤其要注意的是最上面的<build>一定要写!!! 不然很有可能代码没有问题,但是运行一直会报错(找不到statement对象)!!!
项目骨架如下所示:
domain包下的就是实体类对象Account类
public class Account implements Serializable {
private Integer id;
private String name;
private Double money;
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 Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
dao包下有AccountDao接口(Mybatis会自动映射成实体类)
public interface AccountDao {
void inMoney(@Param("name") String name,@Param("money") Double money);
void outMoney(@Param("name") String name,@Param("money") Double money);
}
@Param():
在方法参数的前面写上@Param("参数名"),表示给参数命名,名称就是括号中的内容
例如:
public Student select(@Param("aaaa") String name);
给入参 String name 命名为aaaa,然后sql语句....where s_name= #{aaaa} 中就可以根据aaaa得到参数值了
AccountDao.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.Harmony.dao.AccountDao">
<update id="inMoney">
update account set money=money + #{money} where name = #{name}
</update>
<update id="outMoney">
update account set money=money - #{money} where name = #{name}
</update>
</mapper>
在业务层,实现事务,AccountServiceImpl
public class AccountServiceImpl implements AccountService {
//注入AccountDao
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
//注入数据源对象
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void transfer(String outName, String inName, Double money) {
// 开启事务
PlatformTransactionManager ptm = new DataSourceTransactionManager(dataSource);
// 事务定义的对象
TransactionDefinition td = new DefaultTransactionDefinition();
// 事务状态
TransactionStatus ts = ptm.getTransaction(td);
accountDao.inMoney(outName,money);
//int i = 1/0;
accountDao.outMoney(inName,money);
//提交事务
ptm.commit(ts);
}
}
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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--加载perperties配置文件的信息-->
<context:property-placeholder location="classpath:*.properties"/>
<!--加载druid资源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--配置service作为spring的bean,注入dao-->
<bean id="accountService" class="com.Harmony.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="dataSource" ref="dataSource"/>
</bean>
<!--spring整合mybatis后控制的创建连接用的对象-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="typeAliasesPackage" value="com.Harmony.domain"/>
</bean>
<!--加载mybatis映射配置的扫描,将其作为spring的bean进行管理-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.Harmony.dao"/>
</bean>
</beans>
App类
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accountService = (AccountService) ctx.getBean("accountService");
accountService.transfer("J1","J2",100D);
}
}
所有的代码如上述所示!
使用AOP控制事务
显然关于事务的这一块的代码相对固定,我们很自然的想到了AOP,我们可以对事务的操作相关的代码进行抽取。
这里都是用之前所学过的AOP,仅粘贴核心代码
新建一个包aop,在里面写一个类TxAdvice
public class TxAdvice {
//注入数据源对象
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public Object transactionManger(ProceedingJoinPoint pjp) throws Throwable {
// 开启事务
PlatformTransactionManager ptm = new DataSourceTransactionManager(dataSource);
// 事务定义的对象
TransactionDefinition td = new DefaultTransactionDefinition();
// 事务状态
TransactionStatus ts = ptm.getTransaction(td);
Object ret = pjp.proceed();
//提交事务
ptm.commit(ts);
return ret;
}
}
业务层的AccountServiceImpl 修改如下
public class AccountServiceImpl implements AccountService {
//注入AccountDao
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer(String outName, String inName, Double money) {
accountDao.inMoney(outName,money);
//int i = 1/0;
accountDao.outMoney(inName,money);
}
}
在applicationContext.xml,在最后面加入了aop的配置
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://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/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--加载perperties配置文件的信息-->
<context:property-placeholder location="classpath:*.properties"/>
<!--加载druid资源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--配置service作为spring的bean,注入dao-->
<bean id="accountService" class="com.Harmony.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!--spring整合mybatis后控制的创建连接用的对象-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="typeAliasesPackage" value="com.Harmony.domain"/>
</bean>
<!--加载mybatis映射配置的扫描,将其作为spring的bean进行管理-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.Harmony.dao"/>
</bean>
<!-- AOP配置事务 -->
<bean id="txAdvice" class="com.Harmony.aop.TxAdvice">
<property name="dataSource" ref="dataSource"/>
</bean>
<aop:config>
<aop:pointcut id="pt" expression="execution(* *..transfer(..))"/>
<aop:aspect ref="txAdvice">
<aop:around method="transactionManger" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
</beans>
声明式事务(XML)
我们用Java代码的方式去实现事务控制,但是这一部分的代码相对是固定的,所以Spring底层帮我们写好了,即声明式事务。
我们之前写是创建了一个aop的包,使用aop抽取公共代码到TxAdvice类中
使用声明式事务,就不用创建类了!只要在XML文件中就可以实现。
首先要开启相应的命名空间
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation=
"http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd"
使用XML格式开启声明式事务
<!-- TX格式 -->
<!-- 造一个事务管理器 DataSourceTransactionManager -->
<bean id="txManger" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--定义通知类-->
<tx:advice id="txAdvice" transaction-manager="txManger">
<!--定义控制的事务 -->
<tx:attributes>
<!--
所有事务都是读写事务
以get开头的为只读事务
-->
<tx:method name="*" read-only="false"/>
<tx:method name="get*" read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="pt" expression="execution(* com.Harmony.service.*Service.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
</aop:config>
aop:advice与aop:advisor的区别
-
aop:advice配置的通知类可以是普通java对象,不实现接口,也不使用继承关系
-
aop:advisor配置的通知类必须实现通知接口
-
MethodBeforeAdvice
-
AfterReturningAdvice
-
ThrowsAdvice
-
...
-
关于TX配置
tx:advice
tx:attributes
tx:method
关于tx:method的属性
事务的传播行为
-
事务管理员
-
事务协调员
事务传播行为描述的是事务协调员对事务管理员所携带事务的处理态度。
(1)required(需要)
有就加入,没有就新建。
(2)requires_new
不管有没有都新建!
(3)supports(支持)
有就加入,没有就也跟着没有!
(4)not_supported
不支持事务,不管原来有没有,都没有。
(5)mandatory(命令、必须有)
有,则加入;没有,报错!
(6)never(必须没有事务)
原来有事务,则报错!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)