您的位置:首页 > 编程语言 > Java开发

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

2017-11-13 15:56 316 查看
转自: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.


在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

package com.rockbb.test.api.service;

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);
}
}
}


结论

1.@Transactional 加于private方法, 无效

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

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

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

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

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

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

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

还有一种情况:就是spring配置文件里配置了两个 (这个是我自己犯的错误,一般不是种情况)

<tx:annotation-driven mode="proxy" transaction-manager="sqlServerTransactionManager" />
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: