@Transactional注解

事务概述

spring有编程式事务和声明式事务,一般都比较推荐使用声明式事务。因为编程式事务与业务代码具有一定的耦合性质,在做改动的时候势必会牵连到主业务,所以一般都会使用推荐的声明式事务,即使用注解的方法来进行结构。这篇只看声明式事务的办法。即@Transactional注解。
首先看下这个注解类。

public @interface Transactional {

	/**
	 * Alias for {@link #transactionManager}.
	 * @see #transactionManager
	 */
	@AliasFor("transactionManager")
	String value() default "";

	/**
	 * A qualifier value for the specified transaction.
	 * 

May be used to determine the target transaction manager, * matching the qualifier value (or the bean name) of a specific * {@link org.springframework.transaction.PlatformTransactionManager} * bean definition. * @since 4.2 * @see #value */ @AliasFor("value") String transactionManager() default ""; //事务的传播属性 Propagation propagation() default Propagation.REQUIRED; //事务的隔离级别 Isolation isolation() default Isolation.DEFAULT; //超时时间 int timeout() default TransactionDefinition.TIMEOUT_DEFAULT; /** * A boolean flag that can be set to {@code true} if the transaction is * effectively read-only, allowing for corresponding optimizations at runtime. *

Defaults to {@code false}. *

This just serves as a hint for the actual transaction subsystem; * it will not necessarily cause failure of write access attempts. * A transaction manager which cannot interpret the read-only hint will * not throw an exception when asked for a read-only transaction * but rather silently ignore the hint. * @see org.springframework.transaction.interceptor.TransactionAttribute#isReadOnly() * @see org.springframework.transaction.support.TransactionSynchronizationManager#isCurrentTransactionReadOnly() */ boolean readOnly() default false; //指定异常的回滚 Class<? extends Throwable>[] rollbackFor() default {}; //指定类名的异常回滚 String[] rollbackForClassName() default {}; //指定异常不回滚 Class<? extends Throwable>[] noRollbackFor() default {}; //指定类名的异常不回滚 String[] noRollbackForClassName() default {}; }

事务的传播属性以及事务的隔离级别可以自行百度。这里不做测试。

我们一般使用@Transactional注解都是用在类的方法上。官网也不建议使用在接口类上面,注解肯定都是用到了aop的思想,即使用了动态代理。而如果使用cglib动态代理肯定没有办法代理接口类。所以我们真正使用的时候都在类的方法上面。

测试用例

首先写测试的实例。
主要就是按照下面的测试来写的。使用是springboot+mybatis。因为这个用得熟练一点,主要看测试方法。都是简单的测试代码。过一遍就好。

package com.test.dao;

public class Dao {
	private Integer id;
	private String name;
	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;
	}
	
}

package com.test.mapper;

import org.apache.ibatis.annotations.Insert;

import com.test.dao.Dao;

public interface DaoMapper {

	@Insert("INSERT INTO testtransaction (name) VALUES (#{name})")
	int insertSql(Dao dao);
}

package com.test.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.test.dao.Dao;
import com.test.mapper.DaoMapper;

@Service
public class TestService {

	@Autowired
	private DaoMapper daoMapper;
	
	@Transactional
	public void testpub() {
		Dao dao=new Dao();
		dao.setName("testpub");
		daoMapper.insertSql(dao);
		throw new RuntimeException("has RuntimeException");
	}
	
	@Transactional
	protected void testpro() {
		Dao dao=new Dao();
		dao.setName("testpro");
		daoMapper.insertSql(dao);
		throw new RuntimeException("has RuntimeException");
	}
	
	@Transactional
	public void testNotThis() {
		Dao dao=new Dao();
		dao.setName("testNotThis");
		daoMapper.insertSql(dao);
		throw new RuntimeException("has RuntimeException");
	}
	
	@Transactional
	public void testThis1() {
		Dao dao=new Dao();
		dao.setName("testThis");
		daoMapper.insertSql(dao);
		throw new RuntimeException("has RuntimeException");
	}
	
	public void testThis() {
		this.testThis1();
	}
}



package com.test;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.test.mapper")
public class AppTransaction {
	public static void main(String[] args) {
		SpringApplication.run(AppTransaction.class, args);
	}
}

application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driverClassName=com.mysql.jdbc.Driver
#logging.level.root=debug
package transaction;

import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.test.AppTransaction;
import com.test.service.TestService;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = AppTransaction.class)
public class Test extends TestService {
	@Autowired
	private TestService testService;
	
	
	@org.junit.Test
	public void testPublic() {
		testService.testpub();
	}
	
	@org.junit.Test
	public void testProtectd() {
		super.testpro();
	}
	
	@org.junit.Test
	public void testNotThis() {
		testService.testNotThis();
	}
	
	@org.junit.Test
	public void testThis() {
		testService.testThis();
	}
}

初始的数据库。
@Transactional注解_第1张图片
不加注解会是什么情况都知道,这个就不测试了。

public,protectd,private

首先我们写业务的时候都是使用public方法,极少使用protectd和private方法,但是如果我们使用protectd和private方法呢?
首先都知道private的权限非常的小,而且基本也不会使用。这里就明确的说事务不起作用。

我们执行以下testPublic和testProtectd方法。执行完后数据库如下:
@Transactional注解_第2张图片证明了对于protectd方法事务注解是不起作用的。
因为原理这个注解是通过aop代理的,代理的2种方式不细说,因为对protected以及private方法不能调用,所以注解无法生效,即aop拿到了这个class类,却不能对这2种方法进行操作。

本地方法调用和外部方法调用

清空数据库。
执行方法。
@Transactional注解_第3张图片发现this方法调用后的注解没有生效,依然插入进去了。没有做回滚。说明this调用方法的注解不能使用。这个还是跟aop有关联,首先aop代理了方法testThis这个方法,而方法里面再调用其它方法的时候,这个在aop里面就是一个普通方法了,即aop只对这个方法起作用,而这个方法调用了本类的其它方法只能作为普通方法处理了,但是如果调用其他类的方法aop依然起作用,即注解依然生效。
测试如下:加一个service。

package com.test.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.test.dao.Dao;
import com.test.mapper.DaoMapper;

@Service
public class TestService2 {

	@Autowired
	private DaoMapper daoMapper;
	
	@Transactional
	public void testpub() {
		Dao dao=new Dao();
		dao.setName("testpub");
		daoMapper.insertSql(dao);
		throw new RuntimeException("has RuntimeException");
	}
	
	@Transactional
	protected void testpro() {
		Dao dao=new Dao();
		dao.setName("testpro");
		daoMapper.insertSql(dao);
		throw new RuntimeException("has RuntimeException");
	}
	
	@Transactional
	public void testNotThis() {
		Dao dao=new Dao();
		dao.setName("testNotThis");
		daoMapper.insertSql(dao);
		throw new RuntimeException("has RuntimeException");
	}
	
	@Transactional
	public void testThis1() {
		Dao dao=new Dao();
		dao.setName("testThis");
		daoMapper.insertSql(dao);
		throw new RuntimeException("has RuntimeException");
	}
	
	public void testThis() {
		testThis1();
	}
}

TestService 改为:

package com.test.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.test.dao.Dao;
import com.test.mapper.DaoMapper;

@Service
public class TestService {

	@Autowired
	private DaoMapper daoMapper;
	
	@Transactional
	public void testpub() {
		Dao dao=new Dao();
		dao.setName("testpub");
		daoMapper.insertSql(dao);
		throw new RuntimeException("has RuntimeException");
	}
	
	@Transactional
	protected void testpro() {
		Dao dao=new Dao();
		dao.setName("testpro");
		daoMapper.insertSql(dao);
		throw new RuntimeException("has RuntimeException");
	}
	
	@Transactional
	public void testNotThis() {
		Dao dao=new Dao();
		dao.setName("testNotThis");
		daoMapper.insertSql(dao);
		throw new RuntimeException("has RuntimeException");
	}
	
	@Transactional
	public void testThis1() {
		Dao dao=new Dao();
		dao.setName("testThis");
		daoMapper.insertSql(dao);
		throw new RuntimeException("has RuntimeException");
	}
	
	public void testThis() {
		TestService2 testService2=new TestService2();
		testService2.testThis1();
	}
}

清空数据库。再执行测试类的testThis方法。
@Transactional注解_第4张图片发现生效了,这就是aop代理的机制,详细原理要在spring的源码中才能体现,以后有空来详细阐述一下这方面的aop,现在就只需要了解这个机制就行了。

异常回滚

先给结论:
如果指定异常回滚,出现了这种异常就会回滚。
如果指定异常不回滚,出现了这种异常就不会回滚。
如果都不指定,默认是RuntimeException异常回滚。即便是继承了RuntimeException的异常类也不会出发默认回滚!
所以我们就以继承了RuntimeException的异常类作为示例。
自定义一个异常:

package com.test.service;

public class MyException extends Exception {
	public MyException(String msg) {
		super(msg);
	}

}

测试类(Test)添加如下代码:

	@org.junit.Test
	public void testRollback() {
		try {
			testService.testRollbackFor();
		} catch (MyException e) {
			e.printStackTrace();
		}
	}
	
	@org.junit.Test
	public void testNoRollback() {
		try {
			testService.testNoRollbackFor();
		} catch (MyException e) {
			e.printStackTrace();
		}
	}

	@org.junit.Test
	public void testNoRollbackAndRuntimeExcep() {
		try {
			testService.testNoRollbackForAndRuntimeException();
		} catch (MyException e) {
			e.printStackTrace();
		}
	}

TestService添加如下代码:

	@Transactional(rollbackFor = MyException.class)
	public void testRollbackFor() throws MyException {
		Dao dao=new Dao();
		dao.setName("testRollbackFor");
		daoMapper.insertSql(dao);
		throw new MyException("has RuntimeException");
	}
	
	@Transactional(noRollbackFor = MyException.class)
	public void testNoRollbackFor() throws MyException {
		Dao dao=new Dao();
		dao.setName("testNoRollbackFor");
		daoMapper.insertSql(dao);
		throw new MyException("has RuntimeException");
	}

	@Transactional(noRollbackFor = MyException.class)
	public void testNoRollbackForAndRuntimeException() throws MyException {
		Dao dao=new Dao();
		dao.setName("testNoRollbackForAndRuntimeException");
		daoMapper.insertSql(dao);
		throw new RuntimeException("has RuntimeException");
	}

执行testRollback和testNoRollback方法。
数据库如下:
@Transactional注解_第5张图片证明可以指定异常回滚或不回滚。
当然了RuntimeException是最后一部分,如果前面都没问题,最后还是会判断是不是RuntimeException异常。
借用一张网上的图:
@Transactional注解_第6张图片
清空数据库:
执行testNoRollbackAndRuntimeExcep方法;
@Transactional注解_第7张图片发现依然回滚了。说明RuntimeException是在最后作为判断的依据,即便前面判断不回滚,但最后在判断是RuntimeException还是要回滚的。

总结

@Transactional主要运用了aop,所以如果对aop熟悉的话这些问题自然立马就能想清楚。如果对aop代理不熟悉的话,可能理解起来稍稍困难一点,建议多尝试几次可能性以后看源码的话印象会更加深刻。当然没有举完所有的可能性。如有疑惑请自行尝试。
项目链接:
https://download.csdn.net/download/qq_37822914/12454277

你可能感兴趣的:(简单应用)