数据模块构建
大约 11 分钟
多数据源切换
server:
port: 80
spring:
datasource: # 数据源配置
master: # 主库数据源
type: com.alibaba.druid.pool.xa.DruidXADataSource
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.16.8:3306/yootk
username: yix
password: yix
slave: # 从库数据源
enabled: true #是否启用,默认不启用
type: com.alibaba.druid.pool.xa.DruidXADataSource
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.16.8:3306/muyan
username: yix
password: yix
druid:
initial-size: 5 # 初始化连接池大小
min-idle: 10 # 最小维持的连接池大小
max-active: 20 # 最大支持的连接池大小
max-wait: 60000 # 最大等待时间(毫秒)
connectTimeout: 30000 #配置连接超时时间
socketTimeout: 60000 # 配置网络超时时间
time-between-eviction-runs-millis: 60000 # 关闭空闲连接间隔(毫秒)
min-evictable-idle-time-millis: 300000 # 连接最小生存时间(毫秒)
maxEvictableIdleTimeMillis: 900000 #配置一个连接在池中最大生存的时间,单位是毫秒
validation-query: SELECT 1 FROM dual # 数据库状态检测
test-while-idle: true # 申请连接的时候检测连接是否有效
test-on-borrow: false # 申请连接时检测连接是否有效
test-on-return: false # 归还连接时检测连接是否有效
pool-prepared-statements: false # PSCache缓存
max-pool-prepared-statement-per-connection-size: 20 # 配置PS缓存
filters: stat,wall,slf4j
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=6000;druid.stat.logSlowSql=true;druid.slf4j.enabled=true;druid.slf4j.dataSourceLogEnabled=true;druid.slf4j.statementExecutableSqlLogEnable=true;druid.slf4j.resultSetLogEnabled=true;druid.wall.enabled=true;druid.wall.config.multiStatementAllow=true;druid.wall.config.deleteAllow=false
stat-view-servlet: # 监控界面配置
enabled: true # 启用druid监控界面
allow: 127.0.0.1 # 设置访问白名单,不填则允许所有访问
login-username: yix # 用户名
login-password: yix # 密码
reset-enable: true # 允许重置
url-pattern: /druid/* # 访问路径
web-stat-filter: # WEB监控
enabled: true # 启动URI监控
url-pattern: /* # 跟踪根路径下的全部服务
exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*" # 跟踪排除
aop-patterns: "com.yix.web.action.*" # Spring监控
package com.yix.framework.config.druid;
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.util.Utils;
import com.yix.common.enums.DataSourceType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import javax.servlet.*;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties;
/**
* @author wangdx
*/
@Configuration
public class DruidDataSourceConfig {
// 对应的application.yml配置项前缀:spring.datasource.druid.xx(xx是具体的配置子项)
private static final String DRUID_POOL_PREFIX = "spring.datasource.druid.";
private static final String DATABASE_MASTER_DRUID_PREFIX = "spring.datasource.master.";
private static final String DATABASE_SLAVE_DRUID_PREFIX = "spring.datasource.slave.";
@Bean("masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource(
@Autowired Environment env) { // 数据源的配置
AtomikosDataSourceBean dataSourceBean = new AtomikosDataSourceBean();
dataSourceBean.setXaDataSourceClassName(env.getProperty(DATABASE_MASTER_DRUID_PREFIX + "type"));
dataSourceBean.setUniqueResourceName(DataSourceType.MASTER.getSourceName());
dataSourceBean.setMinPoolSize(env.getProperty(DRUID_POOL_PREFIX + "min-idle", Integer.class));
dataSourceBean.setMaxPoolSize(env.getProperty(DRUID_POOL_PREFIX + "max-active", Integer.class));
Properties properties = build(env, DATABASE_MASTER_DRUID_PREFIX, DRUID_POOL_PREFIX);
dataSourceBean.setXaProperties(properties); // 保存所有的配置属性
return dataSourceBean;
}
@Bean("slaveDataSource")
@ConfigurationProperties(prefix = "spring.datasource.slave")
@ConditionalOnProperty(prefix = "spring.datasource.slave", name = "enabled", havingValue = "true")
public DataSource slaveDataSource(
@Autowired Environment env) { // 数据源的配置
AtomikosDataSourceBean dataSourceBean = new AtomikosDataSourceBean();
dataSourceBean.setXaDataSourceClassName(env.getProperty(DATABASE_SLAVE_DRUID_PREFIX + "type"));
dataSourceBean.setUniqueResourceName(DataSourceType.SLAVE.getSourceName());
dataSourceBean.setMinPoolSize(env.getProperty(DRUID_POOL_PREFIX + "min-idle", Integer.class));
dataSourceBean.setMaxPoolSize(env.getProperty(DRUID_POOL_PREFIX + "max-active", Integer.class));
Properties properties = build(env, DATABASE_SLAVE_DRUID_PREFIX, DRUID_POOL_PREFIX);
dataSourceBean.setXaProperties(properties); // 保存所有的配置属性
return dataSourceBean;
}
private Properties build(Environment env,
String databasePrefix, String druidPrefix) {
Properties prop = new Properties();
prop.put("url", env.getProperty(databasePrefix + "url"));
prop.put("username", env.getProperty(databasePrefix + "username"));
prop.put("password", env.getProperty(databasePrefix + "password"));
prop.put("driverClassName", env.getProperty(
databasePrefix + "driverClassName", ""));
prop.put("initialSize", env.getProperty(
druidPrefix + "initial-size", Integer.class));
prop.put("maxActive", env.getProperty(druidPrefix + "max-active", Integer.class));
prop.put("minIdle", env.getProperty(druidPrefix + "min-idle", Integer.class));
prop.put("maxWait", env.getProperty(druidPrefix + "max-wait", Integer.class));
prop.put("poolPreparedStatements", env.getProperty(
druidPrefix + "pool-prepared-statements", Boolean.class));
prop.put("maxPoolPreparedStatementPerConnectionSize",
env.getProperty(druidPrefix +
"max-pool-prepared-statement-per-connection-size", Integer.class));
prop.put("maxPoolPreparedStatementPerConnectionSize",
env.getProperty(druidPrefix +
"max-pool-prepared-statement-per-connection-size", Integer.class));
prop.put("validationQuery", env.getProperty(druidPrefix + "validation-query"));
prop.put("testOnBorrow", env.getProperty(
druidPrefix + "test-on-borrow", Boolean.class));
prop.put("testOnReturn", env.getProperty(
druidPrefix + "test-on-return", Boolean.class));
prop.put("testWhileIdle", env.getProperty(
druidPrefix + "test-while-idle", Boolean.class));
prop.put("timeBetweenEvictionRunsMillis",
env.getProperty(druidPrefix +
"time-between-eviction-runs-millis", Integer.class));
prop.put("minEvictableIdleTimeMillis", env.getProperty(druidPrefix +
"min-evictable-idle-time-millis", Integer.class));
prop.put("filters", env.getProperty(druidPrefix + "filters"));
prop.put("connectionProperties", env.getProperty(druidPrefix + "connectionProperties"));
return prop;
}
/**
* 去除监控页面底部的广告
*/
@SuppressWarnings({"rawtypes", "unchecked"})
@Bean
@ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled", havingValue = "true")
public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties) {
// 获取web监控页面的参数
DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
// 提取common.js的配置路径
String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
final String filePath = "support/http/resources/js/common.js";
// 创建filter进行过滤
Filter filter = new Filter() {
@Override
public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(request, response);
// 重置缓冲区,响应头不会被重置
response.resetBuffer();
// 获取common.js
String text = Utils.readFromResource(filePath);
// 正则替换banner, 除去底部的广告信息
text = text.replaceAll("<a.*?banner\"></a><br/>", "");
text = text.replaceAll("powered.*?shrek.wang</a>", "");
response.getWriter().write(text);
}
@Override
public void destroy() {
}
};
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(filter);
registrationBean.addUrlPatterns(commonJsPattern);
return registrationBean;
}
}
package com.yix.framework.config.dynamic;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.Map;
/**
* 数据源动态切换
*
* @author wangdx
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
*/
private static final ThreadLocal<String> DATASOURCE_CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 构建动态数据源
*
* @param defaultTargetDataSource 默认的数据源对象
* @param targetDataSources 全部的数据源对象
*/
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource); // 调用父类方法
super.setTargetDataSources(targetDataSources); // 调用父类方法
super.afterPropertiesSet(); // 属性的设置
}
@Override
protected Object determineCurrentLookupKey() { // 获取当前的查询结果
return getDataSource();
}
public static void setDataSource(String dataSourceName) { // 扩充的数据源处理方法
DATASOURCE_CONTEXT_HOLDER.set(dataSourceName); // 保存当前线程多少数据源名称
}
public static String getDataSource() { // 获取数据源的名称标记
return DATASOURCE_CONTEXT_HOLDER.get();
}
public static void clearDataSource() {
DATASOURCE_CONTEXT_HOLDER.remove(); // 清除当前线程之中保存的数据源名称
}
}
package com.yix.framework.config.dynamic;
import com.yix.common.enums.DataSourceType;
import com.yix.common.utils.spring.SpringUtils;
import lombok.extern.slf4j.Slf4j;
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 javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* 动态数据源配置
*
* @author wangdx
*/
@Configuration
@Slf4j
public class DynamicDataSourceConfig {
@Bean("dataSource")
@Primary
public DynamicDataSource dynamicDataSource(DataSource masterDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER.getSourceName(), masterDataSource);
setDataSource(targetDataSources, DataSourceType.SLAVE.getSourceName(), DataSourceType.SLAVE.getBeanName());
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) {
log.warn(sourceName + "数据库未开启!");
}
}
}
package com.yix.framework.config.dynamic;
import com.yix.common.annotation.DS;
import com.yix.common.enums.DataSourceType;
import com.yix.common.utils.StringUtils;
import com.yix.common.utils.spring.SpringUtils;
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.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.util.Objects;
/**
* 数据源的切面处理类
*
* @author wangdx
*/
@Component
@Aspect // 是一个切面处理类
@Order(-100) // 让这个切面处理类的执行顺序可以高一些
@Slf4j // 为了便于观察,将所有的数据全部都以日志的形式输出
public class DataSourceAspect {
@Before("execution(* com.yix.*.dao.master..*.*(..))")
public void switchYootkDataSource() { // 切换指定的数据源
swtichDataSorce(DataSourceType.MASTER);
log.debug("数据源切换到:{}", DynamicDataSource.getDataSource()); // 日志输出
}
@Before("execution(* com.yix.*.dao.slave..*.*(..))")
public void switchMuyanDataSource() { // 切换指定的数据源
swtichDataSorce(DataSourceType.SLAVE);
log.debug("数据源切换到:{}", DynamicDataSource.getDataSource()); // 日志输出
}
@Pointcut("@annotation(com.yix.common.annotation.DS)"
+ "|| @within(com.yix.common.annotation.DS)")
public void dsPointCut() {
}
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
DS ds = getDataSource(point);
if (StringUtils.isNotNull(ds)) {
swtichDataSorce(ds.value());
log.debug("数据源切换到:{}", DynamicDataSource.getDataSource()); // 日志输出
}
try {
return point.proceed();
} finally {
// 销毁数据源 在执行方法之后
DynamicDataSource.clearDataSource();
}
}
/**
* 数据源切换
*
* @param type
*/
public void swtichDataSorce(DataSourceType type) {
try {
DataSource dataSource = SpringUtils.getBean(type.getBeanName());
DynamicDataSource.setDataSource(type.getSourceName());
} catch (Exception e) {
log.warn(type.getSourceName() + "数据库未开启!采用兜底数据库");
DynamicDataSource.setDataSource(DataSourceType.MASTER.getSourceName());
}
}
/**
* 获取需要切换的数据源
*/
public DS getDataSource(ProceedingJoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
DS dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DS.class);
if (Objects.nonNull(dataSource)) {
return dataSource;
}
return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DS.class);
}
}
mybatis+plus 集成
package com.yix.framework.config.mybatis;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.yix.framework.config.jta.dynamic.MultiDataSourceTransactionFactory;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import javax.sql.DataSource;
/**
* Mybatis拦截器配置
*
* @author wangdx
*/
@Configuration
public class MybatisConfig {
private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
@Bean
public MybatisPlusInterceptor getMybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(
new PaginationInnerInterceptor(DbType.MYSQL) // 使用MySQL分页实现
);
return interceptor;
}
@Bean("mybatisSqlSessionFactoryBean")
public MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean(
@Autowired DataSource dataSource, // 要使用的数据源
@Value("${mybatis-plus.config-location}") Resource configLocation, // 资源文件路径
@Value("${mybatis-plus.type-aliases-package}") String typeAliasesPackage, // 扫描别名
@Value("${mybatis-plus.mapper-locations}") String mapperLocations, // Mapping映射路径
@Value("${mybatis-plus.global-config.banner}") Boolean banner,
@Value("${mybatis-plus.global-config.db-config.logic-not-delete-value}") String logicNotDeleteValue,
@Value("${mybatis-plus.global-config.db-config.logic-delete-value:}") String logicDeleteValue
) throws Exception {
MybatisSqlSessionFactoryBean mybatisPlus = new MybatisSqlSessionFactoryBean();
mybatisPlus.setDataSource(dataSource); // 配置项目中要使用的数据源
mybatisPlus.setVfs(SpringBootVFS.class); // 配置程序的扫描类
mybatisPlus.setTypeAliasesPackage(typeAliasesPackage); // 扫描包的别名
mybatisPlus.setTransactionFactory(new MultiDataSourceTransactionFactory()); // 自定义事务工厂
Resource[] mappings = this.resourcePatternResolver.getResources(mapperLocations);
mybatisPlus.setMapperLocations(mappings);
GlobalConfig.DbConfig dbConfig = new GlobalConfig.DbConfig(); // 数据配置
dbConfig.setLogicNotDeleteValue(logicNotDeleteValue); // 未删除时的数据内容
dbConfig.setLogicDeleteValue(logicDeleteValue); // 删除时的数据内容
GlobalConfig globalConfig = new GlobalConfig(); // 定义全局配置
globalConfig.setDbConfig(dbConfig); // 全局配置项
globalConfig.setBanner(banner); //是否显示logo
// globalConfig.setMetaObjectHandler(new FlagMetaObjectHandler());
mybatisPlus.setGlobalConfig(globalConfig);
return mybatisPlus;
}
}
数据源事务处理
package com.yix.framework.config.jta;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import java.util.HashMap;
import java.util.Map;
/**
* @author wangdx
*/
@Configuration
@Aspect
public class TransactionConfig {
private static final int TRANSACTION_METHOD_TIMEOUT = 6; // 事务处理的超时时间
private static final String AOP_POINTCUT_EXPRESSION = "execution (* com.yix..service.*.*(..))";
@Autowired
private TransactionManager transactionManager; // 事务管理器
@Bean("txAdvice")
public TransactionInterceptor transactionInterceptorConfig() {
// 配置数据读取事务规则
RuleBasedTransactionAttribute readOnlyAttribute = new RuleBasedTransactionAttribute();
readOnlyAttribute.setReadOnly(true); // 只读事务
readOnlyAttribute.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED); // 非事务运行
// 配置了数据更新事务规则
RuleBasedTransactionAttribute requiredAttribute = new RuleBasedTransactionAttribute();
requiredAttribute.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); // 事务开启
requiredAttribute.setTimeout(TRANSACTION_METHOD_TIMEOUT); // 事务处理超时时间
// 配置所有要进行事务处理的方法名称定义
Map<String, TransactionAttribute> transactionAttributeMap = new HashMap<>();
transactionAttributeMap.put("add*", requiredAttribute);
transactionAttributeMap.put("edit*", requiredAttribute);
transactionAttributeMap.put("delete*", requiredAttribute);
transactionAttributeMap.put("list*", readOnlyAttribute);
transactionAttributeMap.put("get*", readOnlyAttribute);
NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
source.setNameMap(transactionAttributeMap); // 配置方法名称的映射
TransactionInterceptor interceptor = new TransactionInterceptor(transactionManager, source);
return interceptor;
}
@Bean("Advisor")
public Advisor transactionAdvisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
return new DefaultPointcutAdvisor(pointcut, transactionInterceptorConfig());
}
}
package com.yix.framework.config.jta.dynamic;
import com.yix.framework.config.dynamic.DynamicDataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.DataSourceUtils;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.ConcurrentHashMap;
/**
* 多数据源事务管理器
* 当前的项目都是基于MyBatis / MyBatisPlus开发的,所以此时一定要基于MyBatis标准开发事务管理器
*
* @author wangdx
*/
@Slf4j
public class MultiDataSourceTransaction implements org.apache.ibatis.transaction.Transaction {
private DataSource dataSource; // 事务是需要有DataSource支持
private Connection currentConnection; // 当前的数据库连接
private boolean autoCommit; // 是否要进行自动提交启用
private boolean isConnectionTransactional; // 是否要启用事务
private ConcurrentHashMap<String, Connection> otherConnectionMap; // 保存其他的Connection对象
private String currentDatabaseName; // 当前数据库名称
public MultiDataSourceTransaction(DataSource dataSource) {
this.dataSource = dataSource; // 保存数据元
this.otherConnectionMap = new ConcurrentHashMap<>(); // 保存其他数据库连接
this.currentDatabaseName = DynamicDataSource.getDataSource(); // 获取当前操作的数据库名称
}
private void openMainConnection() throws SQLException { // 打开一个连接
// 通过当前得到的DataSource接口实例来获取一个Connection接口实例
this.currentConnection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.currentConnection.getAutoCommit(); // 获取当前的是否自动提交的状态
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.currentConnection, this.dataSource);
log.debug("当前数据库连接:{}、事务支持状态:{}。", this.currentConnection, this.isConnectionTransactional);
}
@Override
public Connection getConnection() throws SQLException { // 获取数据库连接
// 存在有数据源的前提下才可以实现连接的获取,那么首先要判断是否有数据源存在
String datasourceName = DynamicDataSource.getDataSource(); // 获取当前数据源名称
log.debug("【MultiDataSourceTransaction.getConnection()】数据源名称:{}", datasourceName);
if (null == datasourceName || datasourceName.equals(this.currentDatabaseName)) { // 现在的数据源为当前使用的数据库
if (this.currentConnection != null) { // 当前存在有数据库连接
return this.currentConnection; // 返回当前的连接
} else { // 如果当前的数据源没有开启过连接
openMainConnection(); // 开启一个数据库连接
this.currentDatabaseName = datasourceName; // 保存当前的数据库名称
return this.currentConnection; // 返回当前的连接
}
} else { // 没有连接
if (!this.otherConnectionMap.containsKey(datasourceName)) { // 没有当前这个数据源的名称存在
Connection conn = dataSource.getConnection(); // 获取数据库连接
this.otherConnectionMap.put(datasourceName, conn); // 保存连接
}
return this.otherConnectionMap.get(datasourceName);
}
}
@Override
public void commit() throws SQLException { // 数据库事务提交
log.debug("commit:currentConnection = {}、isConnectionTransactional = {}、autoCommit = {}", this.currentConnection, this.isConnectionTransactional, this.autoCommit);
// 当前存在有Connection接口实例,同时没有开启自动的事务提交,并且存在有支持事务的连接
if (this.currentConnection != null && !this.isConnectionTransactional && !this.autoCommit) {
log.debug("数据库事务提交,当前数据库连接:{}", this.currentConnection);
this.currentConnection.commit(); // 提交当前的数据库事务
for (Connection connecion : this.otherConnectionMap.values()) { // 控制其它的数据库连接
connecion.commit(); // 保证其他的连接提交事务
}
}
}
@Override
public void rollback() throws SQLException { // 事务回滚
log.error("rollback:catalog = {}、isConnectionTransactional = {}、autoCommit = {}", this.currentConnection.getCatalog(), this.isConnectionTransactional, this.autoCommit);
if (this.currentConnection != null && !this.isConnectionTransactional && !this.autoCommit) {
log.error("数据库事务回滚,当前数据库连接:{}", this.currentConnection);
this.currentConnection.rollback(); // 回滚当前的数据库事务
for (Connection connecion : this.otherConnectionMap.values()) { // 控制其它的数据库连接
connecion.rollback(); // 保证其他的连接提交回滚
}
}
}
@Override
public void close() throws SQLException { // 事务关闭
DataSourceUtils.releaseConnection(this.currentConnection, this.dataSource);
for (Connection connecion : this.otherConnectionMap.values()) { // 控制其它的数据库连接
DataSourceUtils.releaseConnection(connecion, this.dataSource);
}
}
@Override
public Integer getTimeout() throws SQLException { // 超时配置
return 5000;
}
}
package com.yix.framework.config.jta.dynamic;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import javax.sql.DataSource;
/**
* @author wangdx
*/
public class MultiDataSourceTransactionFactory extends SpringManagedTransactionFactory {
@Override
public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
return new MultiDataSourceTransaction(dataSource); // 事务控制类
}
}
package com.yix.framework.config.jta.dynamic;
import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.jta.JtaTransactionManager;
import javax.transaction.SystemException;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
/**
* XZA事务管理器
*
* @author wangdx
*/
@Configuration
public class XADruidTransactionManager {
// UserTransaction可以保证在当前线程下的所有数据库操作都使用同一个Connection接口实例
// 最终可以在进行事务提交或回滚的时候保证事务操作的原子性
@Bean(name = "userTransaction")
public UserTransaction getUserTransaction() throws SystemException {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(1000); // 超时配置
return userTransactionImp;
}
@Bean(name = "atomikosTransactionManager")
public TransactionManager getTransactionManager() {
UserTransactionManager transactionManager = new UserTransactionManager();
transactionManager.setForceShutdown(false); // 关闭强制退出
return transactionManager;
}
@Bean("transactionManager")
@DependsOn({"userTransaction", "atomikosTransactionManager"})
public PlatformTransactionManager getPlatformTransactionManager(
UserTransaction userTransaction,
TransactionManager atomikosTransactionManager) {
return new JtaTransactionManager(userTransaction, atomikosTransactionManager);
}
}
联表查询
但数据库可集成,多数据库建议放弃
1.
// https://mvnrepository.com/artifact/com.github.yulichang/mybatis-plus-join-boot-starter
implementation group: 'com.github.yulichang', name: 'mybatis-plus-join-boot-starter', version: '1.4.11'
自动建表工具
actable 集成
考虑性能和开发效率,做成单独模块
1.// https://mvnrepository.com/artifact/com.gitee.sunchenbin.mybatis.actable/mybatis-enhance-actable
implementation group: 'com.gitee.sunchenbin.mybatis.actable', name: 'mybatis-enhance-actable', version: '1.5.0.RELEASE'
2.
actable: #自动建表工具
database:
type: mysql
model:
pack: ${mybatis-plus.type-aliases-package}
table:
auto: update
不支持多数据源,但数据源可用
mybatis-plus-ext 继承
redis
1.
//redis
api(libraries.'commons-pool2')
api('org.springframework.boot:spring-boot-starter-data-redis')
2.
spring:
## redis配置
redis:
host: 192.168.16.8 # Redis服务器地址
port: 6379 # Redis服务器连接端口
password: redis # Redis服务器连接密码
database: 0 # Redis数据库索引
timeout: 200ms # 连接超时时间,不能设置为0
lettuce: # 配置Lettuce
pool: # 配置连接池
max-active: 100 # 连接池最大连接数
max-idle: 29 # 连接池中的最大空闲连接
min-idle: 10 # 连接池中的最小空闲连接
max-wait: 1000 # 连接池最大阻塞等待时间
time-between-eviction-runs: 2000 # 每2秒回收一次空闲连接