1. 全局异常捕获错误问题修复
2. 多数据源允许仅修改配置文件即可追加更多数据源,且与dynamic-datasource使用方式相似,并追加字符串指定数据源的方式 3. 多数据源加分布式事务时从数据源无法被druid监控到问题修复
This commit is contained in:
parent
ee2c9c511d
commit
5a16b39164
149
doc/关于多数据源与分布式事务.drawio
Normal file
149
doc/关于多数据源与分布式事务.drawio
Normal file
@ -0,0 +1,149 @@
|
||||
<mxfile host="65bd71144e">
|
||||
<diagram id="27K8aLRXgDd5Ry5UGMpc" name="第 1 页">
|
||||
<mxGraphModel dx="2298" dy="760" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="7" value="" style="edgeStyle=none;html=1;" parent="1" source="2" target="4" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="2" value="数据源1" style="html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="510" y="340" width="110" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="8" value="" style="edgeStyle=none;html=1;" parent="1" source="3" target="5" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="3" value="数据源2" style="html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="-90" y="340" width="110" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="9" style="edgeStyle=none;html=1;" parent="1" source="4" target="28" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="571.4814814814818" y="470" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="44" style="edgeStyle=none;html=1;" parent="1" source="4" target="43" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="4" value="<div style="background-color: rgb(255, 255, 255); font-family: Consolas, &quot;Courier New&quot;, monospace; font-size: 14px; line-height: 19px;">DataSource1</div>" style="html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="325" y="340" width="110" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="10" style="edgeStyle=none;html=1;" parent="1" source="5" target="28" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="556.25" y="470" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="45" style="edgeStyle=none;html=1;" parent="1" source="5" target="43" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="5" value="<div style="background-color: rgb(255, 255, 255); font-family: Consolas, &quot;Courier New&quot;, monospace; font-size: 14px; line-height: 19px;">DataSource2</div>" style="html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="100" y="340" width="110" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="15" value="" style="edgeStyle=none;html=1;" parent="1" source="11" target="28" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="636.25" y="470" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="11" value="<div style="background-color: rgb(255, 255, 255); font-family: Consolas, &quot;Courier New&quot;, monospace; font-size: 14px; line-height: 19px;"><div style="line-height: 19px;">DynamicDataSourceContextHolder</div></div>" style="html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="460" y="460" width="270" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="13" value="<div style="background-color: rgb(255, 255, 255); font-family: Consolas, &quot;Courier New&quot;, monospace; font-size: 14px; line-height: 19px;"><h1 style="box-sizing: border-box; outline: 0px; margin: 0px; padding: 0px; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 28px; overflow-wrap: break-word; color: rgb(34, 34, 38); word-break: break-all; font-variant-ligatures: common-ligatures; text-align: start;" id="articleContentId" class="title-article">Spring AbstractRoutingDataSource<br><br></h1><div>* determineCurrentLookupKey</div></div>" style="html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="-380" y="455" width="520" height="70" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="19" value="" style="edgeStyle=none;html=1;" parent="1" source="16" target="18" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="16" value="方法1" style="html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="880" y="340" width="110" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="20" value="" style="edgeStyle=none;html=1;" parent="1" source="17" target="18" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="17" value="方法2" style="html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="880" y="580" width="110" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="21" value="" style="edgeStyle=none;html=1;" parent="1" source="18" target="11" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="18" value="<div style="background-color: rgb(255, 255, 255); font-family: Consolas, &quot;Courier New&quot;, monospace; font-size: 14px; line-height: 19px;">DataSourceAspect</div>" style="html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="790" y="460" width="290" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="22" value="" style="edgeStyle=none;html=1;" parent="1" source="23" target="26" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="23" value="数据源1" style="html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="-90" y="580" width="110" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="24" value="" style="edgeStyle=none;html=1;" parent="1" source="25" target="27" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="25" value="数据源2" style="html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="510" y="580" width="110" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="29" style="edgeStyle=none;html=1;" parent="1" source="26" target="28" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="46" style="edgeStyle=none;html=1;" parent="1" source="26" target="47" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="240" y="790" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="26" value="<div style="background-color: rgb(255, 255, 255); font-family: Consolas, &quot;Courier New&quot;, monospace; font-size: 14px; line-height: 19px;"><div style="line-height: 19px;">AtomikosDataSourceBean1</div></div>" style="html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="60" y="580" width="190" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="30" style="edgeStyle=none;html=1;" parent="1" source="27" target="28" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="49" style="edgeStyle=none;html=1;" parent="1" source="27" target="48" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="27" value="<div style="background-color: rgb(255, 255, 255); font-family: Consolas, &quot;Courier New&quot;, monospace; font-size: 14px; line-height: 19px;"><div style="line-height: 19px;">AtomikosDataSourceBean2</div></div>" style="html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="285" y="580" width="190" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="59" style="edgeStyle=none;html=1;" parent="1" source="28" target="13" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="28" value="<div style="background-color: rgb(255, 255, 255); font-family: Consolas, &quot;Courier New&quot;, monospace; font-size: 14px; line-height: 19px;">DynamicDataSource</div>" style="whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="170" y="455" width="230" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="54" value="" style="edgeStyle=none;html=1;" parent="1" source="43" target="53" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="43" value="<div style="background-color: rgb(255, 255, 255); font-family: Consolas, &quot;Courier New&quot;, monospace; font-size: 14px; line-height: 19px;">sqlSessionFactory</div>" style="whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="160" y="210" width="210" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="51" style="edgeStyle=none;html=1;" parent="1" source="47" target="50" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="47" value="<div style="background-color: rgb(255, 255, 255); font-family: Consolas, &quot;Courier New&quot;, monospace; font-size: 14px; line-height: 19px;">SqlSessionFactory1</div>" style="html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="55" y="670" width="200" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="52" style="edgeStyle=none;html=1;" parent="1" source="48" target="50" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="48" value="<div style="background-color: rgb(255, 255, 255); font-family: Consolas, &quot;Courier New&quot;, monospace; font-size: 14px; line-height: 19px;">SqlSessionFactory2</div>" style="whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="280" y="670" width="200" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="56" style="edgeStyle=none;html=1;" parent="1" source="50" target="57" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="-593.9655172413791" y="670" as="targetPoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="-470" y="815"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="50" value="<div style="background-color: rgb(255, 255, 255); font-family: Consolas, &quot;Courier New&quot;, monospace; font-size: 14px; line-height: 19px;">DynamicSqlSessionTemplate</div>" style="whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="160" y="790" width="210" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="58" style="edgeStyle=none;html=1;" parent="1" source="53" target="57" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="53" value="<div style="background-color: rgb(255, 255, 255); font-family: Consolas, &quot;Courier New&quot;, monospace; font-size: 14px; line-height: 19px;"><div style="line-height: 19px;">SqlSessionTemplate</div></div>" style="whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="-573" y="210" width="210" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="57" value="mybatis" style="html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="-523" y="465" width="110" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
@ -3,20 +3,20 @@ spring:
|
||||
datasource:
|
||||
type: com.alibaba.druid.pool.DruidDataSource
|
||||
driverClassName: com.mysql.cj.jdbc.Driver
|
||||
druid:
|
||||
dynamic:
|
||||
primary: MASTER
|
||||
datasource:
|
||||
# 主库数据源
|
||||
master:
|
||||
MASTER:
|
||||
url: jdbc:mysql://127.0.0.1/ry?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
username: root
|
||||
password: 123456
|
||||
# 从库数据源
|
||||
slave:
|
||||
# 从数据源开关/默认关闭
|
||||
enabled: false
|
||||
url: jdbc:mysql://127.0.0.1/ruoyi?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
username: root
|
||||
password: 123456
|
||||
|
||||
# SLAVE:
|
||||
# url: jdbc:mysql://127.0.0.1/ruoyi?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
# username: root
|
||||
# password: 123456
|
||||
druid:
|
||||
# 初始连接数
|
||||
initialSize: 5
|
||||
# 最小连接池数量
|
||||
|
@ -6,6 +6,7 @@ import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import com.ruoyi.common.enums.DataSourceType;
|
||||
|
||||
/**
|
||||
@ -19,10 +20,15 @@ import com.ruoyi.common.enums.DataSourceType;
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
public @interface DataSource
|
||||
{
|
||||
public @interface DataSource {
|
||||
|
||||
/**
|
||||
* 切换数据源名称
|
||||
* 切换数据源名称 - 枚举方式
|
||||
*/
|
||||
public DataSourceType value() default DataSourceType.MASTER;
|
||||
|
||||
/**
|
||||
* 切换数据源名称 - 字符串方式
|
||||
*/
|
||||
public String name() default "";
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.ruoyi.framework.aspectj;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
@ -11,6 +12,7 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.ruoyi.common.annotation.DataSource;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.framework.datasource.DynamicDataSourceContextHolder;
|
||||
@ -23,33 +25,31 @@ import com.ruoyi.framework.datasource.DynamicDataSourceContextHolder;
|
||||
@Aspect
|
||||
@Order(1)
|
||||
@Component
|
||||
public class DataSourceAspect
|
||||
{
|
||||
public class DataSourceAspect {
|
||||
protected Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
@Pointcut("@annotation(com.ruoyi.common.annotation.DataSource)"
|
||||
+ "|| @within(com.ruoyi.common.annotation.DataSource)")
|
||||
public void dsPointCut()
|
||||
{
|
||||
public void dsPointCut() {
|
||||
|
||||
}
|
||||
|
||||
@Around("dsPointCut()")
|
||||
public Object around(ProceedingJoinPoint point) throws Throwable
|
||||
{
|
||||
public Object around(ProceedingJoinPoint point) throws Throwable {
|
||||
DataSource dataSource = getDataSource(point);
|
||||
|
||||
if (StringUtils.isNotNull(dataSource))
|
||||
{
|
||||
if (StringUtils.isNotNull(dataSource)) {
|
||||
if ("".equals(dataSource.name())) {
|
||||
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
|
||||
} else {
|
||||
DynamicDataSourceContextHolder.setDataSourceType(dataSource.name());
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return point.proceed();
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
try {
|
||||
return point.proceed();
|
||||
} finally {
|
||||
// 销毁数据源 在执行方法之后
|
||||
DynamicDataSourceContextHolder.clearDataSourceType();
|
||||
}
|
||||
@ -58,12 +58,10 @@ public class DataSourceAspect
|
||||
/**
|
||||
* 获取需要切换的数据源
|
||||
*/
|
||||
public DataSource getDataSource(ProceedingJoinPoint point)
|
||||
{
|
||||
public DataSource getDataSource(ProceedingJoinPoint point) {
|
||||
MethodSignature signature = (MethodSignature) point.getSignature();
|
||||
DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
|
||||
if (Objects.nonNull(dataSource))
|
||||
{
|
||||
if (Objects.nonNull(dataSource)) {
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@ package com.ruoyi.framework.config;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@ -11,20 +10,13 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import com.alibaba.druid.spring.boot3.autoconfigure.properties.DruidStatProperties;
|
||||
import com.alibaba.druid.util.Utils;
|
||||
import com.atomikos.jdbc.AtomikosDataSourceBean;
|
||||
import com.ruoyi.common.enums.DataSourceType;
|
||||
import com.ruoyi.common.utils.spring.SpringUtils;
|
||||
import com.ruoyi.framework.config.properties.DruidProperties;
|
||||
import com.ruoyi.framework.datasource.DynamicDataSource;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
@ -41,88 +33,20 @@ import jakarta.servlet.ServletResponse;
|
||||
@Configuration
|
||||
public class DruidConfig {
|
||||
|
||||
Logger logger = LoggerFactory.getLogger(DruidConfig.class);
|
||||
|
||||
public static final String MASTER = DataSourceType.MASTER.name();
|
||||
|
||||
public static final String SLAVE = DataSourceType.SLAVE.name();
|
||||
|
||||
@Autowired
|
||||
private DruidProperties druidProperties;
|
||||
DynamicDataSourceProperties dataSourceProperties;
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
@DependsOn({ "transactionManager" })
|
||||
@ConfigurationProperties("spring.datasource.druid.master")
|
||||
public DataSource masterDataSource(Environment env) {
|
||||
String prefix = "spring.datasource.druid.master.";
|
||||
return getDataSource(env, prefix, MASTER);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties("spring.datasource.druid.slave")
|
||||
@DependsOn({ "transactionManager" })
|
||||
@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
|
||||
public DataSource slaveDataSource(Environment env) {
|
||||
String prefix = "spring.datasource.druid.slave.";
|
||||
return getDataSource(env, prefix, SLAVE);
|
||||
}
|
||||
|
||||
protected DataSource getDataSource(Environment env, String prefix, String dataSourceName) {
|
||||
Properties prop = build(env, prefix);
|
||||
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
|
||||
ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
|
||||
// 添加连接池限制
|
||||
ds.setMaxPoolSize(50);
|
||||
ds.setMinPoolSize(5);
|
||||
ds.setBorrowConnectionTimeout(60);
|
||||
ds.setUniqueResourceName(dataSourceName);
|
||||
ds.setXaProperties(prop);
|
||||
return ds;
|
||||
}
|
||||
|
||||
protected 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("initialSize", druidProperties.getInitialSize());
|
||||
prop.put("minIdle", druidProperties.getMinIdle());
|
||||
prop.put("maxActive", druidProperties.getMaxActive());
|
||||
prop.put("maxWait", druidProperties.getMaxWait());
|
||||
prop.put("timeBetweenEvictionRunsMillis", druidProperties.getTimeBetweenEvictionRunsMillis());
|
||||
prop.put("minEvictableIdleTimeMillis", druidProperties.getMinEvictableIdleTimeMillis());
|
||||
prop.put("maxEvictableIdleTimeMillis", druidProperties.getMaxEvictableIdleTimeMillis());
|
||||
prop.put("validationQuery", druidProperties.getValidationQuery());
|
||||
prop.put("testWhileIdle", druidProperties.isTestWhileIdle());
|
||||
prop.put("testOnBorrow", druidProperties.isTestOnBorrow());
|
||||
prop.put("testOnReturn", druidProperties.isTestOnReturn());
|
||||
return prop;
|
||||
}
|
||||
Logger logger = LoggerFactory.getLogger(DruidConfig.class);
|
||||
|
||||
@Bean(name = "dynamicDataSource")
|
||||
@Primary
|
||||
public DynamicDataSource dataSource(DataSource masterDataSource) {
|
||||
Map<Object, Object> targetDataSources = new HashMap<>();
|
||||
targetDataSources.put(MASTER, masterDataSource);
|
||||
setDataSource(targetDataSources, SLAVE, "slaveDataSource");
|
||||
return new DynamicDataSource(masterDataSource, targetDataSources);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据源
|
||||
*
|
||||
* @param targetDataSources 备选数据源集合
|
||||
* @param sourceName 数据源名称
|
||||
* @param beanName bean名称
|
||||
*/
|
||||
public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName) {
|
||||
try {
|
||||
DataSource dataSource = SpringUtils.getBean(beanName);
|
||||
targetDataSources.put(sourceName, dataSource);
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to register a data source:{}", beanName);
|
||||
public DynamicDataSource dataSource() {
|
||||
Map<Object, Object> objectMap = new HashMap<>();
|
||||
Map<String, DataSource> targetDataSources = dataSourceProperties.getTargetDataSources();
|
||||
for (Map.Entry<String, DataSource> entry : targetDataSources.entrySet()) {
|
||||
objectMap.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return new DynamicDataSource(targetDataSources.get(dataSourceProperties.getPrimary()), objectMap);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,134 @@
|
||||
package com.ruoyi.framework.config;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
|
||||
import com.alibaba.druid.pool.DruidDataSource;
|
||||
import com.alibaba.druid.pool.xa.DruidXADataSource;
|
||||
import com.atomikos.jdbc.AtomikosDataSourceBean;
|
||||
import com.ruoyi.common.utils.spring.SpringUtils;
|
||||
import com.ruoyi.framework.config.properties.DruidProperties;
|
||||
|
||||
@Configuration
|
||||
@DependsOn({ "transactionManager" })
|
||||
@ConfigurationProperties(prefix = "spring.datasource.dynamic")
|
||||
public class DynamicDataSourceProperties implements InitializingBean {
|
||||
private Map<String, DataSourceProperties> datasource;
|
||||
private String primary;
|
||||
private Map<String, DataSource> targetDataSources = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
datasource.forEach((name, props) -> targetDataSources.put(name, createDataSource(name, props)));
|
||||
}
|
||||
|
||||
protected DataSource createDataSource(String name, DataSourceProperties dataSourceProperties) {
|
||||
Properties prop = build(dataSourceProperties);
|
||||
DruidXADataSource dataSource = new DruidXADataSource();
|
||||
dataSource.setConnectProperties(prop);
|
||||
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
|
||||
ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
|
||||
// 添加连接池限制
|
||||
ds.setMaxPoolSize(10);
|
||||
ds.setMinPoolSize(3);
|
||||
ds.setBorrowConnectionTimeout(60);
|
||||
ds.setUniqueResourceName(name);
|
||||
ds.setXaProperties(prop);
|
||||
setProperties(dataSource, prop);
|
||||
ds.setXaDataSource(dataSource);
|
||||
return ds;
|
||||
}
|
||||
|
||||
protected Properties build(DataSourceProperties dataSourceProperties) {
|
||||
Properties prop = new Properties();
|
||||
DruidProperties druidProperties = SpringUtils.getBean(DruidProperties.class);
|
||||
prop.put("url", dataSourceProperties.getUrl());
|
||||
prop.put("username", dataSourceProperties.getUsername());
|
||||
prop.put("password", dataSourceProperties.getPassword());
|
||||
prop.put("initialSize", druidProperties.getInitialSize());
|
||||
prop.put("minIdle", druidProperties.getMinIdle());
|
||||
prop.put("maxActive", druidProperties.getMaxActive());
|
||||
prop.put("maxWait", druidProperties.getMaxWait());
|
||||
prop.put("timeBetweenEvictionRunsMillis", druidProperties.getTimeBetweenEvictionRunsMillis());
|
||||
prop.put("minEvictableIdleTimeMillis", druidProperties.getMinEvictableIdleTimeMillis());
|
||||
prop.put("maxEvictableIdleTimeMillis", druidProperties.getMaxEvictableIdleTimeMillis());
|
||||
prop.put("validationQuery", druidProperties.getValidationQuery());
|
||||
prop.put("testWhileIdle", druidProperties.isTestWhileIdle());
|
||||
prop.put("testOnBorrow", druidProperties.isTestOnBorrow());
|
||||
prop.put("testOnReturn", druidProperties.isTestOnReturn());
|
||||
return prop;
|
||||
}
|
||||
|
||||
protected void setProperties(DruidDataSource dataSource, Properties prop) {
|
||||
dataSource.setUrl(prop.getProperty("url"));
|
||||
dataSource.setUsername(prop.getProperty("username"));
|
||||
dataSource.setPassword(prop.getProperty("password"));
|
||||
if(prop.getProperty("initialSize") != null){
|
||||
dataSource.setInitialSize(Integer.parseInt(prop.getProperty("initialSize")));
|
||||
}
|
||||
if(prop.getProperty("minIdle") != null){
|
||||
dataSource.setMinIdle(Integer.parseInt(prop.getProperty("minIdle")));
|
||||
}
|
||||
if(prop.getProperty("maxActive") != null){
|
||||
dataSource.setMaxActive(Integer.parseInt(prop.getProperty("maxActive")));
|
||||
}
|
||||
if(prop.getProperty("maxWait") != null){
|
||||
dataSource.setMaxWait(Long.parseLong(prop.getProperty("maxWait")));
|
||||
}
|
||||
if(prop.getProperty("timeBetweenEvictionRunsMillis") != null){
|
||||
dataSource.setTimeBetweenEvictionRunsMillis(Long.parseLong(prop.getProperty("timeBetweenEvictionRunsMillis")));
|
||||
}
|
||||
if(prop.getProperty("minEvictableIdleTimeMillis") != null){
|
||||
dataSource.setMinEvictableIdleTimeMillis(Long.parseLong(prop.getProperty("minEvictableIdleTimeMillis")));
|
||||
}
|
||||
if(prop.getProperty("maxEvictableIdleTimeMillis") != null){
|
||||
dataSource.setMaxEvictableIdleTimeMillis(Long.parseLong(prop.getProperty("maxEvictableIdleTimeMillis")));
|
||||
}
|
||||
if(prop.getProperty("validationQuery") != null){
|
||||
dataSource.setValidationQuery(prop.getProperty("validationQuery"));
|
||||
}
|
||||
if(prop.getProperty("testWhileIdle") != null){
|
||||
dataSource.setTestWhileIdle(Boolean.parseBoolean(prop.getProperty("testWhileIdle")));
|
||||
}
|
||||
if(prop.getProperty("testOnBorrow") != null){
|
||||
dataSource.setTestOnBorrow(Boolean.parseBoolean(prop.getProperty("testOnBorrow")));
|
||||
}
|
||||
if(prop.getProperty("testOnReturn") != null){
|
||||
dataSource.setTestOnReturn(Boolean.parseBoolean(prop.getProperty("testOnReturn")));
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, DataSourceProperties> getDatasource() {
|
||||
return datasource;
|
||||
}
|
||||
|
||||
public void setDatasource(Map<String, DataSourceProperties> datasource) {
|
||||
this.datasource = datasource;
|
||||
}
|
||||
|
||||
public String getPrimary() {
|
||||
return primary;
|
||||
}
|
||||
|
||||
public void setPrimary(String primary) {
|
||||
this.primary = primary;
|
||||
}
|
||||
|
||||
public Map<String, DataSource> getTargetDataSources() {
|
||||
return targetDataSources;
|
||||
}
|
||||
|
||||
public void setTargetDataSources(Map<String, DataSource> targetDataSources) {
|
||||
this.targetDataSources = targetDataSources;
|
||||
}
|
||||
|
||||
}
|
@ -1,8 +1,5 @@
|
||||
package com.ruoyi.framework.config;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.apache.ibatis.io.VFS;
|
||||
@ -11,19 +8,15 @@ import org.mybatis.spring.SqlSessionFactoryBean;
|
||||
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
|
||||
import com.ruoyi.common.enums.DataSourceType;
|
||||
import com.ruoyi.common.interceptor.mybatis.CreateSqlSessionFactory;
|
||||
import com.ruoyi.common.utils.MybatisUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.spring.SpringUtils;
|
||||
import com.ruoyi.framework.datasource.DynamicSqlSessionTemplate;
|
||||
|
||||
/**
|
||||
* Mybatis支持*匹配扫描包
|
||||
@ -57,25 +50,4 @@ public class MyBatisConfig {
|
||||
};
|
||||
}
|
||||
|
||||
@Bean(name = "sqlSessionTemplate")
|
||||
public DynamicSqlSessionTemplate sqlSessionTemplate(
|
||||
@Qualifier("sqlSessionFactoryMaster") SqlSessionFactory factoryMaster) throws Exception {
|
||||
Map<Object, SqlSessionFactory> sqlSessionFactoryMap = new HashMap<>();
|
||||
sqlSessionFactoryMap.put(DruidConfig.MASTER, factoryMaster);
|
||||
putSqlSessionFactory("sqlSessionFactorySlave", DataSourceType.SLAVE, sqlSessionFactoryMap);
|
||||
DynamicSqlSessionTemplate customSqlSessionTemplate = new DynamicSqlSessionTemplate(factoryMaster);
|
||||
customSqlSessionTemplate.setTargetSqlSessionFactorys(sqlSessionFactoryMap);
|
||||
return customSqlSessionTemplate;
|
||||
}
|
||||
|
||||
private void putSqlSessionFactory(String sqlSessionFactoryName, DataSourceType dataSourceType,
|
||||
Map<Object, SqlSessionFactory> sqlSessionFactoryMap) {
|
||||
try {
|
||||
SqlSessionFactory factorySlave = SpringUtils.getBean(sqlSessionFactoryName);
|
||||
sqlSessionFactoryMap.put(dataSourceType.name(), factorySlave);
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to register a SqlSessionFactory:{}", sqlSessionFactoryName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,17 +1,19 @@
|
||||
package com.ruoyi.framework.config;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.apache.ibatis.session.SqlSessionFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
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 com.atomikos.util.IntraVmObjectRegistry;
|
||||
import com.ruoyi.common.interceptor.mybatis.CreateSqlSessionFactory;
|
||||
import com.ruoyi.framework.datasource.DynamicSqlSessionTemplate;
|
||||
|
||||
@Configuration
|
||||
public class SqlSessionFactoryConfig {
|
||||
@ -19,17 +21,29 @@ public class SqlSessionFactoryConfig {
|
||||
@Autowired
|
||||
CreateSqlSessionFactory createSqlSessionFactory;
|
||||
|
||||
@Bean(name = "sqlSessionFactoryMaster")
|
||||
@Primary
|
||||
public SqlSessionFactory sqlSessionFactoryMaster(Environment env,
|
||||
@Qualifier("masterDataSource") DataSource dataSource) throws Exception {
|
||||
return createSqlSessionFactory.createSqlSessionFactory(env, dataSource);
|
||||
}
|
||||
@Autowired
|
||||
DynamicDataSourceProperties dataSourceProperties;
|
||||
|
||||
@Bean(name = "sqlSessionFactorySlave")
|
||||
@ConditionalOnBean(name = "slaveDataSource")
|
||||
public SqlSessionFactory sqlSessionFactorySlave(Environment env,
|
||||
@Qualifier("slaveDataSource") DataSource dataSource) throws Exception {
|
||||
return createSqlSessionFactory.createSqlSessionFactory(env, dataSource);
|
||||
@Bean(name = "sqlSessionTemplate")
|
||||
public DynamicSqlSessionTemplate sqlSessionTemplate(Environment env) throws Exception {
|
||||
Map<Object, SqlSessionFactory> sqlSessionFactoryMap = new HashMap<>();
|
||||
Map<String, DataSource> targetDataSources = dataSourceProperties.getTargetDataSources();
|
||||
for (Map.Entry<String, DataSource> entry : targetDataSources.entrySet()) {
|
||||
SqlSessionFactory sessionFactory = createSqlSessionFactory.createSqlSessionFactory(env, entry.getValue());
|
||||
sqlSessionFactoryMap.put(entry.getKey(), sessionFactory);
|
||||
|
||||
// 应对热重载的特殊处理
|
||||
Object ret = com.atomikos.icatch.config.Configuration.removeResource(entry.getKey());
|
||||
if (ret != null) {
|
||||
IntraVmObjectRegistry.removeResource(entry.getKey());
|
||||
}
|
||||
}
|
||||
SqlSessionFactory factoryMaster = sqlSessionFactoryMap.get(dataSourceProperties.getPrimary());
|
||||
if (factoryMaster == null) {
|
||||
throw new RuntimeException("找不到主库配置" + dataSourceProperties.getPrimary());
|
||||
}
|
||||
DynamicSqlSessionTemplate customSqlSessionTemplate = new DynamicSqlSessionTemplate(factoryMaster);
|
||||
customSqlSessionTemplate.setTargetSqlSessionFactorys(sqlSessionFactoryMap);
|
||||
return customSqlSessionTemplate;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
package com.ruoyi.framework.web.exception;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
@ -21,6 +19,8 @@ import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.html.EscapeUtil;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user