1. Primero, preparé un dato en Mysql
2. Comience la prueba de manera simple y grosera
1. Nuestro propósito es cambiar delflag a 0 y simplemente preparar el sql
<update id="test">
UPDATE tbl_users set delflag='0' where account='admin'
</update>
2. Vamos a probarlo primero. @Transactional
El código es el siguiente. Todo el mundo sabe que 2/0 lanzará una excepción.
@Override
@Transactional
public Ret test(){
int i = articleMapper.test();
int a = 2/0;
if(i > 0){
ResultUtil.success();
}
return ResultUtil.error();
}
3. Ejecute la prueba i=1 para indicar que la actualización fue exitosa, no se preocupe, sigamos hasta el punto de interrupción y bajemos.
4. Como era de esperar, cuando la ejecución llegó a la línea 54, ocurrió un error.java.lang.ArithmeticException: /by zero
5. Los estudiantes cuidadosos encontrarán que ArithmeticException
esta clase de excepción se RuntimeException
hereda
Y @Transactional
la excepción de reversión predeterminada esRuntimeException
6. Hicimos clic en RuntimeException
esta clase para descubrir que lo que encontramos RuntimeException
fue Exception
heredado
Y todas las clases de excepción son básicamente heredadas, incluida la excepción RuntimeException
justo arribajava.lang.ArithmeticException
Entonces, siempre que RuntimeException
la RuntimeException
excepción lanzada por las siguientes subclases @Transactional
se pueda revertir
7. En este momento, veamos si el valor de la base de datos se ha modificado con éxito, es obvio que los datos se han revertido y no se han modificado a 0.
1. A continuación, estamos probando @Transactional
el código de excepción que no se puede transferir de la siguiente manera
try catch
Lo usamos directamente para capturar excepciones y luego lanzar Exception
excepciones personalizadas en la captura
@Override
@Transactional
public Ret test() throws Exception {
int i = articleMapper.test();
try {
int a = 2 / 0;
} catch (Exception e) {
throw new Exception();
}
if (i > 0) {
ResultUtil.success();
}
return ResultUtil.error();
}
2. La excepción lanzada directamente por ok es la excepción que especificamos.Vamos java.lang.Exception
a la base de datos
3. La base de datos se actualiza a 0, lo que indica que @Transactional no puede revertir la excepción
en conclusión
@Transactional
Solo se puede revertir RuntimeException
y RuntimeException
las excepciones lanzadas por las siguientes subclases no se pueden Exception
revertir
Si necesita admitir Exception
la excepción de reversión, utilice@Transactional(rollbackFor = Exception.class)
Si está agregando, eliminando y modificando aquí, sugiero que todos lo usen@Transactional(rollbackFor = Exception.class)
Introducción a los escenarios de falla transaccional
Cuando el primer tipo de modificador de método de anotación de anotación transaccional no es público, @Transactional
la anotación no funcionará. Por ejemplo el siguiente código.
Defina una implementación de anotación incorrecta @Transactional
, modificando un método de acceso predeterminado
@Component
public class TestServiceImpl {
@Resource
TestMapper testMapper;
@Transactional
void insertTestWrongModifier() {
int re = testMapper.insert(new Test(10,20,30));
if (re > 0) {
throw new NeedToInterceptException("need intercept");
}
testMapper.insert(new Test(210,20,30));
}
}
En el mismo paquete, cree un nuevo objeto de llamada para acceder.
@Component
public class InvokcationService {
@Resource
private TestServiceImpl testService;
public void invokeInsertTestWrongModifier(){
//调用@Transactional标注的默认访问符方法
testService.insertTestWrongModifier();
}
}
caso de prueba
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {
@Resource
InvokcationService invokcationService;
@Test
public void testInvoke(){
invokcationService.invokeInsertTestWrongModifier();
}
}
El método de acceso anterior hace que la transacción no se abra, por lo que cuando el método genera una excepción, testMapper.insert(new Test(10,20,30));
la operación no se revierte. Si TestServiceImpl#insertTestWrongModifier
el método se cambia a público, la transacción se abrirá normalmente y testMapper.insert(new Test(10,20,30));
se revertirá.
el segundo
Llame al método marcado dentro de la clase llamándolo dentro de la clase @Transactional
. En este caso, la transacción no se abrirá. El código de ejemplo es el siguiente.
establecer una llamada interna
@Component
public class TestServiceImpl implements TestService {
@Resource
TestMapper testMapper;
@Transactional
public void insertTestInnerInvoke() {
//正常public修饰符的事务方法
int re = testMapper.insert(new Test(10,20,30));
if (re > 0) {
throw new NeedToInterceptException("need intercept");
}
testMapper.insert(new Test(210,20,30));
}
public void testInnerInvoke(){
//类内部调用@Transactional标注的方法。
insertTestInnerInvoke();
}
}
caso de prueba.
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {
@Resource
TestServiceImpl testService;
/**
* 测试内部调用@Transactional标注方法
*/
@Test
public void testInnerInvoke(){
//测试外部调用事务方法是否正常
//testService.insertTestInnerInvoke();
//测试内部调用事务方法是否正常
testService.testInnerInvoke();
}
}
El anterior es el código de prueba utilizado. Ejecute la prueba para saber que el método de transacción de llamada externa puede iniciar la transacción y testMapper.insert(new Test(10,20,30))
la operación se revertirá;
Luego ejecute otro caso de prueba, llame a un método para llamar al @Transactional
método de transacción marcado internamente dentro de la clase, el resultado de la operación es que la transacción no se abrirá normalmente y testMapper.insert(new Test(10,20,30))
la operación se guardará en la base de datos sin revertirse.
tercero
La excepción se captura dentro del método de transacción y no se lanza ninguna excepción nueva, por lo que la operación de transacción no se revertirá. El código de ejemplo es el siguiente.
@Component
public class TestServiceImpl implements TestService {
@Resource
TestMapper testMapper;
@Transactional
public void insertTestCatchException() {
try {
int re = testMapper.insert(new Test(10,20,30));
if (re > 0) {
//运行期间抛异常
throw new NeedToInterceptException("need intercept");
}
testMapper.insert(new Test(210,20,30));
}catch (Exception e){
System.out.println("i catch exception");
}
}
}
El código del caso de prueba es el siguiente.
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {
@Resource
TestServiceImpl testService;
@Test
public void testCatchException(){
testService.insertTestCatchException();
}
}
Al ejecutar el caso de prueba, se encontró que, aunque se lanzó una excepción, la excepción se capturó y no se eliminó del método, y testMapper.insert(new Test(210,20,30))
la operación no se revirtió.
Los tres anteriores son las razones principales por las que @Transactional
las anotaciones no funcionan y @Transactional
fallan. @Transactional
Lo siguiente combina las anotaciones en primavera para realizar el análisis del código fuente y por qué @Transactional
las anotaciones no funcionan.
@La anotación transaccional no funciona en el análisis de principios
La primera
@Transactional
Cuando el modificador del método de anotación no es público, @Transactional
la anotación no funcionará. El motivo del análisis aquí es que @Transactional
se basa en la implementación dinámica de proxy. @Transactional
El método de implementación se analiza en el principio de implementación de anotaciones. En el proceso de inicialización de beans, @Transactional
se crean objetos proxy para instancias de beans con anotaciones. Hay un @Transactional
proceso de información de anotación de escaneo de primavera. Desafortunadamente, se refleja en el código fuente. @Transactional
Si el modificador del método marcado no es público, entonces la información del método predeterminado @Transactional
está vacía, por lo que el objeto proxy no se creará para el bean o el proxy. no se realizará la llamada para el método.
@Transactional
En el principio de implementación de anotaciones, se introduce cómo determinar si un bean crea un objeto proxy.La lógica general es. Cree una BeanFactoryTransactionAttributeSourceAdvisor
instancia de corte de puntos aop de acuerdo con Spring, recorra el objeto de método de la clase de bean actual y juzgue si la información de anotación en el método está incluida.Si @Transactional
algún método del bean contiene @Transactional
información de anotación, entonces se adapta a este BeanFactoryTransactionAttributeSourceAdvisor
punto -punto de corte. Debe crear un objeto proxy y luego la lógica del proxy administra la lógica de apertura y cierre de la transacción por nosotros.
En el código fuente de Spring, al interceptar el proceso de creación del bean y buscar el punto de corte de la adaptación del bean, se utiliza el siguiente método. El propósito es encontrar la información @ en el método. Si existe, significa que el punto de corte se puede aplicar (canApply) al Transactional
bean BeanFactoryTransactionAttributeSourceAdvisor
,
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
Assert.notNull(pc, "Pointcut must not be null");
if (!pc.getClassFilter().matches(targetClass)) {
return false;
}
MethodMatcher methodMatcher = pc.getMethodMatcher();
if (methodMatcher == MethodMatcher.TRUE) {
// No need to iterate the methods if we're matching any method anyway...
return true;
}
IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
}
//遍历class的方法对象
Set<Class<?>> classes = new LinkedHashSet<Class<?>>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
classes.add(targetClass);
for (Class<?> clazz : classes) {
Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
for (Method method : methods) {
if ((introductionAwareMethodMatcher != null &&
introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||
//适配查询方法上的@Transactional注解信息
methodMatcher.matches(method, targetClass)) {
return true;
}
}
}
return false;
}
Podemos romper el punto en el método anterior, depurar el código de seguimiento paso a paso y, finalmente, el código anterior llamará al siguiente método para juzgar. Los puntos de interrupción en los siguientes métodos y mirar hacia atrás en la pila de llamadas de métodos también es una buena forma de rastrear.
AbstractFallbackTransactionAttributeSource#getTransactionAttribute
AbstractFallbackTransactionAttributeSource#computeTransactionAttribute
protected TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {
// Don't allow no-public methods as required.
//非public 方法,返回@Transactional信息一律是null
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
//后面省略.......
}
ningún objeto proxy creado
Por lo tanto, si los modificadores de todos los métodos no son públicos, no se creará ningún objeto proxy. Tome el código de prueba al principio como ejemplo, si el testService del modificador normal es el objeto proxy creado por cglib en la imagen a continuación.
Si los métodos de la clase no son públicos, no será un objeto proxy.
sin llamadas de proxy
Considere una situación como la que se muestra en el siguiente código. Ambos métodos están @Transactional
anotados, pero uno tiene un modificador público y el otro no, en este caso podemos prever que se creará un objeto proxy, porque hay al menos un @Transactional
método de anotación con un modificador público.
Una vez que se crea el objeto proxy, insertTestWrongModifier
¿se iniciará la transacción? La respuesta es no.
@Component
public class TestServiceImpl implements TestService {
@Resource
TestMapper testMapper;
@Override
@Transactional
public void insertTest() {
int re = testMapper.insert(new Test(10,20,30));
if (re > 0) {
throw new NeedToInterceptException("need intercept");
}
testMapper.insert(new Test(210,20,30));
}
@Transactional
void insertTestWrongModifier() {
int re = testMapper.insert(new Test(10,20,30));
if (re > 0) {
throw new NeedToInterceptException("need intercept");
}
testMapper.insert(new Test(210,20,30));
}
}
La razón es que cuando el objeto de proxy dinámico llama a la lógica de proxy, en la función de intercepción del objeto de proxy creado por cglib, CglibAopProxy.DynamicAdvisedInterceptor#intercept
hay una lógica de la siguiente manera, el propósito es obtener la lógica aop de la adaptación del método actual que necesita ser ejecutado por el objeto proxy actual.
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Para que la anotación @Transactional encuentre el proceso lógico aop, de manera similar, también se ejecuta una vez
AbstractFallbackTransactionAttributeSource#getTransactionAttribute
AbstractFallbackTransactionAttributeSource#computeTransactionAttribute
Es decir, es necesario encontrar una información de anotación sobre el método @Transactional
, si no hay información de anotación, @Transactional
no se ejecutará la lógica de agente correspondiente al agente y se ejecutará directamente el método. Sin @Transactional
la lógica del proxy de anotación, la transacción no se puede abrir, lo cual también se menciona en el artículo anterior.
el segundo
Llame al método marcado dentro de la clase llamándolo dentro de la clase @Transactional
. En este caso, la transacción no se abrirá.
Después de un análisis detallado del primer tipo, también se debe adivinar la razón por la cual la gestión de transacciones no está habilitada en esta situación;
Dado que la gestión de transacciones se implementa en función de la lógica de proxy del objeto de proxy dinámico, si el método de transacción dentro de la clase se llama dentro de la clase, el proceso de llamar al método de transacción no se llama a través del objeto de proxy, sino directamente a través de este. objeto para llamar al método, el objeto de proxy omitido no debe tener lógica de proxy.
De hecho, podemos jugar así, y la llamada interna también puede realizar la apertura de la transacción.El código es el siguiente.
@Component
public class TestServiceImpl implements TestService {
@Resource
TestMapper testMapper;
@Resource
TestServiceImpl testServiceImpl;
@Transactional
public void insertTestInnerInvoke() {
int re = testMapper.insert(new Test(10,20,30));
if (re > 0) {
throw new NeedToInterceptException("need intercept");
}
testMapper.insert(new Test(210,20,30));
}
public void testInnerInvoke(){
//内部调用事务方法
testServiceImpl.insertTestInnerInvoke();
}
}
Lo anterior es el uso de objetos proxy para llamadas de transacciones, por lo que la gestión de transacciones se puede habilitar, pero en la operación real, nadie estará inactivo y jugará así ~
tercero
La excepción se captura dentro del método de transacción y no se lanza ninguna excepción nueva, por lo que la operación de transacción no se revertirá.
En este caso, puede ser más común para nosotros, y el problema radica en la lógica del proxy. Primero veamos cómo la lógica del proxy dinámico en el código fuente maneja los asuntos por nosotros.
TransactionAspectSupport#invokeWithinTransaction
el código se muestra a continuación.
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass);
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
//开启事务
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
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
//异常时,在catch逻辑中回滚事务
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
//提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
}
else {
//....................
}
}
Entonces, después de leer el código anterior, está claro de un vistazo que si la transacción quiere retroceder, debe poder detectar la excepción aquí. Si la excepción se detecta a la mitad, la transacción no se retrotraerá.
Las situaciones anteriores se resumen.