前言

因为最近公司项目升级,需要将外网数据库的信息导入到内网数据库内。于是找了一些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的配置

 <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.zcph</groupId>
<artifactId>oaonline</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>oaonline</name>
<description>zcph online oa</description> <properties>
<java.version>1.8</java.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 修改下版本号,因为我用的mysql版本是5.7,而且5.1.34在多数据源时候会报错-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.36</version>
<scope>runtime</scope>
</dependency>
<!--druid连接池依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
<!--aspectj组件-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<!--通用mapper-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.1.5</version>
</dependency>
<!--pagehelper 分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.12</version>
</dependency>
<!--分布式事务-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!--fork : 如果没有该项配置,这个devtools不会起作用,即应用不会restart -->
<fork>true</fork>
<!--支持静态文件热部署-->
<addResources>true</addResources>
</configuration>
</plugin>
<!--如果要发布生成版本这部分代码要禁止掉,本地开发开启,用于自动生成mybatis的相应类和xml-->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.5</version>
<dependencies>
<!--配置这个依赖主要是为了等下在配置mybatis-generator.xml的时候可以不用配置classPathEntry这样的一个属性,避免代码的耦合度太高-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.36</version>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>3.4.0</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>Generate MyBatis Artifacts</id>
<phase>package</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<!--允许移动生成的文件 -->
<verbose>true</verbose>
<!-- 是否覆盖 -->
<overwrite>true</overwrite>
<!-- 自动生成的配置 -->
<configurationFile>src/main/resources/mybatis-generator.xml</configurationFile>
</configuration>
</plugin> <plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build> </project>

项目整体结构

application.properties和application.yml

代码如下,在application.properties中并没有数据库连接的相关配置。而是放到了application.yml中。

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

在application.yml中配置了数据源的信息

 #mybatis
mybatis.type-aliases-package=com.zcph.oaonline.entity
mybatis.mapper-locations=classpath:mapper/*.xml
# mybatis日志打印sql
logging.level.com.zcph.oaonline.mapper=debug
logging.level.com.zcph.oaonline.mapper2=debug
spring.servlet.multipart.max-file-size=100MB
spring.servlet.multipart.max-request-size=100MB
#mappers 多个接口时逗号隔开
mapper.mappers=com.zcph.oaonline.util.MyMapper
mapper.not-empty=false
mapper.identity=MYSQL #pagehelper
pagehelper.helperDialect=mysql
pagehelper.reasonable=true
pagehelper.supportMethodsArguments=true
pagehelper.params=count=countSql ########################################################
###FREEMARKER (FreeMarkerAutoConfiguration)
########################################################
spring.freemarker.allow-request-override=false
#本机调试时,配置项template_update_delay=0,这样就关闭了模板缓存。注意线上环境要开启缓存
spring.freemarker.cache=false
spring.freemarker.settings.template_update_delay=0
spring.freemarker.check-template-location=true
spring.freemarker.charset=UTF-8
spring.freemarker.content-type=text/html
spring.freemarker.expose-request-attributes=false
spring.freemarker.expose-session-attributes=false
spring.freemarker.expose-spring-macro-helpers=false
spring.freemarker.prefix=
#若在freemarker获取request对象,在spring boot 在application.properties可以这么配置
spring.freemarker.request-context-attribute=request
#spring.freemarker.settings.*=
spring.freemarker.suffix=.ftl
#template-loader-path表示所有的模板文件都放在该目录下
spring.freemarker.template-loader-path=classpath:/templates/
#spring.freemarker.view-names= #whitelistofviewnamesthatcanberesolved #static-locations可以自定义静态资源路径,不过会覆盖springboot默认路径
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/
#在这个最末尾的file:${web.upload-path}之所有要加file:是因为指定的是一个具体的硬盘路径,其他的使用classpath指的是系统环境变量
#spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,file:${web.upload-path} spring.freemarker.settings.auto_import=ftl/spring.ftl as com
spring.freemarker.settings.datetime_format=yyyy-MM-dd
#兼容传统模式
spring.freemarker.settings.classic_compatible=true
#表示访问该路径时代表请求静态资源,用户可以直接访问该请求路径中的静态资源
spring.mvc.static-path-pattern=/static/**
server.port=9090
server.servlet.context-path=/oa_online
server.servlet.session.timeout=10000
#开发模式端口,因为我同时测试多个项目,端口冲突了
spring.devtools.livereload.port=35730
 spring:
datasource:
type: com.alibaba.druid.pool.xa.DruidXADataSource
druid: systemDB:
name: systemDB
url: jdbc:mysql://192.168.128.244:3306/online_oa?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: zcoa
# 下面为连接池的补充设置,应用到上面所有数据源中
# 初始化大小,最小,最大
initialSize: 5
minIdle: 5
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1
validationQueryTimeout: 10000
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
filters: stat,wall
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合并多个DruidDataSource的监控数据
useGlobalDataSourceStat: true businessDB:
name: businessDB
url: jdbc:mysql://192.168.128.61:3306/bgc?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
username: root
password: root
# 下面为连接池的补充设置,应用到上面所有数据源中
# 初始化大小,最小,最大
initialSize: 5
minIdle: 5
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1
validationQueryTimeout: 10000
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
filters: stat,wall
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合并多个DruidDataSource的监控数据
useGlobalDataSourceStat: true #jta相关参数配置
jta:
log-dir: classpath:tx-logs
transaction-manager-id: txManager

 数据源配置及druid配置

DruidConfig.java

实现数据源注册,druid注册和分布式事务管理器的注册

 package com.zcph.oaonline.config;

 import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import com.alibaba.druid.wall.WallConfig;
import com.alibaba.druid.wall.WallFilter;
import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.transaction.jta.JtaTransactionManager; import javax.sql.DataSource;
import javax.transaction.UserTransaction;
import java.util.Properties; /**
* 多数据源和Druid配置
*/
@Configuration
public class DruidConfig { /**
* 数据源1配置
* @param env
* @return
*/
@Bean(name = "systemDataSource")
@Primary
@Autowired
public DataSource systemDataSource(Environment env) {
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
Properties prop = build(env, "spring.datasource.druid.systemDB.");
ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
ds.setUniqueResourceName("systemDB");
ds.setPoolSize(5);
ds.setXaProperties(prop);
return ds;
} /**
* 数据源2配置
* @param env
* @return
*/
@Autowired
@Bean(name = "businessDataSource")
public AtomikosDataSourceBean businessDataSource(Environment env) {
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
Properties prop = build(env, "spring.datasource.druid.businessDB.");
ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
ds.setUniqueResourceName("businessDB");
ds.setPoolSize(5);
ds.setXaProperties(prop);
return ds;
} /**
* 注入事物管理器
* @return
*/
@Bean(name = "xatx")
public JtaTransactionManager regTransactionManager () {
UserTransactionManager userTransactionManager = new UserTransactionManager();
UserTransaction userTransaction = new UserTransactionImp();
return new JtaTransactionManager(userTransaction, userTransactionManager);
} /**
* 从配置文件中加载数据源信息
* @param env
* @param prefix
* @return
*/
private Properties build(Environment env, String prefix) {
Properties prop = new Properties();
prop.put("url", env.getProperty(prefix + "url"));
prop.put("username", env.getProperty(prefix + "username"));
prop.put("password", env.getProperty(prefix + "password"));
prop.put("driverClassName", env.getProperty(prefix + "driverClassName", ""));
prop.put("initialSize", env.getProperty(prefix + "initialSize", Integer.class));
prop.put("maxActive", env.getProperty(prefix + "maxActive", Integer.class));
prop.put("minIdle", env.getProperty(prefix + "minIdle", Integer.class));
prop.put("maxWait", env.getProperty(prefix + "maxWait", Integer.class));
prop.put("poolPreparedStatements", env.getProperty(prefix + "poolPreparedStatements", Boolean.class));
prop.put("maxPoolPreparedStatementPerConnectionSize",
env.getProperty(prefix + "maxPoolPreparedStatementPerConnectionSize", Integer.class));
prop.put("maxPoolPreparedStatementPerConnectionSize",
env.getProperty(prefix + "maxPoolPreparedStatementPerConnectionSize", Integer.class));
prop.put("validationQuery", env.getProperty(prefix + "validationQuery"));
prop.put("validationQueryTimeout", env.getProperty(prefix + "validationQueryTimeout", Integer.class));
prop.put("testOnBorrow", env.getProperty(prefix + "testOnBorrow", Boolean.class));
prop.put("testOnReturn", env.getProperty(prefix + "testOnReturn", Boolean.class));
prop.put("testWhileIdle", env.getProperty(prefix + "testWhileIdle", Boolean.class));
prop.put("timeBetweenEvictionRunsMillis",
env.getProperty(prefix + "timeBetweenEvictionRunsMillis", Integer.class));
prop.put("minEvictableIdleTimeMillis", env.getProperty(prefix + "minEvictableIdleTimeMillis", Integer.class));
prop.put("filters", env.getProperty(prefix + "filters"));
return prop;
} /**
* druid访问配置
* @return
*/
@Bean
public ServletRegistrationBean druidServlet() {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
//控制台管理用户,加入下面2行 进入druid后台就需要登录
servletRegistrationBean.addInitParameter("loginUsername", "admin");
servletRegistrationBean.addInitParameter("loginPassword", "admin");
return servletRegistrationBean;
} @Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new WebStatFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
filterRegistrationBean.addInitParameter("profileEnable", "true");
return filterRegistrationBean;
} @Bean
public StatFilter statFilter(){
StatFilter statFilter = new StatFilter();
statFilter.setLogSlowSql(true); //slowSqlMillis用来配置SQL慢的标准,执行时间超过slowSqlMillis的就是慢。
statFilter.setMergeSql(true); //SQL合并配置
statFilter.setSlowSqlMillis(1000);//slowSqlMillis的缺省值为3000,也就是3秒。
return statFilter;
} @Bean
public WallFilter wallFilter(){
WallFilter wallFilter = new WallFilter();
//允许执行多条SQL
WallConfig config = new WallConfig();
config.setMultiStatementAllow(true);
wallFilter.setConfig(config);
return wallFilter;
}
}
MybatisDatasourceConfig.java
数据源1的sqlSessionFactory和MapperScan扫描包的配置。
 package com.zcph.oaonline.config;

 import com.zcph.oaonline.util.MyMapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import tk.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver; import javax.sql.DataSource; /**
* @description
*/
@Configuration
// 精确到 mapper 目录,以便跟其他数据源隔离
@MapperScan(basePackages = "com.zcph.oaonline.mapper", markerInterface = MyMapper.class, sqlSessionFactoryRef = "sqlSessionFactory")
public class MybatisDatasourceConfig { @Autowired
@Qualifier("systemDataSource")
private DataSource ds; @Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(ds);
//指定mapper xml目录
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
factoryBean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
return factoryBean.getObject(); } @Bean
public SqlSessionTemplate sqlSessionTemplate() throws Exception {
SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory()); // 使用上面配置的Factory
return template;
} }
MybatisDatasource2Config.java
数据源2的sqlSessionFactory和MapperScan扫描包的配置。
 package com.zcph.oaonline.config;

 import com.zcph.oaonline.util.MyMapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import tk.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver; import javax.sql.DataSource; /**
* @description
*/
@Configuration
// 精确到 mapper 目录,以便跟其他数据源隔离
@MapperScan(basePackages = "com.zcph.oaonline.mapper2", markerInterface = MyMapper.class, sqlSessionFactoryRef = "sqlSessionFactory2")
public class MybatisDatasource2Config { @Autowired
@Qualifier("businessDataSource")
private DataSource ds; @Bean
public SqlSessionFactory sqlSessionFactory2() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(ds);
//指定mapper xml目录
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
factoryBean.setMapperLocations(resolver.getResources("classpath:mapper2/*.xml"));
return factoryBean.getObject(); } @Bean
public SqlSessionTemplate sqlSessionTemplate2() throws Exception {
SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory2()); // 使用上面配置的Factory
return template;
}
}

扫描的包不同以及使用的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 自定义拦截器,代码如下:

 package com.zcph.oaonline.interceptor;

 import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; public class CommonInterceptor implements HandlerInterceptor { /**
* 自定义拦截器
* 这里返回了访问路径,方便前台使用
* @param httpServletRequest
* @param httpServletResponse
* @param o
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
String path = httpServletRequest.getContextPath();
String scheme = httpServletRequest.getScheme();
String serverName = httpServletRequest.getServerName();
int port = httpServletRequest.getServerPort();
String basePath = scheme + "://" + serverName + ":" + port + path;
httpServletRequest.setAttribute("basePath", basePath);
return true;
} @Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { } @Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { }
}
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接口

 package com.zcph.oaonline.util;

 import tk.mybatis.mapper.common.Mapper;
import tk.mybatis.mapper.common.MySqlMapper;
/**
* 通用mapper
* 继承自己的MyMapper
*/
public interface MyMapper<T> extends Mapper<T>, MySqlMapper<T> {
//FIXME 特别注意,该接口不能被扫描到,否则会出错
//FIXME 最后在启动类中通过MapperScan注解指定扫描的mapper路径:
}

在resources下的mybatis-generator.xml,这个是两个数据源的都有,只能1个1个的生成,要生成哪个注释另一个就行了。

 <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <!--数据源1-->
<!--<generatorConfiguration>-->
<!-- &lt;!&ndash;加载配置文件,为下面读取数据库信息准备&ndash;&gt;-->
<!-- <properties resource="application.properties"/>--> <!-- <context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">--> <!-- <plugin type="tk.mybatis.mapper.generator.MapperPlugin">-->
<!-- &lt;!&ndash;其中tk.mybatis.mapper.generator.MapperPlugin很重要,用来指定通用Mapper对应的文件,这样我们生成的mapper都会继承这个通用Mapper&ndash;&gt;-->
<!-- <property name="mappers" value="com.zcph.oaonline.util.MyMapper" />-->
<!-- &lt;!&ndash;caseSensitive默认false,当数据库表名区分大小写时,可以将该属性设置为true&ndash;&gt;-->
<!-- <property name="caseSensitive" value="false"/>-->
<!-- </plugin>--> <!-- &lt;!&ndash;数据库链接地址账号密码,这里由于我使用的是根据开发和生产分离的配置文件,所以这里直接写上了&ndash;&gt;-->
<!-- <jdbcConnection driverClass="com.mysql.jdbc.Driver"-->
<!-- connectionURL="jdbc:mysql://192.168.128.244:3306/online_oa?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false"-->
<!-- userId="root"-->
<!-- password="zcoa">-->
<!-- </jdbcConnection>--> <!-- <javaTypeResolver>-->
<!-- <property name="forceBigDecimals" value="false"/>-->
<!-- </javaTypeResolver>--> <!-- &lt;!&ndash;生成Model类存放位置&ndash;&gt;-->
<!-- <javaModelGenerator targetPackage="com.zcph.oaonline.entity" targetProject="src/main/java">-->
<!-- <property name="enableSubPackages" value="true"/>-->
<!-- <property name="trimStrings" value="true"/>-->
<!-- </javaModelGenerator>--> <!-- &lt;!&ndash;生成映射文件存放位置&ndash;&gt;-->
<!-- <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">-->
<!-- <property name="enableSubPackages" value="true"/>-->
<!-- </sqlMapGenerator>--> <!-- &lt;!&ndash;生成Dao类存放位置&ndash;&gt;-->
<!-- &lt;!&ndash; 客户端代码,生成易于使用的针对Model对象和XML配置文件 的代码-->
<!-- type="ANNOTATEDMAPPER",生成Java Model 和基于注解的Mapper对象-->
<!-- type="XMLMAPPER",生成SQLMap XML文件和独立的Mapper接口-->
<!-- &ndash;&gt;-->
<!-- <javaClientGenerator type="XMLMAPPER" targetPackage="com.zcph.oaonline.mapper" targetProject="src/main/java">-->
<!-- <property name="enableSubPackages" value="true"/>-->
<!-- </javaClientGenerator>--> <!-- &lt;!&ndash;生成对应表及类名-->
<!-- 去掉Mybatis Generator生成的一堆 example-->
<!-- &ndash;&gt;-->
<!-- <table tableName="user_login_table" domainObjectName="UserLoginTable">-->
<!-- </table>-->
<!-- </context>-->
<!--</generatorConfiguration>-->
<!--数据源2-->
<generatorConfiguration>
<!--加载配置文件,为下面读取数据库信息准备-->
<properties resource="application.properties"/> <context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat"> <plugin type="tk.mybatis.mapper.generator.MapperPlugin">
<!--其中tk.mybatis.mapper.generator.MapperPlugin很重要,用来指定通用Mapper对应的文件,这样我们生成的mapper都会继承这个通用Mapper-->
<property name="mappers" value="com.zcph.oaonline.util.MyMapper" />
<!--caseSensitive默认false,当数据库表名区分大小写时,可以将该属性设置为true-->
<property name="caseSensitive" value="false"/>
</plugin> <!--数据库链接地址账号密码,这里由于我使用的是根据开发和生产分离的配置文件,所以这里直接写上了-->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://192.168.128.61:3306/bgc?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false"
userId="root"
password="root">
</jdbcConnection> <javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver> <!--生成Model类存放位置-->
<javaModelGenerator targetPackage="com.zcph.oaonline.entity" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
</javaModelGenerator> <!--生成映射文件存放位置-->
<sqlMapGenerator targetPackage="mapper2" targetProject="src/main/resources">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator> <!--生成Dao类存放位置-->
<!-- 客户端代码,生成易于使用的针对Model对象和XML配置文件 的代码
type="ANNOTATEDMAPPER",生成Java Model 和基于注解的Mapper对象
type="XMLMAPPER",生成SQLMap XML文件和独立的Mapper接口
-->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.zcph.oaonline.mapper2" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator> <!--生成对应表及类名
去掉Mybatis Generator生成的一堆 example
-->
<table tableName="news" domainObjectName="News">
</table>
</context>
</generatorConfiguration>

因为我们已经在pom.xml中增加了mybatis-generator的plugin,利用idea的maven命令执行就可以生成对应的文件了。

执行后多出如下文件

因为我这个项目是后续要继续使用的,所以数据库内字段比较多,大家要是做测试的话可以把数据库内多余的字段删一删。

pagehelper的重写

因为我们使用了使用了layui,layui动态表格里需要一个code字段来确认数据。于是我们重写下PageInfo类。如下PageResult.java

 package com.zcph.oaonline.model;

 import com.github.pagehelper.PageInfo;

 import java.util.List;

 public class PageRusult<T> extends PageInfo<T> {
public PageRusult() {
} public PageRusult(List<T> list) {
super(list, 8);
} private Integer code;//layui框架列表模块返回参数中必须包含code状态字段 public Integer getCode() {
return code;
} public void setCode(Integer code) {
this.code = code;
} }

通用service

我们使用了通用mapper,于是我们可以新建一个通用service来简化我们的代码量,只需要其他service继承这个通用sevice即可,这样就不用再其他service里去写基础操作了

Iservice.java

 package com.zcph.oaonline.serviceAll;

 import org.springframework.stereotype.Service;

 import java.util.List;

 /**
* 通用接口
*/
@Service
public interface IService<T> { T selectByKey(Object key); int save(T entity); int saveNotNull(T entity); int delete(Object key); int deleteByExample(Object example); int updateAll(T entity); int updateNotNull(T entity); List<T> selectByExample(Object example); List<T> selectAll(); int selectCountByExample(Object example); }

BaseService.java

 package com.zcph.oaonline.serviceAll.impl;

 import com.zcph.oaonline.serviceAll.IService;
import org.springframework.beans.factory.annotation.Autowired;
import tk.mybatis.mapper.common.Mapper; import java.util.List; /**
* 通用Service
* @param <T>
*/
public abstract class BaseService<T> implements IService<T> { @Autowired
protected Mapper<T> mapper;
public Mapper<T> getMapper() {
return mapper;
} @Override
public T selectByKey(Object key) {
//说明:根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号
return mapper.selectByPrimaryKey(key);
} @Override
public int save(T entity) {
//说明:保存一个实体,null的属性也会保存,不会使用数据库默认值
return mapper.insert(entity);
} @Override
public int saveNotNull(T entity) {
//说明:保存一个实体,属性不为null的值
return mapper.insertSelective(entity);
} @Override
public int delete(Object key) {
//说明:根据主键字段进行删除,方法参数必须包含完整的主键属性
return mapper.deleteByPrimaryKey(key);
} @Override
public int updateAll(T entity) {
//说明:根据主键更新实体全部字段,null值会被更新
return mapper.updateByPrimaryKey(entity);
} @Override
public int updateNotNull(T entity) {
//根据主键更新属性不为null的值
return mapper.updateByPrimaryKeySelective(entity);
} @Override
public List<T> selectByExample(Object example) {
//说明:根据Example条件进行查询
//重点:这个查询支持通过Example类指定查询列,通过selectProperties方法指定查询列
return mapper.selectByExample(example);
} @Override
public List<T> selectAll() {
return mapper.selectAll();
} @Override
public int deleteByExample(Object example) {
//说明:根据主键字段进行删除,方法参数必须包含完整的主键属性
return mapper.deleteByExample(example);
}
@Override
public int selectCountByExample(Object example) {
return mapper.selectCountByExample(example);
}
}

数据源1的业务层

使UserLoginService继承通用接口,并制定泛型为UserLoginTable即可

UserLoginService.java
 package com.zcph.oaonline.service;

 import com.zcph.oaonline.entity.UserLoginTable;
import com.zcph.oaonline.serviceAll.IService;
import com.zcph.oaonline.util.Page; import java.util.List;
import java.util.Map; public interface UserLoginService extends IService<UserLoginTable> {
/**
* 根据传入分页参数查询
* @param page
* @return
*/
public List<UserLoginTable> queryUserLoginList(Page<UserLoginTable> page); /**
* 保存或更新
* @param userLoginTable
* @return
*/
public Map<String,Object> saveOrUpdateUserLogin(UserLoginTable userLoginTable);
}

实现类

UserLoginServiceImpl.java
 package com.zcph.oaonline.service.impl;

 import com.github.pagehelper.PageHelper;
import com.zcph.oaonline.entity.UserLoginTable;
import com.zcph.oaonline.mapper.UserLoginTableMapper;
import com.zcph.oaonline.service.UserLoginService;
import com.zcph.oaonline.serviceAll.impl.BaseService;
import com.zcph.oaonline.util.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import tk.mybatis.mapper.util.StringUtil; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID; @Service("userLoginService")
public class UserLoginServiceImpl extends BaseService<UserLoginTable> implements UserLoginService {
@Autowired
private UserLoginTableMapper userLoginTableMapper; @Override
public List<UserLoginTable> queryUserLoginList(Page<UserLoginTable> page) {
PageHelper.startPage(page.getPage(),page.getRows());
return selectAll();
} @Override
public Map<String, Object> saveOrUpdateUserLogin(UserLoginTable userLoginTable) {
LinkedHashMap<String,Object> resultMap=new LinkedHashMap<String,Object>();
if(userLoginTable!=null){
if(StringUtil.isNotEmpty(userLoginTable.getLoginUid())){//编辑
if(StringUtil.isNotEmpty(userLoginTable.getLoginName())){
updateNotNull(userLoginTable);
resultMap.put("state","success");
resultMap.put("message","修改用户登录信息表成功");
return resultMap;
}else{
resultMap.put("state","fail");
resultMap.put("message","修改用户登录信息表失败,缺少字段");
return resultMap;
}
}else{//新建
if(StringUtil.isNotEmpty(userLoginTable.getLoginName())){
userLoginTable.setLoginUid(UUID.randomUUID().toString().replaceAll("-",""));
saveNotNull(userLoginTable);
resultMap.put("state","success");
resultMap.put("message","新建用户登录信息表成功");
return resultMap;
}else{
resultMap.put("state","fail");
resultMap.put("message","新建用户登录信息表失败,缺少字段");
return resultMap;
}
}
}else{
resultMap.put("state","fail");
resultMap.put("message","失败");
return resultMap;
} }
}

 数据源2的业务层

原理同数据源1,就是为了方便放到了另一个包里

NewsService.java
 package com.zcph.oaonline.service2;

 import com.zcph.oaonline.entity.News;
import com.zcph.oaonline.serviceAll.IService;
import com.zcph.oaonline.util.Page; import java.util.List;
import java.util.Map; public interface NewsService extends IService<News> {
/**
* 根据传入分页参数查询
* @param page
* @return
*/
public List<News> queryNewsList(Page<News> page);
/**
* 保存或更新
* @param news
* @return
*/
public Map<String,Object> saveOrUpdateNews(News news);
}

实现类

NewsServiceImpl.java
 package com.zcph.oaonline.service2.impl;

 import com.github.pagehelper.PageHelper;
import com.zcph.oaonline.entity.News;
import com.zcph.oaonline.mapper2.NewsMapper;
import com.zcph.oaonline.service2.NewsService;
import com.zcph.oaonline.serviceAll.impl.BaseService;
import com.zcph.oaonline.util.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import tk.mybatis.mapper.util.StringUtil; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID; @Service("newsService")
public class NewsServiceImpl extends BaseService<News> implements NewsService {
@Autowired
private NewsMapper newsMapper;
@Override
public List<News> queryNewsList(Page<News> page) {
PageHelper.startPage(page.getPage(),page.getRows());
return selectAll();
} @Override
public Map<String, Object> saveOrUpdateNews(News news) {
LinkedHashMap<String,Object> resultMap=new LinkedHashMap<String,Object>();
if(news!=null){
if(StringUtil.isNotEmpty(news.getNewsId())){//编辑
if(StringUtil.isNotEmpty(news.getNewsTitle())){
updateNotNull(news);
resultMap.put("state","success");
resultMap.put("message","修改新闻表成功");
return resultMap;
}else{
resultMap.put("state","fail");
resultMap.put("message","修改新闻表失败,缺少字段");
return resultMap;
}
}else{//新建
if(StringUtil.isNotEmpty(news.getNewsTitle())){
news.setNewsId(UUID.randomUUID().toString().replaceAll("-",""));
saveNotNull(news);
resultMap.put("state","success");
resultMap.put("message","新建新闻表成功");
return resultMap;
}else{
resultMap.put("state","fail");
resultMap.put("message","新建新闻表失败,缺少字段");
return resultMap;
}
}
}else{
resultMap.put("state","fail");
resultMap.put("message","失败");
return resultMap;
} }
}

事务测试业务层

向两个表内同时插入数据

JtaTestService.java
 package com.zcph.oaonline.serviceAll;

 import java.util.Map;

 public interface JtaTestService {
/**
* 向两个表内同时插入数据
* @return
*/
public Map<String,Object> insertToTwoDatebaseService();
}

实现类

JtaTestServiceImpl.java
 package com.zcph.oaonline.serviceAll.impl;

 import com.zcph.oaonline.entity.News;
import com.zcph.oaonline.entity.UserLoginTable;
import com.zcph.oaonline.service.UserLoginService;
import com.zcph.oaonline.service2.NewsService;
import com.zcph.oaonline.serviceAll.JtaTestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import java.util.LinkedHashMap;
import java.util.Map; @Service("jtaTestService")
public class JtaTestServiceImpl implements JtaTestService {
@Autowired
@Qualifier("userLoginService")
private UserLoginService userLoginService; @Autowired
@Qualifier("newsService")
private NewsService newsService; @Override
@Transactional(transactionManager = "xatx", propagation = Propagation.REQUIRED, rollbackFor = { java.lang.RuntimeException.class })
public Map<String, Object> insertToTwoDatebaseService() {
LinkedHashMap<String,Object> resultMap=new LinkedHashMap<String,Object>();
UserLoginTable userLoginTable=new UserLoginTable();
userLoginTable.setLoginName("8888");
userLoginService.saveOrUpdateUserLogin(userLoginTable); News news=new News();
news.setNewsTitle("8888");
newsService.saveOrUpdateNews(news);
//测试事务中断
System.out.println(1/0); resultMap.put("state","success");
resultMap.put("message","分布式事务同步成功");
return resultMap;
}
}

Web层

为了方便都放到了一个测试的controller里了

JtaTestController.java
 package com.zcph.oaonline.web;

 import com.zcph.oaonline.entity.News;
import com.zcph.oaonline.entity.UserLoginTable;
import com.zcph.oaonline.model.PageRusult;
import com.zcph.oaonline.service.UserLoginService;
import com.zcph.oaonline.service2.NewsService;
import com.zcph.oaonline.serviceAll.JtaTestService;
import com.zcph.oaonline.util.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import tk.mybatis.mapper.util.StringUtil; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; @Controller
@RequestMapping("/jtaTest")
public class JtaTestController {
@Autowired
@Qualifier("userLoginService")
private UserLoginService userLoginService; @Autowired
@Qualifier("newsService")
private NewsService newsService; @Autowired
@Qualifier("jtaTestService")
private JtaTestService jtaTestService;
@ResponseBody
@RequestMapping("/queryUserLogin")
public PageRusult selectUserLoginByPages(Page<UserLoginTable> page){
List<UserLoginTable> userLoginTableList=userLoginService.queryUserLoginList(page);
PageRusult<UserLoginTable> pageRusult =new PageRusult<UserLoginTable>(userLoginTableList);
pageRusult.setCode(0);
return pageRusult;
} @ResponseBody
@RequestMapping("/saveOrUpdateUserLogin")
public Map<String,Object> saveOrUpdateUserLogin(UserLoginTable userLoginTable){
LinkedHashMap<String,Object> resultMap=new LinkedHashMap<String,Object>();
try {
return userLoginService.saveOrUpdateUserLogin(userLoginTable);
}catch (Exception e){
resultMap.put("state","fail");
resultMap.put("message","操作失败");
return resultMap;
}
} @ResponseBody
@RequestMapping("/deleteUserLogin")
public Map<String,Object> deleteUserLogin(String loginUid){
LinkedHashMap<String,Object> resultMap=new LinkedHashMap<String,Object>();
try {
if(StringUtil.isNotEmpty(loginUid)){
userLoginService.delete(loginUid);
resultMap.put("state","success");
resultMap.put("message","删除用户登录信息表成功");
return resultMap;
}else{
resultMap.put("state","fail");
resultMap.put("message","删除用户登录信息表失败");
return resultMap;
}
}catch (Exception e){
resultMap.put("state","fail");
resultMap.put("message","操作异常,删除用户登录信息表失败");
return resultMap;
}
} @ResponseBody
@RequestMapping("/selectNewsByPages")
public PageRusult selectNewsByPages(Page<News> page){
List<News> newsList=newsService.queryNewsList(page);
PageRusult<News> pageRusult =new PageRusult<News>(newsList);
pageRusult.setCode(0);
return pageRusult;
} @ResponseBody
@RequestMapping("/saveOrUpdateNews")
public Map<String,Object> saveOrUpdateNews(News news){
LinkedHashMap<String,Object> resultMap=new LinkedHashMap<String,Object>();
try {
return newsService.saveOrUpdateNews(news);
}catch (Exception e){
resultMap.put("state","fail");
resultMap.put("message","操作失败");
return resultMap;
}
} @ResponseBody
@RequestMapping("/deleteNews")
public Map<String,Object> deleteNews(String newsId){
LinkedHashMap<String,Object> resultMap=new LinkedHashMap<String,Object>();
try {
if(StringUtil.isNotEmpty(newsId)){
newsService.delete(newsId);
resultMap.put("state","success");
resultMap.put("message","删除新闻表成功");
return resultMap;
}else{
resultMap.put("state","fail");
resultMap.put("message","删除新闻表失败");
return resultMap;
}
}catch (Exception e){
resultMap.put("state","fail");
resultMap.put("message","操作异常,删除新闻表失败");
return resultMap;
}
} @ResponseBody
@RequestMapping("/insertToTwoDatebase")
public Map<String,Object> insertToTwoDatebase(){
LinkedHashMap<String,Object> resultMap=new LinkedHashMap<String,Object>();
try {
return jtaTestService.insertToTwoDatebaseService();
}catch (Exception e){
e.printStackTrace();
resultMap.put("state","fail");
resultMap.put("message","分布式事务同步失败");
return resultMap;
}
}
}

测试页面test.ftl

 <@com.head title="">
<base id="base" href="${basePath!}">
<link href="${basePath!}/static/plugins/layui/css/layui.css" type="text/css" media="screen" rel="stylesheet"/>
<script src="${basePath!}/static/plugins/layui/layui.js" type="text/javascript"></script> <script>
//一般直接写在一个js文件中
layui.use(['layer', 'form', 'table'], function () {
var layer = layui.layer
, form = layui.form
, $ = layui.$
, laytpl = layui.laytpl
, table = layui.table;
// 第一个数据库
var tableUserLogin = table.render({
id: 'userLoginTableId'
, elem: '#UserLoginTable'
, height: 460
, width: 755
, url: '${basePath!}/jtaTest/queryUserLogin' //数据接口
, page: true //开启分页
, cols: [[ //表头
{type: 'numbers', title: '序号', width: 80}
, {field: 'loginUid', title: '主键', width: 300}
, {field: 'loginName', title: '登录名', width: 200}
, {width: 170, title: '操作', align: 'center', toolbar: '#barDemo'} //这里的toolbar值是模板元素的选择器
]],
method: 'post',
request: {
pageName: 'page' //页码的参数名称,默认:page
, limitName: 'rows' //每页数据量的参数名,默认:limit
},
response: {
statusName: 'code'
, statusCode: 0
, countName: 'total' //数据总数的字段名称,默认:count
, dataName: 'list' //数据列表的字段名称,默认:data
},
}); var UserLoginInsertLayerIndex; //新建
$("#UserLoginInsert").click(function () {
//置空表单
$("#UserLoginInsertForm").find(":input[name='loginName']").val("");
$("#UserLoginInsertForm").find(":input[name='loginUid']").val("");
UserLoginInsertLayerIndex = layer.open({
title: "新建",
type: 1,
content: $('#UserLoginInsertDiv')
});
}); form.on('submit(UserLoginInsertFormSubmit)', function (data) {
$.ajax({
type: "POST",
url: "${basePath!}/jtaTest/saveOrUpdateUserLogin",
data: $("#UserLoginInsertForm").serialize(),
async: false,
error: function (request) {
layer.alert("与服务器连接失败/(ㄒoㄒ)/~~");
return false;
},
success: function (data) {
if (data.state == 'fail') {
layer.alert(data.message);
layer.close(UserLoginInsertLayerIndex);
return false;
} else if (data.state == 'success') {
layer.alert(data.message);
layer.close(UserLoginInsertLayerIndex);
tableUserLogin.reload({
page: {
curr: 1 //重新从第 1 页开始
}
});
}
}
});
return false; //这个阻止表单跳转。如果需要表单跳转, 去掉这段即可。
}); //监听工具条
table.on('tool(UserLoginTable)', function (obj) { //注:tool是工具条事件名,UserLogin是table原始容器的属性 lay-filter="对应的值"
var data = obj.data; //获得当前行数据
var layEvent = obj.event; //获得 lay-event 对应的值(也可以是表头的 event 参数对应的值)
if (layEvent === 'del') { //删除
layer.confirm('真的删除该行数据吗', function (index) {
obj.del(); //删除对应行(tr)的DOM结构,并更新缓存
layer.close(index);
//向服务端发送删除指令
$.ajax({
type: "POST",
url: "${basePath!}/jtaTest/deleteUserLogin",
data: {loginUid: data.loginUid},
async: false,
error: function (request) {
layer.alert("与服务器连接失败/(ㄒoㄒ)/~~");
return false;
},
success: function (data) {
if (data.state == 'fail') {
layer.alert(data.message);
return false;
} else if (data.state == 'success') {
}
}
});
});
} else if (layEvent === 'edit') { //编辑
//置空表单
$("#UserLoginInsertForm").find(":input[name='loginName']").val("");
$("#UserLoginInsertForm").find(":input[name='loginUid']").val("");
//添加值
$("#UserLoginInsertForm").find(":input[name='loginName']").val(data.loginName);
$("#UserLoginInsertForm").find(":input[name='loginUid']").val(data.loginUid);
UserLoginInsertLayerIndex = layer.open({
title: "编辑",
type: 1,
content: $('#UserLoginInsertDiv')
});
}
});
// 第二个数据库
var tableNews = table.render({
id: 'newsTableId'
, elem: '#News'
, height: 460
, width: 755
, url: '${basePath!}/jtaTest/selectNewsByPages' //数据接口
, page: true //开启分页
, cols: [[ //表头
{type: 'numbers', title: '序号', width: 80, sort: true}
, {field: 'newsId', title: '主键', width: 300, unresize: true}
, {field: 'newsTitle', title: '新闻标题', width: 200, unresize: true}
, {width: 170, title: '操作', align: 'center', toolbar: '#barDemo'} //这里的toolbar值是模板元素的选择器
]],
method: 'post',
request: {
pageName: 'page' //页码的参数名称,默认:page
, limitName: 'rows' //每页数据量的参数名,默认:limit
},
response: {
statusName: 'code'
, statusCode: 0
, countName: 'total' //数据总数的字段名称,默认:count
, dataName: 'list' //数据列表的字段名称,默认:data
},
}); var NewsInsertLayerIndex; //新建
$("#NewsInsert").click(function () {
//置空表单
$("#NewsInsertForm").find(":input[name='newsTitle']").val("");
$("#NewsInsertForm").find(":input[name='newsId']").val("");
NewsInsertLayerIndex = layer.open({
title: "新建",
type: 1,
content: $('#NewsInsertDiv')
});
}); form.on('submit(NewsInsertFormSubmit)', function (data) {
$.ajax({
type: "POST",
url: "${basePath!}/jtaTest/saveOrUpdateNews",
data: $("#NewsInsertForm").serialize(),
async: false,
error: function (request) {
layer.alert("与服务器连接失败/(ㄒoㄒ)/~~");
return false;
},
success: function (data) {
if (data.state == 'fail') {
layer.alert(data.message);
layer.close(NewsInsertLayerIndex);
return false;
} else if (data.state == 'success') {
layer.alert(data.message);
layer.close(NewsInsertLayerIndex);
tableNews.reload({
page: {
curr: 1 //重新从第 1 页开始
}
});
}
}
}); return false; //这个阻止表单跳转。如果需要表单跳转, 去掉这段即可。
}); //监听工具条
table.on('tool(News)', function (obj) { //注:tool是工具条事件名,UserLogin是table原始容器的属性 lay-filter="对应的值"
var data = obj.data; //获得当前行数据
var layEvent = obj.event; //获得 lay-event 对应的值(也可以是表头的 event 参数对应的值)
var tr = obj.tr; //获得当前行 tr 的DOM对象 if (layEvent === 'detail') { //查看
//do somehing
} else if (layEvent === 'del') { //删除
layer.confirm('真的删除该行数据吗', function (index) {
obj.del(); //删除对应行(tr)的DOM结构,并更新缓存
layer.close(index);
//向服务端发送删除指令
$.ajax({
type: "POST",
url: "${basePath!}/jtaTest/deleteNews",
data: {newsId: data.newsId},
async: false,
error: function (request) {
layer.alert("与服务器连接失败/(ㄒoㄒ)/~~");
return false;
},
success: function (data) {
if (data.state == 'fail') {
layer.alert(data.message);
return false;
} else if (data.state == 'success') {
}
}
});
});
} else if (layEvent === 'edit') { //编辑
//do something
//置空表单
$("#NewsInsertForm").find(":input[name='newsTitle']").val("");
$("#NewsInsertForm").find(":input[name='newsId']").val("");
//添加值
$("#NewsInsertForm").find(":input[name='newsTitle']").val(data.newsTitle);
$("#NewsInsertForm").find(":input[name='newsId']").val(data.newsId);
NewsInsertLayerIndex = layer.open({
title: "编辑",
type: 1,
content: $('#NewsInsertDiv')
});
}
}); //分布式事务测试
$("#JTATest").click(function () {
$.ajax({
type: "POST",
url: "${basePath!}/jtaTest/insertToTwoDatebase",
data: {},
async: false,
error: function (request) {
layer.alert("与服务器连接失败/(ㄒoㄒ)/~~");
return false;
},
success: function (data) {
if (data.state == 'fail') {
layer.alert(data.message);
return false;
} else if (data.state == 'success') {
layer.alert(data.message);
}
}
});
}); });
</script>
</@com.head>
<@com.body>
<#--第一个数据库表-->
<fieldset class="layui-elem-field">
<legend>用户登录信息</legend>
<div class="layui-field-box">
<div class="layui-fluid">
<div class="layui-row">
<button class="layui-btn" id="UserLoginInsert">新建</button>
</div>
<div class="layui-row">
<table id="UserLoginTable" lay-filter="UserLoginTable"></table>
</div>
</div>
</div>
</fieldset>
<div id="UserLoginInsertDiv" style="display: none">
<form class="layui-form" action="" id="UserLoginInsertForm">
<input type="hidden" id="UserLoginInsertFormId" name="loginUid"/>
<div class="layui-form-item">
<label class="layui-form-label">登录名</label>
<div class="layui-input-block">
<input type="text" name="loginName" required lay-verify="required" placeholder="请输登陆名称"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="UserLoginInsertFormSubmit">立即提交</button>
<button type="reset" class="layui-btn layui-btn-primary" id="UserLoginInsertFormReset">重置</button>
</div>
</div>
</form>
</div>
<#-- 第二个数据库表-->
<fieldset class="layui-elem-field">
<legend>新闻信息</legend>
<div class="layui-field-box">
<div class="layui-fluid">
<div class="layui-row">
<button class="layui-btn" id="NewsInsert">新建</button>
</div>
<div class="layui-row">
<table id="News" lay-filter="News"></table>
</div>
</div>
</div>
</fieldset>
<div id="NewsInsertDiv" style="display: none">
<form class="layui-form" action="" id="NewsInsertForm">
<input type="hidden" id="NewsInsertFormId" name="newsId"/>
<div class="layui-form-item">
<label class="layui-form-label">新闻标题</label>
<div class="layui-input-block">
<input type="text" name="newsTitle" required lay-verify="required" placeholder="请输新闻标题"
autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="NewsInsertFormSubmit">立即提交</button>
<button type="reset" class="layui-btn layui-btn-primary" id="NewsInsertFormReset">重置</button>
</div>
</div>
</form>
</div> <#------------------------------------>
<fieldset class="layui-elem-field">
<legend>分布式事务测试</legend>
<div class="layui-field-box">
<div class="layui-fluid">
<div class="layui-row">
<button class="layui-btn" id="JTATest">同时向两个表内插入信息为8888的数据</button>
</div>
</div>
</div>
</fieldset>
<script type="text/html" id="barDemo">
<#--<a class="layui-btn layui-btn-xs" lay-event="detail">查看</a>-->
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
<!-- 序号监听事件 -->
<script type="text/html" id="indexTpl">
{{d.LAY_TABLE_INDEX+1}}
</script>
</@com.body>

代码至此结束,关于项目的其他文件,可以到文末处我的git上去看。这里就不再书写了。


测试

1.测试分页查询

启动项目,成功后输入地址 http://localhost:9090/oa_online/toTest,返回页面如下图

可以看到后台打印出来了sql同时有warn提示。这个提示的意思就是事务还没有运行,原因是我们还没有执行过使用事务部分的代码(同时向两个表内插入信息为8888的数据)。

 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'
2019-08-22 17:00:51.919 INFO 10740 --- [nio-9090-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2019-08-22 17:00:51.926 INFO 10740 --- [nio-9090-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 7 ms
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?
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?
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
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?
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
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?
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?
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?
2019-08-22 17:00:52.766 DEBUG 10740 --- [nio-9090-exec-2] c.z.o.m.U.selectAll_COUNT : ==> Parameters:
2019-08-22 17:00:52.766 DEBUG 10740 --- [nio-9090-exec-3] c.z.o.m.NewsMapper.selectAll_COUNT : ==> Parameters:
2019-08-22 17:00:52.783 DEBUG 10740 --- [nio-9090-exec-3] c.z.o.m.NewsMapper.selectAll_COUNT : <== Total: 1
2019-08-22 17:00:52.783 DEBUG 10740 --- [nio-9090-exec-2] c.z.o.m.U.selectAll_COUNT : <== Total: 1
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 ?
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?
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?
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 ?
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?
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?
2019-08-22 17:00:52.792 DEBUG 10740 --- [nio-9090-exec-2] c.z.o.m.UserLoginTableMapper.selectAll : ==> Parameters: 10(Integer)
2019-08-22 17:00:52.793 DEBUG 10740 --- [nio-9090-exec-3] c.z.o.mapper2.NewsMapper.selectAll : ==> Parameters: 10(Integer)
2019-08-22 17:00:52.800 DEBUG 10740 --- [nio-9090-exec-2] c.z.o.m.UserLoginTableMapper.selectAll : <== Total: 10
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?
2019-08-22 17:00:52.804 DEBUG 10740 --- [nio-9090-exec-3] c.z.o.mapper2.NewsMapper.selectAll : <== Total: 10
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的数据)按钮时,打断点之后单步走可以看到执行了事务的回滚。到数据库内查看也没有新增对应的数据。

执行后日志如下:

可以看出执行这段代码时是 现注册的事务,所以之前才会有事务并未运行的警告。

 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
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
or register at http://www.atomikos.com/Main/RegisterYourDownload to disable this message and receive FREE tips & advice
Thanks for using Atomikos! Evaluate http://www.atomikos.com/Main/ExtremeTransactions for advanced features and professional support
or register at http://www.atomikos.com/Main/RegisterYourDownload to disable this message and receive FREE tips & advice
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
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
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
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
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
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
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
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
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 = ./
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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...
2019-08-22 17:04:29.221 INFO 10740 --- [nio-9090-exec-7] c.a.d.xa.XATransactionalResource : businessDB: refreshed XAResource
2019-08-22 17:04:29.252 INFO 10740 --- [nio-9090-exec-7] c.a.d.xa.XATransactionalResource : systemDB: refreshed XAResource
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( ?,? )
2019-08-22 17:04:29.363 DEBUG 10740 --- [nio-9090-exec-7] c.z.o.m.U.insertSelective : ==> Parameters: ef575e338ca14f42a1966348ade0df81(String), 8888(String)
2019-08-22 17:04:29.366 DEBUG 10740 --- [nio-9090-exec-7] c.z.o.m.U.insertSelective : <== Updates: 1
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( ?,? )
2019-08-22 17:04:29.376 DEBUG 10740 --- [nio-9090-exec-7] c.z.o.m.NewsMapper.insertSelective : ==> Parameters: 1ae4327ec2104da0aec8b8897baa9069(String), 8888(String)
2019-08-22 17:04:29.382 DEBUG 10740 --- [nio-9090-exec-7] c.z.o.m.NewsMapper.insertSelective : <== Updates: 1
java.lang.ArithmeticException: / by zero
at com.zcph.oaonline.serviceAll.impl.JtaTestServiceImpl.insertToTwoDatebaseService(JtaTestServiceImpl.java:39)
at com.zcph.oaonline.serviceAll.impl.JtaTestServiceImpl$$FastClassBySpringCGLIB$$d75a57c1.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:749)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:295)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
at com.zcph.oaonline.serviceAll.impl.JtaTestServiceImpl$$EnhancerBySpringCGLIB$$38d77f20.insertToTwoDatebaseService(<generated>)
at com.zcph.oaonline.web.JtaTestController.insertToTwoDatebase(JtaTestController.java:128)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:892)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1039)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:908)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:660)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at com.alibaba.druid.support.http.WebStatFilter.doFilter(WebStatFilter.java:123)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:853)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1587)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)

不信我们可以再次刷新下页面,日志展示如下,只有查询语句了,没有了事务没有运行的警告了。

 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
2019-08-22 17:12:41.718 DEBUG 4428 --- [io-9090-exec-10] c.z.o.m.U.selectAll_COUNT : ==> Parameters:
2019-08-22 17:12:41.719 DEBUG 4428 --- [io-9090-exec-10] c.z.o.m.U.selectAll_COUNT : <== Total: 1
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 ?
2019-08-22 17:12:41.720 DEBUG 4428 --- [io-9090-exec-10] c.z.o.m.UserLoginTableMapper.selectAll : ==> Parameters: 10(Integer)
2019-08-22 17:12:41.726 DEBUG 4428 --- [io-9090-exec-10] c.z.o.m.UserLoginTableMapper.selectAll : <== Total: 10
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
2019-08-22 17:12:41.747 DEBUG 4428 --- [nio-9090-exec-3] c.z.o.m.NewsMapper.selectAll_COUNT : ==> Parameters:
2019-08-22 17:12:41.749 DEBUG 4428 --- [nio-9090-exec-3] c.z.o.m.NewsMapper.selectAll_COUNT : <== Total: 1
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 ?
2019-08-22 17:12:41.750 DEBUG 4428 --- [nio-9090-exec-3] c.z.o.mapper2.NewsMapper.selectAll : ==> Parameters: 10(Integer)
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可直接打开。需要修改下数据源,请自行修改。

 

springboot+mybatis+druid+atomikos框架搭建及测试的更多相关文章

  1. springboot+mybatis+springMVC基础框架搭建

    项目结构概览 pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http: ...

  2. 3分钟搞定SpringBoot+Mybatis+druid多数据源和分布式事务

    文章来自: https://blog.csdn.net/qq_29242877/article/details/79033287 在一些复杂的应用开发中,一个应用可能会涉及到连接多个数据源,所谓多数据 ...

  3. springboot+mybatis+druid+sqlite/mysql/oracle

    搭建springboot+mybatis+druid+sqlite/mysql/oracle附带测试 1.版本 springboot2.1.6 jdk1.8 2.最简springboot环境 http ...

  4. 基于Maven的Springboot+Mybatis+Druid+Swagger2+mybatis-generator框架环境搭建

    基于Maven的Springboot+Mybatis+Druid+Swagger2+mybatis-generator框架环境搭建 前言 最近做回后台开发,重新抓起以前学过的SSM(Spring+Sp ...

  5. 记录一下自己搭建springboot+mybatis+druid 多数据源的过程

    前言  上次的一个项目(springboot+mybatis+vue),做到后面的时间发现需要用到多数据源.当时没有思路..后来直接用了jdbc来实现.这几天不是很忙,所以决定自己再搭建一次.不多说, ...

  6. 整合springboot(app后台框架搭建四)

    springboot可以说是为了适用SOA服务出现,一方面,极大的简便了配置,加速了开发速度:第二方面,也是一个嵌入式的web服务,通过jar包运行就是一个web服务: 还有提供了很多metric,i ...

  7. SpringBoot+Mybatis多模块项目搭建教程

    一.前言 框架为SpringBoot+Mybatis,本篇主要记录了在IDEA中搭建SpringBoot多模块项目的过程. 1.开发工具及系统环境 IDE:IntelliJ IDEA 2018.2 系 ...

  8. ssm框架搭建整合测试

    下载各种jar包 mybatis下载 https://github.com/mybatis/mybatis-3/releases mysql驱动下载 http://mvnrepository.com/ ...

  9. spring+mybatis+mina+logback框架搭建

    第一次接触spring,之前从来没有学过spring,所以算是赶鸭子上架,花了差不多一个星期来搭建,中间遇到各种各样的问题,一度觉得这个框架搭建非常麻烦,没有一点技术含量,纯粹就是配置,很低级!但随着 ...

随机推荐

  1. JavaScript作用域及预编译

    几乎所有的编程语言都可以存储,访问,修改变量,那在JavaScript中这些变量放在那里?程序如何找到他们? js被归类于解释执行语言,但事实上他也是一门编译语言,因为他也要编译,但于传统的编译语言不 ...

  2. Spring_简单入门(学习笔记1)

    Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架. 具体介绍参考 一:IoC(Inversion of Control)控制反转,将创建对象实例反转给spri ...

  3. 网络框架,互联网的组成,OSI七层协议,抽象层

    6.25自我总结 1.网络框架 1.单机 单机游戏 以下两个基于网络的 2.CS架构 cs--->client客户/server服务 服务端(应用程序)一个就够了,客户端(应用程序)可以有多个 ...

  4. Python常用的标准库以及第三方库

    Python常用的标准库以及第三方库有哪些?   20个必不可少的Python库也是基本的第三方库 读者您好.今天我将介绍20个属于我常用工具的Python库,我相信你看完之后也会觉得离不开它们.他们 ...

  5. JAVA面试题 请谈谈你对Sychronized关键字的理解?

    面试官:sychronized关键字有哪些特性? 应聘者: 可以用来修饰方法; 可以用来修饰代码块; 可以用来修饰静态方法; 可以保证线程安全; 支持锁的重入; sychronized使用不当导致死锁 ...

  6. laravel 5.6初学笔记

    laravel 5.6初学笔记 http://note.youdao.com/noteshare?id=bf4b701b49dd035564e7145ba2d978b4 框架简介 laravel文档齐 ...

  7. Office2010安装问题锦集

    问题一,每次打开office 2010,都会出现重新配置的对话框. 解决姿势: 大部分搜索到的解决方法大概有2种, 一个是修改注册表,在注册表中这个这个位置增加一个名为NoRereg的32位World ...

  8. Linux 下实践 VxLAN:虚拟机和 Docker 场景

    本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复 「1024」 即可领取,欢迎大家关注,二维码文末可以扫. 在上篇文章 ...

  9. 转发后找不到css

    当在jsp中引入css时,如果其相对路径相对于当前jsp文件的,而在一个和这个jsp的路径不一样的servlet中forward这个jsp时,就会发现这个css样式根本没有起作用. 这是因为在serv ...

  10. C# StackTrace

    StackTrace trace = new StackTrace(); //获取是哪个类来调用的 Type type = trace.GetFrame().GetMethod().Declaring ...