SpringBoot整合Mybatis-plus(二) 多數據源Druid監控,Atomikos處理事務,跨庫連表查詢
在上一章中文,我使用springboot工程整合了mp,以及mp基礎使用,代碼生成器使用,Druid 數據監控等,但僅限於單數據源 請戳SpringBoot整合Mybatis-plus(一)基本使用與自定義模板代碼生成器
因爲很多時候,在開發中,並不會只有一個數據庫,在保存一條數據的時候,可能需要向幾個數據庫保存,那麼當發生異常時候,就會面臨事務問題了,需要將保存的數據進行回滾,所以,本文開始講解,整合 Mp 多數據源下的druid監控以及事務處理
一.添加新的依賴
<!--分佈式事務-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
<!--由於我mysql重裝了,使用8版本驅動有點問題,所以我回退到了5版本-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
二.多數據源情況下的YML配置
spring:
datasource:
type: com.alibaba.druid.pool.xa.DruidXADataSource
druid:
#第一個數據源
one:
name: oneDataSource
url: jdbc:mysql://127.0.0.1:3306/mybatis-plus?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
driver-class-name: com.mysql.jdbc.Driver
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/mybatis-plus2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
driver-class-name: com.mysql.jdbc.Driver
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
#第三個數據源
three:
name: threeDataSource
url: jdbc:mysql://127.0.0.1:3306/mybatis-plus3?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
driver-class-name: com.mysql.jdbc.Driver
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:
atomikos:
properties:
log-base-dir: tx-logs
transaction-manager-id: txManager #默認取計算機的IP地址 需保證生產環境值唯一
三.配置Bean形式讀取數據庫信息
由於配置了one two three 三個數據源 已經是改變了springBoot 對Mysql 連接讀取的配置路徑,所以呢,我們需要自定義數據源的位置,在啓動項目時才能成功讀取數據庫相關信息
無論使用Mybatis 還是mp 在訪問數據庫時都需要 數據源 , SqlSessionFactory,SqlSessionTemplate
首先需要從配置文件中,獲取到數據源信息,以及Druid需要監控的信息
讀取我們配置文件中數據庫路徑 生成對象並配置成Bean 交由spring管理
附上我完整代碼
DruidConfig
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 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 java.util.Properties;
/**
* 多數據源和Druid配置
*
* @author leilei
*/
@Configuration
public class DruidConfig {
/**
* 數據源1配置 使用AtomikosDataSourceBean 支持多數據源事務
*
* @param env
* @return Primary 指定主庫 (必須指定一個主庫 否則會報錯)
*/
@Bean(name = "MybatisPlusOneDataSource")
@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 = "MybatisPlusTwoDataSource")
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;
}
@Autowired
@Bean(name = "MybatisPlusThreeDataSource")
public AtomikosDataSourceBean threeDataSource(Environment env) {
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
Properties prop = build(env, "spring.datasource.druid.three.");
ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
ds.setUniqueResourceName("threeDataSource");
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;
}
}
在DruidConfig中看到了 每個數據源的Bean 都是 AtomikosDataSourceBean 類型 ,只要數據源是此類型,在多個數據源下,打上Spring自帶的事務註解@Transactional 即可
AtomikosDataSourceBean類型數據源
/**
* 數據源1配置 使用AtomikosDataSourceBean 支持多數據源事務
*
* @param env
* @return Primary 指定主庫 (必須指定一個主庫 否則會報錯)
*/
@Bean(name = "MybatisPlusOneDataSource")
@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;
}
配置每個數據源的sqlsessionfactory sqlSessionTemplate
這裏貼上我的第一個數據源的sqlsessionfactory sqlSessionTemplate,無論還有多少數據源,按照我這個配置即可,改改包路徑,@Qualifier選擇對應數據源 ,改改mpper.xml路徑即可
需注意的是,必須有一個數據源的所有相關(datasource,sqlsessionfactory sqlSessionTemplate)要使用**@Primary**註解指定一個主庫,否則會啓動報錯。
package com.leilei.config;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
/**
* @author leilei
*/
@Configuration
@MapperScan(basePackages = "com.leilei.mapper.one", sqlSessionFactoryRef = "oneSqlSessionFactory")
public class OneDataSourceConfig {
@Primary
@Bean(name = "oneSqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("MybatisPlusOneDataSource") DataSource dataSource) throws Exception {
//配置myabtisSqlSession
MybatisSqlSessionFactoryBean sessionFactoryBean = new MybatisSqlSessionFactoryBean();
// 指明mapper.xml位置(配置文件中指明的xml位置會失效用此方式代替,具體原因未知)
sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:com/leilei/mapper/one/*/*Mapper.xml"));
// 指明實體掃描(多個package用逗號或者分號分隔)
sessionFactoryBean.setTypeAliasesPackage("com.leilei.entity.one");
MybatisConfiguration mybatisConfiguration = new MybatisConfiguration();
// mybatisConfiguration.setJdbcTypeForNull(JdbcType.NULL);
//駝峯
mybatisConfiguration.setMapUnderscoreToCamelCase(true);
//是否開啓緩存
mybatisConfiguration.setCacheEnabled(false);
//多數據源下分頁模式
mybatisConfiguration.addInterceptor(new PaginationInterceptor());
// 配置打印sql語句
mybatisConfiguration.setLogImpl(StdOutImpl.class);
sessionFactoryBean.setConfiguration(mybatisConfiguration);
//數據源注入
sessionFactoryBean.setDataSource(dataSource);
return sessionFactoryBean.getObject();
}
@Primary
@Bean(name = "oneSqlSessionTemplate")
public SqlSessionTemplate sqlSessionTemplate(@Qualifier("oneSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
其他數據源的sqlsessionfactory sqlSessionTemplate 照着我這個配置即可 ,去掉 @Primary註解 注意Bean 名稱設爲唯一。
其實到這裏 多數據源下的事務管理以及Druid監控已經是整合完畢了
四.本文需注意的點
1.導入atomikos 依賴
2.mysql 驅動爲5 版本
3.包路徑劃分
在使用代碼生成器時,將每個數據源的mapper, entity ,mapper.xml包路徑劃分開來
4.多數據源也需要@Transactional 註解
五.多數據源事務測試
事務測試
一個方法中,調用多個數據源插入數據,並中途製造異常,報錯後,查看數據回滾·
body 爲我封裝的一個對象,因爲本文設計三個對象,我準備向三個數據源一起插入數據測試事務
操作數據庫
@Override
@Transactional //數據源配置了AtomikosDataSourceBean 再打上此註解即可做到多數據源事務控制
public Map<String, Object> insertAto(BodyVo bodyVo) {
userMapper.insert(bodyVo.getUser());
userRoleMapper.insert(bodyVo.getUserRole());
int a = 1 / 0; //製造異常
roleMapper.insert(bodyVo.getRole());
HashMap<String, Object> map = new HashMap<>();
map.put("user", bodyVo.getUser());
map.put("user_role", bodyVo.getUserRole());
map.put("role", bodyVo.getRole());
return map;
}
未開始測試前,數據庫數據
運行測試結果,可以看到是報了異常
那麼,此時再查看數據數據,刷新後,發現數據並未改變
那麼說明,多數據源下的事務問題已經解決了!!!!!
多數據源下Mp 分頁以及跨表查詢
本文是將一個多對多關係的三張表放在了三個庫中
依User 爲第一查詢視角
本文中usermapper.MoreDatasourceFindAll 是自定義的方法,爲了有分頁效果必須構造一個Page對象,將Page對象與查詢條件一起作爲參數傳到UserMapper.interface中,再有mapper.xml操作數據庫
IPage<User> MoreDatasourceFindAll(Page<User> page, @Param("id") Long id);
<!-- 通用查詢映射結果 -->
<resultMap id="BaseResultMap" type="com.leilei.entity.one.User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<collection property="roleList" javaType="list" ofType="com.leilei.entity.three.Role">
<id column="roleid" property="id"/>
<result column="rolename" property="roleName"/>
</collection>
</resultMap>
<select id="MoreDatasourceFindAll" resultMap="BaseResultMap">
SELECT mpu.*,mp3r.id roleid,mp3r.role_name rolename
FROM `mybatis-plus`.`user` mpu ,`
mybatis-plus2`.user_role mp2ur,
`mybatis-plus3`.role mp3r
<where>
and mp2ur.user_id=mpu.id AND mp2ur.role_id=mp3r.id
<if test="id!=null and id !='' ">
AND mpu.id=#{id}
</if>
</where>
</select>
查看Druid監控
發現三個數據源均在此排列
Url監控
結語
到這裏,本次項目整合就結束了,附上我的項目源碼
SpringBoot整合Mybatis-plus(二) 多數據源Druid監控,Atomikos處理事務,跨庫連表查詢