美文网首页Spring
Spring默认事务机制中@Transactional 无效的原

Spring默认事务机制中@Transactional 无效的原

作者: stephen_wu | 来源:发表于2017-12-05 16:03 被阅读53次

    今天同事遇到一个事务无效的情况, 他在一个A方法中去调用B方法, A是一个rpc方法供外部服务调用, 而B是一个service内部方法, 在B上写了Transactional注解后发现事务无效. 百思不得其解, 后发现一篇blog如下:https://www.cnblogs.com/milton/p/6046699.html

    Spring中 @Transactional 注解的限制

    1. 同一个类中, 一个nan-transactional的方法去调用transactional的方法, 事务会失效

    If you use (default) Spring Proxy AOP, then all AOP functionality provided by Spring (like @Transational) will only be taken into account if the call goes through the proxy. -- This is normally the case if the annotated method is invoked from another bean.

    2. 在private方法上标注transactional, 事务无效

    When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.

    这里有一个详细的说明http://stackoverflow.com/questions/4396284/does-spring-transactional-attribute-work-on-a-private-method

    测试代码

    TestCase01.java

    public interface TestCase01 {

    void init();

    void clean();

    void txnInLocalPrivate();

    void txnInLocalPublic();

    void txnInOpenPublic();

    void txnInOpenPublicByInvokePrivate();

    void txnInOpenPublicByInvokePublic();

    }


    TestCase01Impl.java

    package com.rockbb.test.impl.service.impl;

    import com.rockbb.test.api.dto.AccountDTO;

    import com.rockbb.test.api.dto.AccountDetailDTO;

    import com.rockbb.test.api.service.TestCase01;

    import com.rockbb.test.impl.mapper.AccountDetailMapper;

    import com.rockbb.test.impl.mapper.AccountMapper;

    import org.springframework.stereotype.Repository;

    import org.springframework.transaction.annotation.Propagation;

    import org.springframework.transaction.annotation.Transactional;

    import javax.annotation.Resource;

    import java.math.BigDecimal;

    @Repository("testCase01")

    public class TestCase01Impl implements TestCase01 {

    @Resource(name="accountMapper")

    private AccountMapper accountMapper;

    @Resource(name="accountDetailMapper")

    private AccountDetailMapper accountDetailMapper;

    @Resource(name="testCase01")

    private TestCase01 testCase01;

    /**

    * 无效, 未回退

    *

    * 结论: 在私有方法上加事务注解无效

    */

    @Override

    public void txnInLocalPrivate() {

    localPrivate();

    }

    /**

    * 无效, 未回退

    *

    * 结论: 在公有方法上事务注解, 再通过接口方法调用, 无效

    */

    @Override

    public void txnInLocalPublic() {

    localPublic();

    }

    /**

    * 有效, 无论下面调用的是否是私有方法

    *

    * 结论: 在接口方法上加事务, 无论下面调用的是否是私有方法, 都有效

    */

    @Override

    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)

    public void txnInOpenPublic() {

    localPrivate();

    }

    @Override

    public void txnInOpenPublicByInvokePrivate() {

    }

    /**

    *

    * 结论: 普通接口方法直接调用同类带事务的方法, 无效. 通过接口调用则有效

    */

    @Override

    public void txnInOpenPublicByInvokePublic() {

    //txnInOpenPublic(); // 无效

    testCase01.txnInOpenPublic(); // 有效

    }

    @Override

    public void init() {

    accountMapper.truncate();

    for (int i = 0; i < 10; i++) {

    BigDecimal amount = BigDecimal.valueOf(i * 10);

    add(String.valueOf(i), BigDecimal.ZERO);

    increase(String.valueOf(i), BigDecimal.valueOf(100 + i));

    }

    }

    @Override

    public void clean() {

    accountMapper.truncate();

    }

    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)

    private void localPrivate() {

    AccountDTO dto = new AccountDTO().initialize();

    dto.setId("test");

    dto.setAmount(BigDecimal.ZERO);

    accountMapper.insert(dto);

    throw new RuntimeException("localPrivate");

    }

    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)

    public void localPublic() {

    AccountDTO dto = new AccountDTO().initialize();

    dto.setId("test");

    dto.setAmount(BigDecimal.ZERO);

    accountMapper.insert(dto);

    throw new RuntimeException("localPublic");

    }

    public void openPublic() {

    AccountDTO dto = new AccountDTO().initialize();

    dto.setId("test");

    dto.setAmount(BigDecimal.ZERO);

    accountMapper.insert(dto);

    throw new RuntimeException("openPublic");

    }

    private int add(String id, BigDecimal amount) {

    AccountDTO dto = new AccountDTO().initialize();

    dto.setId(id);

    dto.setAmount(amount);

    return accountMapper.insert(dto);

    }

    private int increase(String id, BigDecimal amount) {

    AccountDTO dto = accountMapper.select(id);

    AccountDetailDTO detail = new AccountDetailDTO().initialize();

    detail.setAmount(amount);

    detail.setPreAmount(dto.getAmount());

    dto.setAmount(dto.getAmount().add(amount));

    detail.setPostAmount(dto.getAmount());

    if (accountMapper.update(dto) != 1) {

    throw new RuntimeException();

    }

    return accountDetailMapper.insert(detail);

    }

    }


    TestCase02.java

    package com.rockbb.test.api.service;

    public interface TestCase02 {

    void txnInOpenPublicByPublic();

    void txnInOpenPublicByPrivate();

    }


    TestCase02Impl.java

    package com.rockbb.test.impl.service.impl;

    import com.rockbb.test.api.service.TestCase01;

    import com.rockbb.test.api.service.TestCase02;

    import org.springframework.stereotype.Repository;

    import javax.annotation.Resource;

    @Repository("testCase02")

    public class TestCase02Impl implements TestCase02 {

    @Resource(name="testCase01")

    private TestCase01 testCase01;

    /**

    * 有效

    *

    * 结论: 在接口方法上加事务, 再被他类的接口方法调用, 无论此接口方法是否加事务, 都有效

    */

    @Override

    public void txnInOpenPublicByPublic() {

    testCase01.txnInOpenPublic();

    }

    /**

    * 有效

    *

    * 结论: 在接口方法上加事务, 再被他类的私有方法调用后, 依然有效

    */

    @Override

    public void txnInOpenPublicByPrivate() {

    localPrivate();

    }

    private void localPrivate() {

    testCase01.txnInOpenPublic();

    }

    }


    *测试样例 AccountCheckTest.java

    package com.rockbb.test.test;

    import com.rockbb.test.api.service.TestCase01;

    import com.rockbb.test.api.service.TestCase02;

    import org.junit.Test;

    import org.junit.runner.RunWith;

    import org.slf4j.Logger;

    import org.slf4j.LoggerFactory;

    import org.springframework.test.context.ContextConfiguration;

    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

    import javax.annotation.Resource;

    @RunWith(SpringJUnit4ClassRunner.class)

    @ContextConfiguration(locations = {"/spring/spring-commons.xml"})

    public class AccountCheckTest {

    private static final Logger logger = LoggerFactory.getLogger(AccountCheckTest.class);

    @Resource(name="testCase01")

    private TestCase01 testCase01;

    @Resource(name="testCase02")

    private TestCase02 testCase02;

    @Test

    public void test01() {

    testCase01.init();

    try {

    testCase01.txnInLocalPrivate();

    } catch (Exception e) {

    logger.error(e.getMessage(), e);

    }

    }

    @Test

    public void test02() {

    testCase01.init();

    try {

    testCase01.txnInLocalPublic();

    } catch (Exception e) {

    logger.error(e.getMessage(), e);

    }

    }

    @Test

    public void test03() {

    testCase01.init();

    try {

    testCase01.txnInOpenPublic();

    } catch (Exception e) {

    logger.error(e.getMessage(), e);

    }

    }

    @Test

    public void test04() {

    testCase01.init();

    try {

    testCase02.txnInOpenPublicByPublic();

    } catch (Exception e) {

    logger.error(e.getMessage(), e);

    }

    }

    @Test

    public void test05() {

    testCase01.init();

    try {

    testCase02.txnInOpenPublicByPrivate();

    } catch (Exception e) {

    logger.error(e.getMessage(), e);

    }

    }

    @Test

    public void test06() {

    testCase01.init();

    try {

    testCase01.txnInOpenPublicByInvokePublic();

    } catch (Exception e) {

    logger.error(e.getMessage(), e);

    }

    }

    }


    结论

    @Transactional 加于private方法, 无效

    @Transactional 加于未加入接口的public方法, 再通过普通接口方法调用, 无效

    @Transactional 加于接口方法, 无论下面调用的是private或public方法, 都有效

    @Transactional 加于接口方法后, 被本类普通接口方法直接调用, 无效

    @Transactional 加于接口方法后, 被本类普通接口方法通过接口调用, 有效

    @Transactional 加于接口方法后, 被它类的接口方法调用, 有效

    @Transactional 加于接口方法后, 被它类的私有方法调用后, 有效

    总结: Transactional是否生效, 仅取决于是否加载于接口方法, 并且是否通过接口方法调用(而不是本类调用)

    相关文章

      网友评论

        本文标题:Spring默认事务机制中@Transactional 无效的原

        本文链接:https://www.haomeiwen.com/subject/cgscixtx.html