一、添加依赖
在maven中添加spring依赖、spring aop依赖、spring数据库依赖、spring事务依赖、mysql依赖
Spring事务在业务逻辑上添加事务,容器中保存的是这个业务逻辑的代理对象,所有需要aop依赖(aop的实现是依靠动态代理,生成代理对象)
<properties>
<spring.version>5.1.9.RELEASE</spring.version>
<mysql.version>8.0.15</mysql.version>
</properties>
<dependencies>
<!--spring依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>
<!--spring aop依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<!--spring数据库模块 -->
<!--spring jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!--spring orm-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<!--spring tx-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
</dependencies>
二、配置文件
spring配置文件
spring-conf.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"
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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--扫描包-->
<context:component-scan base-package="com.example.dao"/>
<context:component-scan base-package="com.example.service"/>
<!-- dataSource -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
<property name="username" value="root" />
<property name="password" value="123456" />
<property name="url" value="jdbc:mysql://localhost:3306/jdbc_test?useSSL=false" />
<property name="driverClass" value="com.mysql.cj.jdbc.Driver" />
</bean>
<!--jdbc template-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"/>
</bean>
<!--事务控制 配置事务管理器-->
<bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--控制住数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--支持事务注解的(@Transactional)-->
<tx:annotation-driven transaction-manager="tm"/>
</beans>
三、测试
1.数据库表:
bankuser有字段bid、balance
2.实体类:
Bankuser.java
public class Bankuser {
private Integer bid;
private Integer balance;
public Bankuser() {
}
public Bankuser(Integer bid, Integer balance) {
this.bid = bid;
this.balance = balance;
}
@Override
public String toString() {
return "Bankuser{" +
"bid=" + bid +
", balance=" + balance +
'}';
}
//set、get方法
}
3.dao层
BankuserDao.java
@Repository
public class BankuserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public int updateBalance(Integer bid, Integer money) {
String sql = "update bankuser set balance=balance+? where bid = ?";
int update = jdbcTemplate.update(sql, money, bid);
return update;
}
public List<Bankuser> selectAll(){
String sql = "select bid,balance from bankuser";
List<Bankuser> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Bankuser.class));
return list;
}
}
4.service层
BankuserService.java
transfer()方法上添加事务
对id为1的用户banlance减100,id为2的用户减200;
如果方法上添加事务,1/0语句会发生异常,触发事务回滚;如果方法上没有添加事务,会造成第一个updateBalance()成功修改数据,第二个updateBalance()执行不到,没有修改数据
@Service
public class BankuserService {
@Autowired
private BankuserDao bankuserDao;
@Transactional
public void transfer(){
bankuserDao.updateBalance(1,-100);
int i = 1/0;
bankuserDao.updateBalance(2,100);
}
public List<Bankuser> selectAll(){
return bankuserDao.selectAll();
}
}
5.测试类
有事务的业务逻辑,容器中保存的是这个业务逻辑的代理对象
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-conf1.xml");
IBankuserService bankuserService = context.getBean(IBankuserService.class);
try {
bankuserService.transfer();
System.out.println("操作成功");
} catch (Exception e) {
System.out.println(String.format("操作失败:%s", e.getMessage()));
}
List<Bankuser> list = bankuserService.selectAll();
list.forEach(System.out::println);
}
}
四、事务属性
只有 @Transactional
注解应用到 public 方法,才能进行事务管理
1.timeout
默认值为-1
@Transactional(timeout = 3)
,超时
事务超出指定执行时长后自动终止并回滚(单位为秒)
2.readOnly
默认值为 readOnly = false
@Transactional(readOnly = true)
,设置事务为只读事务
可以进行事务优化,加快查询速度,只能用于查询
3.noRollbackFor、noRollbackForClassName
异常分为:运行时异常、编译时异常
运行时异常,可以处理,默认都回滚;编译时异常,try-catch或throws处理,默认不回滚
@Transactional(noRollbackFor = {ArithmeticException.class, NullPointerException.class})
哪些异常事务可以不回滚(原本运行时异常是回滚的)
4.rollbackFor、rollbackForClassName
@Transactional(rollbackFor = {FileNotFoundException.class})
哪些异常事务需要回滚(原本编译时异常是不回滚的)
5.isolation
默认值采用 DEFAULT
数据库事务并发问题:
1)脏读:A修改数据,B读取修改后的数据,A回滚,B属于脏读
2)不可重复读:A读数据,B修改数据,A再次读两次数据不一致
3)幻读:A读数据,B插入数据,A读数据多了几条数据
隔离级别:
1)读未提交:read uncommited,允许B读取A未提交的数据(会发生脏读)
2)读已提交:read commited,要求B只能读取A以提交的修改(避免脏读,会发生不可重复读,发生幻读)
3)可重复读:repeatable read,确保A可以多次从一个字段中读取到相同的值,即A执行期间禁止其它事务对这个字段进行更新
4)串行化:serializable,确保A可以多次从一个表中读取相同的行,在A执行期间,禁止其它事务对这个表进行添加、更新、删除操作,可以避免任何并发问题
mysql支持1)、2)、3)、4),oracle支持2)、4)
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
,设置隔离级别为读未提交
6.propagation
默认值为 Propagation.REQUIRED
事务传播行为:
required(当前方法需要事务,如果有事务,就用别人的事务)、required_new(开启一个新事务)
如果是如果是required事务的属性都是继承大事务的,而propagation=Propagation.REQUIRES_NEW可以调整
@Transactional(propagation = Propagation.REQUIRED)
,当前方法需要事务,如果有事务,就用别人的事务
五、基于XML的事务配置
spring-conf.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"
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
http://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">
<!-- dataSource -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
<property name="username" value="root" />
<property name="password" value="123456" />
<property name="url" value="jdbc:mysql://localhost:3306/jdbc_test?useSSL=false" />
<property name="driverClass" value="com.mysql.cj.jdbc.Driver" />
</bean>
<!--jdbc template-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"/>
</bean>
<bean id="bankuserDao" class="com.example.dao.BankuserDao" >
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<bean id="bankuserService" class="com.example.service.BankuserService">
<property name="bankuserDao" ref="bankuserDao"/>
</bean>
<!--事务控制 配置事务管理器-->
<bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--控制住数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--支持事务注解的(@Transactional)-->
<!--<tx:annotation-driven transaction-manager="tm"/>-->
<!--配置切面 aop-->
<aop:config>
<!--切入点表达式表明事务管理器切入哪些方法-->
<aop:pointcut id="txPont" expression="execution(* com.example.service.*.*(..))"/>
<!-- advice-ref指向切面类,有前置通知,后置通知等 -->
<aop:advisor advice-ref="myAdvice" pointcut-ref="txPont"/>
</aop:config>
<!--transaction-manager指定配置哪个事务管理器-->
<tx:advice id="myAdvice" transaction-manager="tm">
<!--事务属性-->
<tx:attributes>
<!--指明哪些方法是事务方法-->
<tx:method name="transfer" propagation="REQUIRED" timeout="3"/>
<tx:method name="get" read-only="true"/>
</tx:attributes>
</tx:advice>
</beans>
测试类
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-conf1.xml");
IBankuserService bankuserService = context.getBean(IBankuserService.class);
try {
bankuserService.transfer();
System.out.println("操作成功");
} catch (Exception e) {
System.out.println(String.format("操作失败:%s", e.getMessage()));
}
List<Bankuser> list = bankuserService.selectAll();
list.forEach(System.out::println);
}
}
六、spring 事务原理
<tx:advice>
会转化为 TransactionInterceptor
例如,由 Proxy 形成的代理,每次调用方法时,会执行 InvocationHandler 的 invoke() 方法,即 JdkDynamicAopProxy 的 invoke() 方法
JdkDynamicAopProxy 的 invoke() 方法中,形成拦截链有TransactionInterceptor
// TransactionInterceptor.java
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// Work out the target class: may be {@code null}.
// The TransactionAttributeSource should be passed the target class
// as well as the method, which may be from an interface.
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
createTransactionIfNecessary()
创建事务
// TransactionAspectSupport.java
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
commitTransactionAfterReturning(txInfo);
return retVal;
}
else {
final ThrowableHolder throwableHolder = new ThrowableHolder();
// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
try {
Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
try {
return invocation.proceedWithInvocation();
}
catch (Throwable ex) {
if (txAttr.rollbackOn(ex)) {
// A RuntimeException: will lead to a rollback.
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
else {
throw new ThrowableHolderException(ex);
}
}
else {
// A normal return value: will lead to a commit.
throwableHolder.throwable = ex;
return null;
}
}
finally {
cleanupTransactionInfo(txInfo);
}
});
// Check result state: It might indicate a Throwable to rethrow.
if (throwableHolder.throwable != null) {
throw throwableHolder.throwable;
}
return result;
}
catch (ThrowableHolderException ex) {
throw ex.getCause();
}
catch (TransactionSystemException ex2) {
if (throwableHolder.throwable != null) {
logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
ex2.initApplicationException(throwableHolder.throwable);
}
throw ex2;
}
catch (Throwable ex2) {
if (throwableHolder.throwable != null) {
logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
}
throw ex2;
}
}
}
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
// If no name specified, apply method identification as transaction name.
if (txAttr != null && txAttr.getName() == null) {
txAttr = new DelegatingTransactionAttribute(txAttr) {
@Override
public String getName() {
return joinpointIdentification;
}
};
}
TransactionStatus status = null;
if (txAttr != null) {
if (tm != null) {
status = tm.getTransaction(txAttr);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
"] because no transaction manager has been configured");
}
}
}
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
DataSourceTransactionManager 继承 AbstractPlatformTransactionManager
// AbstractPlatformTransactionManager.java
@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
Object transaction = doGetTransaction();
// Cache debug flag to avoid repeated checks.
boolean debugEnabled = logger.isDebugEnabled();
if (definition == null) {
// Use defaults if no transaction definition given.
definition = new DefaultTransactionDefinition();
}
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
return handleExistingTransaction(definition, transaction, debugEnabled);
}
// Check definition settings for new transaction.
if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
}
// No existing transaction found -> check propagation behavior to find out how to proceed.
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
SuspendedResourcesHolder suspendedResources = suspend(null);
if (debugEnabled) {
logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
}
try {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
}
else {
// Create "empty" transaction: no actual transaction, but potentially synchronization.
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
logger.warn("Custom isolation level specified but no actual transaction initiated; " +
"isolation level will effectively be ignored: " + definition);
}
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
}
}
从 TransactionSynchronizationManager 获得 ConnectionHolder
doBegin() 方法判断如果 ConnectionHolder 为 null,则从数据库连接池得到一个连接,创建一个新的 ConnectionHolder,并把这个连接设置非自动提交
调用 TransactionSynchronizationManager.bindResource() 方法,datasouce 为 key,connectionHolder 为 value,放入 ThreadLocal 中
// DataSourceTransactionManager.java
@Override
protected Object doGetTransaction() {
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
txObject.setSavepointAllowed(isNestedTransactionAllowed());
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
txObject.setConnectionHolder(conHolder, false);
return txObject;
}
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
Connection newCon = obtainDataSource().getConnection();
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
// so we don't want to do it unnecessarily (for example if we've explicitly
// configured the connection pool to set it already).
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
con.setAutoCommit(false);
}
prepareTransactionalConnection(con, definition);
txObject.getConnectionHolder().setTransactionActive(true);
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
// Bind the connection holder to the thread.
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
}
catch (Throwable ex) {
if (txObject.isNewConnectionHolder()) {
DataSourceUtils.releaseConnection(con, obtainDataSource());
txObject.setConnectionHolder(null, false);
}
throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
}
}
// TransactionSynchronizationManager.java
public static void bindResource(Object key, Object value) throws IllegalStateException {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Assert.notNull(value, "Value must not be null");
Map<Object, Object> map = resources.get();
// set ThreadLocal Map if none found
if (map == null) {
map = new HashMap<>();
resources.set(map);
}
Object oldValue = map.put(actualKey, value);
// Transparently suppress a ResourceHolder that was marked as void...
if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
oldValue = null;
}
if (oldValue != null) {
throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
}
if (logger.isTraceEnabled()) {
logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
Thread.currentThread().getName() + "]");
}
}
@Nullable
public static Object getResource(Object key) {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Object value = doGetResource(actualKey);
if (value != null && logger.isTraceEnabled()) {
logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
Thread.currentThread().getName() + "]");
}
return value;
}
@Nullable
private static Object doGetResource(Object actualKey) {
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
Object value = map.get(actualKey);
// Transparently remove ResourceHolder that was marked as void...
if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
map.remove(actualKey);
// Remove entire ThreadLocal if empty...
if (map.isEmpty()) {
resources.remove();
}
value = null;
}
return value;
}
实现jdbc操作,提交,回滚。保证同一个连接,依靠 aop 形成代理类,执行方法前,执行 TransactionInterceptor 拦截器。
TransactionInterceptor 利用 DataSourceTransactionManager 获得连接,把连接放入 TransactionSynchronizationManager 的 ThreadLocal<Map<Object, Object>> resources
中
执行操作时,例如 jdbcTemplate,通过 TransactionSynchronizationManager
获得连接
七、事务失效
1.访问权限问题
spring 要求被代理方法必须是 public 的
在 AbstractFallbackTransactionAttributeSource 类的 computeTransactionAttribute 方法中有个判断,如果目标方法不是 public ,则 TransactionAttribute 返回 null ,即不支持事务
// AbstractFallbackTransactionAttributeSource.java
@Nullable
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// The method may be on an interface, but we need attributes from the target class.
// If the target class is null, the method will be unchanged.
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
// First try is the method in the target class.
TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
if (txAttr != null) {
return txAttr;
}
// Second try is the transaction attribute on the target class.
txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
if (specificMethod != method) {
// Fallback is to look at the original method.
txAttr = findTransactionAttribute(method);
if (txAttr != null) {
return txAttr;
}
// Last fallback is the class of the original method.
txAttr = findTransactionAttribute(method.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
}
return null;
}
2. 方法用 final 或 static 修饰
spring 事务底层使用了 aop,也就是通过 jdk 动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。
但如果某个方法用 final 修饰了,那么在它的代理类中,就无法重写该方法,而添加事务功能。
如果某个方法是 static 的,同样无法通过动态代理,变成事务方法。
3.方法内部调用
方法拥有事务的能力是因为 spring aop 生成代理了对象,但是如果方法内部调用,则直接调用了this对象的方法
解决办法:
- 新加一个Service方法
- 在该Service类中注入自己
- 通过AopContent类,使用AopContext.currentProxy()获取代理对象
需要配置 <aop:config expose-proxy="true">
,则才会在每次调用方法时,把代理对象设置到 AopContext 中的 ThreadLocal<Object> currentProxy
中
4.未被spring管理
即使用spring事务的前提是:对象要被spring管理,需要创建bean实例。
5.多线程调用
两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。
同一个事务,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接肯定是不一样的,所以是不同的事务。
6.表不支持事务
myisam好用,但有个很致命的问题是:不支持事务。
7.未开启事务
如果使用的是springboot项目,springboot通过 DataSourceTransactionManagerAutoConfiguration 类,已经默默开启了事务。只需要配置spring.datasource相关参数即可。
如果使用的还是传统的spring项目,则需要在 applicationContext.xml 文件中,手动配置事务相关参数。如果忘了配置,事务肯定是不会生效的。
<!-- 配置事务管理器 -->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="tm">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:advice id="advice" transaction-manager="tm">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 用切点把事务切进去 -->
<aop:config>
<aop:pointcut expression="execution(* com.example.*.*(..))" id="pointcut"/>
<aop:advisor advice-ref="advice" pointcut-ref="pointcut"/>
</aop:config>