赞
踩
数据源配置
mybatis.config-location=classpath:mybatis/mybatis-config.xml
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
mybatis.type-aliases-package=com.cnpc.dj.party.entity
spring.http.multipart.maxFileSize=52428800
spring.http.multipart.maxRequestSize=52428800
spring.datasource.report.driverClassName=oracle.jdbc.driver.OracleDriver
spring.datasource.report.url=连接信息
spring.datasource.report.username=账号
spring.datasource.report.password=密码
spring.datasource.report.maxActive=1
#验证连接是否有效。此参数必须设置为非空字符串,下面三项设置成true才能生效
spring.datasource.report.validationQuery=SELECT 1
#指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除.
spring.datasource.report.testWhileIdle= true
#指明是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
spring.datasource.report.testOnBorrow= true
#指明是否在归还到池中前进行检验
spring.datasource.report.testOnReturn= false
#指定连接池等待连接返回的最大等待时间,毫秒单位
spring.datasource.report.maxWait=10000
#SQL 查询验证超时时间(秒)
spring.datasource.report.validationQueryTimeout=3
spring.datasource.detail.driverClassName=oracle.jdbc.driver.OracleDriver
spring.datasource.detail.url=连接信息
spring.datasource.detail.username=账号
spring.datasource.detail.password=密码
spring.datasource.detail.maxActive=1
#验证连接是否有效。此参数必须设置为非空字符串,下面三项设置成true才能生效
spring.datasource.detail.validationQuery: SELECT 1
#指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除.
spring.datasource.detail.testWhileIdle: true
#指明是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
spring.datasource.detail.testOnBorrow: true
#指明是否在归还到池中前进行检验
spring.datasource.detail.testOnReturn: false
#指定连接池等待连接返回的最大等待时间,毫秒单位
spring.datasource.detail.maxWait=10000
#SQL 查询验证超时时间(秒)
spring.datasource.detail.validationQueryTimeout=3
配置文件-----Start
package com.cnpc.dj.party.config;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.cnpc.dj.party.enums.DataSourceEnum;
import com.cnpc.dj.party.multiple.MultipleDataSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
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.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
@Configuration // 注册到springboot容器中
public class MybatisConfig {
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
Properties properties = new Properties();
//数据库
properties.setProperty("dialect", "oracle");
//是否将参数offset作为PageNum使用
properties.setProperty("offsetAsPageNum", "true");
//是否进行count查询
properties.setProperty("rowBoundsWithCount", "true");
//是否分页合理化
properties.setProperty("reasonable", "false");
paginationInterceptor.setProperties(properties);
return paginationInterceptor;
}
/**
*
* @methodDesc: 功能描述:(配置test1数据库)
*
*/
@Bean(name = “reportDb”)
@ConfigurationProperties(prefix = “spring.datasource.report” )//把同类的配置信息自动封装成实体类
public DataSource reportDb() {
return DataSourceBuilder.create().build();
}
/**
*
* @methodDesc: 功能描述:(配置test2数据库)
*
*/
@Bean(name = “detailDb”)
@ConfigurationProperties(prefix = “spring.datasource.detail” )
public DataSource detailDb() {
return DataSourceBuilder.create().build();
}
/** @Qualifier 根据名称进行注入,通常是在具有相同的多个类型的实例的一个注入(例如有多个DataSource类型的实例) * 动态数据源配置 * @return */ @Bean @Primary public DataSource multipleDataSource(@Qualifier("reportDb") DataSource reportDb, @Qualifier("detailDb") DataSource detailDb ) { MultipleDataSource multipleDataSource = new MultipleDataSource(); Map< Object, Object > targetDataSources = new HashMap<>(); targetDataSources.put(DataSourceEnum.REPORT.getValue(), reportDb); targetDataSources.put(DataSourceEnum.DETAIL.getValue(), detailDb); //添加数据源 multipleDataSource.setTargetDataSources(targetDataSources); //设置默认数据源 multipleDataSource.setDefaultTargetDataSource(reportDb); return multipleDataSource; } @Bean("sqlSessionFactory") public SqlSessionFactory sqlSessionFactory() throws Exception { MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean(); sqlSessionFactory.setDataSource(multipleDataSource(reportDb(),detailDb())); /* sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/*.xml"));*/ // 加载配置mybatis-config DefaultResourceLoader configResourceLoader = new DefaultResourceLoader(); Resource configResource = configResourceLoader.getResource("classpath:mybatis/mybatis-config.xml"); sqlSessionFactory.setConfigLocation(configResource); // 添加mapper文件XML目录 ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource[] resources = resolver.getResources("classpath:mybatis/mapper/*.xml"); sqlSessionFactory.setMapperLocations(resources); sqlSessionFactory.setPlugins(new Interceptor[]{ //PerformanceInterceptor(),OptimisticLockerInterceptor() paginationInterceptor() //添加分页功能 }); return sqlSessionFactory.getObject(); }
/**
**一、 首先继承AbstractRoutingDataSource,从名称上看为抽象路由数据源,就是spring为提供动态数据库而设定的。在这个类中,需要重写determineCurrentLookupKey这个方法,这个方法就是动态从
private Map<Object, Object> targetDataSources(AbstractRoutingDataSource类里面的属性)里面获取对应的数据源
**
package com.cnpc.dj.party.multiple;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
动态数据源(需要继承AbstractRoutingDataSource)
*/
public class MultipleDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}
/**
*
public class DataSourceContextHolder {
private static final ThreadLocal<String> 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(); }
}
6、
①查询test1的数据时,可以直接调用对应的mapper;(因为test1是默认数据源)
②查询test2的数据时,需要在调用之前指定数据源,如下代码:
DatabaseContextHolder.setDatabaseType(DatabaseType.test2);
③同时需要查询test1和test2的数据时,需要在调用之前指定正确的数据源。
这种是在业务中使用代码设置数据源的方式,也可以使用AOP+注解的方式实现控制,还可以前端头部设置后端通过拦截器统一设置!
使用注解的方式切块数据库
三、在controller层或者service的每个方法前切换数据源(aop切入点不同),用到AOP
重点说明:每多人会有疑问,springcloud的controller是单例,这样在多用户的情况下,会不会窜请求,线程不安全
原因解答:(1)请看上面,其实我们使用了ThreadLocal,如果不理解的,请去补ThreadLocal知识了
用多线程测试:我用多个线程,调用同一个查询方法,但每个请求的header中的dsNo都不一样,这样就真实模拟生产环境,多用户查的情况,看是否有有窜请求,线程不安全的情况,实际测试并没有这种情况出现
package com.cnpc.dj.party.aop;
import com.cnpc.dj.party.annotation.AnalyzeDataSource;
import com.cnpc.dj.party.multiple.DataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Slf4j
@Aspect
@Order(-1)
public class DataSourceAspect {
@Pointcut("@within(com.cnpc.dj.party.annotation.AnalyzeDataSource) || @annotation(com.cnpc.dj.party.annotation.AnalyzeDataSource)")
public void pointCut(){
}
@Before("pointCut() && @annotation(analyzeDataSource)")
public void doBefore(AnalyzeDataSource analyzeDataSource){
log.info("选择数据源---"+analyzeDataSource.value().getValue());
DataSourceContextHolder.setDataSource(analyzeDataSource.value().getValue());
}
@After("pointCut()")
public void doAfter(){
DataSourceContextHolder.clear();
}
}
自定义注解
package com.cnpc.dj.party.annotation;
import com.cnpc.dj.party.enums.DataSourceEnum;
import java.lang.annotation.*;
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AnalyzeDataSource {
/**
* zj2020-07-23默认是查询汇总数据
* @return
*/
DataSourceEnum value() default DataSourceEnum.REPORT;
}
枚举类
package com.cnpc.dj.party.enums;
public enum DataSourceEnum {
/**
* 测试用的一个数据库
/
REPORT(“report”),
/*
* 测试用的一个数据库
*/
DETAIL(“detail”);
private String value;
DataSourceEnum(String value){this.value=value;}
public String getValue() {
return value;
}
}
mybatis-config 文件
<?xml version="1.0" encoding="UTF-8" ?><!-- 全局启用或禁用延迟加载。当禁用时,所有关联对象都会即时加载。 --> <!-- <setting name="lazyLoadingEnabled" value="true"/> --> <!-- 当启用时,有延迟加载属性的对象在被调用时将会完全加载任意属性。否则,每种属性将会按需要加载。 --> <setting name="aggressiveLazyLoading" value="true"/> <!-- 是否允许单条sql 返回多个数据集 (取决于驱动的兼容性) default:true --> <setting name="multipleResultSetsEnabled" value="true"/> <!-- 是否可以使用列的别名 (取决于驱动的兼容性) default:true --> <setting name="useColumnLabel" value="true"/> <!-- 允许JDBC 生成主键。需要驱动器支持。如果设为了true,这个设置将强制使用被生成的主键,有一些驱动器不兼容不过仍然可以执行。 default:false --> <setting name="useGeneratedKeys" value="false"/> <!-- 指定 MyBatis 如何自动映射 数据基表的列 NONE:不隐射 PARTIAL:部分 FULL:全部 --> <setting name="autoMappingBehavior" value="PARTIAL"/> <!-- 这是默认的执行类型 (SIMPLE: 简单; REUSE: 执行器可能重复使用prepared statements语句;BATCH: 执行器可以重复执行语句和批量更新) --> <setting name="defaultExecutorType" value="SIMPLE"/> <!-- 使用驼峰命名法转换字段。 --> <setting name="mapUnderscoreToCamelCase" value="true"/> <!-- 设置本地缓存范围 session:就会有数据的共享 statement:语句范围 (这样就不会有数据的共享 ) defalut:session --> <setting name="localCacheScope" value="SESSION"/> <!-- 设置但JDBC类型为空时,某些驱动程序 要指定值,default:OTHER,插入空值时不需要指定类型 --> <setting name="jdbcTypeForNull" value="NULL"/> <!-- 打印查询语句 --> <setting name="logImpl" value="STDOUT_LOGGING" /> </settings> <typeAliases> <typeAlias alias="Integer" type="java.lang.Integer" /> <typeAlias alias="Long" type="java.lang.Long" /> <typeAlias alias="HashMap" type="java.util.HashMap" /> <typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap" /> <typeAlias alias="ArrayList" type="java.util.ArrayList" /> <typeAlias alias="LinkedList" type="java.util.LinkedList" /> </typeAliases>
<mappers>
</mappers>
前端头部设置后端通过拦截器统一设置
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
//
@Aspect
@Order(1)
@Configuration
public class DataSourceAspect {
private static final String dsNo=“dsNo”;//数据库编号 从header中取
/** * 切入点,放在controller的每个方法上进行切入,更新数据源 */ @Pointcut("execution(* com.eck.auto.controller..*.*(..))") private void anyMethod(){}//定义一个切入点 @Before("anyMethod()") public void dataSourceChange() { //请求头head中获取对应数据库编号 String no = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader(dsNo); System.out.print("当前数据源编号:"+no); if(StringUtils.isEmpty(no)){ //TODO 根据业务抛异常 } DataSourceHolder.setDataSource(no); /*这里数据库项目编号来更改对应的数据源*/ }
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。