Spring是web框架,它其實並不與MySQL數據庫進行直連,通常都是經過ORM層框架,諸如JDBC
、iBatis
、MyBatis
、Hibernate
、JPA
等,與數據庫進行鏈接的,而事務的管理都是交由各持久層框架自行處理的,Spring只是出具了事務管理器接口org.springframework.transaction.PlatformTransactionManager
及不一樣的PlatformTransactionManager
實現類,爲不一樣的持久層框架提供對應的實現類,將管理事務的職責下放到各持久層框架,交由持久層框架處理。java
org.springframework.jdbc.datasource.DataSourceTransactionManager
:使用JDBC
、iBatis
、MyBatis
進行持久化數據時使用org.springframework.orm.hibernate5.HibernateTransactionManager
:使用Hibernate5
版本進行持久化數據時使用org.springframework.orm.jpa.JpaTransactionManager
:使用JPA
進行持久化數據時使用org.springframework.jdo.JdoTransactionManager
:當持久化機制是jdo
時使用org.springframework.transaction.jta.JtaTransactionManager
:使用一個JTA
實現來管理事務,在一個事務跨越多個資源時必須使用我不知道該怎麼稱呼它,由於自從工做以來,也沒有遇到過,可是不表明人家不存在,也許很老的項目裏依然存在。主要依靠手動處理來管理事務,對業務代碼有侵入性。web
1)編程式,可使用TransactionTemplate
或者直接使用底層的PlatformTransactionManager
。對於編程式事務管理,Spring推薦使用TransactionTemplate
spring
代碼不作展現,太冗餘了數據庫
聲明式事務本質是創建在AOP基礎之上的,在代理類實例裏攔截目標方法,在方法執行前,開啓或者加入一個事務,執行完畢或者發生異常後,根據具體狀況進行提交或者回滾。
相比較編程式事務,其最大的特色就是對業務代碼沒有侵入性,只需在配置文件中對事務進行配置管理(或者基於@Transactional
註解的方式)便可,這樣就能夠將業務代碼和事務管理隔離開。編程
1)TransactionProxyFactoryBean
聲明式事務管理
此方式是最傳統,配置文件最臃腫、難以閱讀的一種方式安全
代碼不作展現,太冗餘了併發
2)@Transactional
註解事務管理
基於註解的事務管理,在須要開啓事務的類或方法上添加@Transactional
,這種方式比較靈活,隨用隨加,可是容易處處亂用,凌亂不堪。更偏重靈活性。app
<!-- 開啓註解管理事務 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
複製代碼
3)Aspectj AOP
配置事務管理框架
<!-- 事務AOP -->
<aop:config>
<!-- pointcut:切入點 aop:advisor 適配器,是要注入的方法和pointcut鏈接的橋樑 -->
<aop:advisor pointcut="execution(* cn.x.x..*.service..*.*(..))" advice-ref="txAdvice" />
</aop:config>
<!-- 須要注入的方法,可使用通配符進行匹配 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true" />
<tx:method name="insert*" isolation="REPEATABLE_READ" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="update" isolation="REPEATABLE_READ" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="delete*" isolation="REPEATABLE_READ" propagation="REQUIRED" rollback-for="Exception"/>
</tx:attributes>
</tx:advice>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
複製代碼
首先重溫一下:在上一篇《MySQL事務及隔離級別》
裏,咱們介紹了MySQL事務若是沒有設置隔離級別,併發可能產生的幾個安全問題:異步
一些大佬,對此設計出了數據庫事務的隔離級別,來確保根據具體狀況,適時適當選用對應的策略,這種策略咱們稱之爲事務的隔離級別
,以下:
<tx:method
name="insert*"
isolation="REPEATABLE_READ"
propagation="REQUIRED"
rollback-for="Exception"
read-only="false"
no-rollback-for="RuntimeException"
timeout="xxx"/>
複製代碼
可使用通配符(*)關聯一批相同事務屬性的方法
默認值:DEFAULT
默認值:REQUIRED
新建一個嵌套事務
,該事務能夠有多個回滾點,若是沒有事務,則按REQUIRED
屬性執行,此方法對應的事務回滾不會對外部(嵌套)事務形成影響,可是外部事務回滾會影響內部事務。默認值 -1
挺有意思的一個小東西,若是項目裏使用MyBatis
,即便設置了超時時間,也是沒有任何效果的,暫時的解決辦法是,使用MyBatis的超時設置或者使用JdbcTemplate
進行數據庫的操做。
我相信,你必定對這個有興趣
默認值 false
事務是否只讀
若是當前事務是隻讀,且在事務內進行增、刪、改操做,則會拋出異常(Cannot execute statement in a READ ONLY transaction。)
只讀屬性有什麼用?
能夠有多個,以逗號相隔,若是是自定義異常,則須要類的徹底限定名,我查閱不少資料,發現你們都趨向於下面的結論:
若是發生的是檢查時異常,那麼事務不會自動回滾,若是是運行時異常,事務會自動回滾
通過實際測試後發現,若是咱們指定rollback-for="Exception"
,那麼,拋出檢查型異常同樣能夠觸發事務的回滾。
官方文檔給出的信息是:
默認狀況下,檢查時異常不會引發事務回滾,可是RuntimeException及其子類會引發事務回滾
和上面的相似,請自我揣摩~
<!-- 事務AOP -->
<aop:config>
<!-- pointcut:切入點 aop:advisor 適配器,是要注入的方法和pointcut鏈接的橋樑 -->
<aop:advisor pointcut="execution(* x.x..*.service..*.*(..))" advice-ref="txAdvice" />
</aop:config>
<!-- 須要注入的方法,可使用通配符進行匹配 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true" />
<tx:method name="insert*" isolation="REPEATABLE_READ" propagation="REQUIRED" rollback-for="Exception"/>
</tx:attributes>
</tx:advice>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
複製代碼
public interface StudentService {
List<Student> getStudents(String name);
void insertStudent(Student student);
deleteStudent(int id);
}
複製代碼
@Service
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentDao studentDao;
@Override
public List<Student> getStudents(String name) {
Map<String, Object> params = Maps.newHashMap();
params.put("name", name);
return studentDao.getStudents(params);
}
@Override
public void insertStudent(Student student) {
studentDao.insert(student);
}
@Override
public void deleteStudent(int id) {
studentDao.delete(id);
}
}
複製代碼
@RestController
@RequestMapping("/students")
public class StudentController {
@Autowired
private StudentService studentService;
@GetMapping("/test")
public void run() {
student.getStudents("張三");
Student student = Student.builder()
.id(1)
.name("張三")
.age(19)
.build();
student.insertStudent(student);
student.deleteStudent(student.getId());
}
}
複製代碼
如上,StudentService 的 insert和get方法均開啓了事務,可是deleteStudent方法沒有開啓事務。
問題:
1)在
deleteStudent()
裏調用insertStudent()
,試問,insertStudent()
是否開啓事務?爲何?
還原代碼:
@Service
public class StudentServiceImpl {
@Autowired
private StudentDao studentDao;
public void insertStudent(Student student) {
studentDao.insert(student);
}
public void deleteStudent(int id) {
// 刪除邏輯代碼
// 調用
insertStudent(student);
}
}
複製代碼
答案:
不開啓事務
解讀:
① 當StudentService.java
文件進行編譯的時候,編譯器會將deleteStudent()
方法裏的insertStudent(student);
編譯成this.insertStudent(student);
② Controller層使用@Autowired
註解進行StudentService
的自動裝配,但裝配的實例並非StudentServiceImpl類的實例,而是代理類的實例!聲明式事務是創建在AOP基礎上的
,而AOP的實現原理就是代理模式!因此咱們在Controller層所調用的方法會被代理類攔截。
③ 在代理類的攔截方法內,若是發現要調用的目標類方法是要開啓事務的,那麼就會在調用目標類方法以前開啓事務,在目標類方法執行完畢或者發生異常後,根據狀況執行回滾或不執行回滾。迴歸正題,當代理類發現deleteStudent()
沒有開啓事務的要求,那麼代理類是不會開啓事務的。
當咱們在deleteStudent()
裏調用insertStudent()
時,重點來了,jvm執行的是this.insertStudent()
這個this是目標類自己,而不是代理類!可是事務的管理卻在代理類裏,因此,insertStudent()
方法並不會開啓事務!
思考:
經過上面的解讀,咱們知道了service上的事務是由代理類來管理的,相似的像@Async異步標籤,其實也是由代理類進行管理的,在service內部調用打上@Async標籤的內部方法,異步也不會起做用的。
若是想調用內部方法,還想讓事務/異步起做用,咱們可使用:xml配置方式暴露代理對象,經過代理對象AopContext.currentProxy()去調用方法。
xml:
<aop:aspectj-autoproxy
proxy-target-class="true"
expose-proxy="true"/>
複製代碼
@Service
public class StudentServiceImpl {
@Autowired
private StudentDao studentDao;
public void insertStudent(Student student) {
studentDao.insert(student);
}
public void deleteStudent(int id) {
// 刪除邏輯代碼
// 調用
(StudentService) AopContext.currentProxy()).insertStudent(student);
}
}
複製代碼
2)在
insertStudent()
裏調用deleteStudent()
,試問,若是deleteStudent()
拋出運行時異常,insertStudent()
對應的操做是否回滾?爲何?
還原代碼:
@Service
public class StudentServiceImpl {
@Autowired
private StudentDao studentDao;
public void insertStudent(Student student) {
studentDao.insert(student);
deleteStudent(student.getId());
}
public void deleteStudent(int id) {
studentDao.delete(id);
if (1 == 1)
throw new RuntimeException("測試回滾");
}
}
複製代碼
答案:
回滾
解讀:
① 當StudentService.java
文件進行編譯的時候,insertStudent(student)
方法裏的deleteStudent();
變成this.deleteStudent();
,沒毛病,繼續。
② Controller層調用insertStudent()
時,會開啓事務,同樣沒毛病,繼續。
③ 在insertStudent()
裏調用this.deleteStudent()
,至關於將deleteStudent()
的代碼塊加入到當前事務中,因此若是deleteStudent()
拋出運行時異常,那麼insertStudent()
對應的操做會回滾。