Spring事务管理
介绍了事务的概念,事务的特性,JDBC 事务管理的步骤和操作过程,以及Spring事务管理的两种实现方式:编程式事务管理和声明式事务管理。
1.事务的概念
事务(Transaction)就是将一系列的数据库操作作为一个整体来执行,它代表了一组被视为单个工作单元的操作。这些操作要么全部成功执行,要么全部不执行,从而确保数据库的一致性和完整性。事务处理主要用于管理对数据库的访问,以支持ACID特性:
-
原子性(Atomicity):事务是一个不可分割的工作单位,其中的操作要么全部完成,要么全部不进行。如果在执行过程中发生了错误,所有已经执行的操作都将被撤销,就像这些操作从未发生过一样。
-
一致性(Consistency):一个事务必须使数据库从一个一致性状态变换到另一个一致性状态。这意味着事务应该遵循所有的预定义规则和约束,包括但不限于实体完整性、域完整性和参照完整性。
-
隔离性(Isolation):并发执行的多个事务之间应该是相互隔离的,即一个事务的中间状态不会影响其他事务。根据不同的隔离级别,可能会出现脏读、不可重复读或幻读等问题。
-
持久性(Durability):一旦一个事务被提交,其结果就是永久性的,即使系统遇到故障也不会丢失已提交的数据。
2.JDBC事务管理
JDBC(Java Database Connectivity)提供了一种标准的API,用于让Java应用程序与数据库进行交互。在使用JDBC进行数据库操作时,事务管理是一个重要的方面,它确保了数据的一致性和完整性。
在JDBC中,Connection提供了事务处理的方法,通过调用setAutoCommit(false)可以设置手动提交事务;当事务完成后在一连串的SQL语句后面可用commit()显示提交事务;如果在事务处理过程中发生异常,则可通过rollbackO进行事务回滚。
2.1 JDBC事务管理案例
案例:银行转账
(1)beans-spring-jdbc.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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- <context:component-scan base-package="com.rainpet"/>-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/mall"/>
<property name="username" value="root"/>
<property name="password" value="123"/>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
</bean>
<bean id="accountDao" class="com.rainpet.dao.impl.AccountDaoJdbcImpl">
<property name="datasource" ref="dataSource"/>
</bean>
</beans>
(2)AccountDao.java
package com.rainpet.dao;
public interface AccountDao {
public boolean addMoney(String username, Double money);
public boolean subMoney(String username, Double money);
public boolean transferMoney(String username1, String username2, Double money);
}
(3)AccountDaoJdbcImpl.java
package com.rainpet.dao.impl;
import com.rainpet.dao.AccountDao;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class AccountDaoJdbcImpl implements AccountDao {
private DataSource datasource;
private Connection conn;
private DataSource getDatasource() {
return datasource;
}
public void setDatasource(DataSource datasource) {
this.datasource = datasource;
try {
conn = datasource.getConnection();
} catch (SQLException e) {
//throw new RuntimeException(e);
e.printStackTrace();
}
}
@Override
public boolean addMoney(String username, Double money) {
PreparedStatement psmt = null;
try{
int retCount=0;
psmt=conn.prepareStatement("update t_account set money=money+? where username=?");
psmt.setString(2,username);
psmt.setDouble(1,money);
retCount=psmt.executeUpdate();
System.out.println("addMoney amount "+money.toString()+" successfully");
return true;
}catch (SQLException e){
e.printStackTrace();
try {
conn.rollback();
System.out.println("addMoney amount "+money.toString()+" rollback success");
} catch (SQLException ex) {
ex.printStackTrace();
System.out.println("addMoney amount "+money.toString()+" rollback failed");
//throw new RuntimeException(ex);
}
System.out.println("addMoney amount "+money.toString()+" failed");
return false;
}
}
@Override
public boolean subMoney(String username, Double money) {
PreparedStatement psmt = null;
try{
psmt=conn.prepareStatement("update t_account set money=money-? where username=? and (money-?)>=0");
psmt.setString(2,username);
psmt.setDouble(1,money);
psmt.executeUpdate();
System.out.println("subMoney amount "+money.toString()+" successfully");
return true;
} catch (SQLException e) {
try {
conn.rollback();
System.out.println("subMoney amount "+money.toString()+" rollback success");
return false;
} catch (SQLException ex) {
ex.printStackTrace();
System.out.println("subMoney amount "+money.toString()+" rollback failed");
return false;
//throw new RuntimeException(ex);
}
//throw new RuntimeException(e);
}
}
@Override
public boolean transferMoney(String sourceusername, String desusername, Double money) {
try {
conn.setAutoCommit(false);
boolean subflag= subMoney(sourceusername,money);
//Integer i=1/0;
boolean addflag= addMoney(desusername,money);
if(subflag&&addflag){
System.out.println("transferMoney "+money.toString()+" successfully");
conn.commit();
return true;
}else
{
System.out.println("transferMoney "+money.toString()+" failed");
conn.rollback();
return false;
}
} catch (SQLException e) {
//throw new RuntimeException(e);
e.printStackTrace();
try {
conn.rollback();
System.out.println("transferMoney "+money.toString()+" failed");
return false;
}catch (SQLException ex){
ex.printStackTrace();
return false;
}
}
}
}
(4)案例验证
package com.rainpet;
import com.rainpet.dao.AccountDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class test_spring_jdbc {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("beans-spring-jdbc.xml");
AccountDao accountDao= (AccountDao) ac.getBean("accountDao");
accountDao.transferMoney("zhangsan","lisi",1000.0);
}
}
3.Spring事务管理
Spring框架提供了统一的事务管理抽象,使得开发者可以以声明式的方式管理事务,而不必直接操作底层的JDBC、Hibernate等资源。Spring支持编程式和声明式两种方式进行事务管理。编程式事务管理允许开发者通过编写代码来控制事务的边界和行为。声明式事务管理允许开发者以声明的方式定义事务,通常不涉及具体的事务管理代码,而是通过配置或注解来指定哪些方法应该在事务中执行。
Spring将所有的事务管理都抽象为PlatformTransactionManager、TransactionStatus和TransactionDefinition三个接口
-
PlatformTransactionManager:这是Spring事务管理的核心接口,它定义了用于管理事务的方法。根据不同的数据访问技术,Spring提供了多种
PlatformTransactionManager
的实现,例如:DataSourceTransactionManager
:适用于JDBC和MyBatis。HibernateTransactionManager
:适用于Hibernate。JtaTransactionManager
:适用于分布式事务。
-
TransactionDefinition:此接口提供事务的相关属性设置,包括隔离级别、传播行为、超时时间以及是否只读等。
-
TransactionStatus:描述了给定事务的具体状态,如是否为新事务、是否有保存点等。
3.1 编程式事务管理
Spring提供了TransactionTemplate
或者直接使用PlatformTransactionManager
来进行编程式的事务管理。
Spring编程式事务一般流程如下:
(1)在Spring配置文件中声明数据源。
(2)在Spring配置文件中声明 个事务管理类,例如,DataSourceTransactionManagr、Hibernate TransactionManger、JTATransactionManager 等
(3)在代码中加入事务处理代码。
关键代码如下:
TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 执行数据库操作
transactionManager.commit(status);
} catch (Exception ex) {
transactionManager.rollback(status);
throw ex;
}
3.2 编程式事务管理案例
(1)beans-spring.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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- <context:component-scan base-package="com.rainpet"/>-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/mall"/>
<property name="username" value="root"/>
<property name="password" value="123"/>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="accountDao" class="com.rainpet.dao.impl.AccountDaoSpringImpl">
<property name="transactionManager" ref="transactionManager"/>
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
</beans>
(2)AccountDao.java
package com.rainpet.dao;
public interface AccountDao {
public boolean addMoney(String username, Double money);
public boolean subMoney(String username, Double money);
public boolean transferMoney(String username1, String username2, Double money);
}
(3)AccountDaoSpringImpl.java
package com.rainpet.dao.impl;
import com.rainpet.dao.AccountDao;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import java.sql.PreparedStatement;
public class AccountDaoSpringImpl implements AccountDao {
private DataSourceTransactionManager transactionManager;
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
private DataSourceTransactionManager getTransactionManager() {
return transactionManager;
}
public void setTransactionManager(DataSourceTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
@Override
public boolean addMoney(String username, Double money) {
int retCount=0;
retCount=jdbcTemplate.update("update t_account set money=money+? where username=?",money,username);
System.out.println("addMoney amount "+money.toString()+" successfully");
return true;
}
@Override
public boolean subMoney(String username, Double money) {
PreparedStatement psmt = null;
int retCount=0;
retCount=jdbcTemplate.update("update t_account set money=money-? where username=?",money,username);
if(retCount==0){
return false;
}
System.out.println("subMoney amount "+money.toString()+" successfully");
return true;
}
@Override
public boolean transferMoney(String sourceusername, String desusername, Double money) {
DefaultTransactionDefinition tdefinition=new DefaultTransactionDefinition();
tdefinition.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus ts= transactionManager.getTransaction(tdefinition);
boolean subflag= subMoney(sourceusername,money);
System.out.println("subbing amount "+money.toString());
boolean addflag= addMoney(desusername,money);
System.out.println("adding amount "+money.toString());
if(subflag&&addflag){
transactionManager.commit(ts);
System.out.println("transferMoney "+money.toString()+" successfully");
return true;
}else
{
transactionManager.rollback(ts);
System.out.println("transferMoney "+money.toString()+" failed");
return false;
}
}
}
(4)案例验证
package com.rainpet;
import com.rainpet.dao.AccountDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class test_spring {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("beans-spring.xml");
AccountDao accountDao= (AccountDao) ac.getBean("accountDao");
accountDao.transferMoney("zhangsan","lisi",1000.0);
}
}
3.3 声明式事务管理
Spring推荐使用声明式事务管理,这通常通过XML配置或注解来完成。
Spring声明式事务的实现方式有以下四种:
- 基于@Transactional的声明式事务管理
- 基于tx命名空间的声明式事务管理。
- 基于TransactionProxyFactoryBean的声明式事务管理。
- 基于Transactionlnterceptor的声明式事务管理。
基于tx命名空间的声明式事务管理的配置步骤如下:
1. 引入必要的命名空间
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
</beans>
2.在XML在中配置通知、切入点以及Advisor(通知器)
<tx:advice>
定义事务行为,包括传播行为、隔离级别、只读标志等,transaction-manage属性用于指定事物管理器
<tx:attributes>为一个或多个方法声明Spring事务的规则
<tx:method>为一个给定的方法签名描述事务规则
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="serviceMethods" expression="execution(* com.example.service.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethods" />
</aop:config>
3.4 基于tx命名空间的声明式事务管理案例
Spring事务管理-积分功能实现https://download.csdn.net/download/m0_74808313/90524606
4.Spring事务的传播方式和隔离级别
4.1 传播行为(Propagation Behavior)
传播行为定义了在一个事务上下文中调用另一个需要事务的方法时应该如何处理。Spring提供了7种不同的传播行为:
- REQUIRED(默认):如果当前存在事务,则加入该事务;如果不存在,则创建一个新的事务。
- SUPPORTS:如果当前存在事务,则加入该事务;如果不存在,以非事务方式继续运行。
- MANDATORY:如果当前存在事务,则加入该事务;如果不存在,则抛出异常。
- REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则将当前事务挂起。
- NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则将当前事务挂起。
- NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
- NESTED:如果当前存在事务,则在嵌套事务内执行;如果不存在,则等同于REQUIRED。
4.2 隔离级别(Isolation Level)
隔离级别定义了一个事务可能受其他并发事务影响的程度。Spring支持的隔离级别包括:
- DEFAULT:使用底层数据库的默认隔离级别。
- READ_UNCOMMITTED:允许读取未提交的数据变更,可能导致脏读、不可重复读和幻读。
- READ_COMMITTED:允许从已经提交的并发事务读取数据,可以防止脏读,但无法避免不可重复读和幻读。
- REPEATABLE_READ:对相同字段的多次读取结果一致,除非数据被当前事务本身改变,可以防止脏读和不可重复读,但幻读仍可能发生。
- SERIALIZABLE:完全串行化的读,每次读都需要获得表级共享锁,可避免所有并行问题,但代价最大。