Springboot 主從庫切換配置


應用場景:

在主從庫讀寫分離時,讓程序自動根據業務來區分對主庫還是從庫進行讀寫操作,在所有的寫操作時,自動對主庫進行操作,所有的讀操作時,則訪問從庫。

應用前提:

在兩台機器上配置好兩個數據庫,建立主從關系,接下來在springboot的框架中配置

 

首先在.yml或者.porperties文件中配置主從數據庫

#自定義druid主從連接
druid:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
#主節點
master:
url: jdbc:mysql://192.168.1.1:3306/datebase1?characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true&serverTimezone=GMT
username: root
password: root
driverClassName: com.mysql.cj.jdbc.Driver
#下面為連接池的補充設置,應用到上面所有數據源中
filters: stat,wall,log4j
initialSize: 5
maxActive: 20
maxPoolPreparedStatementPerConnectionSize: 20
maxWait: 60000
minEvictableIdleTimeMillis: 300000
minIdle: 5
poolPreparedStatements: true
testOnBorrow: false
testOnReturn: false
testWhileIdle: true
timeBetweenEvictionRunsMillis: 60000
useGlobalDataSourceStat: true
validationQuery: SELECT 1 FROM DUAL
#從節點
slave:
url: jdbc:mysql://192.168.1.2:3306/datebase2?characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true&serverTimezone=GMT
username: root
password: root
driverClassName: com.mysql.cj.jdbc.Driver
#下面為連接池的補充設置,應用到上面所有數據源中
initialSize: 5
minIdle: 5
maxActive: 20
# 配置獲取連接等待超時的時間
maxWait: 60000
# 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一個連接在池中最小生存的時間,單位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打開PSCache,並且指定每個連接上PSCache的大小
poolPreparedStatements: true
# 配置監控統計攔截的filters,去掉后監控界面sql無法統計,'wall'用於防火牆
filters: stat,wall,log4j
logSlowSql: true
maxPoolPreparedStatementPerConnectionSize: 20
# 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
connectionProperties:
druid:
stat:
mergeSql: true
slowSqlMillis: 5000
# 合並多個DruidDataSource的監控數據
useGlobalDataSourceStat: true


緊接着配置mybatis相關
#mybatis
mybatis:
#掃描xml
mapper-locations: classpath:com/email/support/mapping/*.xml
#配置
# configuration:
#控制台sql日志輸出
# log-impl: org.apache.ibatis.logging.log4j2.Log4j2Impl
#mybatis返回類型省略全路徑包名
type-aliases-package: com.email.support.mapper



然后添加datesource配置類


/**
* 數據庫連接池配置
*/
@Configuration
@EnableTransactionManagement
public class DatasourceConfig {
private static Logger LOG = LoggerFactory.getLogger(DatasourceConfig.class);

/**
* 引入druid數據庫連接池類型
*/
@Value("${druid.datasource.type}")
private Class<? extends DataSource> datasourceType;

/**
* 主數據源
* @return DataSource
*/
@Primary
@Bean("masterDatasource")
@ConfigurationProperties("druid.datasource.master")
public DataSource masterDatasource() {
LOG.info("=========== master run =========");
return DataSourceBuilder.create().type(datasourceType).build();
}

/**
* 從數據源
* @return DataSource
*/
@Bean("slaveDatasource")
@ConfigurationProperties("druid.datasource.slave")
public DataSource slaveDatasource() {
LOG.info("============ slave run ==========");
return DataSourceBuilder.create().type(datasourceType).build();
}

/**
* druid的servlet
* @return ServletRegistrationBean
*/
@Bean
public ServletRegistrationBean druidServlet() {
ServletRegistrationBean<StatViewServlet> servletRegistrationBean = new ServletRegistrationBean<>();
servletRegistrationBean.setServlet(new StatViewServlet());
servletRegistrationBean.addUrlMappings("/druid/*");
servletRegistrationBean.addInitParameter("allow", "127.0.0.1");
servletRegistrationBean.addInitParameter("deny", "/deny");
return servletRegistrationBean;
}

/**
* druid的filter
* @return FilterRegistrationBean
*/
@Bean
public FilterRegistrationBean druidFilter() {
FilterRegistrationBean<WebStatFilter> webStatFilterRegistrationBean = new FilterRegistrationBean<>();
webStatFilterRegistrationBean.setFilter(new WebStatFilter());
webStatFilterRegistrationBean.addUrlPatterns("/*");
webStatFilterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return webStatFilterRegistrationBean;
}
}
然后配置數據庫的讀寫操作
新建contextholder類
**
* 數據源本地線程操作
*/
public class DynamicDataSourceContextHolder {

private static final ThreadLocal<DatasourseType> CONTEXT_HOLDER = new ThreadLocal<>();

public enum DatasourseType{
/**
* 數據源類型
*/
MASTER,
SLAVE;

}

/**
* 設置本地線程變量
* @param datasourseType 數據類型
*/
public static void setContextHolder(DatasourseType datasourseType){
CONTEXT_HOLDER.set(datasourseType);
}

/**
* 獲取本地線程變量的值 默認返回主數據源
* @return DatasourseType
*/
public static DatasourseType getContextHolder(){
DatasourseType datasourseType = CONTEXT_HOLDER.get();
return Objects.isNull(datasourseType) ? DatasourseType.MASTER :datasourseType;
}

/**
* 清空本地線程變量
*/
public static void removeDataSourceType(){
CONTEXT_HOLDER.remove();
}

}


添加mybatis配置類繼承MybatisAutoConfiguration接口  注意:mybatis和datesource有強依賴關系   所以必須先添加datesource的相關配置再進行mybatis的配置
然后將datesource通過@Resource注解 進行注入 放入mybatis的sqlsessionfactory進行管理

/**
* mybatis配置
*/
@Configuration
public class MybatisConfig extends MybatisAutoConfiguration {
/**
* 主數據源 根據名稱注入
*/
@Resource(name = "slaveDatasource")
private DataSource slaveDatasource;

@Resource(name = "masterDatasource")
private DataSource masterDatasource;

@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
return super.sqlSessionFactory(datasourceStrategy());
}

/**
* 數據源策略 ----此方法是在添加DynamicDataSource后 將數據源注入進sqlsessionfactroy進行數據源管理
* @return 數據源
*/
@SuppressWarnings("unchecked")
private AbstractRoutingDataSource datasourceStrategy(){
DynamicDataSource dynamicDataSource = new DynamicDataSource();
//軟引用map
ClassLoaderRepository.SoftHashMap softHashMap = new ClassLoaderRepository.SoftHashMap();
//設置主從
softHashMap.put(DynamicDataSourceContextHolder.DatasourseType.MASTER, masterDatasource);
softHashMap.put(DynamicDataSourceContextHolder.DatasourseType.SLAVE, slaveDatasource);
//默認使用主數據源
dynamicDataSource.setTargetDataSources(softHashMap);
//將兩個數據源放入map
dynamicDataSource.setDefaultTargetDataSource(masterDatasource);
return dynamicDataSource;
}
}

 
 
        
接下來就是要把主從庫動態切換的配置
新建RoutingDataSource 繼承 AbstractRoutingDataSource
 
        
 
        
 
        
/**
* 配置數據源容器對象
*/
public class DynamicDataSource extends AbstractRoutingDataSource {

@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getContextHolder();
}

}

 


接下來使用自定義注解告訴方法訪問哪個數據庫
建立SlaveDatasource注解
/**
* slave數據源標識
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SlaveDatasource {

}


新建自定義注解的aop切面
 
        
/**
* slave數據源aop
*/
@Aspect
@Component
public class SlaveDatasourceAop implements Ordered {
private static final Logger LOGGER = LoggerFactory.getLogger(SlaveDatasourceAop.class);

@Pointcut("@annotation(com.email.support.annotation.SlaveDatasource)")
public void slaveDatasource(){}

@Around("slaveDatasource()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
try {
DynamicDataSourceContextHolder.setContextHolder(DynamicDataSourceContextHolder.DatasourseType.SLAVE);
LOGGER.info("========== change to slave ==========");
return joinPoint.proceed();
}finally {
DynamicDataSourceContextHolder.removeDataSourceType();
}
}

@Override
public int getOrder() {
return 0;
}
}
 
至此配置完成 下面測試查看效果

添加測試方法


//測試主從庫切換
@Resource(name = "slaveDatasource")
private DataSource slaveDatasource;

@Resource(name = "masterDatasource")
private DataSource masterDatasource;

@Test
public void contextLoads() throws Exception{
Connection c1=masterDatasource.getConnection("主數據庫賬號","主數據庫密碼");
System.err.println("c1:" + c1.getMetaData().getURL());
Connection c2=slaveDatasource.getConnection("從數據庫賬號","從數據庫密碼");
System.err.println("c2:" + c2.getMetaData().getURL());
}



輸出結果

 

 

 

c1:為我的主庫地址

c2:為我的從庫地址

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM