springboot+mybatis+druid+atomikos框架搭建及測試


 前言

因為最近公司項目升級,需要將外網數據庫的信息導入到內網數據庫內。於是找了一些springboot多數據源的文章來看,同時也親自動手實踐。可是過程中也踩了不少的坑,主要原因是我看的文章大部分都是springboot 1.X版本的。

於是我就打算把這次搭建springboot+mybatis+druid+atomikos的框架過程記錄下來,方便大家借鑒,也方便自己以后使用。這里不單單記錄搭建過程,同時也會對一些配置文件進行說明,以及我在看別人文章並實踐時碰到的一些坑的說明。

開發環境

系統:windows10 

開發工具:IDEA2019.1

jdk版本: jdk1.8.0_144

springboot版本:2.1.7

數據庫:mysql

涉及的框架及工具:mybatis,druid,atomikos,通用mapper,pagehelper,mybatis-generator,freemarker,layui。

 

新建項目

1.IDEA   菜單欄——file——New——Project...

2.選擇Spring Initializr——選擇jdk版本后,next

3.設置項目名稱,打包方式等配置

這里有個位置要說明下。Packaging下有兩個選項,一個是Jar,另一個是War。

區別是Jar打包成jar包后包含tomcat的lib,可以直接通過命令行部署,但War則依賴tomcat等容器。

4.組件選擇

選擇如下:

Spring Boot DevTools: 用於測試時項目的熱部署

Spring Web Starter:  包含springMVC,tomcat等容器

Apache Freemaker: 前端模版

MySQL Driver: 連接mysql

MyBatis 

5. 選擇文件位置后,完成即可

pom.xml如下代碼

需要注意的是,在plugins部分增加了mybatis-generator自動生成實體類和mapper的配置

  1 <?xml version="1.0" encoding="UTF-8"?>
  2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  4     <modelVersion>4.0.0</modelVersion>
  5     <parent>
  6         <groupId>org.springframework.boot</groupId>
  7         <artifactId>spring-boot-starter-parent</artifactId>
  8         <version>2.1.7.RELEASE</version>
  9         <relativePath/> <!-- lookup parent from repository -->
 10     </parent>
 11     <groupId>com.zcph</groupId>
 12     <artifactId>oaonline</artifactId>
 13     <version>0.0.1-SNAPSHOT</version>
 14     <name>oaonline</name>
 15     <description>zcph online oa</description>
 16 
 17     <properties>
 18         <java.version>1.8</java.version>
 19     </properties>
 20 
 21     <dependencies>
 22         <dependency>
 23             <groupId>org.springframework.boot</groupId>
 24             <artifactId>spring-boot-starter-freemarker</artifactId>
 25         </dependency>
 26         <dependency>
 27             <groupId>org.springframework.boot</groupId>
 28             <artifactId>spring-boot-starter-web</artifactId>
 29         </dependency>
 30         <dependency>
 31             <groupId>org.springframework.boot</groupId>
 32             <artifactId>spring-boot-starter-aop</artifactId>
 33         </dependency>
 34         <dependency>
 35             <groupId>org.mybatis.spring.boot</groupId>
 36             <artifactId>mybatis-spring-boot-starter</artifactId>
 37             <version>2.1.0</version>
 38         </dependency>
 39         <dependency>
 40             <groupId>org.springframework.boot</groupId>
 41             <artifactId>spring-boot-devtools</artifactId>
 42             <scope>runtime</scope>
 43             <optional>true</optional>
 44         </dependency>
 45         <dependency>
 46             <groupId>org.springframework.boot</groupId>
 47             <artifactId>spring-boot-starter-test</artifactId>
 48             <scope>test</scope>
 49         </dependency>
 50         <!-- 修改下版本號,因為我用的mysql版本是5.7,而且5.1.34在多數據源時候會報錯-->
 51         <dependency>
 52             <groupId>mysql</groupId>
 53             <artifactId>mysql-connector-java</artifactId>
 54             <version>5.1.36</version>
 55             <scope>runtime</scope>
 56         </dependency>
 57         <!--druid連接池依賴-->
 58         <dependency>
 59             <groupId>com.alibaba</groupId>
 60             <artifactId>druid-spring-boot-starter</artifactId>
 61             <version>1.1.10</version>
 62         </dependency>
 63         <dependency>
 64             <groupId>com.alibaba</groupId>
 65             <artifactId>fastjson</artifactId>
 66             <version>1.2.58</version>
 67         </dependency>
 68         <!--aspectj組件-->
 69         <dependency>
 70             <groupId>org.aspectj</groupId>
 71             <artifactId>aspectjweaver</artifactId>
 72             <version>1.9.4</version>
 73         </dependency>
 74         <!--通用mapper-->
 75         <dependency>
 76             <groupId>tk.mybatis</groupId>
 77             <artifactId>mapper-spring-boot-starter</artifactId>
 78             <version>2.1.5</version>
 79         </dependency>
 80         <!--pagehelper 分頁插件-->
 81         <dependency>
 82             <groupId>com.github.pagehelper</groupId>
 83             <artifactId>pagehelper-spring-boot-starter</artifactId>
 84             <version>1.2.12</version>
 85         </dependency>
 86         <!--分布式事務-->
 87         <dependency>
 88             <groupId>org.springframework.boot</groupId>
 89             <artifactId>spring-boot-starter-jta-atomikos</artifactId>
 90         </dependency>
 91     </dependencies>
 92 
 93     <build>
 94         <plugins>
 95             <plugin>
 96                 <groupId>org.springframework.boot</groupId>
 97                 <artifactId>spring-boot-maven-plugin</artifactId>
 98                 <configuration>
 99                     <!--fork :  如果沒有該項配置,這個devtools不會起作用,即應用不會restart -->
100                     <fork>true</fork>
101                     <!--支持靜態文件熱部署-->
102                     <addResources>true</addResources>
103                 </configuration>
104             </plugin>
105             <!--如果要發布生成版本這部分代碼要禁止掉,本地開發開啟,用於自動生成mybatis的相應類和xml-->
106             <plugin>
107                 <groupId>org.mybatis.generator</groupId>
108                 <artifactId>mybatis-generator-maven-plugin</artifactId>
109                 <version>1.3.5</version>
110                 <dependencies>
111                     <!--配置這個依賴主要是為了等下在配置mybatis-generator.xml的時候可以不用配置classPathEntry這樣的一個屬性,避免代碼的耦合度太高-->
112                     <dependency>
113                         <groupId>mysql</groupId>
114                         <artifactId>mysql-connector-java</artifactId>
115                         <version>5.1.36</version>
116                     </dependency>
117                     <dependency>
118                         <groupId>tk.mybatis</groupId>
119                         <artifactId>mapper</artifactId>
120                         <version>3.4.0</version>
121                     </dependency>
122                 </dependencies>
123                 <executions>
124                     <execution>
125                         <id>Generate MyBatis Artifacts</id>
126                         <phase>package</phase>
127                         <goals>
128                             <goal>generate</goal>
129                         </goals>
130                     </execution>
131                 </executions>
132                 <configuration>
133                     <!--允許移動生成的文件 -->
134                     <verbose>true</verbose>
135                     <!-- 是否覆蓋 -->
136                     <overwrite>true</overwrite>
137                     <!-- 自動生成的配置 -->
138                     <configurationFile>src/main/resources/mybatis-generator.xml</configurationFile>
139                 </configuration>
140             </plugin>
141 
142             <plugin>
143                 <artifactId>maven-compiler-plugin</artifactId>
144                 <configuration>
145                     <source>1.8</source>
146                     <target>1.8</target>
147                 </configuration>
148             </plugin>
149         </plugins>
150     </build>
151 
152 </project>

 

項目整體結構

application.properties和application.yml

代碼如下,在application.properties中並沒有數據庫連接的相關配置。而是放到了application.yml中。

在application.properties中配置了mybatis,通用mapper,pagehelper,freemaker,項目名稱等配置。

在application.yml中配置了數據源的信息

 1 #mybatis
 2 mybatis.type-aliases-package=com.zcph.oaonline.entity
 3 mybatis.mapper-locations=classpath:mapper/*.xml
 4 # mybatis日志打印sql
 5 logging.level.com.zcph.oaonline.mapper=debug
 6 logging.level.com.zcph.oaonline.mapper2=debug
 7 spring.servlet.multipart.max-file-size=100MB
 8 spring.servlet.multipart.max-request-size=100MB
 9 #mappers 多個接口時逗號隔開
10 mapper.mappers=com.zcph.oaonline.util.MyMapper
11 mapper.not-empty=false
12 mapper.identity=MYSQL
13 
14 #pagehelper
15 pagehelper.helperDialect=mysql
16 pagehelper.reasonable=true
17 pagehelper.supportMethodsArguments=true
18 pagehelper.params=count=countSql
19 
20 ########################################################
21 ###FREEMARKER (FreeMarkerAutoConfiguration)
22 ########################################################
23 spring.freemarker.allow-request-override=false
24 #本機調試時,配置項template_update_delay=0,這樣就關閉了模板緩存。注意線上環境要開啟緩存
25 spring.freemarker.cache=false
26 spring.freemarker.settings.template_update_delay=0
27 spring.freemarker.check-template-location=true
28 spring.freemarker.charset=UTF-8
29 spring.freemarker.content-type=text/html
30 spring.freemarker.expose-request-attributes=false
31 spring.freemarker.expose-session-attributes=false
32 spring.freemarker.expose-spring-macro-helpers=false
33 spring.freemarker.prefix=
34 #若在freemarker獲取request對象,在spring boot 在application.properties可以這么配置
35 spring.freemarker.request-context-attribute=request
36 #spring.freemarker.settings.*=
37 spring.freemarker.suffix=.ftl
38 #template-loader-path表示所有的模板文件都放在該目錄下
39 spring.freemarker.template-loader-path=classpath:/templates/ 
40 #spring.freemarker.view-names= #whitelistofviewnamesthatcanberesolved
41 
42 #static-locations可以自定義靜態資源路徑,不過會覆蓋springboot默認路徑
43 spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/
44 #在這個最末尾的file:${web.upload-path}之所有要加file:是因為指定的是一個具體的硬盤路徑,其他的使用classpath指的是系統環境變量
45 #spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,file:${web.upload-path}
46 
47 spring.freemarker.settings.auto_import=ftl/spring.ftl as com
48 spring.freemarker.settings.datetime_format=yyyy-MM-dd
49 #兼容傳統模式
50 spring.freemarker.settings.classic_compatible=true
51 #表示訪問該路徑時代表請求靜態資源,用戶可以直接訪問該請求路徑中的靜態資源
52 spring.mvc.static-path-pattern=/static/**
53 server.port=9090
54 server.servlet.context-path=/oa_online
55 server.servlet.session.timeout=10000
56 #開發模式端口,因為我同時測試多個項目,端口沖突了
57 spring.devtools.livereload.port=35730 
 1 spring:
 2   datasource:
 3     type: com.alibaba.druid.pool.xa.DruidXADataSource
 4     druid:
 5 
 6       systemDB:
 7         name: systemDB
 8         url: jdbc:mysql://192.168.128.244:3306/online_oa?useUnicode=true&characterEncoding=utf-8&useSSL=false
 9         username: root
10         password: zcoa
11         # 下面為連接池的補充設置,應用到上面所有數據源中
12         # 初始化大小,最小,最大
13         initialSize: 5
14         minIdle: 5
15         maxActive: 20
16         # 配置獲取連接等待超時的時間
17         maxWait: 60000
18         # 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒
19         timeBetweenEvictionRunsMillis: 60000
20         # 配置一個連接在池中最小生存的時間,單位是毫秒
21         minEvictableIdleTimeMillis: 300000
22         validationQuery: SELECT 1
23         validationQueryTimeout: 10000
24         testWhileIdle: true
25         testOnBorrow: false
26         testOnReturn: false
27         # 打開PSCache,並且指定每個連接上PSCache的大小
28         poolPreparedStatements: true
29         maxPoolPreparedStatementPerConnectionSize: 20
30         filters: stat,wall
31         # 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
32         connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
33         # 合並多個DruidDataSource的監控數據
34         useGlobalDataSourceStat: true
35 
36       businessDB:
37         name: businessDB
38         url: jdbc:mysql://192.168.128.61:3306/bgc?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
39         username: root
40         password: root
41         # 下面為連接池的補充設置,應用到上面所有數據源中
42         # 初始化大小,最小,最大
43         initialSize: 5
44         minIdle: 5
45         maxActive: 20
46         # 配置獲取連接等待超時的時間
47         maxWait: 60000
48         # 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒
49         timeBetweenEvictionRunsMillis: 60000
50         # 配置一個連接在池中最小生存的時間,單位是毫秒
51         minEvictableIdleTimeMillis: 300000
52         validationQuery: SELECT 1
53         validationQueryTimeout: 10000
54         testWhileIdle: true
55         testOnBorrow: false
56         testOnReturn: false
57         # 打開PSCache,並且指定每個連接上PSCache的大小
58         poolPreparedStatements: true
59         maxPoolPreparedStatementPerConnectionSize: 20
60         filters: stat,wall
61         # 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
62         connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
63         # 合並多個DruidDataSource的監控數據
64         useGlobalDataSourceStat: true
65 
66   #jta相關參數配置
67   jta:
68     log-dir: classpath:tx-logs
69     transaction-manager-id: txManager

 數據源配置及druid配置

DruidConfig.java

實現數據源注冊,druid注冊和分布式事務管理器的注冊

  1 package com.zcph.oaonline.config;
  2 
  3 import com.alibaba.druid.filter.stat.StatFilter;
  4 import com.alibaba.druid.support.http.StatViewServlet;
  5 import com.alibaba.druid.support.http.WebStatFilter;
  6 import com.alibaba.druid.wall.WallConfig;
  7 import com.alibaba.druid.wall.WallFilter;
  8 import com.atomikos.icatch.jta.UserTransactionImp;
  9 import com.atomikos.icatch.jta.UserTransactionManager;
 10 import org.springframework.beans.factory.annotation.Autowired;
 11 import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
 12 import org.springframework.boot.web.servlet.FilterRegistrationBean;
 13 import org.springframework.boot.web.servlet.ServletRegistrationBean;
 14 import org.springframework.context.annotation.Bean;
 15 import org.springframework.context.annotation.Configuration;
 16 import org.springframework.context.annotation.Primary;
 17 import org.springframework.core.env.Environment;
 18 import org.springframework.transaction.jta.JtaTransactionManager;
 19 
 20 import javax.sql.DataSource;
 21 import javax.transaction.UserTransaction;
 22 import java.util.Properties;
 23 
 24 /**
 25  * 多數據源和Druid配置
 26  */
 27 @Configuration
 28 public class DruidConfig {
 29 
 30     /**
 31      * 數據源1配置
 32      * @param env
 33      * @return
 34      */
 35     @Bean(name = "systemDataSource")
 36     @Primary
 37     @Autowired
 38     public DataSource systemDataSource(Environment env) {
 39         AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
 40         Properties prop = build(env, "spring.datasource.druid.systemDB.");
 41         ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
 42         ds.setUniqueResourceName("systemDB");
 43         ds.setPoolSize(5);
 44         ds.setXaProperties(prop);
 45         return ds;
 46     }
 47 
 48     /**
 49      * 數據源2配置
 50      * @param env
 51      * @return
 52      */
 53     @Autowired
 54     @Bean(name = "businessDataSource")
 55     public AtomikosDataSourceBean businessDataSource(Environment env) {
 56         AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
 57         Properties prop = build(env, "spring.datasource.druid.businessDB.");
 58         ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
 59         ds.setUniqueResourceName("businessDB");
 60         ds.setPoolSize(5);
 61         ds.setXaProperties(prop);
 62         return ds;
 63     }
 64 
 65     /**
 66      * 注入事物管理器
 67      * @return
 68      */
 69     @Bean(name = "xatx")
 70     public JtaTransactionManager regTransactionManager () {
 71         UserTransactionManager userTransactionManager = new UserTransactionManager();
 72         UserTransaction userTransaction = new UserTransactionImp();
 73         return new JtaTransactionManager(userTransaction, userTransactionManager);
 74     }
 75 
 76     /**
 77      * 從配置文件中加載數據源信息
 78      * @param env
 79      * @param prefix
 80      * @return
 81      */
 82     private Properties build(Environment env, String prefix) {
 83         Properties prop = new Properties();
 84         prop.put("url", env.getProperty(prefix + "url"));
 85         prop.put("username", env.getProperty(prefix + "username"));
 86         prop.put("password", env.getProperty(prefix + "password"));
 87         prop.put("driverClassName", env.getProperty(prefix + "driverClassName", ""));
 88         prop.put("initialSize", env.getProperty(prefix + "initialSize", Integer.class));
 89         prop.put("maxActive", env.getProperty(prefix + "maxActive", Integer.class));
 90         prop.put("minIdle", env.getProperty(prefix + "minIdle", Integer.class));
 91         prop.put("maxWait", env.getProperty(prefix + "maxWait", Integer.class));
 92         prop.put("poolPreparedStatements", env.getProperty(prefix + "poolPreparedStatements", Boolean.class));
 93         prop.put("maxPoolPreparedStatementPerConnectionSize",
 94                 env.getProperty(prefix + "maxPoolPreparedStatementPerConnectionSize", Integer.class));
 95         prop.put("maxPoolPreparedStatementPerConnectionSize",
 96                 env.getProperty(prefix + "maxPoolPreparedStatementPerConnectionSize", Integer.class));
 97         prop.put("validationQuery", env.getProperty(prefix + "validationQuery"));
 98         prop.put("validationQueryTimeout", env.getProperty(prefix + "validationQueryTimeout", Integer.class));
 99         prop.put("testOnBorrow", env.getProperty(prefix + "testOnBorrow", Boolean.class));
100         prop.put("testOnReturn", env.getProperty(prefix + "testOnReturn", Boolean.class));
101         prop.put("testWhileIdle", env.getProperty(prefix + "testWhileIdle", Boolean.class));
102         prop.put("timeBetweenEvictionRunsMillis",
103                 env.getProperty(prefix + "timeBetweenEvictionRunsMillis", Integer.class));
104         prop.put("minEvictableIdleTimeMillis", env.getProperty(prefix + "minEvictableIdleTimeMillis", Integer.class));
105         prop.put("filters", env.getProperty(prefix + "filters"));
106         return prop;
107     }
108 
109     /**
110      * druid訪問配置
111      * @return
112      */
113     @Bean
114     public ServletRegistrationBean druidServlet() {
115         ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
116         //控制台管理用戶,加入下面2行 進入druid后台就需要登錄
117         servletRegistrationBean.addInitParameter("loginUsername", "admin");
118         servletRegistrationBean.addInitParameter("loginPassword", "admin");
119         return servletRegistrationBean;
120     }
121 
122     @Bean
123     public FilterRegistrationBean filterRegistrationBean() {
124         FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
125         filterRegistrationBean.setFilter(new WebStatFilter());
126         filterRegistrationBean.addUrlPatterns("/*");
127         filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
128         filterRegistrationBean.addInitParameter("profileEnable", "true");
129         return filterRegistrationBean;
130     }
131 
132     @Bean
133     public StatFilter statFilter(){
134         StatFilter statFilter = new StatFilter();
135         statFilter.setLogSlowSql(true); //slowSqlMillis用來配置SQL慢的標准,執行時間超過slowSqlMillis的就是慢。
136         statFilter.setMergeSql(true); //SQL合並配置
137         statFilter.setSlowSqlMillis(1000);//slowSqlMillis的缺省值為3000,也就是3秒。
138         return statFilter;
139     }
140 
141     @Bean
142     public WallFilter wallFilter(){
143         WallFilter wallFilter = new WallFilter();
144         //允許執行多條SQL
145         WallConfig config = new WallConfig();
146         config.setMultiStatementAllow(true);
147         wallFilter.setConfig(config);
148         return wallFilter;
149     }
150 }
MybatisDatasourceConfig.java
數據源1的sqlSessionFactory和MapperScan掃描包的配置。
 1 package com.zcph.oaonline.config;
 2 
 3 import com.zcph.oaonline.util.MyMapper;
 4 import org.apache.ibatis.session.SqlSessionFactory;
 5 import org.mybatis.spring.SqlSessionFactoryBean;
 6 import org.mybatis.spring.SqlSessionTemplate;
 7 import tk.mybatis.spring.annotation.MapperScan;
 8 import org.springframework.beans.factory.annotation.Autowired;
 9 import org.springframework.beans.factory.annotation.Qualifier;
10 import org.springframework.context.annotation.Bean;
11 import org.springframework.context.annotation.Configuration;
12 import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
13 import org.springframework.core.io.support.ResourcePatternResolver;
14 
15 import javax.sql.DataSource;
16 
17 /**
18  * @description
19  */
20 @Configuration
21 // 精確到 mapper 目錄,以便跟其他數據源隔離
22 @MapperScan(basePackages = "com.zcph.oaonline.mapper", markerInterface = MyMapper.class, sqlSessionFactoryRef = "sqlSessionFactory")
23 public class MybatisDatasourceConfig {
24 
25     @Autowired
26     @Qualifier("systemDataSource")
27     private DataSource ds;
28 
29     @Bean
30     public SqlSessionFactory sqlSessionFactory() throws Exception {
31         SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
32         factoryBean.setDataSource(ds);
33         //指定mapper xml目錄
34         ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
35         factoryBean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
36         return factoryBean.getObject();
37 
38     }
39 
40     @Bean
41     public SqlSessionTemplate sqlSessionTemplate() throws Exception {
42         SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory()); // 使用上面配置的Factory
43         return template;
44     }
45 
46 }
MybatisDatasource2Config.java
數據源2的sqlSessionFactory和MapperScan掃描包的配置。
 1 package com.zcph.oaonline.config;
 2 
 3 import com.zcph.oaonline.util.MyMapper;
 4 import org.apache.ibatis.session.SqlSessionFactory;
 5 import org.mybatis.spring.SqlSessionFactoryBean;
 6 import org.mybatis.spring.SqlSessionTemplate;
 7 import tk.mybatis.spring.annotation.MapperScan;
 8 import org.springframework.beans.factory.annotation.Autowired;
 9 import org.springframework.beans.factory.annotation.Qualifier;
10 import org.springframework.context.annotation.Bean;
11 import org.springframework.context.annotation.Configuration;
12 import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
13 import org.springframework.core.io.support.ResourcePatternResolver;
14 
15 import javax.sql.DataSource;
16 
17 /**
18  * @description
19  */
20 @Configuration
21 // 精確到 mapper 目錄,以便跟其他數據源隔離
22 @MapperScan(basePackages = "com.zcph.oaonline.mapper2", markerInterface = MyMapper.class, sqlSessionFactoryRef = "sqlSessionFactory2")
23 public class MybatisDatasource2Config {
24 
25     @Autowired
26     @Qualifier("businessDataSource")
27     private DataSource ds;
28 
29     @Bean
30     public SqlSessionFactory sqlSessionFactory2() throws Exception {
31         SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
32         factoryBean.setDataSource(ds);
33         //指定mapper xml目錄
34         ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
35         factoryBean.setMapperLocations(resolver.getResources("classpath:mapper2/*.xml"));
36         return factoryBean.getObject();
37 
38     }
39 
40     @Bean
41     public SqlSessionTemplate sqlSessionTemplate2() throws Exception {
42         SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory2()); // 使用上面配置的Factory
43         return template;
44     }
45 }

掃描的包不同以及使用的sqlSessionFactory不同。

這里需要注意的一點是,在MybatisDatasourceConfig.java和MybatisDatasource2Config.java中在使用@MapperScan注解是引入的是tk.mybatis.spring.annotation.MapperScan。

這里之前我踩過坑,之前看別人的文章復制代碼后,引入了org.mybatis.spring.annotation.MapperScan,這樣就會報找不到對應方法的錯:java.lang.NoSuchMethodException: tk.mybatis.mapper.provider.base.BaseSelectProvider.<init>()

如下圖:只需引入正確的包后就可以了

 

 配置攔截器

CommonInterceptor.java 自定義攔截器,代碼如下:

 1 package com.zcph.oaonline.interceptor;
 2 
 3 import org.springframework.web.servlet.HandlerInterceptor;
 4 import org.springframework.web.servlet.ModelAndView;
 5 
 6 import javax.servlet.http.HttpServletRequest;
 7 import javax.servlet.http.HttpServletResponse;
 8 
 9 public class CommonInterceptor implements HandlerInterceptor {
10 
11     /**
12      * 自定義攔截器
13      * 這里返回了訪問路徑,方便前台使用
14      * @param httpServletRequest
15      * @param httpServletResponse
16      * @param o
17      * @return
18      * @throws Exception
19      */
20     @Override
21     public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
22         String path = httpServletRequest.getContextPath();
23         String scheme = httpServletRequest.getScheme();
24         String serverName = httpServletRequest.getServerName();
25         int port = httpServletRequest.getServerPort();
26         String basePath = scheme + "://" + serverName + ":" + port + path;
27         httpServletRequest.setAttribute("basePath", basePath);
28         return true;
29     }
30 
31     @Override
32     public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
33 
34     }
35 
36     @Override
37     public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
38 
39     }
40 }
CommonInterceptorConfig.java 注入自定義攔截器,設置靜態資源以及處理前台返回值中文亂碼問題

這個位置有個問題需要注意:這里是繼承了WebMvcConfigurationSupport,但在其他文章中有的是繼承了WebMvcConfigurerAdapter。
WebMvcConfigurerAdapter在spring5以后就被標記為過時了,而WebMvcConfigurationSupport方法一定要重寫addResourceHandlers方法,否則靜態資源會訪問失效
package com.zcph.oaonline.interceptor;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

import java.nio.charset.Charset;
import java.util.List;

@Configuration
public class CommonInterceptorConfig extends WebMvcConfigurationSupport
{
    /**
     * 注入攔截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new CommonInterceptor()).addPathPatterns("/**");
    }

    /**
     * 設置靜態資源
     * @param registry
     */
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
        super.addResourceHandlers(registry);
    }
    /**
     * 以前要訪問一個頁面需要先創建個Controller控制類,在寫方法跳轉到頁面
     * 在這里配置后就不需要那么麻煩了,直接訪問http://localhost:9090/toTest就跳轉到test.ftl頁面了
     *
     * @param registry
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/toTest").setViewName("test");
        super.addViewControllers(registry);
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        super.configureMessageConverters(converters);
        //解決中文亂碼
        converters.add(responseBodyConverter());

        //解決: 添加解決中文亂碼后的配置之后,返回json數據直接報錯 500:no convertter for return value of type
        //或這個:Could not find acceptable representation
        converters.add(messageConverter());
    }
    //1.這個為解決中文亂碼
    @Bean
    public HttpMessageConverter<String> responseBodyConverter() {
        StringHttpMessageConverter converter = new StringHttpMessageConverter(Charset.forName("UTF-8"));
        return converter;
    }
    //2.1:解決中文亂碼后,返回json時可能會出現No converter found for return value of type: xxxx
    //或這個:Could not find acceptable representation
    //解決此問題如下
    public ObjectMapper getObjectMapper() {
        return new ObjectMapper();
    }

    //2.2:解決No converter found for return value of type: xxxx
    public MappingJackson2HttpMessageConverter messageConverter() {
        MappingJackson2HttpMessageConverter converter=new MappingJackson2HttpMessageConverter();
        converter.setObjectMapper(getObjectMapper());
        return converter;
    }
}

利用mybatis-generator生成對應實體和mapper和xml

因為我們要使用通用mapper,所以要新建下MyMapper接口

 1 package com.zcph.oaonline.util;
 2 
 3 import tk.mybatis.mapper.common.Mapper;
 4 import tk.mybatis.mapper.common.MySqlMapper;
 5 /**
 6  * 通用mapper
 7  * 繼承自己的MyMapper
 8  */
 9 public interface MyMapper<T> extends Mapper<T>, MySqlMapper<T> {
10     //FIXME 特別注意,該接口不能被掃描到,否則會出錯
11     //FIXME 最后在啟動類中通過MapperScan注解指定掃描的mapper路徑:
12 }

在resources下的mybatis-generator.xml,這個是兩個數據源的都有,只能1個1個的生成,要生成哪個注釋另一個就行了。

  1 <?xml version="1.0" encoding="UTF-8"?>
  2 <!DOCTYPE generatorConfiguration
  3         PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
  4         "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
  5 
  6 <!--數據源1-->
  7 <!--<generatorConfiguration>-->
  8 <!--    &lt;!&ndash;加載配置文件,為下面讀取數據庫信息准備&ndash;&gt;-->
  9 <!--    <properties resource="application.properties"/>-->
 10 
 11 <!--    <context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">-->
 12 
 13 <!--        <plugin type="tk.mybatis.mapper.generator.MapperPlugin">-->
 14 <!--            &lt;!&ndash;其中tk.mybatis.mapper.generator.MapperPlugin很重要,用來指定通用Mapper對應的文件,這樣我們生成的mapper都會繼承這個通用Mapper&ndash;&gt;-->
 15 <!--            <property name="mappers" value="com.zcph.oaonline.util.MyMapper" />-->
 16 <!--            &lt;!&ndash;caseSensitive默認false,當數據庫表名區分大小寫時,可以將該屬性設置為true&ndash;&gt;-->
 17 <!--            <property name="caseSensitive" value="false"/>-->
 18 <!--        </plugin>-->
 19 
 20 
 21 <!--        &lt;!&ndash;數據庫鏈接地址賬號密碼,這里由於我使用的是根據開發和生產分離的配置文件,所以這里直接寫上了&ndash;&gt;-->
 22 <!--        <jdbcConnection driverClass="com.mysql.jdbc.Driver"-->
 23 <!--                        connectionURL="jdbc:mysql://192.168.128.244:3306/online_oa?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false"-->
 24 <!--                        userId="root"-->
 25 <!--                        password="zcoa">-->
 26 <!--        </jdbcConnection>-->
 27 
 28 <!--        <javaTypeResolver>-->
 29 <!--            <property name="forceBigDecimals" value="false"/>-->
 30 <!--        </javaTypeResolver>-->
 31 
 32 <!--        &lt;!&ndash;生成Model類存放位置&ndash;&gt;-->
 33 <!--        <javaModelGenerator targetPackage="com.zcph.oaonline.entity" targetProject="src/main/java">-->
 34 <!--            <property name="enableSubPackages" value="true"/>-->
 35 <!--            <property name="trimStrings" value="true"/>-->
 36 <!--        </javaModelGenerator>-->
 37 
 38 <!--        &lt;!&ndash;生成映射文件存放位置&ndash;&gt;-->
 39 <!--        <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">-->
 40 <!--            <property name="enableSubPackages" value="true"/>-->
 41 <!--        </sqlMapGenerator>-->
 42 
 43 <!--        &lt;!&ndash;生成Dao類存放位置&ndash;&gt;-->
 44 <!--        &lt;!&ndash; 客戶端代碼,生成易於使用的針對Model對象和XML配置文件 的代碼-->
 45 <!--                type="ANNOTATEDMAPPER",生成Java Model 和基於注解的Mapper對象-->
 46 <!--                type="XMLMAPPER",生成SQLMap XML文件和獨立的Mapper接口-->
 47 <!--        &ndash;&gt;-->
 48 <!--        <javaClientGenerator type="XMLMAPPER" targetPackage="com.zcph.oaonline.mapper" targetProject="src/main/java">-->
 49 <!--            <property name="enableSubPackages" value="true"/>-->
 50 <!--        </javaClientGenerator>-->
 51 
 52 <!--        &lt;!&ndash;生成對應表及類名-->
 53 <!--        去掉Mybatis Generator生成的一堆 example-->
 54 <!--        &ndash;&gt;-->
 55 <!--        <table tableName="user_login_table" domainObjectName="UserLoginTable">-->
 56 <!--        </table>-->
 57 <!--    </context>-->
 58 <!--</generatorConfiguration>-->
 59 <!--數據源2-->
 60 <generatorConfiguration>
 61 <!--加載配置文件,為下面讀取數據庫信息准備-->
 62 <properties resource="application.properties"/>
 63 
 64 <context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">
 65 
 66     <plugin type="tk.mybatis.mapper.generator.MapperPlugin">
 67         <!--其中tk.mybatis.mapper.generator.MapperPlugin很重要,用來指定通用Mapper對應的文件,這樣我們生成的mapper都會繼承這個通用Mapper-->
 68         <property name="mappers" value="com.zcph.oaonline.util.MyMapper" />
 69         <!--caseSensitive默認false,當數據庫表名區分大小寫時,可以將該屬性設置為true-->
 70         <property name="caseSensitive" value="false"/>
 71     </plugin>
 72 
 73 
 74     <!--數據庫鏈接地址賬號密碼,這里由於我使用的是根據開發和生產分離的配置文件,所以這里直接寫上了-->
 75     <jdbcConnection driverClass="com.mysql.jdbc.Driver"
 76                     connectionURL="jdbc:mysql://192.168.128.61:3306/bgc?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false"
 77                     userId="root"
 78                     password="root">
 79     </jdbcConnection>
 80 
 81     <javaTypeResolver>
 82         <property name="forceBigDecimals" value="false"/>
 83     </javaTypeResolver>
 84 
 85     <!--生成Model類存放位置-->
 86     <javaModelGenerator targetPackage="com.zcph.oaonline.entity" targetProject="src/main/java">
 87         <property name="enableSubPackages" value="true"/>
 88         <property name="trimStrings" value="true"/>
 89     </javaModelGenerator>
 90 
 91     <!--生成映射文件存放位置-->
 92     <sqlMapGenerator targetPackage="mapper2" targetProject="src/main/resources">
 93         <property name="enableSubPackages" value="true"/>
 94     </sqlMapGenerator>
 95 
 96     <!--生成Dao類存放位置-->
 97     <!-- 客戶端代碼,生成易於使用的針對Model對象和XML配置文件 的代碼
 98             type="ANNOTATEDMAPPER",生成Java Model 和基於注解的Mapper對象
 99             type="XMLMAPPER",生成SQLMap XML文件和獨立的Mapper接口
100     -->
101     <javaClientGenerator type="XMLMAPPER" targetPackage="com.zcph.oaonline.mapper2" targetProject="src/main/java">
102         <property name="enableSubPackages" value="true"/>
103     </javaClientGenerator>
104 
105     <!--生成對應表及類名
106     去掉Mybatis Generator生成的一堆 example
107     -->
108     <table tableName="news" domainObjectName="News">
109     </table>
110 </context>
111 </generatorConfiguration>

因為我們已經在pom.xml中增加了mybatis-generator的plugin,利用idea的maven命令執行就可以生成對應的文件了。

執行后多出如下文件

因為我這個項目是后續要繼續使用的,所以數據庫內字段比較多,大家要是做測試的話可以把數據庫內多余的字段刪一刪。

pagehelper的重寫

因為我們使用了使用了layui,layui動態表格里需要一個code字段來確認數據。於是我們重寫下PageInfo類。如下PageResult.java

 1 package com.zcph.oaonline.model;
 2 
 3 import com.github.pagehelper.PageInfo;
 4 
 5 import java.util.List;
 6 
 7 public class PageRusult<T> extends PageInfo<T> {
 8     public PageRusult() {
 9     }
10 
11     public PageRusult(List<T> list) {
12         super(list, 8);
13     }
14 
15     private Integer code;//layui框架列表模塊返回參數中必須包含code狀態字段
16 
17     public Integer getCode() {
18         return code;
19     }
20 
21     public void setCode(Integer code) {
22         this.code = code;
23     }
24 
25 }

通用service

我們使用了通用mapper,於是我們可以新建一個通用service來簡化我們的代碼量,只需要其他service繼承這個通用sevice即可,這樣就不用再其他service里去寫基礎操作了

Iservice.java

 1 package com.zcph.oaonline.serviceAll;
 2 
 3 import org.springframework.stereotype.Service;
 4 
 5 import java.util.List;
 6 
 7 /**
 8  * 通用接口
 9  */
10 @Service
11 public interface IService<T> {
12 
13     T selectByKey(Object key);
14 
15     int save(T entity);
16 
17     int saveNotNull(T entity);
18 
19     int delete(Object key);
20 
21     int deleteByExample(Object example);
22 
23     int updateAll(T entity);
24 
25     int updateNotNull(T entity);
26 
27     List<T> selectByExample(Object example);
28 
29     List<T> selectAll();
30 
31     int selectCountByExample(Object example);
32 
33 }

BaseService.java

 1 package com.zcph.oaonline.serviceAll.impl;
 2 
 3 import com.zcph.oaonline.serviceAll.IService;
 4 import org.springframework.beans.factory.annotation.Autowired;
 5 import tk.mybatis.mapper.common.Mapper;
 6 
 7 import java.util.List;
 8 
 9 /**
10  * 通用Service
11  * @param <T>
12  */
13 public abstract class BaseService<T> implements IService<T> {
14 
15     @Autowired
16     protected Mapper<T> mapper;
17     public Mapper<T> getMapper() {
18         return mapper;
19     }
20 
21     @Override
22     public T selectByKey(Object key) {
23         //說明:根據主鍵字段進行查詢,方法參數必須包含完整的主鍵屬性,查詢條件使用等號
24         return mapper.selectByPrimaryKey(key);
25     }
26 
27     @Override
28     public int save(T entity) {
29         //說明:保存一個實體,null的屬性也會保存,不會使用數據庫默認值
30         return mapper.insert(entity);
31     }
32 
33     @Override
34     public int saveNotNull(T entity) {
35         //說明:保存一個實體,屬性不為null的值
36         return mapper.insertSelective(entity);
37     }
38 
39     @Override
40     public int delete(Object key) {
41         //說明:根據主鍵字段進行刪除,方法參數必須包含完整的主鍵屬性
42         return mapper.deleteByPrimaryKey(key);
43     }
44 
45     @Override
46     public int updateAll(T entity) {
47         //說明:根據主鍵更新實體全部字段,null值會被更新
48         return mapper.updateByPrimaryKey(entity);
49     }
50 
51     @Override
52     public int updateNotNull(T entity) {
53         //根據主鍵更新屬性不為null的值
54         return mapper.updateByPrimaryKeySelective(entity);
55     }
56 
57     @Override
58     public List<T> selectByExample(Object example) {
59         //說明:根據Example條件進行查詢
60         //重點:這個查詢支持通過Example類指定查詢列,通過selectProperties方法指定查詢列
61         return mapper.selectByExample(example);
62     }
63 
64     @Override
65     public List<T> selectAll() {
66         return mapper.selectAll();
67     }
68 
69     @Override
70     public int deleteByExample(Object example) {
71         //說明:根據主鍵字段進行刪除,方法參數必須包含完整的主鍵屬性
72         return mapper.deleteByExample(example);
73     }
74     @Override
75     public int selectCountByExample(Object example) {
76         return mapper.selectCountByExample(example);
77     }
78 }

數據源1的業務層

使UserLoginService繼承通用接口,並制定泛型為UserLoginTable即可

UserLoginService.java
 1 package com.zcph.oaonline.service;
 2 
 3 import com.zcph.oaonline.entity.UserLoginTable;
 4 import com.zcph.oaonline.serviceAll.IService;
 5 import com.zcph.oaonline.util.Page;
 6 
 7 import java.util.List;
 8 import java.util.Map;
 9 
10 public interface UserLoginService extends IService<UserLoginTable> {
11     /**
12      * 根據傳入分頁參數查詢
13      * @param page
14      * @return
15      */
16     public List<UserLoginTable> queryUserLoginList(Page<UserLoginTable> page);
17 
18     /**
19      * 保存或更新
20      * @param userLoginTable
21      * @return
22      */
23     public Map<String,Object> saveOrUpdateUserLogin(UserLoginTable userLoginTable);
24 }

 

實現類

UserLoginServiceImpl.java
 1 package com.zcph.oaonline.service.impl;
 2 
 3 import com.github.pagehelper.PageHelper;
 4 import com.zcph.oaonline.entity.UserLoginTable;
 5 import com.zcph.oaonline.mapper.UserLoginTableMapper;
 6 import com.zcph.oaonline.service.UserLoginService;
 7 import com.zcph.oaonline.serviceAll.impl.BaseService;
 8 import com.zcph.oaonline.util.Page;
 9 import org.springframework.beans.factory.annotation.Autowired;
10 import org.springframework.stereotype.Service;
11 import tk.mybatis.mapper.util.StringUtil;
12 
13 import java.util.LinkedHashMap;
14 import java.util.List;
15 import java.util.Map;
16 import java.util.UUID;
17 
18 @Service("userLoginService")
19 public class UserLoginServiceImpl extends BaseService<UserLoginTable> implements UserLoginService {
20     @Autowired
21     private UserLoginTableMapper userLoginTableMapper;
22 
23     @Override
24     public List<UserLoginTable> queryUserLoginList(Page<UserLoginTable> page) {
25         PageHelper.startPage(page.getPage(),page.getRows());
26         return  selectAll();
27     }
28 
29     @Override
30     public Map<String, Object> saveOrUpdateUserLogin(UserLoginTable userLoginTable) {
31         LinkedHashMap<String,Object> resultMap=new LinkedHashMap<String,Object>();
32         if(userLoginTable!=null){
33             if(StringUtil.isNotEmpty(userLoginTable.getLoginUid())){//編輯
34                 if(StringUtil.isNotEmpty(userLoginTable.getLoginName())){
35                     updateNotNull(userLoginTable);
36                     resultMap.put("state","success");
37                     resultMap.put("message","修改用戶登錄信息表成功");
38                     return resultMap;
39                 }else{
40                     resultMap.put("state","fail");
41                     resultMap.put("message","修改用戶登錄信息表失敗,缺少字段");
42                     return resultMap;
43                 }
44             }else{//新建
45                 if(StringUtil.isNotEmpty(userLoginTable.getLoginName())){
46                     userLoginTable.setLoginUid(UUID.randomUUID().toString().replaceAll("-",""));
47                     saveNotNull(userLoginTable);
48                     resultMap.put("state","success");
49                     resultMap.put("message","新建用戶登錄信息表成功");
50                     return resultMap;
51                 }else{
52                     resultMap.put("state","fail");
53                     resultMap.put("message","新建用戶登錄信息表失敗,缺少字段");
54                     return resultMap;
55                 }
56             }
57         }else{
58             resultMap.put("state","fail");
59             resultMap.put("message","失敗");
60             return resultMap;
61         }
62 
63     }
64 }

 數據源2的業務層

原理同數據源1,就是為了方便放到了另一個包里

NewsService.java
 1 package com.zcph.oaonline.service2;
 2 
 3 import com.zcph.oaonline.entity.News;
 4 import com.zcph.oaonline.serviceAll.IService;
 5 import com.zcph.oaonline.util.Page;
 6 
 7 import java.util.List;
 8 import java.util.Map;
 9 
10 public interface NewsService extends IService<News> {
11     /**
12      * 根據傳入分頁參數查詢
13      * @param page
14      * @return
15      */
16     public List<News> queryNewsList(Page<News> page);
17     /**
18      * 保存或更新
19      * @param news
20      * @return
21      */
22     public Map<String,Object> saveOrUpdateNews(News news);
23 }

實現類

NewsServiceImpl.java
 1 package com.zcph.oaonline.service2.impl;
 2 
 3 import com.github.pagehelper.PageHelper;
 4 import com.zcph.oaonline.entity.News;
 5 import com.zcph.oaonline.mapper2.NewsMapper;
 6 import com.zcph.oaonline.service2.NewsService;
 7 import com.zcph.oaonline.serviceAll.impl.BaseService;
 8 import com.zcph.oaonline.util.Page;
 9 import org.springframework.beans.factory.annotation.Autowired;
10 import org.springframework.stereotype.Service;
11 import tk.mybatis.mapper.util.StringUtil;
12 
13 import java.util.LinkedHashMap;
14 import java.util.List;
15 import java.util.Map;
16 import java.util.UUID;
17 
18 @Service("newsService")
19 public class NewsServiceImpl extends BaseService<News> implements NewsService {
20     @Autowired
21     private NewsMapper newsMapper;
22     @Override
23     public List<News> queryNewsList(Page<News> page) {
24         PageHelper.startPage(page.getPage(),page.getRows());
25         return  selectAll();
26     }
27 
28     @Override
29     public Map<String, Object> saveOrUpdateNews(News news) {
30         LinkedHashMap<String,Object> resultMap=new LinkedHashMap<String,Object>();
31         if(news!=null){
32             if(StringUtil.isNotEmpty(news.getNewsId())){//編輯
33                 if(StringUtil.isNotEmpty(news.getNewsTitle())){
34                     updateNotNull(news);
35                     resultMap.put("state","success");
36                     resultMap.put("message","修改新聞表成功");
37                     return resultMap;
38                 }else{
39                     resultMap.put("state","fail");
40                     resultMap.put("message","修改新聞表失敗,缺少字段");
41                     return resultMap;
42                 }
43             }else{//新建
44                 if(StringUtil.isNotEmpty(news.getNewsTitle())){
45                     news.setNewsId(UUID.randomUUID().toString().replaceAll("-",""));
46                     saveNotNull(news);
47                     resultMap.put("state","success");
48                     resultMap.put("message","新建新聞表成功");
49                     return resultMap;
50                 }else{
51                     resultMap.put("state","fail");
52                     resultMap.put("message","新建新聞表失敗,缺少字段");
53                     return resultMap;
54                 }
55             }
56         }else{
57             resultMap.put("state","fail");
58             resultMap.put("message","失敗");
59             return resultMap;
60         }
61 
62     }
63 }

事務測試業務層

向兩個表內同時插入數據

JtaTestService.java
 1 package com.zcph.oaonline.serviceAll;
 2 
 3 import java.util.Map;
 4 
 5 public interface JtaTestService {
 6     /**
 7      * 向兩個表內同時插入數據
 8      * @return
 9      */
10     public Map<String,Object> insertToTwoDatebaseService();
11 }

實現類

JtaTestServiceImpl.java
 1 package com.zcph.oaonline.serviceAll.impl;
 2 
 3 import com.zcph.oaonline.entity.News;
 4 import com.zcph.oaonline.entity.UserLoginTable;
 5 import com.zcph.oaonline.service.UserLoginService;
 6 import com.zcph.oaonline.service2.NewsService;
 7 import com.zcph.oaonline.serviceAll.JtaTestService;
 8 import org.springframework.beans.factory.annotation.Autowired;
 9 import org.springframework.beans.factory.annotation.Qualifier;
10 import org.springframework.stereotype.Service;
11 import org.springframework.transaction.annotation.Propagation;
12 import org.springframework.transaction.annotation.Transactional;
13 
14 import java.util.LinkedHashMap;
15 import java.util.Map;
16 
17 @Service("jtaTestService")
18 public class JtaTestServiceImpl implements JtaTestService {
19     @Autowired
20     @Qualifier("userLoginService")
21     private UserLoginService userLoginService;
22 
23     @Autowired
24     @Qualifier("newsService")
25     private NewsService newsService;
26 
27     @Override
28     @Transactional(transactionManager = "xatx", propagation = Propagation.REQUIRED, rollbackFor = { java.lang.RuntimeException.class })
29     public Map<String, Object> insertToTwoDatebaseService() {
30         LinkedHashMap<String,Object> resultMap=new LinkedHashMap<String,Object>();
31         UserLoginTable userLoginTable=new UserLoginTable();
32         userLoginTable.setLoginName("8888");
33         userLoginService.saveOrUpdateUserLogin(userLoginTable);
34 
35         News news=new News();
36         news.setNewsTitle("8888");
37         newsService.saveOrUpdateNews(news);
38         //測試事務中斷
39         System.out.println(1/0);
40 
41         resultMap.put("state","success");
42         resultMap.put("message","分布式事務同步成功");
43         return resultMap;
44     }
45 }

Web層

為了方便都放到了一個測試的controller里了

JtaTestController.java
  1 package com.zcph.oaonline.web;
  2 
  3 import com.zcph.oaonline.entity.News;
  4 import com.zcph.oaonline.entity.UserLoginTable;
  5 import com.zcph.oaonline.model.PageRusult;
  6 import com.zcph.oaonline.service.UserLoginService;
  7 import com.zcph.oaonline.service2.NewsService;
  8 import com.zcph.oaonline.serviceAll.JtaTestService;
  9 import com.zcph.oaonline.util.Page;
 10 import org.springframework.beans.factory.annotation.Autowired;
 11 import org.springframework.beans.factory.annotation.Qualifier;
 12 import org.springframework.stereotype.Controller;
 13 import org.springframework.web.bind.annotation.RequestMapping;
 14 import org.springframework.web.bind.annotation.ResponseBody;
 15 import tk.mybatis.mapper.util.StringUtil;
 16 
 17 import java.util.LinkedHashMap;
 18 import java.util.List;
 19 import java.util.Map;
 20 
 21 @Controller
 22 @RequestMapping("/jtaTest")
 23 public class JtaTestController {
 24     @Autowired
 25     @Qualifier("userLoginService")
 26     private UserLoginService userLoginService;
 27 
 28     @Autowired
 29     @Qualifier("newsService")
 30     private NewsService newsService;
 31 
 32     @Autowired
 33     @Qualifier("jtaTestService")
 34     private JtaTestService jtaTestService;
 35     @ResponseBody
 36     @RequestMapping("/queryUserLogin")
 37     public PageRusult selectUserLoginByPages(Page<UserLoginTable> page){
 38         List<UserLoginTable> userLoginTableList=userLoginService.queryUserLoginList(page);
 39         PageRusult<UserLoginTable> pageRusult =new PageRusult<UserLoginTable>(userLoginTableList);
 40         pageRusult.setCode(0);
 41         return pageRusult;
 42     }
 43 
 44     @ResponseBody
 45     @RequestMapping("/saveOrUpdateUserLogin")
 46     public Map<String,Object> saveOrUpdateUserLogin(UserLoginTable userLoginTable){
 47         LinkedHashMap<String,Object> resultMap=new LinkedHashMap<String,Object>();
 48         try {
 49             return userLoginService.saveOrUpdateUserLogin(userLoginTable);
 50         }catch (Exception e){
 51             resultMap.put("state","fail");
 52             resultMap.put("message","操作失敗");
 53             return resultMap;
 54         }
 55     }
 56 
 57     @ResponseBody
 58     @RequestMapping("/deleteUserLogin")
 59     public Map<String,Object> deleteUserLogin(String loginUid){
 60         LinkedHashMap<String,Object> resultMap=new LinkedHashMap<String,Object>();
 61         try {
 62             if(StringUtil.isNotEmpty(loginUid)){
 63                 userLoginService.delete(loginUid);
 64                 resultMap.put("state","success");
 65                 resultMap.put("message","刪除用戶登錄信息表成功");
 66                 return resultMap;
 67             }else{
 68                 resultMap.put("state","fail");
 69                 resultMap.put("message","刪除用戶登錄信息表失敗");
 70                 return resultMap;
 71             }
 72         }catch (Exception e){
 73             resultMap.put("state","fail");
 74             resultMap.put("message","操作異常,刪除用戶登錄信息表失敗");
 75             return resultMap;
 76         }
 77     }
 78 
 79     @ResponseBody
 80     @RequestMapping("/selectNewsByPages")
 81     public PageRusult selectNewsByPages(Page<News> page){
 82         List<News> newsList=newsService.queryNewsList(page);
 83         PageRusult<News> pageRusult =new PageRusult<News>(newsList);
 84         pageRusult.setCode(0);
 85         return pageRusult;
 86     }
 87 
 88     @ResponseBody
 89     @RequestMapping("/saveOrUpdateNews")
 90     public Map<String,Object> saveOrUpdateNews(News news){
 91         LinkedHashMap<String,Object> resultMap=new LinkedHashMap<String,Object>();
 92         try {
 93             return newsService.saveOrUpdateNews(news);
 94         }catch (Exception e){
 95             resultMap.put("state","fail");
 96             resultMap.put("message","操作失敗");
 97             return resultMap;
 98         }
 99     }
100 
101     @ResponseBody
102     @RequestMapping("/deleteNews")
103     public Map<String,Object> deleteNews(String newsId){
104         LinkedHashMap<String,Object> resultMap=new LinkedHashMap<String,Object>();
105         try {
106             if(StringUtil.isNotEmpty(newsId)){
107                 newsService.delete(newsId);
108                 resultMap.put("state","success");
109                 resultMap.put("message","刪除新聞表成功");
110                 return resultMap;
111             }else{
112                 resultMap.put("state","fail");
113                 resultMap.put("message","刪除新聞表失敗");
114                 return resultMap;
115             }
116         }catch (Exception e){
117             resultMap.put("state","fail");
118             resultMap.put("message","操作異常,刪除新聞表失敗");
119             return resultMap;
120         }
121     }
122 
123     @ResponseBody
124     @RequestMapping("/insertToTwoDatebase")
125     public Map<String,Object> insertToTwoDatebase(){
126         LinkedHashMap<String,Object> resultMap=new LinkedHashMap<String,Object>();
127         try {
128             return jtaTestService.insertToTwoDatebaseService();
129         }catch (Exception e){
130             e.printStackTrace();
131             resultMap.put("state","fail");
132             resultMap.put("message","分布式事務同步失敗");
133             return resultMap;
134         }
135     }
136 }

測試頁面test.ftl

  1 <@com.head title="">
  2     <base id="base" href="${basePath!}">
  3     <link href="${basePath!}/static/plugins/layui/css/layui.css" type="text/css" media="screen" rel="stylesheet"/>
  4     <script src="${basePath!}/static/plugins/layui/layui.js" type="text/javascript"></script>
  5 
  6     <script>
  7         //一般直接寫在一個js文件中
  8         layui.use(['layer', 'form', 'table'], function () {
  9             var layer = layui.layer
 10                 , form = layui.form
 11                 , $ = layui.$
 12                 , laytpl = layui.laytpl
 13                 , table = layui.table;
 14             // 第一個數據庫
 15             var tableUserLogin = table.render({
 16                 id: 'userLoginTableId'
 17                 , elem: '#UserLoginTable'
 18                 , height: 460
 19                 , width: 755
 20                 , url: '${basePath!}/jtaTest/queryUserLogin' //數據接口
 21                 , page: true //開啟分頁
 22                 , cols: [[ //表頭
 23                     {type: 'numbers', title: '序號', width: 80}
 24                     , {field: 'loginUid', title: '主鍵', width: 300}
 25                     , {field: 'loginName', title: '登錄名', width: 200}
 26                     , {width: 170, title: '操作', align: 'center', toolbar: '#barDemo'} //這里的toolbar值是模板元素的選擇器
 27                 ]],
 28                 method: 'post',
 29                 request: {
 30                     pageName: 'page' //頁碼的參數名稱,默認:page
 31                     , limitName: 'rows' //每頁數據量的參數名,默認:limit
 32                 },
 33                 response: {
 34                     statusName: 'code'
 35                     , statusCode: 0
 36                     , countName: 'total' //數據總數的字段名稱,默認:count
 37                     , dataName: 'list' //數據列表的字段名稱,默認:data
 38                 },
 39             });
 40 
 41             var UserLoginInsertLayerIndex;
 42 
 43             //新建
 44             $("#UserLoginInsert").click(function () {
 45                 //置空表單
 46                 $("#UserLoginInsertForm").find(":input[name='loginName']").val("");
 47                 $("#UserLoginInsertForm").find(":input[name='loginUid']").val("");
 48                 UserLoginInsertLayerIndex = layer.open({
 49                     title: "新建",
 50                     type: 1,
 51                     content: $('#UserLoginInsertDiv')
 52                 });
 53             });
 54 
 55             form.on('submit(UserLoginInsertFormSubmit)', function (data) {
 56                 $.ajax({
 57                     type: "POST",
 58                     url: "${basePath!}/jtaTest/saveOrUpdateUserLogin",
 59                     data: $("#UserLoginInsertForm").serialize(),
 60                     async: false,
 61                     error: function (request) {
 62                         layer.alert("與服務器連接失敗/(ㄒoㄒ)/~~");
 63                         return false;
 64                     },
 65                     success: function (data) {
 66                         if (data.state == 'fail') {
 67                             layer.alert(data.message);
 68                             layer.close(UserLoginInsertLayerIndex);
 69                             return false;
 70                         } else if (data.state == 'success') {
 71                             layer.alert(data.message);
 72                             layer.close(UserLoginInsertLayerIndex);
 73                             tableUserLogin.reload({
 74                                 page: {
 75                                     curr: 1 //重新從第 1 頁開始
 76                                 }
 77                             });
 78                         }
 79                     }
 80                 });
 81                 return false; //這個阻止表單跳轉。如果需要表單跳轉, 去掉這段即可。
 82             });
 83 
 84             //監聽工具條
 85             table.on('tool(UserLoginTable)', function (obj) { //注:tool是工具條事件名,UserLogin是table原始容器的屬性 lay-filter="對應的值"
 86                 var data = obj.data; //獲得當前行數據
 87                 var layEvent = obj.event; //獲得 lay-event 對應的值(也可以是表頭的 event 參數對應的值)
 88                 if (layEvent === 'del') { //刪除
 89                     layer.confirm('真的刪除該行數據嗎', function (index) {
 90                         obj.del(); //刪除對應行(tr)的DOM結構,並更新緩存
 91                         layer.close(index);
 92                         //向服務端發送刪除指令
 93                         $.ajax({
 94                             type: "POST",
 95                             url: "${basePath!}/jtaTest/deleteUserLogin",
 96                             data: {loginUid: data.loginUid},
 97                             async: false,
 98                             error: function (request) {
 99                                 layer.alert("與服務器連接失敗/(ㄒoㄒ)/~~");
100                                 return false;
101                             },
102                             success: function (data) {
103                                 if (data.state == 'fail') {
104                                     layer.alert(data.message);
105                                     return false;
106                                 } else if (data.state == 'success') {
107                                 }
108                             }
109                         });
110                     });
111                 } else if (layEvent === 'edit') { //編輯
112                     //置空表單
113                     $("#UserLoginInsertForm").find(":input[name='loginName']").val("");
114                     $("#UserLoginInsertForm").find(":input[name='loginUid']").val("");
115                     //添加值
116                     $("#UserLoginInsertForm").find(":input[name='loginName']").val(data.loginName);
117                     $("#UserLoginInsertForm").find(":input[name='loginUid']").val(data.loginUid);
118                     UserLoginInsertLayerIndex = layer.open({
119                         title: "編輯",
120                         type: 1,
121                         content: $('#UserLoginInsertDiv')
122                     });
123                 }
124             });
125             // 第二個數據庫
126             var tableNews = table.render({
127                 id: 'newsTableId'
128                 , elem: '#News'
129                 , height: 460
130                 , width: 755
131                 , url: '${basePath!}/jtaTest/selectNewsByPages' //數據接口
132                 , page: true //開啟分頁
133                 , cols: [[ //表頭
134                     {type: 'numbers', title: '序號', width: 80, sort: true}
135                     , {field: 'newsId', title: '主鍵', width: 300, unresize: true}
136                     , {field: 'newsTitle', title: '新聞標題', width: 200, unresize: true}
137                     , {width: 170, title: '操作', align: 'center', toolbar: '#barDemo'} //這里的toolbar值是模板元素的選擇器
138                 ]],
139                 method: 'post',
140                 request: {
141                     pageName: 'page' //頁碼的參數名稱,默認:page
142                     , limitName: 'rows' //每頁數據量的參數名,默認:limit
143                 },
144                 response: {
145                     statusName: 'code'
146                     , statusCode: 0
147                     , countName: 'total' //數據總數的字段名稱,默認:count
148                     , dataName: 'list' //數據列表的字段名稱,默認:data
149                 },
150             });
151 
152             var NewsInsertLayerIndex;
153 
154             //新建
155             $("#NewsInsert").click(function () {
156                 //置空表單
157                 $("#NewsInsertForm").find(":input[name='newsTitle']").val("");
158                 $("#NewsInsertForm").find(":input[name='newsId']").val("");
159                 NewsInsertLayerIndex = layer.open({
160                     title: "新建",
161                     type: 1,
162                     content: $('#NewsInsertDiv')
163                 });
164             });
165 
166             form.on('submit(NewsInsertFormSubmit)', function (data) {
167                 $.ajax({
168                     type: "POST",
169                     url: "${basePath!}/jtaTest/saveOrUpdateNews",
170                     data: $("#NewsInsertForm").serialize(),
171                     async: false,
172                     error: function (request) {
173                         layer.alert("與服務器連接失敗/(ㄒoㄒ)/~~");
174                         return false;
175                     },
176                     success: function (data) {
177                         if (data.state == 'fail') {
178                             layer.alert(data.message);
179                             layer.close(NewsInsertLayerIndex);
180                             return false;
181                         } else if (data.state == 'success') {
182                             layer.alert(data.message);
183                             layer.close(NewsInsertLayerIndex);
184                             tableNews.reload({
185                                 page: {
186                                     curr: 1 //重新從第 1 頁開始
187                                 }
188                             });
189                         }
190                     }
191                 });
192 
193                 return false; //這個阻止表單跳轉。如果需要表單跳轉, 去掉這段即可。
194             });
195 
196             //監聽工具條
197             table.on('tool(News)', function (obj) { //注:tool是工具條事件名,UserLogin是table原始容器的屬性 lay-filter="對應的值"
198                 var data = obj.data; //獲得當前行數據
199                 var layEvent = obj.event; //獲得 lay-event 對應的值(也可以是表頭的 event 參數對應的值)
200                 var tr = obj.tr; //獲得當前行 tr 的DOM對象
201 
202                 if (layEvent === 'detail') { //查看
203                     //do somehing
204                 } else if (layEvent === 'del') { //刪除
205                     layer.confirm('真的刪除該行數據嗎', function (index) {
206                         obj.del(); //刪除對應行(tr)的DOM結構,並更新緩存
207                         layer.close(index);
208                         //向服務端發送刪除指令
209                         $.ajax({
210                             type: "POST",
211                             url: "${basePath!}/jtaTest/deleteNews",
212                             data: {newsId: data.newsId},
213                             async: false,
214                             error: function (request) {
215                                 layer.alert("與服務器連接失敗/(ㄒoㄒ)/~~");
216                                 return false;
217                             },
218                             success: function (data) {
219                                 if (data.state == 'fail') {
220                                     layer.alert(data.message);
221                                     return false;
222                                 } else if (data.state == 'success') {
223                                 }
224                             }
225                         });
226                     });
227                 } else if (layEvent === 'edit') { //編輯
228                     //do something
229                     //置空表單
230                     $("#NewsInsertForm").find(":input[name='newsTitle']").val("");
231                     $("#NewsInsertForm").find(":input[name='newsId']").val("");
232                     //添加值
233                     $("#NewsInsertForm").find(":input[name='newsTitle']").val(data.newsTitle);
234                     $("#NewsInsertForm").find(":input[name='newsId']").val(data.newsId);
235                     NewsInsertLayerIndex = layer.open({
236                         title: "編輯",
237                         type: 1,
238                         content: $('#NewsInsertDiv')
239                     });
240                 }
241             });
242 
243             //分布式事務測試
244             $("#JTATest").click(function () {
245                 $.ajax({
246                     type: "POST",
247                     url: "${basePath!}/jtaTest/insertToTwoDatebase",
248                     data: {},
249                     async: false,
250                     error: function (request) {
251                         layer.alert("與服務器連接失敗/(ㄒoㄒ)/~~");
252                         return false;
253                     },
254                     success: function (data) {
255                         if (data.state == 'fail') {
256                             layer.alert(data.message);
257                             return false;
258                         } else if (data.state == 'success') {
259                             layer.alert(data.message);
260                         }
261                     }
262                 });
263             });
264 
265 
266         });
267     </script>
268 </@com.head>
269 <@com.body>
270 <#--第一個數據庫表-->
271     <fieldset class="layui-elem-field">
272         <legend>用戶登錄信息</legend>
273         <div class="layui-field-box">
274             <div class="layui-fluid">
275                 <div class="layui-row">
276                     <button class="layui-btn" id="UserLoginInsert">新建</button>
277                 </div>
278                 <div class="layui-row">
279                     <table id="UserLoginTable" lay-filter="UserLoginTable"></table>
280                 </div>
281             </div>
282         </div>
283     </fieldset>
284     <div id="UserLoginInsertDiv" style="display: none">
285         <form class="layui-form" action="" id="UserLoginInsertForm">
286             <input type="hidden" id="UserLoginInsertFormId" name="loginUid"/>
287             <div class="layui-form-item">
288                 <label class="layui-form-label">登錄名</label>
289                 <div class="layui-input-block">
290                     <input type="text" name="loginName" required lay-verify="required" placeholder="請輸登陸名稱"
291                            autocomplete="off" class="layui-input">
292                 </div>
293             </div>
294             <div class="layui-form-item">
295                 <div class="layui-input-block">
296                     <button class="layui-btn" lay-submit lay-filter="UserLoginInsertFormSubmit">立即提交</button>
297                     <button type="reset" class="layui-btn layui-btn-primary" id="UserLoginInsertFormReset">重置</button>
298                 </div>
299             </div>
300         </form>
301     </div>
302 <#--    第二個數據庫表-->
303     <fieldset class="layui-elem-field">
304         <legend>新聞信息</legend>
305         <div class="layui-field-box">
306             <div class="layui-fluid">
307                 <div class="layui-row">
308                     <button class="layui-btn" id="NewsInsert">新建</button>
309                 </div>
310                 <div class="layui-row">
311                     <table id="News" lay-filter="News"></table>
312                 </div>
313             </div>
314         </div>
315     </fieldset>
316     <div id="NewsInsertDiv" style="display: none">
317         <form class="layui-form" action="" id="NewsInsertForm">
318             <input type="hidden" id="NewsInsertFormId" name="newsId"/>
319             <div class="layui-form-item">
320                 <label class="layui-form-label">新聞標題</label>
321                 <div class="layui-input-block">
322                     <input type="text" name="newsTitle" required lay-verify="required" placeholder="請輸新聞標題"
323                            autocomplete="off" class="layui-input">
324                 </div>
325             </div>
326             <div class="layui-form-item">
327                 <div class="layui-input-block">
328                     <button class="layui-btn" lay-submit lay-filter="NewsInsertFormSubmit">立即提交</button>
329                     <button type="reset" class="layui-btn layui-btn-primary" id="NewsInsertFormReset">重置</button>
330                 </div>
331             </div>
332         </form>
333     </div>
334 
335 <#------------------------------------>
336     <fieldset class="layui-elem-field">
337         <legend>分布式事務測試</legend>
338         <div class="layui-field-box">
339             <div class="layui-fluid">
340                 <div class="layui-row">
341                     <button class="layui-btn" id="JTATest">同時向兩個表內插入信息為8888的數據</button>
342                 </div>
343             </div>
344         </div>
345     </fieldset>
346     <script type="text/html" id="barDemo">
347         <#--<a class="layui-btn layui-btn-xs" lay-event="detail">查看</a>-->
348         <a class="layui-btn layui-btn-xs" lay-event="edit">編輯</a>
349         <a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">刪除</a>
350     </script>
351     <!-- 序號監聽事件 -->
352     <script type="text/html" id="indexTpl">
353         {{d.LAY_TABLE_INDEX+1}}
354     </script>
355 </@com.body>

 

代碼至此結束,關於項目的其他文件,可以到文末處我的git上去看。這里就不再書寫了。


 

測試

1.測試分頁查詢

啟動項目,成功后輸入地址 http://localhost:9090/oa_online/toTest,返回頁面如下圖

可以看到后台打印出來了sql同時有warn提示。這個提示的意思就是事務還沒有運行,原因是我們還沒有執行過使用事務部分的代碼(同時向兩個表內插入信息為8888的數據)。

 1 2019-08-22 17:00:51.918  INFO 10740 --- [nio-9090-exec-1] o.a.c.c.C.[.[localhost].[/oa_online]     : Initializing Spring DispatcherServlet 'dispatcherServlet'
 2 2019-08-22 17:00:51.919  INFO 10740 --- [nio-9090-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
 3 2019-08-22 17:00:51.926  INFO 10740 --- [nio-9090-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 7 ms
 4 2019-08-22 17:00:52.653  WARN 10740 --- [nio-9090-exec-2] c.atomikos.jdbc.AtomikosConnectionProxy  : atomikos connection proxy for com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@1c486d48: WARNING: transaction manager not running?
 5 2019-08-22 17:00:52.653  WARN 10740 --- [nio-9090-exec-3] c.atomikos.jdbc.AtomikosConnectionProxy  : atomikos connection proxy for com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@73539df1: WARNING: transaction manager not running?
 6 2019-08-22 17:00:52.657 DEBUG 10740 --- [nio-9090-exec-3] c.z.o.m.NewsMapper.selectAll_COUNT       : ==>  Preparing: SELECT count(0) FROM news 
 7 2019-08-22 17:00:52.657  WARN 10740 --- [nio-9090-exec-3] c.atomikos.jdbc.AtomikosConnectionProxy  : atomikos connection proxy for com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@73539df1: WARNING: transaction manager not running?
 8 2019-08-22 17:00:52.657 DEBUG 10740 --- [nio-9090-exec-2] c.z.o.m.U.selectAll_COUNT                : ==>  Preparing: SELECT count(0) FROM user_login_table 
 9 2019-08-22 17:00:52.658  WARN 10740 --- [nio-9090-exec-3] c.atomikos.jdbc.AtomikosConnectionProxy  : atomikos connection proxy for com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@73539df1: WARNING: transaction manager not running?
10 2019-08-22 17:00:52.658  WARN 10740 --- [nio-9090-exec-2] c.atomikos.jdbc.AtomikosConnectionProxy  : atomikos connection proxy for com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@1c486d48: WARNING: transaction manager not running?
11 2019-08-22 17:00:52.658  WARN 10740 --- [nio-9090-exec-2] c.atomikos.jdbc.AtomikosConnectionProxy  : atomikos connection proxy for com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@1c486d48: WARNING: transaction manager not running?
12 2019-08-22 17:00:52.766 DEBUG 10740 --- [nio-9090-exec-2] c.z.o.m.U.selectAll_COUNT                : ==> Parameters: 
13 2019-08-22 17:00:52.766 DEBUG 10740 --- [nio-9090-exec-3] c.z.o.m.NewsMapper.selectAll_COUNT       : ==> Parameters: 
14 2019-08-22 17:00:52.783 DEBUG 10740 --- [nio-9090-exec-3] c.z.o.m.NewsMapper.selectAll_COUNT       : <==      Total: 1
15 2019-08-22 17:00:52.783 DEBUG 10740 --- [nio-9090-exec-2] c.z.o.m.U.selectAll_COUNT                : <==      Total: 1
16 2019-08-22 17:00:52.788 DEBUG 10740 --- [nio-9090-exec-2] c.z.o.m.UserLoginTableMapper.selectAll   : ==>  Preparing: SELECT login_uid,login_name,login_password,remarks,status,update_date,create_date FROM user_login_table LIMIT ? 
17 2019-08-22 17:00:52.788  WARN 10740 --- [nio-9090-exec-2] c.atomikos.jdbc.AtomikosConnectionProxy  : atomikos connection proxy for com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@1c486d48: WARNING: transaction manager not running?
18 2019-08-22 17:00:52.788  WARN 10740 --- [nio-9090-exec-2] c.atomikos.jdbc.AtomikosConnectionProxy  : atomikos connection proxy for com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@1c486d48: WARNING: transaction manager not running?
19 2019-08-22 17:00:52.789 DEBUG 10740 --- [nio-9090-exec-3] c.z.o.mapper2.NewsMapper.selectAll       : ==>  Preparing: SELECT news_id,news_type,news_title,news_introduction,news_cover,news_content,news_date,news_read_times,news_create,news_update,news_show_status,news_status,news_spare_varchar,news_spare_date,news_spare_int FROM news LIMIT ? 
20 2019-08-22 17:00:52.790  WARN 10740 --- [nio-9090-exec-3] c.atomikos.jdbc.AtomikosConnectionProxy  : atomikos connection proxy for com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@73539df1: WARNING: transaction manager not running?
21 2019-08-22 17:00:52.790  WARN 10740 --- [nio-9090-exec-3] c.atomikos.jdbc.AtomikosConnectionProxy  : atomikos connection proxy for com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@73539df1: WARNING: transaction manager not running?
22 2019-08-22 17:00:52.792 DEBUG 10740 --- [nio-9090-exec-2] c.z.o.m.UserLoginTableMapper.selectAll   : ==> Parameters: 10(Integer)
23 2019-08-22 17:00:52.793 DEBUG 10740 --- [nio-9090-exec-3] c.z.o.mapper2.NewsMapper.selectAll       : ==> Parameters: 10(Integer)
24 2019-08-22 17:00:52.800 DEBUG 10740 --- [nio-9090-exec-2] c.z.o.m.UserLoginTableMapper.selectAll   : <==      Total: 10
25 2019-08-22 17:00:52.801  WARN 10740 --- [nio-9090-exec-2] c.atomikos.jdbc.AtomikosConnectionProxy  : atomikos connection proxy for com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@1c486d48: WARNING: transaction manager not running?
26 2019-08-22 17:00:52.804 DEBUG 10740 --- [nio-9090-exec-3] c.z.o.mapper2.NewsMapper.selectAll       : <==      Total: 10
27 2019-08-22 17:00:52.804  WARN 10740 --- [nio-9090-exec-3] c.atomikos.jdbc.AtomikosConnectionProxy  : atomikos connection proxy for com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@73539df1: WARNING: transaction manager not running?

當我們執行事務測試。點擊(同時向兩個表內插入信息為8888的數據)按鈕時,打斷點之后單步走可以看到執行了事務的回滾。到數據庫內查看也沒有新增對應的數據。

 

 執行后日志如下:

可以看出執行這段代碼時是 現注冊的事務,所以之前才會有事務並未運行的警告。

 1 2019-08-22 17:04:29.050  INFO 10740 --- [nio-9090-exec-7] c.a.icatch.provider.imp.AssemblerImp     : Loaded jar:file:/D:/codesoft/maven/repository/com/atomikos/transactions/4.0.6/transactions-4.0.6.jar!/transactions-defaults.properties
 2 2019-08-22 17:04:29.053  WARN 10740 --- [nio-9090-exec-7] c.a.icatch.provider.imp.AssemblerImp     : Thanks for using Atomikos! Evaluate http://www.atomikos.com/Main/ExtremeTransactions for advanced features and professional support
 3 or register at http://www.atomikos.com/Main/RegisterYourDownload to disable this message and receive FREE tips & advice
 4 Thanks for using Atomikos! Evaluate http://www.atomikos.com/Main/ExtremeTransactions for advanced features and professional support
 5 or register at http://www.atomikos.com/Main/RegisterYourDownload to disable this message and receive FREE tips & advice
 6 2019-08-22 17:04:29.062  INFO 10740 --- [nio-9090-exec-7] c.a.icatch.provider.imp.AssemblerImp     : USING: com.atomikos.icatch.default_max_wait_time_on_shutdown = 9223372036854775807
 7 2019-08-22 17:04:29.062  INFO 10740 --- [nio-9090-exec-7] c.a.icatch.provider.imp.AssemblerImp     : USING: com.atomikos.icatch.allow_subtransactions = true
 8 2019-08-22 17:04:29.062  INFO 10740 --- [nio-9090-exec-7] c.a.icatch.provider.imp.AssemblerImp     : USING: com.atomikos.icatch.recovery_delay = 10000
 9 2019-08-22 17:04:29.063  INFO 10740 --- [nio-9090-exec-7] c.a.icatch.provider.imp.AssemblerImp     : USING: com.atomikos.icatch.automatic_resource_registration = true
10 2019-08-22 17:04:29.063  INFO 10740 --- [nio-9090-exec-7] c.a.icatch.provider.imp.AssemblerImp     : USING: com.atomikos.icatch.oltp_max_retries = 5
11 2019-08-22 17:04:29.063  INFO 10740 --- [nio-9090-exec-7] c.a.icatch.provider.imp.AssemblerImp     : USING: com.atomikos.icatch.client_demarcation = false
12 2019-08-22 17:04:29.063  INFO 10740 --- [nio-9090-exec-7] c.a.icatch.provider.imp.AssemblerImp     : USING: com.atomikos.icatch.threaded_2pc = false
13 2019-08-22 17:04:29.063  INFO 10740 --- [nio-9090-exec-7] c.a.icatch.provider.imp.AssemblerImp     : USING: com.atomikos.icatch.serial_jta_transactions = true
14 2019-08-22 17:04:29.063  INFO 10740 --- [nio-9090-exec-7] c.a.icatch.provider.imp.AssemblerImp     : USING: com.atomikos.icatch.log_base_dir = ./
15 2019-08-22 17:04:29.063  INFO 10740 --- [nio-9090-exec-7] c.a.icatch.provider.imp.AssemblerImp     : USING: com.atomikos.icatch.rmi_export_class = none
16 2019-08-22 17:04:29.063  INFO 10740 --- [nio-9090-exec-7] c.a.icatch.provider.imp.AssemblerImp     : USING: com.atomikos.icatch.max_actives = 50
17 2019-08-22 17:04:29.063  INFO 10740 --- [nio-9090-exec-7] c.a.icatch.provider.imp.AssemblerImp     : USING: com.atomikos.icatch.checkpoint_interval = 500
18 2019-08-22 17:04:29.063  INFO 10740 --- [nio-9090-exec-7] c.a.icatch.provider.imp.AssemblerImp     : USING: com.atomikos.icatch.enable_logging = true
19 2019-08-22 17:04:29.063  INFO 10740 --- [nio-9090-exec-7] c.a.icatch.provider.imp.AssemblerImp     : USING: com.atomikos.icatch.log_base_name = tmlog
20 2019-08-22 17:04:29.063  INFO 10740 --- [nio-9090-exec-7] c.a.icatch.provider.imp.AssemblerImp     : USING: com.atomikos.icatch.max_timeout = 300000
21 2019-08-22 17:04:29.063  INFO 10740 --- [nio-9090-exec-7] c.a.icatch.provider.imp.AssemblerImp     : USING: com.atomikos.icatch.trust_client_tm = false
22 2019-08-22 17:04:29.063  INFO 10740 --- [nio-9090-exec-7] c.a.icatch.provider.imp.AssemblerImp     : USING: java.naming.factory.initial = com.sun.jndi.rmi.registry.RegistryContextFactory
23 2019-08-22 17:04:29.063  INFO 10740 --- [nio-9090-exec-7] c.a.icatch.provider.imp.AssemblerImp     : USING: com.atomikos.icatch.tm_unique_name = 192.168.128.132.tm
24 2019-08-22 17:04:29.063  INFO 10740 --- [nio-9090-exec-7] c.a.icatch.provider.imp.AssemblerImp     : USING: com.atomikos.icatch.forget_orphaned_log_entries_delay = 86400000
25 2019-08-22 17:04:29.063  INFO 10740 --- [nio-9090-exec-7] c.a.icatch.provider.imp.AssemblerImp     : USING: com.atomikos.icatch.oltp_retry_interval = 10000
26 2019-08-22 17:04:29.063  INFO 10740 --- [nio-9090-exec-7] c.a.icatch.provider.imp.AssemblerImp     : USING: java.naming.provider.url = rmi://localhost:1099
27 2019-08-22 17:04:29.063  INFO 10740 --- [nio-9090-exec-7] c.a.icatch.provider.imp.AssemblerImp     : USING: com.atomikos.icatch.force_shutdown_on_vm_exit = false
28 2019-08-22 17:04:29.063  INFO 10740 --- [nio-9090-exec-7] c.a.icatch.provider.imp.AssemblerImp     : USING: com.atomikos.icatch.default_jta_timeout = 10000
29 2019-08-22 17:04:29.064  INFO 10740 --- [nio-9090-exec-7] c.a.icatch.provider.imp.AssemblerImp     : Using default (local) logging and recovery...
30 2019-08-22 17:04:29.221  INFO 10740 --- [nio-9090-exec-7] c.a.d.xa.XATransactionalResource         : businessDB: refreshed XAResource
31 2019-08-22 17:04:29.252  INFO 10740 --- [nio-9090-exec-7] c.a.d.xa.XATransactionalResource         : systemDB: refreshed XAResource
32 2019-08-22 17:04:29.342 DEBUG 10740 --- [nio-9090-exec-7] c.z.o.m.U.insertSelective                : ==>  Preparing: INSERT INTO user_login_table ( login_uid,login_name ) VALUES( ?,? ) 
33 2019-08-22 17:04:29.363 DEBUG 10740 --- [nio-9090-exec-7] c.z.o.m.U.insertSelective                : ==> Parameters: ef575e338ca14f42a1966348ade0df81(String), 8888(String)
34 2019-08-22 17:04:29.366 DEBUG 10740 --- [nio-9090-exec-7] c.z.o.m.U.insertSelective                : <==    Updates: 1
35 2019-08-22 17:04:29.372 DEBUG 10740 --- [nio-9090-exec-7] c.z.o.m.NewsMapper.insertSelective       : ==>  Preparing: INSERT INTO news ( news_id,news_title ) VALUES( ?,? ) 
36 2019-08-22 17:04:29.376 DEBUG 10740 --- [nio-9090-exec-7] c.z.o.m.NewsMapper.insertSelective       : ==> Parameters: 1ae4327ec2104da0aec8b8897baa9069(String), 8888(String)
37 2019-08-22 17:04:29.382 DEBUG 10740 --- [nio-9090-exec-7] c.z.o.m.NewsMapper.insertSelective       : <==    Updates: 1
38 java.lang.ArithmeticException: / by zero
39     at com.zcph.oaonline.serviceAll.impl.JtaTestServiceImpl.insertToTwoDatebaseService(JtaTestServiceImpl.java:39)
40     at com.zcph.oaonline.serviceAll.impl.JtaTestServiceImpl$$FastClassBySpringCGLIB$$d75a57c1.invoke(<generated>)
41     at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
42     at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:749)
43     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
44     at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:295)
45     at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
46     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
47     at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
48     at com.zcph.oaonline.serviceAll.impl.JtaTestServiceImpl$$EnhancerBySpringCGLIB$$38d77f20.insertToTwoDatebaseService(<generated>)
49     at com.zcph.oaonline.web.JtaTestController.insertToTwoDatebase(JtaTestController.java:128)
50     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
51     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
52     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
53     at java.lang.reflect.Method.invoke(Method.java:498)
54     at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
55     at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
56     at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
57     at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:892)
58     at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)
59     at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
60     at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1039)
61     at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
62     at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
63     at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:908)
64     at javax.servlet.http.HttpServlet.service(HttpServlet.java:660)
65     at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
66     at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
67     at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
68     at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
69     at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
70     at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
71     at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
72     at com.alibaba.druid.support.http.WebStatFilter.doFilter(WebStatFilter.java:123)
73     at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
74     at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
75     at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
76     at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118)
77     at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
78     at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
79     at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
80     at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
81     at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)
82     at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
83     at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
84     at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
85     at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
86     at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
87     at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
88     at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:853)
89     at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1587)
90     at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
91     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
92     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
93     at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
94     at java.lang.Thread.run(Thread.java:748)

不信我們可以再次刷新下頁面,日志展示如下,只有查詢語句了,沒有了事務沒有運行的警告了。

 1 2019-08-22 17:12:41.718 DEBUG 4428 --- [io-9090-exec-10] c.z.o.m.U.selectAll_COUNT                : ==>  Preparing: SELECT count(0) FROM user_login_table 
 2 2019-08-22 17:12:41.718 DEBUG 4428 --- [io-9090-exec-10] c.z.o.m.U.selectAll_COUNT                : ==> Parameters: 
 3 2019-08-22 17:12:41.719 DEBUG 4428 --- [io-9090-exec-10] c.z.o.m.U.selectAll_COUNT                : <==      Total: 1
 4 2019-08-22 17:12:41.719 DEBUG 4428 --- [io-9090-exec-10] c.z.o.m.UserLoginTableMapper.selectAll   : ==>  Preparing: SELECT login_uid,login_name,login_password,remarks,status,update_date,create_date FROM user_login_table LIMIT ? 
 5 2019-08-22 17:12:41.720 DEBUG 4428 --- [io-9090-exec-10] c.z.o.m.UserLoginTableMapper.selectAll   : ==> Parameters: 10(Integer)
 6 2019-08-22 17:12:41.726 DEBUG 4428 --- [io-9090-exec-10] c.z.o.m.UserLoginTableMapper.selectAll   : <==      Total: 10
 7 2019-08-22 17:12:41.747 DEBUG 4428 --- [nio-9090-exec-3] c.z.o.m.NewsMapper.selectAll_COUNT       : ==>  Preparing: SELECT count(0) FROM news 
 8 2019-08-22 17:12:41.747 DEBUG 4428 --- [nio-9090-exec-3] c.z.o.m.NewsMapper.selectAll_COUNT       : ==> Parameters: 
 9 2019-08-22 17:12:41.749 DEBUG 4428 --- [nio-9090-exec-3] c.z.o.m.NewsMapper.selectAll_COUNT       : <==      Total: 1
10 2019-08-22 17:12:41.750 DEBUG 4428 --- [nio-9090-exec-3] c.z.o.mapper2.NewsMapper.selectAll       : ==>  Preparing: SELECT news_id,news_type,news_title,news_introduction,news_cover,news_content,news_date,news_read_times,news_create,news_update,news_show_status,news_status,news_spare_varchar,news_spare_date,news_spare_int FROM news LIMIT ? 
11 2019-08-22 17:12:41.750 DEBUG 4428 --- [nio-9090-exec-3] c.z.o.mapper2.NewsMapper.selectAll       : ==> Parameters: 10(Integer)
12 2019-08-22 17:12:41.758 DEBUG 4428 --- [nio-9090-exec-3] c.z.o.mapper2.NewsMapper.selectAll       : <==      Total: 10

至於其他的測試,我們這里就不再贅述了。


 

疑問

1、我們出現了

atomikos connection proxy for com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@1c486d48: WARNING: transaction manager not running?

這個警告,怎么才能不出現呢。嘗試了一些方法終究沒能去掉。

2、在執行后前往druid監控查看時,發現並未記錄回滾和提交的次數。這個着實不知道為何。

希望有大佬看后能給予解答,有問題也可以留言問我。我盡量解答。

代碼地址:https://github.com/keepme19910311/SpringBootMultipleDatasource

下載后idea可直接打開。需要修改下數據源,請自行修改。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM