這是我參與更文挑戰的第1天,活動詳情查看: 更文挑戰java
本文正在參加「Java主題月 - Java 開發實戰」,詳情查看 活動連接mysql
事務(Transaction),通常是指要作的或所作的事情。在計算機術語中是指訪問並可能更新數據庫中各類數據項的一個程序執行單元(unit)。AOP利用的是動態代理,AOP中有個接口是 方法攔截器,藉助這個接口咱們能夠在想要操做的方法外加一些操做。事務攔截的對象是TransactionInterceptor,能夠看出它繼承了TransactionAspectSupport. TransactionAspectSupport內部是真正的操做部分。web
Spring事務中有幾個對象很重要,理解了這幾個對象就至關於抓住了整體,剩下的一些細節多花些時間就懂了。spring
PlatformTransactionManager 事務管理器,聽名字就知道它是管理事務的操做的,它只包含三個方法。獲取事務,回顧事務,提交事務。sql
TransactionDefiition 定義事務的類型,事務包含不少屬性,是否可讀,事務隔離級別,事務傳播級別。經過事務的定義,咱們根據定義獲取特定的事務。數據庫
TransactionStatus 表明一個事務運行的狀態,事務管理器經過狀態能夠知道事務的狀態信息,而後進行事務的控制。事務是否完成,是不是新的事務,是否是隻能回滾等。markdown
事務傳播級別處理是事務中的一個重點,那麼源碼中如何處理的呢?從建立事務部分開始看mvc
protected TransactionInfo createTransactionIfNecessary( PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) {
// If no name specified, apply method identification as transaction name.
if (txAttr != null && txAttr.getName() == null) {
txAttr = new DelegatingTransactionAttribute(txAttr) {
@Override
public String getName() {
return joinpointIdentification;
}
};
}
TransactionStatus status = null;
if (txAttr != null) {
if (tm != null) {
status = tm.getTransaction(txAttr);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
"] because no transaction manager has been configured");
}
}
}
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
複製代碼
tm.getTransaction,如何獲取事務的呢?app
doGetTransaction是抽象方法,由其父類實現,看名字知道這個是真正獲取事務對象的方法,單數據源的通常實現是DatasourceTransactionManager.ide
建立事務的定義,TransactionDefinition,前面有說到這個對象的內容,定義事務的類型等
若是已經存在了事務,根據事務的傳播級別進行存在事務處理; 若是不存在事務,根據定義設置事務的超時時間,是否只讀,是否新建事務
在使用Spring的事務管理時,有時候就會莫名其妙的發現事務沒生效,其實並不是Spring事務管理自己的問題,而是開發人員在使用時使用方式不對,Spring事務管理的底層機制沒搞懂,又或者數據庫層的問題致使,今天總結下Spring事務是失效的集中場景。
不是由Spring管理的Bean,例如,一個類沒有在xml中聲明或者沒有@Service,@RestController,@Component等註解的方式聲明由Spring管理,但在類的方法中使用@Transactional註解聲明事務,這種狀況事務是不會生效的,也就是說Spring管理的事務只能是在Spring容器管理的Bean中聲明纔會生效。以下代碼,testA方法雖然聲明瞭事務,可是因爲@Service註解被註釋了,因此事務不會生效。
複製代碼
//@Service
public class TransactionService {
private final AccountMapper accountMapper;
private Account account = null;
@Autowired
public TransactionService(AccountMapper accountMapper) {
this.accountMapper = accountMapper;
}
@Transactional(propagation = Propagation.NESTED)
public void testA() {
insertB();
account.getBalance();
insertB();
}
public void insertA() {
Account account = new Account();
account.setBalance(new Long(100));
account.setCreateTime(new Date());
account.setFreezeAmount(new Long(0));
account.setUpdateTime(new Date());
account.setUserId("123");
accountMapper.insertSelective(account);
}
public void insertB() {
Account account = new Account();
account.setBalance(new Long(200));
account.setCreateTime(new Date());
account.setFreezeAmount(new Long(0));
account.setUpdateTime(new Date());
account.setUserId("456");
accountMapper.insertSelective(account);
}
}
複製代碼
先上代碼,required方法沒有加註解聲明事務,required方法調用了testA方法,testA方法聲明瞭事務,可是這裏事務不會生效,其緣由是由於Spring事務管理是經過代理類實現的,然而這種自調用至關於this.testA(),是經過對象調用,沒法走到事務的切面中,因此事務就不生效了。
@Service
public class TransactionService {
private final AccountMapper accountMapper;
private Account account = null;
@Autowired
public TransactionService(AccountMapper accountMapper) {
this.accountMapper = accountMapper;
}
public void required() {
insertA();
testA();
}
@Transactional
public void testA() {
insertB();
account.getBalance();
insertB();
}
}
複製代碼
再看下面代碼,這時required()方法聲明瞭事務,這時testA方法聲明瞭REQUIRES_NEW傳播機制的事務,testA方法的事務會生效嗎?答案是不會生效,同樣的問題產生了自調用,並無通過Spring的代理,因此事務不生效。
@Service
public class TransactionService {
private final AccountMapper accountMapper;
private Account account = null;
@Autowired
public TransactionService(AccountMapper accountMapper) {
this.accountMapper = accountMapper;
}
@Transactional
public void required() {
insertA();
testA();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void testA() {
insertB();
account.getBalance();
insertB();
}
}
複製代碼
來自 Spring 官方文檔:
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.
意思是 @Transactional 只能用於 public 的方法上,不然事務不會失效,若是要用在非 public 方法上,能夠開啓 AspectJ 代理模式。
數據源沒有配置事務管理器,聲明瞭事務也是白搭,下面是配置事務管理器的代碼
@Bean(name = "transactionManager")
public DataSourceTransactionManager sentinelTransactionManager(@Qualifier("datasource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
複製代碼
以MySQL爲例,其MyISAM引擎是不支持事務操做的,InnoDB纔是支持事務的引擎,通常要支持事務都會使用InnoDB。若是數據庫引擎不支持事務,那也是白搭。
Propagation.NOT_SUPPORTED: 表示不以事務運行,當前若存在事務則掛起。
以下面的代碼,若是insertB方法中,插入數據庫後產生異常,並不會回滾,由於異常被catch調了,事務失效。
@Transactional
public void testA() {
try {
insertB();
} catch (Exception e) {
e.printStackTrace();
}
}
複製代碼
事務默認回滾的異常是RuntimeException,若是想觸發其餘異常的回滾,須要在註解上配置,例如:
@Transactional(rollbackFor = Exception.class)
複製代碼
常見的事務失效緣由:
如使用mysql且引擎是MyISAM,則事務會不起做用,緣由是MyISAM不支持事務,能夠改爲InnoDB。
若是使用了spring+mvc,則context:component-scan重複掃描問題可能會引發事務失敗。
@Transactional 註解開啓配置,必須放到listener里加載,若是放到DispatcherServlet的配置裏,事務也是不起做用的。
@Transactional 註解只能應用到 public 可見度的方法上。 若是你在 protected、private 或者 package-visible 的方法上使用 @Transactional 註解,它也不會報錯,事務也會失效。
Spring團隊建議在具體的類(或類的方法)上使用 @Transactional 註解,而不要使用在類所要實現的任何接口上。在接口上使用 @Transactional 註解,只能當你設置了基於接口的代理時它才生效。由於註解是 不能繼承 的,這就意味着若是正在使用基於類的代理時,那麼事務的設置將不能被基於類的代理所識別,並且對象也將不會被事務代理所包裝。
本文總結了八種事務失效的場景,其實發生最多就是自身調用、異常被吃、異常拋出類型不對這三個了。而遇到事務失效的時候,先看一下是否被事務管理器接管了,若是有,那麼多半是容器的問題,這裏並不必定是父子容器,也有多是父父容器。