在上一篇博客中已经讲解了SpringBoot整合Mybatis单数据源以及Druid监控 ,如未了解请移步:SpringBoot整合Mybatis 以及Druid 数据监控
下面开始正文
一.所需依赖
<!-- SpringBoot集成mybatis框架 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.boot.starter.version}</version>
</dependency>
<!-- pagehelper 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper.spring.boot.starter.version}</version>
</dependency>
<!--阿里数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!--注意我这里是5版本须声明 version-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.driver.version}</version>
</dependency>
<!--分布式事务-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
二.多数据源下SpringBoot的Yml配置文件
spring:
datasource:
type: com.alibaba.druid.pool.xa.DruidXADataSource
druid:
#第一个数据源
one:
name: oneDataSource
url: jdbc:mysql://127.0.0.1:3306/one?useUnicode=true&characterEncoding=utf-8&useSSL=false
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
#第二个数据源
two:
name: twoDataSource
url: jdbc:mysql://127.0.0.1:3306/two?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:
#日志存放位置 默认在项目根目录下的transaction-logs下
#log-dir: classpath:tx-logs
transaction-manager-id: txManager
三.开始事务排除默认加载数据源
springboot 一启动就自会动寻找 数据源并设为单数据源,所以我们需要先将他排除掉
在主启动类中的@SpringBootApplication 添加(exclude = {DataSourceAutoConfiguration.class})
并开启事务管理器
/**
* @author leilei
* desc EnableTransactionManagement 开启事务管理器
* exclude = {DataSourceAutoConfiguration.class 排除springboot 一启动就自动寻找 数据源并设为单数据源
*/
@EnableTransactionManagement
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class SpringbootMybatisAtomikosMoredatasourceApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootMybatisAtomikosMoredatasourceApplication.class, args);
}
}
四.多数据源下Druid 监控配置
1.需要将单数据源的DataSource换为支持多数据源事务的AtomikosDataSourceBean ,并加载自定义的一二数据源Druid 连接信息
2.配置Druid 访问设置以及并发数 慢查询 过滤,,,,
整体代码如下
package com.leilei.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.transaction.UserTransaction;
import java.util.Properties;
/**
* 多数据源和Druid配置
* @author leilei
*/
@Configuration
public class DruidConfig {
/**
* 数据源1配置 使用AtomikosDataSourceBean 支持多数据源事务
* @param env
* @return Primary 指定主库 (必须指定一个主库 否则会报错)
*/
@Bean(name = "MybatisOneDataSource")
@Primary
@Autowired
public AtomikosDataSourceBean oneDataSource(Environment env) {
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
Properties prop = build(env, "spring.datasource.druid.one.");
ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
ds.setUniqueResourceName("oneDataSource");
ds.setPoolSize(5);
ds.setXaProperties(prop);
return ds;
}
/**
* 数据源2配置 使用AtomikosDataSourceBean 支持多数据源事务
* @param env
* @return
*/
@Autowired
@Bean(name = "MybatisTwoDataSource")
public AtomikosDataSourceBean twoDataSource(Environment env) {
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
Properties prop = build(env, "spring.datasource.druid.two.");
ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
ds.setUniqueResourceName("twoDataSource");
ds.setPoolSize(5);
ds.setXaProperties(prop);
return ds;
}
// /**
// * 注入事物管理器
// * @return
// */
// @Bean(name = "leijta")
// 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", "leilei");
servletRegistrationBean.addInitParameter("loginPassword", "123456");
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();
//slowSqlMillis用来配置SQL慢的标准,执行时间超过slowSqlMillis的就是慢。
statFilter.setLogSlowSql(true);
//SQL合并配置
statFilter.setMergeSql(true);
//slowSqlMillis的缺省值为3000,也就是3秒。
statFilter.setSlowSqlMillis(1000);
return statFilter;
}
@Bean
public WallFilter wallFilter(){
WallFilter wallFilter = new WallFilter();
//允许执行多条SQL
WallConfig config = new WallConfig();
config.setMultiStatementAllow(true);
wallFilter.setConfig(config);
return wallFilter;
}
}
五.SqlSessionFactory 和 SqlSessionTemplate 配置
SqlSessionFactory是MyBatis的关键对象,它是个单个数据库映射关系经过编译后的内存镜像
SqlSession是MyBatis的关键对象,是执行持久化操作的独享,类似于JDBC中的Connection.它是应用程序与持久层之间执行交互操作的一个单线程对象,也是MyBatis执行持久化操作的关键对象.SqlSession对象完全包含以数据库为背景的所有执行SQL操作的方法,它的底层封装了JDBC连接,可以用SqlSession实例来直接执行被映射的SQL语句。
第一个数据源的SqlSessionFactory SqlSessionTemplate 配置
package com.leilei.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.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;
/**
* @author : leilei
* @date : 16:30 2020/3/1
* @desc : Mybatis 多数据源 第一个数据源配置
*/
@Configuration
@MapperScan(basePackages = "com.leilei.mapper.one", sqlSessionFactoryRef = "sqlSessionFactory")
public class MybatisOneDataSource {
@Autowired
@Qualifier("MybatisOneDataSource")
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:com/leilei/mapper/one/*.xml"));
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
factoryBean.setConfiguration(configuration);
return factoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate() throws Exception {
// 使用上面配置的Factory
SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory());
return template;
}
}
其实第二个或者以下的N个数据源也都一样,只需要扫描不同数据源对应的mapper层以及实体类映射层 注入不同的数据源即可
@Qualifier 是Spring中的注解 在有相同类型不同名称的多个bean 情况下 根据bean的名称选择注入
package com.leilei.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.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;
/**
* @author : leilei
* @date : 16:31 2020/3/1
* @desc : Mybvatis 第二个数据源配置 精确到 mapper 目录,以便跟其他数据源隔离
*/
@Configuration
@MapperScan(basePackages = "com.leilei.mapper.two", sqlSessionFactoryRef = "sqlSessionFactory2")
public class MybatisTwoDataSource {
@Autowired
@Qualifier("MybatisTwoDataSource")
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:com/leilei/mapper/two/*.xml"));
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
factoryBean.setConfiguration(configuration);
return factoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate2() throws Exception {
// 使用上面配置的Factory
SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory2());
return template;
}
}
接下来将数据导入不同的数据库 我这里real_eseate 导入了数据库一 user 导入了数据库二 ,sql文件会在文章末尾项目源码中提供
导入后,使用Mybatis的代码生成器生成一波,可查看我的上一篇博客,其中讲解了Mybatis代码生成器的使用:SpringBoot整合Mybatis 以及Druid 数据监控
自定义异常响应
/**
* @author : leilei
* @date : 18:20 2020/3/1
* @desc : 自定义异常响应
*/
@RestControllerAdvice
public class ExceptionHadler {
@ExceptionHandler(value = Exception.class)
public Map<String, Object> exceptionHandler(HttpServletRequest req, Exception e) {
HashMap<String, Object> map = new HashMap<>(4);
map.put("请求状态", "False");
map.put("请求路径", req.getRequestURI());
map.put("请求方式", req.getMethod());
map.put("错误信息", e.getMessage());
return map;
}
}
查看Druid监控页面:
多个数据源的信息会排列展示出来,向下翻可继续查看
六.事务测试
使用一个数据源 测试
@PostMapping("insert")
public Long insert(User user) {
userService.insert(user);
return user.getId();
}
故意制造异常测试 是否插入成功
@Override
public int insert(User record) {
//单个数据源二测试事务
userMapper.insert(new User("test", "12345"));
int a = 1 / 0;
return userMapper.insert(record);
}
未测试前数据库数据:
结果
多次更新数据库后,发现结果未变
发现单个数据源事务是OK的
测试多数据源事务
未开始前数据
数据库一六条数据 数据库二 2条数据
制造异常,测试
@Override
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = { java.lang.RuntimeException.class })
public Map<String, Object> insertToTwoDatebaseService(BodyVo bodyVo) {
userMapper.insert(bodyVo.getUser());
//故意制造异常
// int a = 1 / 0;
bodyVo.getRealEseate().setBuildTime(new Date());
realEseateMapper.insert(bodyVo.getRealEseate());
HashMap<String, Object> map = new HashMap<>();
map.put("user", bodyVo.getUser());
map.put("realReseate", bodyVo.getRealEseate());
return map;
}
如图,在数据源二保存后,在保存数据源一之前 发生了异常,那么我们此时查看数据库,看数据源二和一中的数据
多次刷新后,发现数据库并未改变,这说明 当数据源二保存后 数据源一保存时发生了异常 数据源二保存的数据已经做了回滚 !!!
多数据源事务问题 解决
此时发现,我们项目中多了一个文件夹
这是多数据源事务日志默认存放的位置 可在SpringBoot Yml配置文件中更改
#jta多数据源事务相关参数配置
jta:
#日志存放位置 默认在项目根目录下的transaction-logs下
#log-dir: classpath:tx-logs
transaction-manager-id: txManager
查看日志
七.跨数据源连表查询
本文中,由于使用了两个数据库 且他们中间有的表存在着关联关系,那么为了查询出详细连表信息呢,就需要跨库连表查询
跨库连表查询其实也和 普通的连表查询一样,只是要指定具体数据库的某个表
不跨库连表查询:
select real.* ,u.* from real_reas real left join user u on real.user_id=u.id
跨库连表查询: one 库中的real_reas two库中的user
select real.* ,u.* from one.real_reas real left join two.user u on real.user_id=u.id
在连表查询的时候记得向本身对象中添加关联类型的字段 以及在映射结果图中添加关联对象属性
一对多 多对一 多对多 等,在本文的上一篇博客中已经很详细的写了
项目中跨库连表查询以及Pagehelper的使用:
/**
* 跨数据源连表查询 controller
* @param page
* @param size
* @return
*/
@PostMapping("findall")
public List<RealEseate> findall(Integer page, Integer size) {
return atomikosService.findall(page, size);
}
/**
* 多数据源连表查询 service
*/
List<RealEseate> findall(Integer page, Integer size);
@Override //serviceimpl
public List<RealEseate> findall(Integer page, Integer size) {
PageHelper.startPage(page, size);
PageHelper.orderBy("build_time desc");
return realEseateMapper.findMoreDatasource();
}
/**
* 跨数据源连表查询 mapper
*/
List<RealEseate> findMoreDatasource();
/**
* mapper.xml
*/
<select id="findMoreDatasource" resultMap="BaseResultMap">
SELECT onere.id, onere.project_name, onere.address,
onere.house_type, onere.area, onere.build_time, onere.user_id ,
twouser.id ,twouser.username ,twouser.card_id
FROM one.real_eseate onere
LEFT JOIN two.`user` twouser
ON onere.user_id=twouser.id
</select>
/**映射结果图*/
<resultMap id="BaseResultMap" type="com.leilei.entity.one.RealEseate">
<id column="id" jdbcType="BIGINT" property="id"/>
<result column="project_name" jdbcType="VARCHAR" property="projectName"/>
<result column="address" jdbcType="VARCHAR" property="address"/>
<result column="house_type" jdbcType="VARCHAR" property="houseType"/>
<result column="area" jdbcType="INTEGER" property="area"/>
<result column="build_time" jdbcType="TIMESTAMP" property="buildTime"/>
<result column="user_id" jdbcType="BIGINT" property="userId"/>
<!--关联对象结果映射-->
<result column="id" jdbcType="BIGINT" property="user.id"/>
<result column="username" jdbcType="VARCHAR" property="user.username"/>
<result column="card_id" jdbcType="VARCHAR" property="user.cardId"/>
</resultMap>
八.结语
本文到这里就结束了!!!!