前言
因为最近公司项目升级,需要将外网数据库的信息导入到内网数据库内。于是找了一些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 <!-- <!–加载配置文件,为下面读取数据库信息准备–>--> 9 <!-- <properties resource="application.properties"/>--> 10 11 <!-- <context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">--> 12 13 <!-- <plugin type="tk.mybatis.mapper.generator.MapperPlugin">--> 14 <!-- <!–其中tk.mybatis.mapper.generator.MapperPlugin很重要,用来指定通用Mapper对应的文件,这样我们生成的mapper都会继承这个通用Mapper–>--> 15 <!-- <property name="mappers" value="com.zcph.oaonline.util.MyMapper" />--> 16 <!-- <!–caseSensitive默认false,当数据库表名区分大小写时,可以将该属性设置为true–>--> 17 <!-- <property name="caseSensitive" value="false"/>--> 18 <!-- </plugin>--> 19 20 21 <!-- <!–数据库链接地址账号密码,这里由于我使用的是根据开发和生产分离的配置文件,所以这里直接写上了–>--> 22 <!-- <jdbcConnection driverClass="com.mysql.jdbc.Driver"--> 23 <!-- connectionURL="jdbc:mysql://192.168.128.244:3306/online_oa?useUnicode=true&characterEncoding=utf-8&useSSL=false"--> 24 <!-- userId="root"--> 25 <!-- password="zcoa">--> 26 <!-- </jdbcConnection>--> 27 28 <!-- <javaTypeResolver>--> 29 <!-- <property name="forceBigDecimals" value="false"/>--> 30 <!-- </javaTypeResolver>--> 31 32 <!-- <!–生成Model类存放位置–>--> 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 <!-- <!–生成映射文件存放位置–>--> 39 <!-- <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">--> 40 <!-- <property name="enableSubPackages" value="true"/>--> 41 <!-- </sqlMapGenerator>--> 42 43 <!-- <!–生成Dao类存放位置–>--> 44 <!-- <!– 客户端代码,生成易于使用的针对Model对象和XML配置文件 的代码--> 45 <!-- type="ANNOTATEDMAPPER",生成Java Model 和基于注解的Mapper对象--> 46 <!-- type="XMLMAPPER",生成SQLMap XML文件和独立的Mapper接口--> 47 <!-- –>--> 48 <!-- <javaClientGenerator type="XMLMAPPER" targetPackage="com.zcph.oaonline.mapper" targetProject="src/main/java">--> 49 <!-- <property name="enableSubPackages" value="true"/>--> 50 <!-- </javaClientGenerator>--> 51 52 <!-- <!–生成对应表及类名--> 53 <!-- 去掉Mybatis Generator生成的一堆 example--> 54 <!-- –>--> 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&characterEncoding=utf-8&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可直接打开。需要修改下数据源,请自行修改。