關於事務就不介紹了,前面在研究spring的時候就已經研究過了,參考:https://www.cnblogs.com/qlqwjy/p/7296493.html
這里直接研究springboot中事務的開啟以及測試方法。
在Spring Boot中推薦使用@Transactional注解來申明事務。
首先需要導入依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency>
當引入jdbc依賴之后,Spring Boot會自動默認分別注入DataSourceTransactionManager或JpaTransactionManager,所以我們不需要任何額外配置就可以用@Transactional注解進行事務的使用。
在Service中添加@Transactional注解:
===================測試事務效果===========
Service層代碼:
package cn.qlq.service.impl.user; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import cn.qlq.bean.user.User;import cn.qlq.mapper.user.UserMapper; import cn.qlq.service.user.UserService; @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public void addUser(User user) { userMapper.insert(user); int i = 1 / 0; } }
(1)測試未添加事務
數據庫原來記錄:
測試添加后查看日志:(插入數據之后報異常)
2019-02-21 16:25:20.983 INFO 20960 --- [nio-8088-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet' 2019-02-21 16:25:20.984 INFO 20960 --- [nio-8088-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started 2019-02-21 16:25:21.004 INFO 20960 --- [nio-8088-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 20 ms 2019-02-21 16:25:21.047 INFO 20960 --- [nio-8088-exec-1] cn.qlq.action.UserController : user -> User [id=null, username=2, password=2, userfullname=2, createtime=Thu Feb 21 16:25:21 CST 2019, isdeleted=null, sex=女, address=2] 2019-02-21 16:25:21.320 DEBUG 20960 --- [nio-8088-exec-1] cn.qlq.mapper.user.UserMapper.insert : ==> Preparing: insert into user (id, username, password, userfullname, createtime, isdeleted, sex, address) values (?, ?, ?, ?, ?, ?, ?, ?) 2019-02-21 16:25:21.335 DEBUG 20960 --- [nio-8088-exec-1] cn.qlq.mapper.user.UserMapper.insert : ==> Parameters: null, 2(String), 2(String), 2(String), 2019-02-21(Date), null, 女(String), 2(String) 2019-02-21 16:25:21.674 DEBUG 20960 --- [nio-8088-exec-1] cn.qlq.mapper.user.UserMapper.insert : <== Updates: 1 java.lang.ArithmeticException: / by zero at cn.qlq.service.impl.user.UserServiceImpl.addUser(UserServiceImpl.java:27) at cn.qlq.action.UserController.addUser(UserController.java:31) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:116) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872) at javax.servlet.http.HttpServlet.service(HttpServlet.java:648) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:230) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:105) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:474) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:349) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:783) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:798) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1434) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:745) 2019-02-21 16:25:21.763 WARN 20960 --- [nio-8088-exec-1] .m.m.a.ExceptionHandlerExceptionResolver : Resolved exception caused by Handler execution: java.lang.ArithmeticException: / by zero
查看數據庫:(數據增加,沒有事務回滾)
(2)測試添加事務
只需在類加上@Transactional注解就可以了。
測試查看日志同上面一樣,但是事務進行回滾了,只是沒有打出日志。(下次提交發現id會跳過本次提交的值)
查看數據庫記錄:
補充:原理理解
在再次使用事務的時候發現其回滾沒有打印日志,於是就想的研究一下原理。
其自動配置是在springboot-autoconfig-xxx.jar中。入口類是:DataSourceTransactionManagerAutoConfiguration
(1)查看DataSourceTransactionManagerAutoConfiguration源碼:
/** <a href="http://www.cpupk.com/decompiler">Eclipse Class Decompiler</a> plugin, Copyright (c) 2017 Chen Chao. */ package org.springframework.boot.autoconfigure.jdbc; import javax.sql.DataSource; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigureOrder; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; /** * {@link EnableAutoConfiguration Auto-configuration} for * {@link DataSourceTransactionManager}. * * @author Dave Syer * @author Stephane Nicoll * @author Andy Wilkinson * @author Kazuki Shimizu * @since 1.0.0 */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ JdbcTemplate.class, PlatformTransactionManager.class }) @AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE) @EnableConfigurationProperties(DataSourceProperties.class) public class DataSourceTransactionManagerAutoConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnSingleCandidate(DataSource.class) static class DataSourceTransactionManagerConfiguration { @Bean @ConditionalOnMissingBean(PlatformTransactionManager.class) DataSourceTransactionManager transactionManager(DataSource dataSource, ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) { DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource); transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager)); return transactionManager; } } }
ConditionalOnClass表示在當前類路徑下存在對應的類。
第一步:去掉上面的pom配置,並且去掉JPA等配置,發現不存在PlatformTransactionManager類。(沒有這個class類,但是沒報錯是因為是class文件不會再次編譯)
如果需要我們自己寫判斷可以用name判斷,值是包全路徑。源碼如下:
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnClassCondition.class) public @interface ConditionalOnClass { Class<?>[] value() default {}; String[] name() default {}; }
第二部:加上上面的pom配置發現在當前工程的classpath環境中可以找到上面類。
也可以在反編譯的class文件中加斷點測試,如果類路徑有上面兩個類會進入斷點創建transactionManager對象;如果不存在不會進入斷點。
在我們需要設置自己的事務管理器的時候可以重新返回一個PlatformTransactionManager對象,這些經常在使用動態數據源的時候需要設置自己的事務管理器,如下:
@Bean public PlatformTransactionManager transactionManager() { // 配置事務管理, 使用事務時在方法頭部添加@Transactional注解即可 return new DataSourceTransactionManager(dynamicDataSource()); }
(2)