近来公司制定了未来一年的的大方向,博主负责的当前的项目还是多年前的springcloud那套架构,而其他项目组都还是单体项目或者是更新的架构,各种架构层出不穷,数据和接口,业务打通比较困难,为了短时间内迅速的进行开发,博主亲自操刀整合了个单体项目,采用的是springboot+mybatisplus+shiro+redis+swagger+多数据源的方案,之前博主最先想采用satoken的权限框架的,后来问了下团队其它成员不会使用,为了尽快开发,还是选用的是shiro的权限框架,姜还是老的辣嘛,本期重点来了,由于业务需要接入多个数据源,所以采坑实现多数据源方案,博主之前还没仔细研究过这个多数据源的方案,踩坑几天,总结了一下,文笔有限,看看就好,如有不对,还请指出。
目前实现的多数据源的方案有两种:
先前讲下springboot注入数据源的目前有两种数据库连接池,
第一种是:
com.alibaba
druid
1.2.6
第二种:
com.alibaba
druid-spring-boot-starter
1.1.22
大概的区别就是,两者其实包含关系,第二种对第一种做了整合,第一种方式还得写注入datasource的一些getset方法,第二种就不用写了,直接就可以让spring自动扫描读配置文件的信息,建议还是用第二种,简单方便。
接下来进入正题,多数据源的方案:
第一种:使用分包方式,不同的数据源配置不同的MapperScan和mapper文件;
第二种:使用APO切片方式,实现动态数据源切换;
第三种:使用多数据源的框架:dynamic-datasource-spring-boot-starter,前提是用的是mubatisplus;
三种方式的区别:
(1)、分包方式可以使用Transactional API实现分布式事务。
(2)、AOP动态配置数据源方式缺点在于无法实现全局分布式事务。
(3)、dynamic框架比较灵活,可以实现事务隔离和注解的形式,简单高效,就是受限于mybatisplus框架。
相比之下,博主也是采坑几天,所有的方式都实现了一遍。贴上不同方式的实现过程如下:
POM:
com.baomidou
mybatis-plus-boot-starter
3.4.2
com.baomidou
mybatis-plus-generator
3.4.1
com.alibaba
druid-spring-boot-starter
1.2.6
ru.yandex.clickhouse
clickhouse-jdbc
0.3.0
mysql
mysql-connector-java
runtime
org.springframework.boot
spring-boot-starter-aop
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
master:
url: jdbc:mysql://localhost:3306/reddata?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=true&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
click:
url: jdbc:clickhouse://
driverClassName: ru.yandex.clickhouse.ClickHouseDriver
initialSize: 10
maxActive: 100
minIdle: 10
maxWait: 6000
testOnBorrow: true
validationQuery: SELECT 1
目录结构:
核心config类:
package cn.reddata.plus.www.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
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.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
/**
* MybatisConfig配置类
*
* @author aries
* @date 2021/12/03 0:31
*/
@Configuration
@ConditionalOnClass(value = {PaginationInterceptor.class})
@MapperScan("cn.reddata.plus.www.domain.mapper.mysql")
@MapperScan("cn.reddata.plus.www.domain.mapper.click")
public class MybatisPlusConfig {
//设置分页的bean=====================================================================================================
@Bean
public PaginationInterceptor mysqlPaginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
paginationInterceptor.setDbType(DbType.MYSQL);
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
@Bean
public PaginationInterceptor clickPaginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
paginationInterceptor.setDbType(DbType.CLICK_HOUSE);
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
// @Bean(name = "db1")
// @ConditionalOnProperty(name = "spring.datasource.druid.enabled",havingValue = "true")
// @ConfigurationProperties(prefix = "spring.datasource.druid.master" )
// public DataSource db1() {
// return DruidDataSourceBuilder.create().build();
// }
/**
* 创建数据源
*/
@Bean(name = "db1")
@ConfigurationProperties(prefix = "spring.datasource.druid.master")
public DataSource db1() {
return new DruidDataSource();
}
/**
* 创建数据源
*/
@Bean(name = "db2")
@ConfigurationProperties(prefix = "spring.datasource.click")
public DataSource db2() {
return new DruidDataSource();
}
@Configuration
@MapperScan(basePackages = "cn.reddata.plus.www.domain.mapper.mysql", sqlSessionTemplateRef = "sqlSessionTemplate")
public class Db1 {
@Bean
@Primary
public SqlSessionFactory sqlSessionFactory(@Qualifier("db1") DataSource dataSource) throws Exception {
//mybatis应该使用 SqlSessionFactoryBean
//SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
//mybatis-plus应该使用 MybatisSqlSessionFactoryBean 否则无法使用baseMapper
MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
sqlSessionFactory.setDataSource(dataSource);
sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:/mappers/mysql/*.xml"));
//添加分页功能================================================================================================
sqlSessionFactory.setPlugins(new Interceptor[]{mysqlPaginationInterceptor()});
return sqlSessionFactory.getObject();
}
@Bean
@Primary
public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
@Bean
@Primary
public DataSourceTransactionManager dataSourceTransactionManager(@Qualifier("db1") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
@Configuration
@MapperScan(basePackages = "cn.reddata.plus.www.domain.mapper.click", sqlSessionTemplateRef = "sqlSessionTemplate2")
public class Db2 {
@Bean
public SqlSessionFactory sqlSessionFactory2(@Qualifier("db2") DataSource dataSource) throws Exception {
//mybatis应该使用 SqlSessionFactoryBean
//SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
//mybatis-plus应该使用 MybatisSqlSessionFactoryBean 否则无法使用baseMapper
MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
sqlSessionFactory.setDataSource(dataSource);
sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:/mappers/click/*.xml"));
//添加分页功能================================================================================================
sqlSessionFactory.setPlugins(new Interceptor[]{clickPaginationInterceptor()});
return sqlSessionFactory.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate2(@Qualifier("sqlSessionFactory2") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
@Bean
public DataSourceTransactionManager dataSourceTransactionManager2(@Qualifier("db2") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
}
启动类:
2, 分页插件需要指定数据库:
3,分页功能还没测试,依照之前的经验,click的分页需要自定义扩展。后续有空完善下这个。
pom:
UTF-8
UTF-8
1.8
1.1.9
2.7.0
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-devtools
runtime
mysql
mysql-connector-java
runtime
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
com.baomidou
mybatis-plus-boot-starter
2.1.9
com.alibaba
druid-spring-boot-starter
${druid.version}
io.springfox
springfox-swagger2
${swagger.version}
io.springfox
springfox-swagger-ui
${swagger.version}
org.springframework.boot
spring-boot-starter-aop
yml:
spring:
datasource:
druid:
db1:
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql:///db1
initialSize: 5
minIdle: 5
maxActive: 20
db2:
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql:///db2
initialSize: 5
minIdle: 5
maxActive: 20
目录结构:
核心config类:
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
DataSourceEnum value() default DataSourceEnum.DB1;
}
@Component
@Slf4j
@Aspect
@Order(-1)
public class DataSourceAspect {
@Pointcut("@within(com.seawaterbt.ssm.annotation.DataSource) || @annotation(com.seawaterbt.ssm.annotation.DataSource)")
public void pointCut(){
}
@Before("pointCut() && @annotation(dataSource)")
public void doBefore(DataSource dataSource){
log.info("选择数据源---"+dataSource.value().getValue());
DataSourceContextHolder.setDataSource(dataSource.value().getValue());
}
@After("pointCut()")
public void doAfter(){
DataSourceContextHolder.clear();
}
}
@Configuration
public class DruidConfiguration {
@Bean
public ServletRegistrationBean startViewServlet(){
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(),"/druid/*");
// IP白名单
servletRegistrationBean.addInitParameter("allow","127.0.0.1");
// IP黑名单(共同存在时,deny优先于allow)
servletRegistrationBean.addInitParameter("deny","127.0.0.1");
//控制台管理用户
servletRegistrationBean.addInitParameter("loginUsername","admin");
servletRegistrationBean.addInitParameter("loginPassword","123456");
//是否能够重置数据
servletRegistrationBean.addInitParameter("resetEnable","false");
return servletRegistrationBean;
}
@Bean
public FilterRegistrationBean statFilter(){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
//添加过滤规则
filterRegistrationBean.addUrlPatterns("/*");
//忽略过滤的格式
filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
}
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.baomidou.mybatisplus.MybatisConfiguration;
import com.baomidou.mybatisplus.entity.GlobalConfiguration;
import com.baomidou.mybatisplus.mapper.LogicSqlInjector;
import com.baomidou.mybatisplus.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.plugins.PerformanceInterceptor;
import com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean;
import com.seawaterbt.ssm.enums.DataSourceEnum;
import com.seawaterbt.ssm.multiple.MultipleDataSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
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.context.annotation.Profile;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
@MapperScan("com.seawaterbt.ssm.mapper*")
public class MyBatiesPlusConfiguration {
/*
* 分页插件,自动识别数据库类型
* 多租户,请参考官网【插件扩展】
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 开启 PageHelper 的支持
paginationInterceptor.setLocalPage(true);
return paginationInterceptor;
}
/**
* SQL执行效率插件
*/
@Bean
@Profile({"dev","qa"})// 设置 dev test 环境开启
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(1000);
performanceInterceptor.setFormat(true);
return performanceInterceptor;
}
@Bean(name = "db1")
@ConfigurationProperties(prefix = "spring.datasource.druid.db1" )
public DataSource db1() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "db2")
@ConfigurationProperties(prefix = "spring.datasource.druid.db2" )
public DataSource db2() {
return DruidDataSourceBuilder.create().build();
}
/**
* 动态数据源配置
* @return
*/
@Bean
@Primary
public DataSource multipleDataSource(@Qualifier("db1") DataSource db1, @Qualifier("db2") DataSource db2) {
MultipleDataSource multipleDataSource = new MultipleDataSource();
Map< Object, Object > targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceEnum.DB1.getValue(), db1);
targetDataSources.put(DataSourceEnum.DB2.getValue(), db2);
//添加数据源
multipleDataSource.setTargetDataSources(targetDataSources);
//设置默认数据源
multipleDataSource.setDefaultTargetDataSource(db1);
return multipleDataSource;
}
@Bean("sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory() throws Exception {
MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
sqlSessionFactory.setDataSource(multipleDataSource(db1(),db2()));
//sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/*/*Mapper.xml"));
MybatisConfiguration configuration = new MybatisConfiguration();
//configuration.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class);
configuration.setJdbcTypeForNull(JdbcType.NULL);
configuration.setMapUnderscoreToCamelCase(true);
configuration.setCacheEnabled(false);
sqlSessionFactory.setConfiguration(configuration);
sqlSessionFactory.setPlugins(new Interceptor[]{ //PerformanceInterceptor(),OptimisticLockerInterceptor()
paginationInterceptor() //添加分页功能
});
//sqlSessionFactory.setGlobalConfig(globalConfiguration());
return sqlSessionFactory.getObject();
}
/*@Bean
public GlobalConfiguration globalConfiguration() {
GlobalConfiguration conf = new GlobalConfiguration(new LogicSqlInjector());
conf.setLogicDeleteValue("-1");
conf.setLogicNotDeleteValue("1");
conf.setIdType(0);
//conf.setMetaObjectHandler(new MyMetaObjectHandler());
conf.setDbColumnUnderline(true);
conf.setRefresh(true);
return conf;
}*/
}
public enum DataSourceEnum {
DB1("db1"),DB2("db2");
private String value;
DataSourceEnum(String value){this.value=value;}
public String getValue() {
return value;
}
}
public class DataSourceContextHolder {
private static final ThreadLocal contextHolder = new InheritableThreadLocal<>();
/**
* 设置数据源
* @param db
*/
public static void setDataSource(String db){
contextHolder.set(db);
}
/**
* 取得当前数据源
* @return
*/
public static String getDataSource(){
return contextHolder.get();
}
/**
* 清除上下文数据
*/
public static void clear(){
contextHolder.remove();
}
}
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class MultipleDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}
启动类:
使用注解方式:
@Override
@DataSource(DataSourceEnum.DB2)
public boolean insert(Teacher entity) {
return super.insert(entity);
}
pom:
1.8
1.0.29
4.1
3.1.1
1.4.0
3.1.0
org.springframework.boot
spring-boot-starter-actuator
org.springframework.boot
spring-boot-starter-web
mysql
mysql-connector-java
runtime
com.alibaba
druid-spring-boot-starter
1.2.6
com.baomidou
dynamic-datasource-spring-boot-starter
3.2.0
ru.yandex.clickhouse
clickhouse-jdbc
0.3.0
com.baomidou
mybatis-plus-boot-starter
${mybatis-plus.version}
com.baomidou
mybatis-plus-generator
${mybatis-plus.version}
com.baomidou
mybatis-plus-generator
3.4.1
yml:
spring:
autoconfigure:
exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
datasource:
dynamic:
primary: master
strict: false
datasource:
master:
url: jdbc:mysql://localhost:3306/reddata?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=true&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
click:
url: jdbc:clickhouse://
driverClassName: ru.yandex.clickhouse.ClickHouseDriver
initialSize: 10
maxActive: 100
minIdle: 10
maxWait: 6000
type: com.alibaba.druid.pool.DruidDataSource
druid:
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000;config.decrypt=true;config.decrypt.key=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKPYsCl3alwZlRb1vKoFdVu0LP3Nm/+vH5iOWxI83pkUbrQc13Lxz/VT3D+H+ziaUpUsA+ZjG4iZGTDJWZnP8kcCAwEAAQ==
initialSize: 10
minIdle: 10
maxActive: 500
maxWait: 60000
testOnReturn: false
testOnBorrow: false
testWhileIdle: true
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 30000
poolPreparedStatements: true
validationQuery: select 'x'
maxPoolPreparedStatementPerConnectionSize: 50
filters: config,stat
目录结构:
核心congfig:
package cn.reddata.plus.www.config;
import com.baomidou.mybatisplus.core.parser.ISqlParser;
import com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
//import com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import java.util.ArrayList;
import java.util.List;
/**
* MyBatisPlus 配置
*
* 创建人:Aries-li
* 创建时间:2019-06-14 10:40
*
* 修改人:
* 修改时间:
* 修改备注:
*
*/
@Configuration
@MapperScan(basePackages = {"cn.reddata.plus.www.domain.mapper"})
public class MyBatisPlusConfig {
/**
* 分页插件
* @author :Aries-li
* @date :2019-06-14 10:43
*
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
List sqlParserList = new ArrayList<>();
// 攻击 SQL 阻断解析器、加入解析链 作用!阻止恶意的全表更新删除
sqlParserList.add(new BlockAttackSqlParser());
paginationInterceptor.setSqlParserList(sqlParserList);
return paginationInterceptor;
}
/**
* 乐观锁
* @author :Aries-li
* @date :2019-06-14 10:55
*
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
package cn.reddata.plus.www.config;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Druid监控配置,使用基于Java代码实现的servlet和filter
*
* 具体实现参考
*
* 基于注解的配置StatView的Servlet
*
*
* 创建人:Aries-li
* 创建时间:2018-11-10 13:43
*
* 修改人:
* 修改时间:
* 修改备注:
*
*/
@Configuration
@Slf4j
public class DruidMonitorConfig {
/**
* 注册ServletRegistrationBean
* @return
*/
@Bean
public ServletRegistrationBean registrationBean() {
//org.springframework.boot.context.embedded.ServletRegistrationBean提供类的进行注册.
ServletRegistrationBean bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
/** 初始化参数配置,initParams**/
//白名单
bean.addInitParameter("allow", "127.0.0.1");//多个ip逗号隔开
//IP黑名单 (存在共同时,deny优先于allow) : 如果满足deny的话提示:Sorry, you are not permitted to view this page.
//bean.addInitParameter("deny", "192.168.1.110");
//登录查看信息的账号密码.
bean.addInitParameter("loginUsername", "admin");
bean.addInitParameter("loginPassword", "123456");
//是否能够重置数据.
bean.addInitParameter("resetEnable", "false");
return bean;
}
/**
* 注册FilterRegistrationBean
* @return
*/
@Bean
public FilterRegistrationBean druidStatFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean<>(new WebStatFilter());
//添加过滤规则.
bean.addUrlPatterns("/*");
//添加不需要忽略的格式信息.
bean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return bean;
}
}
启动类:
package cn.reddata.plus.www;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableTransactionManagement
public class SpringBootShiroApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootShiroApplication.class, args);
}
}
使用方法:
/**
* 主键查询用户信息
*
* @param id
* @return
*/
@DS("click")
@InterceptorIgnore(tenantLine = "true")
Order findById(@Param("id") long id);
此方法注意事项:
写在结束的总结:
整合多数据源这个需求还是频率挺高的,技术这行,活到老学到老,目前三种方式的分页功能还未测试,如有问题后续补上哈,欢迎点赞订阅。