On the mystery of the failure of the spring Affairs

introduction:

Whenever we use Spring declarative transaction, we just need to declare @Transactional on a class or method can be, but we have not encountered a transaction failure when it?

Seven kinds of large transactions fail because the spring

  1. Such as using mysql and the engine is MyISAM, the transaction will not work, because MyISAM does not support transactions, it can be changed to InnoDB.
  2. If the spring + mvc, the context: component-scan repeat scanning problems may cause the transaction to fail.
  3. @Transactional comment open configuration, the listener must be placed in the load, if placed DispatcherServlet configuration, the transaction also does not work.
  4. @Transactional annotation method can only be applied to public visibility. If you use the method @Transactional annotation in private or protected, it will not be an error, the transaction will fail.
  5. Spring team recommends (method or class) in the specific class notes on the @Transactional, rather than on any interface type to be achieved. Use @Transactional annotation on an interface only when you set it to take effect when the agent interface-based. Because annotations are not inherited, which means that if the class-based proxy is being used, then set the transaction will not be recognized based on the proxy class, and the object will not be the Acting packaged (but on the Interface The above is achievable affairs drops, I did not find out).
  6. A transaction method to call another method of the same class inside a transaction, the transaction method is called failure. (Using the native objects, such agents failed)
  7. The exception code to use throw new Exception ( "xxxxxxxxxxxx") capture

The container Spring Sons (configuration transaction into DispatcherServlet failure)

Parent vessel: Root WebAplicationContext
child container: Servlet WebAplicationContext
Here Insert Picture Description
figure, you can see what the child container needed is to be the parent container, but the container is not to be a parent to the child container. So when we are using Spring + SpringMVC for web application development, Spring is responsible for scanning DAO and Service layer, SpringMVC responsible for scanning controller on the line (do not scan DAO and Service layer, do their own thing on it, although this do not go wrong, but a waste of memory space).

This figure also explains why we configure SpringMVC in a transaction scans do not take effect ------ "transactions are generally done in the Service layer, we scan configuration in SpringMVC affairs, we are unable to parent container the resulting, thus leading to the failure of the transaction. . . . .

It must be loaded into the listener in, instead of the configuration in webMVC

A transaction method to call another method of the same class inside a transaction, the transaction method is called failure

(Root cause - "JDK agent or agents Cglib failure, use the native object)

@Service
public class CityServiceImpl implements CityService {
    private final static Logger LOGGER = LoggerFactory.getLogger(CityServiceImpl.class);
    
    @Autowired
     private CityDao cityDao;
    
     private CityServiceImpl proxy;

    @Override
    @Transactional
    public void parent() {
        LOGGER.info("======================insertParent()===================");
        //这种情况child事务失效
        //根据动态代理分析 此处的child()不是由 AopProxy调用的 而是 this对象
        try {                  
            child();                        
            }catch (Exception e){
            LOGGER.error("parent catch child execption ",e);
            throw new  RuntimeException();
        }
        //以下代码为Parent业务
        City city= new City();
        city.setProvinceId(Long.valueOf(99));
        city.setCityName("parent");
        city.setDescription("parentparentparentparent");
        cityDao.insertCity(city);
    }
  
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void child() {
        LOGGER.info("======================insertChild()===================");
        City city= new City();
        city.setProvinceId(Long.valueOf(99));
        city.setCityName("Child");
        city.setDescription("ChildChildChildChildChildChild");
        cityDao.insertCity(city);
        int a=1/0;//此处异常
    }
    

After we run this code will find that when Child abnormal, insert the data has not rolled back, then the failure of the transaction took place. Next Spring transaction reason given by our failure to analyze what I have, assuming we do not make the first five errors, so this time our question is, in the end is what caused the Child approach to transaction failure it?

We know SpringAop achieve and transactions are done by JDK dynamic proxy or Cglib, then they will not be the failure of it?

The real cause of failure

The reason Spring transaction failure ----- "simple plane analysis

Spring dynamic proxy Currently, there are two ways, one is cglib, one is the jdk, two implementations are not the same, but the transaction failed for the same reason. Next we JDK dynamic proxy as an example

JDKProxy

public interface JDKProxy {

    void parent();

    void child();
}

JDKProxyImpl

public class JDKProxyImpl implements  JDKProxy{

    public void parent() {
        System.out.println("parent......");
//        child();
    }

    @Override
    public void child() {
        System.out.println("child......");
    }
}

JDKProxyTest

package fileTest;/*
 @author yyc
 @DESCRIPTION 
 @create 2019/8/19
*/

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JDKProxyTest {

    public static void main(String[] args) {
    
       JDKProxy jdkProxy = new JDKProxyImpl();
       ProxyInvocationHandler handler = new ProxyInvocationHandler(jdkProxy);
       JDKProxy jdkProxy1 = (JDKProxy) Proxy.newProxyInstance(jdkProxy.getClass().getClassLoader(), jdkProxy.getClass().getInterfaces(), handler);
       
       jdkProxy1.parent();
       jdkProxy1.child();
       System.out.println(jdkProxy1.getClass().getName());
    }
}

class ProxyInvocationHandler implements InvocationHandler{

    private JDKProxy target;

    public ProxyInvocationHandler(JDKProxy target) {
        this.target=target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理方法被执行了。。。");
        return method.invoke(target, args);
    }

}

After the operation, we found that the two methods have been enhanced
Here Insert Picture Description
and the method we use child in the parent method JDKProxyImpl in while running only parent in JDKProxyTest method, in which case we take for granted that the results will be the same as last time.

Hypothesis
Here Insert Picture Description

Reality
Here Insert Picture Description
is not that we are above Spring and failure scenario is the same? Our Spring transaction implementation is not dependent on it or Cglib JDK dynamic proxy to achieve, so the reason for this failure is because the scene Spring underlying JDK dynamic proxy or Cglib failure.

Of course to this, we did not analyze how Clear Spring transaction is invalid (JDK proxy fail why here), let's continue to work in vain

The reason JDK agent failed ---- "in-depth analysis of plane

Here Insert Picture Description
We know that child () and this.child () are equivalent, then who in this is this is it, by printing Here Insert Picture Description
we can see this here is fileTest.JDKProxyImpl

And we enhanced the Parent method, and is enhanced by whom to call it,
Here Insert Picture Description
Here Insert Picture Description
before, run directly jdkProxy1.parent In the main method (); jdkProxy1.child (); have been enhanced, but also the use of proxy objects.

Well, now we finally know why the parent method call fails with Child class Child method will make a transaction. ---- "want to use JDK dynamic proxy, you must use a proxy object. So I want to let the Spring transaction to take effect, you must use a proxy object.

So how do we solve the above problem?

  1. The easiest way is not to write a single class. . . .
  2. Acquired from AopContext current thread
CityServiceImpl proxy=(CityServiceImpl)AopContext.currentProxy();
proxy.child()
  1. Context acquired proxy object spring application (a single embodiment of the ApplicationContext ioc container)
 @Autowired
    private ApplicationContext context;
    @PostConstruct
    public  void init(){
        proxy=context.getBean(CityServiceImpl.class);
    }
 proxy.child()

The method is not abnormal transaction rollback

Spring's Transactional API documentation:

If no rules are relevant to the exception, it will be treated like DefaultTransactionAttribute (rolling back on runtime exceptions).

在业务代码中,有如下两种情况,比如:
throw new RuntimeException(“xxxxxxxxxxxx”); 事务回滚
throw new Exception(“xxxxxxxxxxxx”); 事务没有回滚
spring内部catch的就是 RuntimeException, service抛出RuntimeException可以回滚
如果抛出Exception,就不回滚….

1. Spring的AOP即声明式事务管理默认是针对unchecked exception回滚。

也就是默认对RuntimeException()异常或是其子类进行事务回滚;checked异常,即Exception可try{}捕获的不会回滚,如果使用try-catch捕获抛出的unchecked异常后没有在catch块中采用页面硬编码的方式使用spring api对事务做显式的回滚,则事务不会回滚, “将异常捕获,并且在catch块中不对事务做显式提交=生吞掉异常” ,要想捕获非运行时异常则需要如下配置:

解决办法:
1.在针对事务的类中抛出RuntimeException异常,而不是抛出Exception。
2.在txAdive中增加rollback-for,里面写自己的exception,例如自己写的exception:

<tx:advice id="txAdvice" transaction-manager="transactionManager">
   <tx:attributes>
     <tx:method name="*" rollback-for="com.cn.untils.exception.XyzException"/>
   </tx:attributes>
 </tx:advice>

或者定义不会滚的异常

<tx:advice id="txAdvice">
    <tx:attributes>
       <tx:method name="update*" no-rollback-for="IOException"/>
       <tx:method name="*"/>
    </tx:attributes>
 </tx:advice>

2. spring的事务边界是在调用业务方法之前开始的,业务方法执行完毕之后来执行commit or rollback(Spring默认取决于是否抛出runtime异常).

如果抛出runtime exception 并在你的业务方法中没有catch到的话,事务会回滚。
一般不需要在业务方法中catch异常,如果非要catch,在做完你想做的工作后(比如关闭文件等)一定要抛出runtime exception,否则spring会将你的操作commit,这样就会产生脏数据.所以你的catch代码是画蛇添足。

例如:

try {  
    //bisiness logic code  
} catch(Exception e) {  
    //handle the exception  
}  

由此可以推知,在spring中如果某个业务方法被一个 整个包裹起来,则这个业务方法也就等于脱离了spring事务的管理,因为没有任何异常会从业务方法中抛出!全被捕获并吞掉,导致spring异常抛出触发事务回滚策略失效。

注:不过,如果在catch代码块中采用页面硬编码的方式使用spring api对事务做显式的回滚,这样写也未尝不可。

3. 基于注解的事务:

Transactional的异常控制,默认是Check Exception 不回滚,unCheck Exception回滚
如果配置了rollbackFor 和 noRollbackFor 且两个都是用同样的异常,那么遇到该异常,还是回滚
rollbackFor 和noRollbackFor 配置也许不会含盖所有异常,对于遗漏的按照Check Exception 不回滚,unCheck Exception回滚

如果只是@Transactional失效的话,可以考虑改成:@Transactional(rollbackFor=Exception.class)
例子如下,在类或方法上的加入:

@Transactional(rollbackFor=Exception.class)
Published 45 original articles · won praise 3 · Views 2314

Guess you like

Origin blog.csdn.net/weixin_44046437/article/details/99712344