這篇文章主要介紹springboot+mybatisplus+druid如何實(shí)現(xiàn)多數(shù)據(jù)源+分布式事務(wù),文中介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們一定要看完!
成都創(chuàng)新互聯(lián)服務(wù)項(xiàng)目包括浉河網(wǎng)站建設(shè)、浉河網(wǎng)站制作、浉河網(wǎng)頁制作以及浉河網(wǎng)絡(luò)營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,浉河網(wǎng)站推廣取得了明顯的社會效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到?jīng)负邮》莸牟糠殖鞘校磥硐嘈艜^續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!
jdk環(huán)境:1.8
springboot:2.1.3.RELEASE
mybatisplus:3.2.0
本文主要用atomikos的AtomikosDataSourceBean+druid配置連接池,另一個是AtomikosNonXADataSourceBean
1、maven 需要注意MySQL-connector-java版本為6.x,超過這個版本可能會報異常
com.baomidou
mybatis-plus-boot-starter
3.2.0
org.springframework.boot
spring-boot-starter-jta-atomikos
mysql
mysql-connector-java
6.0.6
org.springframework.boot
spring-boot-starter-aop
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-configuration-processor
true
com.alibaba
druid-spring-boot-starter
1.1.10
2、新建DataSourceContextHolder.java 提供設(shè)置、獲取、清除當(dāng)前數(shù)據(jù)源的方法;
public class DataSourceContextHolder {
private static final ThreadLocal contextHolder = new InheritableThreadLocal<>();
/**
* 設(shè)置數(shù)據(jù)源
*
* @param db
*/
public static void setDataSource(String db) {
contextHolder.set(db);
}
/**
* 取得當(dāng)前數(shù)據(jù)源
*
* @return
*/
public static String getDataSource() {
return contextHolder.get();
}
/**
* 清除上下文數(shù)據(jù)
*/
public static void clear() {
contextHolder.remove();
}
}
3、DataSource.java 數(shù)據(jù)源注解 及DataSourceKeyEnum.java 數(shù)據(jù)源枚舉類;并提供獲取枚舉的方法
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
DataSourceKeyEnum value();
}
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public enum DataSourceKeyEnum {
MASTER("master"),
/**
* 表示 所有的SLAVE, 隨機(jī)選擇一個SLAVE0, 或者SLAVE1
*/
SLAVE("slave"),
SLAVE0("slave0"),
SLAVE1("slave1"),;
@Getter
private String value;
DataSourceKeyEnum(String value) {
this.value = value;
}
public static List getSlaveList() {
return Arrays.asList(SLAVE0, SLAVE1);
}
/**
* 根據(jù)方法名稱選擇
*
* @param name 方法名稱
*/
public static DataSourceKeyEnum getDSKeyByMethodName(String name) {
if (StringUtils.isEmpty(name)) {
return null;
}
if (name.contains("update") || name.contains("delete") || name.contains("remove") || name.contains("insert")) {
return MASTER;
}
if (name.contains("select") || name.contains("query") || name.contains("find") || name.contains("get")) {
List list = getSlaveList();
Collections.shuffle(list);
return list.get(0);
}
return MASTER;
}
/**
* 根據(jù)注解獲取數(shù)據(jù)源
*
* @param dataSource
* @return
*/
public static DataSourceKeyEnum getDataSourceKey(DataSource dataSource) {
if (dataSource == null) {
return MASTER;
}
if (dataSource.value() == DataSourceKeyEnum.SLAVE) {
List dataSourceKeyList = DataSourceKeyEnum.getSlaveList();
// FIXME 目前亂序
Collections.shuffle(dataSourceKeyList);
return dataSourceKeyList.get(0);
} else {
return dataSource.value();
}
}
/**
* 根據(jù)className和Method 獲取數(shù)據(jù)源枚舉
*
* @param className
* @return
*/
public static String getByClassName(String className, Method method) {
//方法上的注解
DataSource dataSource = AnnotationUtils.findAnnotation(method, DataSource.class);
DataSourceKeyEnum keyEnum;
if (dataSource != null) {//注解存在則以注解為主
keyEnum = DataSourceKeyEnum.getDataSourceKey(dataSource);
} else {
keyEnum = DataSourceKeyEnum.getDSKeyByMethodName(method.getName());
}
return keyEnum.getValue();
}
}
4、DataSourceAspect.java 數(shù)據(jù)源切面類,這里是只做了對mapper層攔截,包括攔截了mybatisplus的公共BaseMapper
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
@Component
@Slf4j
@Aspect
@Order(-1)
public class DataSourceAspect {
@Pointcut("execution(* com.admin.*.dao.*Mapper.*(..))||execution(* com.baomidou.mybatisplus.core.mapper.*Mapper.*(..)))")
public void pointCut() {
}
@Around("pointCut()")
public Object doBefore(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
//攔截到的公共BaseMapper里的接口可通過該方式獲得具體實(shí)現(xiàn)類的信息
Type[] types = AopUtils.getTargetClass(pjp.getTarget()).getGenericInterfaces(); // getGenericInterfaces方法能夠獲取類/接口實(shí)現(xiàn)的所有接口
String name = types[0].getTypeName();
String dataSource = DataSourceKeyEnum.getByClassName(name, method);
log.info("選擇的數(shù)據(jù)源:"+dataSource);
DataSourceContextHolder.setDataSource(dataSource);
Object o=pjp.proceed();
DataSourceContextHolder.clear();
return o;
}
}
5、application.yml 相關(guān)配置
spring:
datasource:
druid:
master:
xaDataSourceClassName: com.alibaba.druid.pool.xa.DruidXADataSource
uniqueResourceName: master
xaDataSource:
url: jdbc:mysql://127.0.0.1:3306/test?charset=utf8mb4&useUnicode=true&characterEncoding=utf8&useSSL=false&autoReconnect=true&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
initial-size: 5
min-idle: 5
max-active: 20
#獲取連接等待超時時間
max-wait: 60000
#間隔多久進(jìn)行一次檢測,檢測需要關(guān)閉的空閑連接
time-between-eviction-runs-millis: 60000
#一個連接在池中最小生存的時間
min-evictable-idle-time-millis: 300000
#指定獲取連接時連接校驗(yàn)的sql查詢語句
validation-query: SELECT 'x'
#驗(yàn)證連接的有效性
test-while-idle: true
#獲取連接時候驗(yàn)證,會影響性能(不建議true)
test-on-borrow: false
#打開PSCache,并指定每個連接上PSCache的大小。oracle設(shè)為true,mysql設(shè)為false。分庫分表較多推薦設(shè)置為false
pool-prepared-statements: false
max-pool-prepared-statement-per-connection-size: 20
# 配置監(jiān)控統(tǒng)計(jì)攔截的filters,去掉后監(jiān)控界面sql無法統(tǒng)計(jì),'wall'用于防火墻
filters: config,wall,stat
slave0:
xaDataSourceClassName: com.alibaba.druid.pool.xa.DruidXADataSource
uniqueResourceName: slave0
xaDataSource:
url: jdbc:mysql://127.0.0.1:3306/test?charset=utf8mb4&useUnicode=true&characterEncoding=utf8&useSSL=false&autoReconnect=true&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
initial-size: 5
min-idle: 5
max-active: 20
#獲取連接等待超時時間
max-wait: 60000
#間隔多久進(jìn)行一次檢測,檢測需要關(guān)閉的空閑連接
time-between-eviction-runs-millis: 60000
#一個連接在池中最小生存的時間
min-evictable-idle-time-millis: 300000
#指定獲取連接時連接校驗(yàn)的sql查詢語句
validation-query: SELECT 'x'
#驗(yàn)證連接的有效性
test-while-idle: true
#獲取連接時候驗(yàn)證,會影響性能(不建議true)
test-on-borrow: false
#打開PSCache,并指定每個連接上PSCache的大小。oracle設(shè)為true,mysql設(shè)為false。分庫分表較多推薦設(shè)置為false
pool-prepared-statements: false
max-pool-prepared-statement-per-connection-size: 20
# 配置監(jiān)控統(tǒng)計(jì)攔截的filters,去掉后監(jiān)控界面sql無法統(tǒng)計(jì),'wall'用于防火墻
filters: config,wall,stat
slave1:
xaDataSourceClassName: com.alibaba.druid.pool.xa.DruidXADataSource
uniqueResourceName: slave1
xaDataSource:
url: jdbc:mysql://127.0.0.1:3306/test?charset=utf8mb4&useUnicode=true&characterEncoding=utf8&useSSL=false&autoReconnect=true&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
initial-size: 5
min-idle: 5
max-active: 20
#獲取連接等待超時時間
max-wait: 60000
#間隔多久進(jìn)行一次檢測,檢測需要關(guān)閉的空閑連接
time-between-eviction-runs-millis: 60000
#一個連接在池中最小生存的時間
min-evictable-idle-time-millis: 300000
#指定獲取連接時連接校驗(yàn)的sql查詢語句
validation-query: SELECT 'x'
#驗(yàn)證連接的有效性
test-while-idle: true
#獲取連接時候驗(yàn)證,會影響性能(不建議true)
test-on-borrow: false
#打開PSCache,并指定每個連接上PSCache的大小。oracle設(shè)為true,mysql設(shè)為false。分庫分表較多推薦設(shè)置為false
pool-prepared-statements: false
max-pool-prepared-statement-per-connection-size: 20
# 配置監(jiān)控統(tǒng)計(jì)攔截的filters,去掉后監(jiān)控界面sql無法統(tǒng)計(jì),'wall'用于防火墻
filters: config,wall,stat
web-stat-filter:
enabled: true
url-pattern: /*
exclusions: /druid/*,*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico
session-stat-enable: true
session-stat-max-count: 10
stat-view-servlet:
enabled: true
url-pattern: /druid/*
reset-enable: true
login-username: admin
login-password: admin
jta:
atomikos:
properties:
log-base-dir: ../logs
transaction-manager-id: txManager #默認(rèn)取計(jì)算機(jī)的IP地址 需保證生產(chǎn)環(huán)境值唯一
6、復(fù)制SqlSessionTemplate.java里所有代碼新建到MySqlSessionTemplate.java然后繼承SqlSessionTemplate.java,并按照下方替換相應(yīng)的方法(只貼出了更改了的地方)
import lombok.Getter;
import lombok.Setter;
public class MySqlSessionTemplate extends SqlSessionTemplate {
@Getter
@Setter
private Map targetSqlSessionFactories;
@Getter
@Setter
private SqlSessionFactory defaultTargetSqlSessionFactory;
public MySqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
super(sqlSessionFactory, executorType, exceptionTranslator);
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[]{SqlSession.class}, new MySqlSessionTemplate.SqlSessionInterceptor());
this.defaultTargetSqlSessionFactory = sqlSessionFactory;
}
//TODO 主要修改了這一塊,并且用到sqlSessionFactory的地方都改調(diào)用該方法獲取
public SqlSessionFactory getSqlSessionFactory() {
SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactories.get(DataSourceContextHolder.getDataSource());
if (targetSqlSessionFactory != null) {
return targetSqlSessionFactory;
} else if (defaultTargetSqlSessionFactory != null) {
return defaultTargetSqlSessionFactory;
} else {
Assert.notNull(targetSqlSessionFactories, "Property 'targetSqlSessionFactories' or 'defaultTargetSqlSessionFactory' are required");
}
return this.sqlSessionFactory;
}
/**
* {@inheritDoc}
*/
@Override
public Configuration getConfiguration() {
return this.getSqlSessionFactory().getConfiguration();
}
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(MySqlSessionTemplate.this.getSqlSessionFactory(),
MySqlSessionTemplate.this.executorType, MySqlSessionTemplate.this.exceptionTranslator);
try {無錫人流哪家好 http://www.wxbhffk.com/
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, MySqlSessionTemplate.this.getSqlSessionFactory())) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (MySqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, MySqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = MySqlSessionTemplate.this.exceptionTranslator
.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, MySqlSessionTemplate.this.getSqlSessionFactory());
}
}
}
}
}
7、新建MyBatisPlusConfiguration.java,mybatisplus配置 和多數(shù)據(jù)源配置
import com.alibaba.druid.pool.xa.DruidXADataSource;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import com.baomidou.mybatisplus.autoconfigure.SpringBootVFS;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.parser.ISqlParser;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.tenant.TenantHandler;
import com.baomidou.mybatisplus.extension.plugins.tenant.TenantSqlParser;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.admin.util.datasource.DataSourceKeyEnum;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
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 java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Configuration
@MapperScan(basePackages = {"com.admin.*.dao", "com.baomidou.mybatisplus.samples.quickstart.mapper"}, sqlSessionTemplateRef = "sqlSessionTemplate")
public class MyBatisPlusConfiguration {
@Bean
@Primary //多數(shù)據(jù)源時需要加上該注解,加一個即可
@ConfigurationProperties(prefix = "spring.datasource.druid.master")
public AtomikosDataSourceBean userMaster() {
return new AtomikosDataSourceBean();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.druid.slave0")
public AtomikosDataSourceBean userSlave0() {
return new AtomikosDataSourceBean();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.druid.slave1")
public AtomikosDataSourceBean userSlave1() {
return new AtomikosDataSourceBean();
}
@Bean(name = "sqlSessionTemplate")
public MySqlSessionTemplate customSqlSessionTemplate() throws Exception {
Map sqlSessionFactoryMap = new HashMap() {{
put(DataSourceKeyEnum.MASTER.getValue(), createSqlSessionFactory(userMaster()));
put(DataSourceKeyEnum.SLAVE0.getValue(), createSqlSessionFactory(userSlave0()));
put(DataSourceKeyEnum.SLAVE1.getValue(), createSqlSessionFactory(userSlave1()));
}};
MySqlSessionTemplate sqlSessionTemplate = new MySqlSessionTemplate(sqlSessionFactoryMap.get(DataSourceKeyEnum.MASTER.getValue()));
sqlSessionTemplate.setTargetSqlSessionFactories(sqlSessionFactoryMap);
return sqlSessionTemplate;
}
/**
* 創(chuàng)建數(shù)據(jù)源
*
* @param dataSource
* @return
*/
private SqlSessionFactory createSqlSessionFactory(AtomikosDataSourceBean dataSource) throws Exception {
dataSource.setMaxPoolSize(10);
dataSource.setMinPoolSize(2);
dataSource.setPoolSize(2);
dataSource.setMaxIdleTime(60);//最大閑置時間,超過最小連接池的連接將關(guān)閉
dataSource.setMaxLifetime(1200);//連接最大閑置時間 單位s 全部的連接超時將關(guān)閉
dataSource.setTestQuery(druidDataSource.getValidationQuery());//前期先每次請求前都執(zhí)行該操作保證連接有效,后期可用定時任務(wù)執(zhí)行
dataSource.setMaintenanceInterval(60);//定時維護(hù)線程周期 單位秒
//以上配置可提取到.yml內(nèi)通過ConfigurationProperties注解注入
dataSource.init();//項(xiàng)目啟動則初始化連接
MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
sqlSessionFactory.setDataSource(dataSource);
sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/com/admin/*/dao/xml/*.xml"));
sqlSessionFactory.setVfs(SpringBootVFS.class);
MybatisConfiguration configuration = new MybatisConfiguration();
//configuration.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class);
configuration.setJdbcTypeForNull(JdbcType.NULL);
configuration.setMapUnderscoreToCamelCase(false);
configuration.setCacheEnabled(false);
sqlSessionFactory.setConfiguration(configuration);
sqlSessionFactory.setPlugins(paginationInterceptor());
sqlSessionFactory.afterPropertiesSet();
return sqlSessionFactory.getObject();
}
/*
* 自定義的分頁插件,自動識別數(shù)據(jù)庫類型
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
事務(wù)使用方法如單數(shù)據(jù)源一樣:對應(yīng)的方法或類上加@Transactional注解即可
以上是“springboot+mybatisplus+druid如何實(shí)現(xiàn)多數(shù)據(jù)源+分布式事務(wù)”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對大家有幫助,更多相關(guān)知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!