#update
This commit is contained in:
@@ -1,74 +0,0 @@
|
||||
package com.maibu.aspectj;
|
||||
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.maibu.annotation.DataSource;
|
||||
import com.maibu.datasource.DynamicDataSourceContextHolder;
|
||||
import com.maibu.utils.StringUtils;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 多数据源处理
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Aspect
|
||||
@Order(1)
|
||||
@Component
|
||||
public class DataSourceAspect
|
||||
{
|
||||
protected Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
@Pointcut("@annotation(com.maibu.annotation.DataSource)"
|
||||
+ "|| @within(com.maibu.annotation.DataSource)")
|
||||
public void dsPointCut()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Around("dsPointCut()")
|
||||
public Object around(ProceedingJoinPoint point) throws Throwable
|
||||
{
|
||||
DataSource dataSource = getDataSource(point);
|
||||
|
||||
if (StringUtils.isNotNull(dataSource))
|
||||
{
|
||||
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return point.proceed();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 销毁数据源 在执行方法之后
|
||||
DynamicDataSourceContextHolder.clearDataSourceType();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取需要切换的数据源
|
||||
*/
|
||||
public DataSource getDataSource(ProceedingJoinPoint point)
|
||||
{
|
||||
MethodSignature signature = (MethodSignature) point.getSignature();
|
||||
DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
|
||||
if (Objects.nonNull(dataSource))
|
||||
{
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package com.maibu.config;
|
||||
|
||||
import com.alibaba.druid.pool.DruidDataSource;
|
||||
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration;
|
||||
import com.maibu.datasource.DynamicDataSource;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
@Configuration
|
||||
@AutoConfigureBefore({DynamicDataSourceAutoConfiguration.class, SpringBootConfiguration.class})
|
||||
public class DataSourceConfig {
|
||||
|
||||
@Bean(name = "masterDataSource")
|
||||
@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.master")
|
||||
public DruidDataSource masterDataSource() {
|
||||
return new DruidDataSource();
|
||||
}
|
||||
|
||||
// @Bean(name = "slaveDataSource")
|
||||
// @ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.slave")
|
||||
// public DruidDataSource slaveDataSource() {
|
||||
// return new DruidDataSource();
|
||||
// }
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource master) throws SQLException, IOException {
|
||||
Map<Object, Object> targetDataSources = new HashMap<>();
|
||||
targetDataSources.put("master", master);
|
||||
//targetDataSources.put("slave", slave);
|
||||
return new DynamicDataSource(master,targetDataSources);
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
|
||||
return new DataSourceTransactionManager(dataSource);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 将动态数据源设置为首选数据源
|
||||
// */
|
||||
// @Primary
|
||||
// @Bean
|
||||
// public DynamicDataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider) {
|
||||
//// DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
|
||||
//// dataSource.setPrimary(properties.getPrimary());
|
||||
//// dataSource.setStrict(properties.getStrict());
|
||||
//// dataSource.setStrategy(properties.getStrategy());
|
||||
//// dataSource.setProvider(dynamicDataSourceProvider);
|
||||
//// dataSource.setP6spy(properties.getP6spy());
|
||||
//// dataSource.setSeata(properties.getSeata());
|
||||
//// return dataSource;
|
||||
// Map<String, DataSource> targetDataSources = dynamicDataSourceProvider.loadDataSources();
|
||||
// Map<Object, Object> objectObjectMap = new HashMap<>(targetDataSources);
|
||||
// DataSource masterDataSource = targetDataSources.get(properties.getPrimary());
|
||||
// return new DynamicDataSource(masterDataSource,objectObjectMap);
|
||||
// }
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package com.maibu.config;
|
||||
|
||||
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
|
||||
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
|
||||
import com.alibaba.druid.spring.boot.autoconfigure.stat.DruidFilterConfiguration;
|
||||
import com.alibaba.druid.spring.boot.autoconfigure.stat.DruidSpringAopConfiguration;
|
||||
import com.alibaba.druid.spring.boot.autoconfigure.stat.DruidStatViewServletConfiguration;
|
||||
import com.alibaba.druid.spring.boot.autoconfigure.stat.DruidWebStatFilterConfiguration;
|
||||
import com.alibaba.druid.util.Utils;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import javax.servlet.*;
|
||||
import java.io.IOException;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(value = DruidDataSourceAutoConfigure.class)
|
||||
@EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})
|
||||
@Import({DruidSpringAopConfiguration.class, DruidStatViewServletConfiguration.class, DruidWebStatFilterConfiguration.class, DruidFilterConfiguration.class})
|
||||
public class DruidConfig {
|
||||
public DruidConfig() {
|
||||
}
|
||||
/**
|
||||
* 去除监控页面底部的广告
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
|
||||
import com.maibu.datasource.DynamicDataSource;
|
||||
import com.maibu.utils.StringUtils;
|
||||
import org.apache.ibatis.io.VFS;
|
||||
import org.apache.ibatis.session.SqlSessionFactory;
|
||||
@@ -26,6 +25,7 @@ import org.springframework.core.type.classreading.MetadataReader;
|
||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -128,7 +128,7 @@ public class MyBatisConfig
|
||||
*/
|
||||
@Bean(name = "mysqlSessionFactory")
|
||||
@Primary
|
||||
public SqlSessionFactory sqlSessionFactory(DynamicDataSource dataSource) throws Exception
|
||||
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception
|
||||
{
|
||||
String typeAliasesPackage = env.getProperty("mybatis-plus.typeAliasesPackage");
|
||||
String mapperLocations = env.getProperty("mybatis-plus.mapperLocations");
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
package com.maibu.config.properties;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* druid 配置属性
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Configuration
|
||||
public class DruidProperties
|
||||
{
|
||||
// @Value("${spring.datasource.druid.initialSize}")
|
||||
// private int initialSize;
|
||||
//
|
||||
// @Value("${spring.datasource.druid.minIdle}")
|
||||
// private int minIdle;
|
||||
//
|
||||
// @Value("${spring.datasource.druid.maxActive}")
|
||||
// private int maxActive;
|
||||
//
|
||||
// @Value("${spring.datasource.druid.maxWait}")
|
||||
// private int maxWait;
|
||||
//
|
||||
// @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")
|
||||
// private int timeBetweenEvictionRunsMillis;
|
||||
//
|
||||
// @Value("${spring.datasource.druid.minEvictableIdleTimeMillis}")
|
||||
// private int minEvictableIdleTimeMillis;
|
||||
//
|
||||
// @Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}")
|
||||
// private int maxEvictableIdleTimeMillis;
|
||||
//
|
||||
// @Value("${spring.datasource.druid.validationQuery}")
|
||||
// private String validationQuery;
|
||||
//
|
||||
// @Value("${spring.datasource.druid.testWhileIdle}")
|
||||
// private boolean testWhileIdle;
|
||||
//
|
||||
// @Value("${spring.datasource.druid.testOnBorrow}")
|
||||
// private boolean testOnBorrow;
|
||||
//
|
||||
// @Value("${spring.datasource.druid.testOnReturn}")
|
||||
// private boolean testOnReturn;
|
||||
|
||||
// public DruidDataSource dataSource(DruidDataSource datasource)
|
||||
// {
|
||||
// /** 配置初始化大小、最小、最大 */
|
||||
// datasource.setInitialSize(initialSize);
|
||||
// datasource.setMaxActive(maxActive);
|
||||
// datasource.setMinIdle(minIdle);
|
||||
//
|
||||
// /** 配置获取连接等待超时的时间 */
|
||||
// datasource.setMaxWait(maxWait);
|
||||
//
|
||||
// /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */
|
||||
// datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
|
||||
//
|
||||
// /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */
|
||||
// datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
|
||||
// datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
|
||||
//
|
||||
// /**
|
||||
// * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
|
||||
// */
|
||||
// datasource.setValidationQuery(validationQuery);
|
||||
// /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */
|
||||
// datasource.setTestWhileIdle(testWhileIdle);
|
||||
// /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
|
||||
// datasource.setTestOnBorrow(testOnBorrow);
|
||||
// /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
|
||||
// datasource.setTestOnReturn(testOnReturn);
|
||||
// return datasource;
|
||||
// }
|
||||
}
|
||||
@@ -1,262 +0,0 @@
|
||||
package com.maibu.config.sharding;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.alibaba.druid.util.StringUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
import com.maibu.config.sharding.enums.ShardingTableCacheEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shardingsphere.driver.jdbc.core.datasource.ShardingSphereDataSource;
|
||||
import org.apache.shardingsphere.infra.config.RuleConfiguration;
|
||||
import org.apache.shardingsphere.mode.manager.ContextManager;
|
||||
import org.apache.shardingsphere.sharding.algorithm.config.AlgorithmProvidedShardingRuleConfiguration;
|
||||
import org.apache.shardingsphere.sharding.api.config.rule.ShardingTableRuleConfiguration;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import java.sql.*;
|
||||
import java.time.YearMonth;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* <p> @Title ShardingAlgorithmTool
|
||||
* <p> @Description 按月分片算法工具
|
||||
*
|
||||
*/
|
||||
@Slf4j
|
||||
public class ShardingAlgorithmTool {
|
||||
|
||||
/** 表分片符号,例:siot_device_log_202201 中,分片符号为 "_" */
|
||||
private static final String TABLE_SPLIT_SYMBOL = "_";
|
||||
|
||||
/** 数据库配置 */
|
||||
private static final Environment ENV = SpringUtil.getApplicationContext().getEnvironment();
|
||||
private static final String DATASOURCE_URL = ENV.getProperty("spring.shardingsphere.datasource.ds0.url");
|
||||
private static final String DATASOURCE_USERNAME = ENV.getProperty("spring.shardingsphere.datasource.ds0.username");
|
||||
private static final String DATASOURCE_PASSWORD = ENV.getProperty("spring.shardingsphere.datasource.ds0.password");
|
||||
|
||||
|
||||
/**
|
||||
* 检查分表获取的表名是否存在,不存在则自动建表
|
||||
* @param logicTable 逻辑表
|
||||
* @param resultTableNames 真实表名,例:iot_device_log_202201
|
||||
* @return 存在于数据库中的真实表名集合
|
||||
*/
|
||||
public static Set<String> getShardingTablesAndCreate(ShardingTableCacheEnum logicTable, Collection<String> resultTableNames) {
|
||||
return resultTableNames.stream().map(o -> getShardingTableAndCreate(logicTable, o)).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查分表获取的表名是否存在,不存在则自动建表
|
||||
* @param logicTable 逻辑表
|
||||
* @param resultTableName 真实表名,例:iot_device_log_202201
|
||||
* @return 确认存在于数据库中的真实表名
|
||||
*/
|
||||
public static String getShardingTableAndCreate(ShardingTableCacheEnum logicTable, String resultTableName) {
|
||||
// 缓存中有此表则返回,没有则判断创建
|
||||
if (logicTable.resultTableNamesCache().contains(resultTableName)) {
|
||||
return resultTableName;
|
||||
} else {
|
||||
// 未创建的表返回逻辑空表
|
||||
boolean isSuccess = createShardingTable(logicTable, resultTableName);
|
||||
return isSuccess ? resultTableName : logicTable.logicTableName();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重载全部缓存
|
||||
*/
|
||||
public static void tableNameCacheReloadAll() {
|
||||
Arrays.stream(ShardingTableCacheEnum.values()).forEach(ShardingAlgorithmTool::tableNameCacheReload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重载指定分表缓存
|
||||
* @param logicTable 逻辑表
|
||||
*/
|
||||
public static void tableNameCacheReload(ShardingTableCacheEnum logicTable) {
|
||||
// 读取数据库中所有表名
|
||||
List<String> tableNameList = getAllTableNameBySchema(logicTable);
|
||||
// 更新缓存、配置(原子操作)
|
||||
logicTable.atomicUpdateCacheAndActualDataNodes(tableNameList);
|
||||
// 删除旧的缓存(如果存在)
|
||||
logicTable.resultTableNamesCache().clear();
|
||||
// 写入新的缓存
|
||||
logicTable.resultTableNamesCache().addAll(tableNameList);
|
||||
// 动态更新配置 actualDataNodes
|
||||
actualDataNodesRefresh(logicTable.logicTableName(), tableNameList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有表名
|
||||
* @return 表名集合
|
||||
* @param logicTable 逻辑表
|
||||
*/
|
||||
public static List<String> getAllTableNameBySchema(ShardingTableCacheEnum logicTable) {
|
||||
List<String> tableNames = new ArrayList<>();
|
||||
if (StringUtils.isEmpty(DATASOURCE_URL) || StringUtils.isEmpty(DATASOURCE_USERNAME) || StringUtils.isEmpty(DATASOURCE_PASSWORD)) {
|
||||
log.error(">>>>>>>>>> 【ERROR】数据库连接配置有误,请稍后重试,URL:{}, username:{}, password:{}", DATASOURCE_URL, DATASOURCE_USERNAME, DATASOURCE_PASSWORD);
|
||||
throw new IllegalArgumentException("数据库连接配置有误,请稍后重试");
|
||||
}
|
||||
try (Connection conn = DriverManager.getConnection(DATASOURCE_URL, DATASOURCE_USERNAME, DATASOURCE_PASSWORD);
|
||||
Statement st = conn.createStatement()) {
|
||||
String logicTableName = logicTable.logicTableName();
|
||||
try (ResultSet rs = st.executeQuery("show TABLES like '" + logicTableName + TABLE_SPLIT_SYMBOL + "%'")) {
|
||||
log.info("查询数据库所有表:{}","show TABLES like '" + logicTableName + TABLE_SPLIT_SYMBOL + "%'");
|
||||
while (rs.next()) {
|
||||
String tableName = rs.getString(1);
|
||||
log.info("分表格式:{}",String.format("^(%s\\d{6})$", logicTableName + TABLE_SPLIT_SYMBOL));
|
||||
// 匹配分表格式 例:^(t\_contract_\d{6})$
|
||||
if (org.apache.commons.lang3.StringUtils.isNotBlank(tableName) && tableName.matches(String.format("^(%s\\d{6})$", logicTableName + TABLE_SPLIT_SYMBOL))) {
|
||||
tableNames.add(rs.getString(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
log.error(">>>>>>>>>> 【ERROR】数据库连接失败,请稍后重试,原因:{}", e.getMessage(), e);
|
||||
throw new IllegalArgumentException("数据库连接失败,请稍后重试");
|
||||
}
|
||||
return tableNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态更新配置 actualDataNodes
|
||||
*
|
||||
* @param logicTableName 逻辑表名
|
||||
* @param tableNamesCache 真实表名集合
|
||||
*/
|
||||
public static void actualDataNodesRefresh(String logicTableName, List<String> tableNamesCache) {
|
||||
try {
|
||||
if (CollectionUtils.isEmpty(tableNamesCache)) {
|
||||
return;
|
||||
}
|
||||
// 获取数据分片节点
|
||||
String dbName = "ds0";
|
||||
log.info(">>>>>>>>>> 【INFO】更新分表配置,logicTableName:{},tableNamesCache:{}", logicTableName, tableNamesCache);
|
||||
|
||||
// generate actualDataNodes
|
||||
String newActualDataNodes = tableNamesCache.stream().map(o -> String.format("%s.%s", dbName, o)).collect(Collectors.joining(","));
|
||||
ShardingSphereDataSource shardingSphereDataSource = SpringUtil.getBean(ShardingSphereDataSource.class);
|
||||
updateShardRuleActualDataNodes(shardingSphereDataSource, logicTableName, newActualDataNodes);
|
||||
}catch (Exception e){
|
||||
log.error("初始化 动态表单失败,原因:{}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------------------------------
|
||||
// 私有方法
|
||||
// --------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
/**
|
||||
* 刷新ActualDataNodes
|
||||
*/
|
||||
private static void updateShardRuleActualDataNodes(ShardingSphereDataSource dataSource, String logicTableName, String newActualDataNodes) {
|
||||
// Context manager.
|
||||
ContextManager contextManager = dataSource.getContextManager();
|
||||
|
||||
// Rule configuration.
|
||||
String schemaName = "logic_db";
|
||||
Collection<RuleConfiguration> newRuleConfigList = new LinkedList<>();
|
||||
Collection<RuleConfiguration> oldRuleConfigList = dataSource.getContextManager()
|
||||
.getMetaDataContexts()
|
||||
.getMetaData(schemaName)
|
||||
.getRuleMetaData()
|
||||
.getConfigurations();
|
||||
|
||||
for (RuleConfiguration oldRuleConfig : oldRuleConfigList) {
|
||||
if (oldRuleConfig instanceof AlgorithmProvidedShardingRuleConfiguration) {
|
||||
|
||||
// Algorithm provided sharding rule configuration
|
||||
AlgorithmProvidedShardingRuleConfiguration oldAlgorithmConfig = (AlgorithmProvidedShardingRuleConfiguration) oldRuleConfig;
|
||||
AlgorithmProvidedShardingRuleConfiguration newAlgorithmConfig = new AlgorithmProvidedShardingRuleConfiguration();
|
||||
|
||||
// Sharding table rule configuration Collection
|
||||
Collection<ShardingTableRuleConfiguration> newTableRuleConfigList = new LinkedList<>();
|
||||
Collection<ShardingTableRuleConfiguration> oldTableRuleConfigList = oldAlgorithmConfig.getTables();
|
||||
|
||||
oldTableRuleConfigList.forEach(oldTableRuleConfig -> {
|
||||
if (logicTableName.equals(oldTableRuleConfig.getLogicTable())) {
|
||||
ShardingTableRuleConfiguration newTableRuleConfig = new ShardingTableRuleConfiguration(oldTableRuleConfig.getLogicTable(), newActualDataNodes);
|
||||
newTableRuleConfig.setTableShardingStrategy(oldTableRuleConfig.getTableShardingStrategy());
|
||||
newTableRuleConfig.setDatabaseShardingStrategy(oldTableRuleConfig.getDatabaseShardingStrategy());
|
||||
newTableRuleConfig.setKeyGenerateStrategy(oldTableRuleConfig.getKeyGenerateStrategy());
|
||||
|
||||
newTableRuleConfigList.add(newTableRuleConfig);
|
||||
} else {
|
||||
newTableRuleConfigList.add(oldTableRuleConfig);
|
||||
}
|
||||
});
|
||||
|
||||
newAlgorithmConfig.setTables(newTableRuleConfigList);
|
||||
newAlgorithmConfig.setAutoTables(oldAlgorithmConfig.getAutoTables());
|
||||
newAlgorithmConfig.setBindingTableGroups(oldAlgorithmConfig.getBindingTableGroups());
|
||||
newAlgorithmConfig.setBroadcastTables(oldAlgorithmConfig.getBroadcastTables());
|
||||
newAlgorithmConfig.setDefaultDatabaseShardingStrategy(oldAlgorithmConfig.getDefaultDatabaseShardingStrategy());
|
||||
newAlgorithmConfig.setDefaultTableShardingStrategy(oldAlgorithmConfig.getDefaultTableShardingStrategy());
|
||||
newAlgorithmConfig.setDefaultKeyGenerateStrategy(oldAlgorithmConfig.getDefaultKeyGenerateStrategy());
|
||||
newAlgorithmConfig.setDefaultShardingColumn(oldAlgorithmConfig.getDefaultShardingColumn());
|
||||
newAlgorithmConfig.setShardingAlgorithms(oldAlgorithmConfig.getShardingAlgorithms());
|
||||
newAlgorithmConfig.setKeyGenerators(oldAlgorithmConfig.getKeyGenerators());
|
||||
newRuleConfigList.add(newAlgorithmConfig);
|
||||
}
|
||||
}
|
||||
|
||||
// update context
|
||||
contextManager.alterRuleConfiguration(schemaName, newRuleConfigList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建分表
|
||||
* @param logicTable 逻辑表
|
||||
* @param resultTableName 真实表名,例:sys_user_behavior_202201
|
||||
* @return 创建结果(true创建成功,false未创建)
|
||||
*/
|
||||
private static boolean createShardingTable(ShardingTableCacheEnum logicTable, String resultTableName) {
|
||||
// 根据日期判断,当前月份之后分表不提前创建
|
||||
String month = resultTableName.replace(logicTable.logicTableName() + TABLE_SPLIT_SYMBOL,"");
|
||||
YearMonth shardingMonth = YearMonth.parse(month, DateTimeFormatter.ofPattern("yyyyMM"));
|
||||
if (shardingMonth.isAfter(YearMonth.now())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
synchronized (logicTable.logicTableName().intern()) {
|
||||
// 缓存中有此表 返回
|
||||
if (logicTable.resultTableNamesCache().contains(resultTableName)) {
|
||||
return false;
|
||||
}
|
||||
// 缓存中无此表,则建表并添加缓存
|
||||
executeSql(Collections.singletonList("CREATE TABLE IF NOT EXISTS `" + resultTableName + "` LIKE `" + logicTable.logicTableName() + "`;"));
|
||||
// 缓存重载
|
||||
tableNameCacheReload(logicTable);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行SQL
|
||||
* @param sqlList SQL集合
|
||||
*/
|
||||
private static void executeSql(List<String> sqlList) {
|
||||
if (StringUtils.isEmpty(DATASOURCE_URL) || StringUtils.isEmpty(DATASOURCE_USERNAME) || StringUtils.isEmpty(DATASOURCE_PASSWORD)) {
|
||||
log.error(">>>>>>>>>> 【ERROR】数据库连接配置有误,请稍后重试,URL:{}, username:{}, password:{}", DATASOURCE_URL, DATASOURCE_USERNAME, DATASOURCE_PASSWORD);
|
||||
throw new IllegalArgumentException("数据库连接配置有误,请稍后重试");
|
||||
}
|
||||
try (Connection conn = DriverManager.getConnection(DATASOURCE_URL, DATASOURCE_USERNAME, DATASOURCE_PASSWORD)) {
|
||||
try (Statement st = conn.createStatement()) {
|
||||
conn.setAutoCommit(false);
|
||||
for (String sql : sqlList) {
|
||||
st.execute(sql);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
conn.rollback();
|
||||
log.error(">>>>>>>>>> 【ERROR】数据表创建执行失败,请稍后重试,原因:{}", e.getMessage(), e);
|
||||
throw new IllegalArgumentException("数据表创建执行失败,请稍后重试");
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
log.error(">>>>>>>>>> 【ERROR】数据库连接失败,请稍后重试,原因:{}", e.getMessage(), e);
|
||||
throw new IllegalArgumentException("数据库连接失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.maibu.config.sharding;
|
||||
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* <p> @Title ShardingTablesLoadRunner
|
||||
* <p> @Description 项目启动后,读取已有分表,进行缓存
|
||||
*
|
||||
*/
|
||||
@Order(value = 1) // 数字越小,越先执行
|
||||
@Component
|
||||
public class ShardingTablesLoadRunner implements CommandLineRunner {
|
||||
|
||||
@Override
|
||||
public void run(String... args) {
|
||||
// 读取已有分表,进行缓存
|
||||
//ShardingAlgorithmTool.tableNameCacheReloadAll();
|
||||
}
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
package com.maibu.config.sharding;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
import com.maibu.config.sharding.enums.ShardingTableCacheEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;
|
||||
import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue;
|
||||
import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* <p> @Title TimeShardingAlgorithm
|
||||
* <p> @Description 分片算法,按月分片
|
||||
*
|
||||
*/
|
||||
@Slf4j
|
||||
public class TimeShardingAlgorithm implements StandardShardingAlgorithm<Date> {
|
||||
/**
|
||||
* Date类型的分片时间格式
|
||||
*/
|
||||
private static final SimpleDateFormat TABLE_SHARD_Date_FORMATTER = new SimpleDateFormat("yyyyMM");
|
||||
|
||||
/**
|
||||
* 分片时间格式
|
||||
*/
|
||||
private static final DateTimeFormatter TABLE_SHARD_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMM");
|
||||
|
||||
/**
|
||||
* 完整时间格式
|
||||
*/
|
||||
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss");
|
||||
/**
|
||||
* 完整时间格式
|
||||
*/
|
||||
private static final SimpleDateFormat DATE_TIME_FORMATTER_SPILE = new SimpleDateFormat("yyyy-MM-dd");
|
||||
|
||||
/**
|
||||
* 表分片符号,例:t_user_202201 中,分片符号为 "_"
|
||||
*/
|
||||
private final String TABLE_SPLIT_SYMBOL = "_";
|
||||
|
||||
|
||||
/**
|
||||
* 精准分片
|
||||
* @param tableNames 对应分片库中所有分片表的集合
|
||||
* @param preciseShardingValue 分片键值,其中 logicTableName 为逻辑表,columnName 分片键,value 为从 SQL 中解析出来的分片键的值
|
||||
* @return 表名
|
||||
*/
|
||||
@Override
|
||||
public String doSharding(Collection<String> tableNames, PreciseShardingValue<Date> preciseShardingValue) {
|
||||
String logicTableName = preciseShardingValue.getLogicTableName();
|
||||
ShardingTableCacheEnum logicTable = ShardingTableCacheEnum.of(logicTableName);
|
||||
createAllTable(logicTable, tableNames);
|
||||
|
||||
/// 打印分片信息
|
||||
log.info(">>>>>>>>>> 【INFO】精确分片,节点配置表名:{},数据库缓存表名:{}", tableNames, logicTable.resultTableNamesCache());
|
||||
|
||||
Date date = preciseShardingValue.getValue();
|
||||
Instant instant = date.toInstant();
|
||||
LocalDateTime localDateTime = instant.atZone(ZoneId.systemDefault()).toLocalDateTime();
|
||||
String resultTableName = logicTableName + "_" + TABLE_SHARD_TIME_FORMATTER.format(localDateTime);
|
||||
// 检查分表获取的表名是否存在,不存在则自动建表
|
||||
if (!tableNames.contains(resultTableName)){
|
||||
tableNames.add(resultTableName);
|
||||
}
|
||||
return ShardingAlgorithmTool.getShardingTableAndCreate(logicTable, resultTableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 范围分片
|
||||
* @param tableNames 对应分片库中所有分片表的集合
|
||||
* @param rangeShardingValue 分片范围
|
||||
* @return 表名集合
|
||||
*/
|
||||
@Override
|
||||
public Collection<String> doSharding(Collection<String> tableNames, RangeShardingValue<Date> rangeShardingValue) {
|
||||
log.info("开始分表查询开始:{}",System.currentTimeMillis());
|
||||
String logicTableName = rangeShardingValue.getLogicTableName();
|
||||
ShardingTableCacheEnum logicTable = ShardingTableCacheEnum.of(logicTableName);
|
||||
createAllTable(logicTable, tableNames);
|
||||
|
||||
/// 打印分片信息
|
||||
log.info(">>>>>>>>>> 【INFO】范围分片,节点配置表名:{},数据库缓存表名:{}", tableNames, logicTable.resultTableNamesCache());
|
||||
|
||||
// between and 的起始值
|
||||
Range<Date> valueRange = rangeShardingValue.getValueRange();
|
||||
boolean hasLowerBound = valueRange.hasLowerBound();
|
||||
boolean hasUpperBound = valueRange.hasUpperBound();
|
||||
|
||||
// 获取最大值和最小值
|
||||
Set<String> tableNameCache = logicTable.resultTableNamesCache();
|
||||
String min = hasLowerBound ? String.valueOf(valueRange.lowerEndpoint()) : getLowerEndpoint(tableNameCache);
|
||||
String max = hasUpperBound ? String.valueOf(valueRange.upperEndpoint()) : getUpperEndpoint(tableNameCache);
|
||||
// 循环计算分表范围
|
||||
Set<String> resultTableNames = new LinkedHashSet<>();
|
||||
try {
|
||||
Date minDate = DATE_TIME_FORMATTER_SPILE.parse(min);
|
||||
Date maxDate = DATE_TIME_FORMATTER_SPILE.parse(max);
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
while (minDate.before(maxDate) || minDate.equals(maxDate)) {
|
||||
String tableName = logicTableName + TABLE_SPLIT_SYMBOL + TABLE_SHARD_Date_FORMATTER.format(minDate);
|
||||
resultTableNames.add(tableName);
|
||||
calendar.setTime(minDate); // 设置Calendar的时间为Date对象的时间
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 1); // 给日期加一天
|
||||
minDate = calendar.getTime();
|
||||
}
|
||||
log.info("开始分表查询结束:{}",System.currentTimeMillis());
|
||||
return ShardingAlgorithmTool.getShardingTablesAndCreate(logicTable, resultTableNames);
|
||||
} catch (Exception e) {
|
||||
return ShardingAlgorithmTool.getShardingTablesAndCreate(logicTable, logicTable.resultTableNamesCache());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------------------------------
|
||||
// 私有方法
|
||||
// --------------------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 获取 最小分片值
|
||||
* @param tableNames 表名集合
|
||||
* @return 最小分片值
|
||||
*/
|
||||
private String getLowerEndpoint(Collection<String> tableNames) {
|
||||
Optional<LocalDateTime> optional = tableNames.stream()
|
||||
.map(o -> LocalDateTime.parse(o.replace(TABLE_SPLIT_SYMBOL, "") + "01 00:00:00", DATE_TIME_FORMATTER))
|
||||
.min(Comparator.comparing(Function.identity()));
|
||||
if (optional.isPresent()) {
|
||||
ZonedDateTime zonedDateTime = optional.get().atZone(ZoneId.systemDefault());
|
||||
Instant instant = zonedDateTime.toInstant();
|
||||
return String.valueOf(Date.from(instant));
|
||||
} else {
|
||||
log.error(">>>>>>>>>> 【ERROR】获取数据最小分表失败,请稍后重试,tableName:{}", tableNames);
|
||||
throw new IllegalArgumentException("获取数据最小分表失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 最大分片值
|
||||
* @param tableNames 表名集合
|
||||
* @return 最大分片值
|
||||
*/
|
||||
private String getUpperEndpoint(Collection<String> tableNames) {
|
||||
Optional<LocalDateTime> optional = tableNames.stream()
|
||||
.map(o -> LocalDateTime.parse(o.replace(TABLE_SPLIT_SYMBOL, "") + "01 00:00:00", DATE_TIME_FORMATTER))
|
||||
.max(Comparator.comparing(Function.identity()));
|
||||
if (optional.isPresent()) {
|
||||
ZonedDateTime zonedDateTime = optional.get().atZone(ZoneId.systemDefault());
|
||||
Instant instant = zonedDateTime.toInstant();
|
||||
return String.valueOf(Date.from(instant));
|
||||
} else {
|
||||
log.error(">>>>>>>>>> 【ERROR】获取数据最大分表失败,请稍后重试,tableName:{}", tableNames);
|
||||
throw new IllegalArgumentException("获取数据最大分表失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据分片规则获取的表,创建所有的表
|
||||
* @param logicTable
|
||||
* @param tableNames
|
||||
*/
|
||||
private void createAllTable(ShardingTableCacheEnum logicTable, Collection<String> tableNames) {
|
||||
if (!CollectionUtils.isEmpty(logicTable.resultTableNamesCache())) {
|
||||
//如果缓存中有表了,则证明已经创建了表,无需再创建
|
||||
return;
|
||||
}
|
||||
//根据分片规则创建表
|
||||
ShardingAlgorithmTool.getShardingTablesAndCreate(logicTable,tableNames);
|
||||
//刷新缓存
|
||||
ShardingAlgorithmTool.tableNameCacheReload(logicTable);
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
package com.maibu.config.sharding.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
import java.util.*;
|
||||
|
||||
import static com.maibu.config.sharding.ShardingAlgorithmTool.actualDataNodesRefresh;
|
||||
|
||||
|
||||
/**
|
||||
* <p> @Title ShardingTableCacheEnum
|
||||
* <p> @Description 分片表缓存枚举
|
||||
*
|
||||
*/
|
||||
public enum ShardingTableCacheEnum {
|
||||
|
||||
/**
|
||||
* 用户埋点表
|
||||
*/
|
||||
DEVICE_LOG("iot_device_log", new HashSet<>());
|
||||
|
||||
/**
|
||||
* 逻辑表名
|
||||
*/
|
||||
private final String logicTableName;
|
||||
/**
|
||||
* 实际表名
|
||||
*/
|
||||
private final Set<String> resultTableNamesCache;
|
||||
|
||||
private static Map<String, ShardingTableCacheEnum> valueMap = new HashMap<>();
|
||||
|
||||
static {
|
||||
Arrays.stream(ShardingTableCacheEnum.values()).forEach(o -> valueMap.put(o.logicTableName, o));
|
||||
}
|
||||
|
||||
ShardingTableCacheEnum(String logicTableName, Set<String> resultTableNamesCache) {
|
||||
this.logicTableName = logicTableName;
|
||||
this.resultTableNamesCache = resultTableNamesCache;
|
||||
}
|
||||
|
||||
public static ShardingTableCacheEnum of(String value) {
|
||||
return valueMap.get(value);
|
||||
}
|
||||
|
||||
public String logicTableName() {
|
||||
return logicTableName;
|
||||
}
|
||||
|
||||
public Set<String> resultTableNamesCache() {
|
||||
return resultTableNamesCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新缓存、配置(原子操作)
|
||||
*
|
||||
* @param tableNameList
|
||||
*/
|
||||
public void atomicUpdateCacheAndActualDataNodes(List<String> tableNameList) {
|
||||
if (CollectionUtils.isEmpty(tableNameList)) {
|
||||
return;
|
||||
}
|
||||
synchronized (resultTableNamesCache) {
|
||||
// 删除缓存
|
||||
resultTableNamesCache.clear();
|
||||
// 写入新的缓存
|
||||
resultTableNamesCache.addAll(tableNameList);
|
||||
// 动态更新配置 actualDataNodes
|
||||
actualDataNodesRefresh(logicTableName, tableNameList);
|
||||
}
|
||||
}
|
||||
|
||||
public static Set<String> logicTableNames() {
|
||||
return valueMap.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ShardingTableCacheEnum{" +
|
||||
"logicTableName='" + logicTableName + '\'' +
|
||||
", resultTableNamesCache=" + resultTableNamesCache +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.maibu.datasource;
|
||||
|
||||
import java.util.Map;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
|
||||
|
||||
/**
|
||||
* 动态数据源
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Slf4j
|
||||
public class DynamicDataSource extends AbstractRoutingDataSource
|
||||
{
|
||||
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources)
|
||||
{
|
||||
super.setDefaultTargetDataSource(defaultTargetDataSource);
|
||||
super.setTargetDataSources(targetDataSources);
|
||||
super.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object determineCurrentLookupKey()
|
||||
{
|
||||
//log.debug("数据源:" + DynamicDataSourceContextHolder.getDataSourceType());
|
||||
return DynamicDataSourceContextHolder.getDataSourceType();
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package com.maibu.datasource;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* 数据源切换处理
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class DynamicDataSourceContextHolder
|
||||
{
|
||||
public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
|
||||
|
||||
/**
|
||||
* 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
|
||||
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
|
||||
*/
|
||||
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 设置数据源的变量
|
||||
*/
|
||||
public static void setDataSourceType(String dsType)
|
||||
{
|
||||
log.info("切换到{}数据源", dsType);
|
||||
CONTEXT_HOLDER.set(dsType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得数据源的变量
|
||||
*/
|
||||
public static String getDataSourceType()
|
||||
{
|
||||
return CONTEXT_HOLDER.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空数据源变量
|
||||
*/
|
||||
public static void clearDataSourceType()
|
||||
{
|
||||
CONTEXT_HOLDER.remove();
|
||||
}
|
||||
}
|
||||
@@ -109,4 +109,23 @@ public interface BaseMapperX<T> extends BaseMapper<T> {
|
||||
Db.updateBatchById(entities, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存或更新
|
||||
* @param entity 实体
|
||||
* @return 是否成功
|
||||
*/
|
||||
default boolean saveOrUpdate(T entity) {
|
||||
return Db.saveOrUpdate(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量保存或更新
|
||||
* @param entities 实体集合
|
||||
* @return 是否成功
|
||||
*/
|
||||
default boolean saveOrUpdateBatch(Collection<T> entities) {
|
||||
return Db.saveOrUpdateBatch(entities);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user