SpringBoot Druid Mysql多數據源整合
- 一、背景
- 二、配置結果
- 2.1 SpringBoot java 類配置
- 2.1.1 啟動類配置
- 2.1.2 java Config配置
- 2.2 SpringBoot yml 配置
- 三、mybatis插件配置
- 3.1 PageHelper的yml配置
- 3.2 mybatis設置自定義字段默認值
- 四、配置解釋
一、背景
-
公司項目需要連接另外一個數據庫來處理數據
-
處理方法其實很多,如下所示:
- 給這個數據庫單獨跑一個服務,用來查詢數據
- 直接將該數據庫表同步到本地服務數據庫
- 在本地服務中直接配置多數據源用來查詢數據
-
其實每種方法都有自己的利弊,由于各種原因還是選擇了方法3,配置多數據源進行查詢
-
SpringBoot版本 2.3.12.RELEASE
-
Druid版本
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.22</version>
</dependency>
- 其他mysql,mybatis都是正常配置
二、配置結果
2.1 SpringBoot java 類配置
2.1.1 啟動類配置
@SpringBootApplication(exclude = {DruidDataSourceAutoConfigure.class})
@ServletComponentScan
@EnableCaching
public class TestApplication {public static void main(String[] args) {SpringApplication.run(TestApplication.class, args);}}
- 其中使用@SpringBootApplication(exclude = {DruidDataSourceAutoConfigure.class})的原因見1
2.1.2 java Config配置
- 需要建立3個java類,DruidAutoConfiguration.java,DruidConfigTestOne.java,DruidConfigTestTwo.java
- DruidAutoConfiguration作用見2
- DruidConfigTestOne作用見3
- DruidConfigTestTwo作用見4
@Configuration
@ConditionalOnClass(DruidDataSource.class)
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties(DruidStatProperties.class)
@Import({DruidSpringAopConfiguration.class,DruidStatViewServletConfiguration.class,DruidWebStatFilterConfiguration.class,DruidFilterConfiguration.class})
public class DruidAutoConfiguration {
}
@Configuration
@MapperScan(basePackages = DruidConfigTestOne.PACKAGE, sqlSessionTemplateRef = "testOneSqlSessionTemplate")
public class DruidConfigTestOne {/*** mapper 所在包* 這個地方需要注意xml文件要在resources目錄com/test/one/mapper下,mapper java類在com.test.one.mapper包下,這樣就不需要配置xml文件所在位置,否則需要在sqlSessionFactory上面新增mapperLocation的配置* bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/com/test/one/mapper/*.xml"));*/protected static final String PACKAGE = "com.test.one.mapper";/*** 配置自定義數據源,一定要切記,在SpringBoot啟動類上去掉DruidDataSourceAutoConfigure.class** @return javax.sql.DataSource* @author liulin* @date 2025/4/11 16:49*/@Bean("testOneDataSource")@ConfigurationProperties(prefix = "spring.datasource.druid.test-one")@Primarypublic DataSource testOneDataSource(){return DruidDataSourceBuilder.create().build();}/*** 創建并配置一個SqlSessionFactory實例* 該方法使用Spring框架的@Bean注解,表明該方法會返回一個SqlSessionFactory對象,該對象將被Spring容器管理** @param dataSource 數據源,通過該參數指定SqlSessionFactory將要使用的數據庫連接* @return SqlSessionFactory 一個配置好的SqlSessionFactory實例,用于創建SqlSession* @throws Exception 如果創建過程中出現錯誤,將拋出異常*/@Bean(name = "testOneSqlSessionFactory")@Primarypublic SqlSessionFactory sqlSessionFactory(@Qualifier("testOneDataSource") DataSource dataSource) throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();bean.setDataSource(dataSource);return bean.getObject();}/*** 創建一個數據源事務管理器* <p/>* 該方法定義了一個Spring的@Bean注解,用于創建一個DataSourceTransactionManager實例* 這個實例用于管理與數據源相關的事務這個方法被標記為@Primary,意味著在相同類型* 的多個Bean存在時,Spring會默認選擇這個Bean** @param dataSource 數據源實例,用于事務管理器的構造* @return DataSourceTransactionManager實例,用于管理數據庫事務*/@Bean(name = "testOneTransactionManager")@Primarypublic DataSourceTransactionManager transactionManager(@Qualifier("testOneDataSource") DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}/*** 創建并配置一個SqlSessionTemplate實例* 該實例用于執行SQL操作,利用Spring框架的依賴注入功能進行管理** @param sqlSessionFactory 用于創建SqlSessionTemplate的工廠,通過資格器@Qualifier指定特定的工廠* @return 返回配置好的SqlSessionTemplate實例,用于執行數據庫操作* @throws Exception 如果實例化過程中遇到任何問題,則拋出異常*/@Bean(name = "testOneSqlSessionTemplate")@Primarypublic SqlSessionTemplate sqlSessionTemplate(@Qualifier("testOneSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {return new SqlSessionTemplate(sqlSessionFactory);}
}
@Configuration
@MapperScan(basePackages = DruidConfigTestTwo.PACKAGE, sqlSessionTemplateRef = "testTwoSqlSessionTemplate")
public class DruidConfigTestTwo {/*** mapper 所在包*/protected static final String PACKAGE = "com.test.two.mapper";/*** 配置自定義數據源,一定要切記,在SpringBoot啟動類上去掉DruidDataSourceAutoConfigure.class** @return javax.sql.DataSource* @author liulin* @date 2025/4/11 16:49*/@Bean("testTwoDataSource")@ConfigurationProperties(prefix = "spring.datasource.druid.test-two")public DataSource testTwoDataSource(){return DruidDataSourceBuilder.create().build();}/*** 創建并配置一個SqlSessionFactory實例* 該方法使用Spring框架的@Bean注解,表明該方法會返回一個SqlSessionFactory對象,該對象將被Spring容器管理** @param dataSource 數據源,通過該參數指定SqlSessionFactory將要使用的數據庫連接* @return SqlSessionFactory 一個配置好的SqlSessionFactory實例,用于創建SqlSession* @throws Exception 如果創建過程中出現錯誤,將拋出異常*/@Bean(name = "testTwoSqlSessionFactory")public SqlSessionFactory sqlSessionFactory(@Qualifier("testTwoDataSource") DataSource dataSource) throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();bean.setDataSource(dataSource);return bean.getObject();}/*** 創建一個數據源事務管理器* <p/>* 該方法定義了一個Spring的@Bean注解,用于創建一個DataSourceTransactionManager實例* 這個實例用于管理與數據源相關的事務這個方法被標記為@Primary,意味著在相同類型* 的多個Bean存在時,Spring會默認選擇這個Bean** @param dataSource 數據源實例,用于事務管理器的構造* @return DataSourceTransactionManager實例,用于管理數據庫事務*/@Bean(name = "testTwoTransactionManager")public DataSourceTransactionManager transactionManager(@Qualifier("testTwoDataSource") DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}/*** 創建并配置一個SqlSessionTemplate實例* 該實例用于執行SQL操作,利用Spring框架的依賴注入功能進行管理** @param sqlSessionFactory 用于創建SqlSessionTemplate的工廠,通過資格器@Qualifier指定特定的工廠* @return 返回配置好的SqlSessionTemplate實例,用于執行數據庫操作* @throws Exception 如果實例化過程中遇到任何問題,則拋出異常*/@Bean(name = "testTwoSqlSessionTemplate")public SqlSessionTemplate sqlSessionTemplate(@Qualifier("testTwoSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {return new SqlSessionTemplate(sqlSessionFactory);}
}
2.2 SpringBoot yml 配置
- yml 配置原因見5
spring:datasource:# 這個配置可要可不要,意義不大type: com.alibaba.druid.pool.DruidDataSourcedruid:# Spring監控aop-patterns: com.test.one.service.*,com.test.two.service.*# 監控配置web-stat-filter:# 是否啟用StatFilter默認值trueenabled: true# 添加過濾規則url-pattern: /*# 忽略過濾的格式exclusions: /druid/*,*.js,*.gif,*.jpg,*.png,*.css,*.ico# 關閉session監控session-stat-enable: false# Druid內置提供了一個StatViewServlet用于展示Druid的統計信息stat-view-servlet:# 是否啟用StatViewServlet默認值trueenabled: true# 訪問路徑為/druid時,跳轉到StatViewServleturl-pattern: /druid/*# 是否能夠重置數據reset-enable: false# 需要賬號密碼才能訪問控制臺,默認為rootlogin-username: testlogin-password: test# IP白名單allow:# IP黑名單(共同存在時,deny優先于allow)deny:test-one:url: jdbc:mysql://127.0.0.1:3306/test_one?useSSL=false&characterEncoding=utf8&useTimezone=true&serverTimezone=GMT%2B8username: rootpassword: '123456'driver-class-name: com.mysql.cj.jdbc.Driver# 初始化時建立物理連接的個數initial-size: 5# 連接池的最小空閑數量min-idle: 5# 連接池最大連接數量max-active: 200# 獲取連接時最大等待時間,單位毫秒max-wait: 60000# 申請連接的時候檢測,如果空閑時間大于timeBetweenEvictionRunsMillis,執行validationQuery檢測連接是否有效。test-while-idle: true# 既作為檢測的間隔時間又作為testWhileIdel執行的依據time-between-eviction-runs-millis: 60000# 銷毀線程時檢測當前連接的最后活動時間和當前時間差大于該值時,關閉當前連接(配置連接在池中的最小生存時間)min-evictable-idle-time-millis: 30000# 用來檢測數據庫連接是否有效的sql 必須是一個查詢語句(oracle中為 select 1 from dual)validation-query: select 1# 申請連接時會執行validationQuery檢測連接是否有效,開啟會降低性能,默認為truetest-on-borrow: false# 歸還連接時會執行validationQuery檢測連接是否有效,開啟會降低性能,默認為truetest-on-return: false# 是否緩存preparedStatement, 也就是PSCache,PSCache對支持游標的數據庫性能提升巨大,比如說oracle,在mysql下建議關閉。pool-prepared-statements: false# 置監控統計攔截的filters,去掉后監控界面sql無法統計,stat: 監控統計、Slf4j:日志記錄、waLL: 防御sqL注入filters: stat,wall,slf4j# 要啟用PSCache,必須配置大于0,當大于0時,poolPreparedStatements自動觸發修改為true。在Druid中,不會存在Oracle下PSCache占用內存過多的問題,可以把這個數值配置大一些,比如說100max-pool-prepared-statement-per-connection-size: -1# 合并多個DruidDataSource的監控數據use-global-data-source-stat: true# 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000test-two:url: jdbc:mysql://127.0.0.1:3306/test_two?useSSL=false&characterEncoding=utf8&useTimezone=true&serverTimezone=GMT%2B8username: rootpassword: '123456'driver-class-name: com.mysql.cj.jdbc.Driver# 初始化時建立物理連接的個數initial-size: 5# 連接池的最小空閑數量min-idle: 5# 連接池最大連接數量max-active: 200# 獲取連接時最大等待時間,單位毫秒max-wait: 60000# 申請連接的時候檢測,如果空閑時間大于timeBetweenEvictionRunsMillis,執行validationQuery檢測連接是否有效。test-while-idle: true# 既作為檢測的間隔時間又作為testWhileIdel執行的依據time-between-eviction-runs-millis: 60000# 銷毀線程時檢測當前連接的最后活動時間和當前時間差大于該值時,關閉當前連接(配置連接在池中的最小生存時間)min-evictable-idle-time-millis: 30000# 用來檢測數據庫連接是否有效的sql 必須是一個查詢語句(oracle中為 select 1 from dual)validation-query: select 1# 申請連接時會執行validationQuery檢測連接是否有效,開啟會降低性能,默認為truetest-on-borrow: false# 歸還連接時會執行validationQuery檢測連接是否有效,開啟會降低性能,默認為truetest-on-return: false# 是否緩存preparedStatement, 也就是PSCache,PSCache對支持游標的數據庫性能提升巨大,比如說oracle,在mysql下建議關閉。pool-prepared-statements: false# 置監控統計攔截的filters,去掉后監控界面sql無法統計,stat: 監控統計、Slf4j:日志記錄、waLL: 防御sqL注入filters: stat,wall,slf4j# 要啟用PSCache,必須配置大于0,當大于0時,poolPreparedStatements自動觸發修改為true。在Druid中,不會存在Oracle下PSCache占用內存過多的問題,可以把這個數值配置大一些,比如說100max-pool-prepared-statement-per-connection-size: -1# 合并多個DruidDataSource的監控數據use-global-data-source-stat: true# 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
三、mybatis插件配置
3.1 PageHelper的yml配置
- yml配置原因見6
#分頁插件配置
pagehelper:helper-dialect: mysqlreasonable: truesupport-methods-arguments: trueparams: count=countSql
3.2 mybatis設置自定義字段默認值
- 需要2個java類,MyBatisInterceptorConfig.java,SetMySqlValueAutoConfiguration.java
- MyBatisInterceptorConfig見原因7
- SetMySqlValueAutoConfiguration見原因8
@Slf4j
@SuppressWarnings("unchecked")
@Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }) })
public class MyBatisInterceptorConfig implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];// 獲取 SQL 命令SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();// 獲取參數Object parameter = invocation.getArgs()[1];// 如果不存在參數,直接返回if (parameter == null) {return invocation.proceed();}// 如果是批量插入一般是List類型,包裝在ParamMap中if (parameter instanceof MapperMethod.ParamMap) {MapperMethod.ParamMap<Object> paramMap = (MapperMethod.ParamMap<Object>) parameter;for (Map.Entry<String, Object> entry : paramMap.entrySet()) {Object paramValue = entry.getValue();if (paramValue instanceof List) {List<Object> list = (List<Object>) paramValue;for (Object value : list) {// 設置對應數據值setFieldValues(value, sqlCommandType);}break;}}} else {setFieldValues(parameter, sqlCommandType);}return invocation.proceed();}/*** 設置字段對應值** @param parameter 實體信息* @param sqlCommandType sql類型* @author liulin* @date 2024-04-22 16:32:39**/private void setFieldValues(Object parameter, SqlCommandType sqlCommandType) throws IllegalAccessException, InvocationTargetException {// 獲取私有成員變量Method[] declaredMethods = parameter.getClass().getDeclaredMethods();for (Method method : declaredMethods) {if (SqlCommandType.INSERT.equals(sqlCommandType)) {String name = method.getName();switch (name) {case "setUpdateUser":case "setCreateUser":method.invoke(parameter, getCurrentUserId());break;case "setCreateTime":case "setUpdateTime":method.invoke(parameter, new Date());break;case "setUpdateUserType":case "setCreateUserType":method.invoke(parameter, getCurrentUserType());break;default:break;}} else if (SqlCommandType.UPDATE.equals(sqlCommandType)) {String name = method.getName();switch (name) {case "setUpdateUser":method.invoke(parameter, getCurrentUserId());break;case "setUpdateTime":method.invoke(parameter, new Date());break;case "setUpdateUserType":method.invoke(parameter, getCurrentUserType());break;default:break;}}}}/*** 獲取當前用戶類型** @return int* @author liulin* @date 2024-11-14 15:05:55**/private Integer getCurrentUserType() {Authentication authentication = SecurityContextHolder.getContext().getAuthentication();if (authentication == null) {return null;}Object principal = authentication.getPrincipal();if (principal instanceof CarfiSessionUser) {return 1;}return null;}/*** 獲取當前用戶id** @return String* @author liulin* @date 2024-04-22 13:42:37**/private String getCurrentUserId() {Authentication authentication = SecurityContextHolder.getContext().getAuthentication();if (authentication == null) {return null;}Object principal = authentication.getPrincipal();if (principal instanceof CarfiSessionUser) {return CarfiUserUtils.getSysUser().getUserId();}return null;}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {// comment explaining why the method is empty}
}
@Configuration
@ConditionalOnBean({SqlSessionFactory.class})
@AutoConfigureAfter({MybatisAutoConfiguration.class})
@Lazy(false)
public class SetMySqlValueAutoConfiguration implements InitializingBean {@Resourceprivate List<SqlSessionFactory> sqlSessionFactoryList;@Overridepublic void afterPropertiesSet() throws Exception {MyBatisInterceptorConfig interceptor = new MyBatisInterceptorConfig();for (SqlSessionFactory sqlSessionFactory : this.sqlSessionFactoryList) {org.apache.ibatis.session.Configuration configuration = sqlSessionFactory.getConfiguration();if (!this.containsInterceptor(configuration, interceptor)) {configuration.addInterceptor(interceptor);}}}private boolean containsInterceptor(org.apache.ibatis.session.Configuration configuration, Interceptor interceptor) {try {return configuration.getInterceptors().contains(interceptor);} catch (Exception var4) {return false;}}
}
四、配置解釋
DruidDataSourceAutoConfigure
- 為什么要在啟動類上排除DruidDataSourceAutoConfigure,是因為這個配置類com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure是下面這個樣子
- 它自動裝配了一堆druid的默認配置,包括數據源,監控界面,druid AOP切面等等類,其中DataSourceProperties導致spring.datasource下面必須要有默認數據庫配置,否則就會報錯,所以其實這里可以弄一個主數據源放上去,當然意義不是很大。
- 為什么要在啟動類上排除DruidDataSourceAutoConfigure,是因為這個配置類com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure是下面這個樣子
DruidAutoConfiguration
- 因為我排除了DruidDataSourceAutoConfigure,所以必須要把監控界面,druid AOP切面等等重新引入,否則druid的監控界面就沒法正常使用了,druid監控界面進入路徑: http://127.0.0.1:8080/login.html
DruidConfigTestOne
- 這個就很簡單咯,就是配置數據源,sqlSessionFactory,事務管理器等一堆和數據配置有關的,@Primary保證是主數據源加載
DruidConfigTestTwo
- 和DruidConfigTestOne作用一致,就是副數據源
Druid 的 yml配置
- yml為什么可以像我上面這么配置,其實主要還是看DruidDataSourceAutoConfigure,他里面@EnableConfigurationProperties,@Import都可以點進去,可以看里面的字段屬性,實際與yml的配置相關
PageHelper的yml配置
- 這個主要見PageHelperAutoConfiguration.java,com.github.pagehelper.autoconfigure.PageHelperAutoConfiguration
- 可以看到實現了InitializingBean,并且在afterPropertiesSet方法中設置了攔截器PageInterceptor,com.github.pagehelper.PageInterceptor
- 這個主要見PageHelperAutoConfiguration.java,com.github.pagehelper.autoconfigure.PageHelperAutoConfiguration
MyBatisInterceptorConfig
- 這個沒啥好說的,就是在新增,更新時自動注入一些自定義字段值
SetMySqlValueAutoConfiguration
- 這個是因為MybatisAutoConfiguration.java類,org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration中的sqlSessionFactory被我自定義的sqlSessionFactory干擾了導致無法實現mybatis的攔截器注入,所以我就仿照PageHelperAutoConfiguration實現了一次自動注入,這樣mybatis的自定義攔截器就不會因為自定義sqlSessionFactory而失效了。MybatisAutoConfiguration的sqlSessionFactory是下面這樣的
- 這個是因為MybatisAutoConfiguration.java類,org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration中的sqlSessionFactory被我自定義的sqlSessionFactory干擾了導致無法實現mybatis的攔截器注入,所以我就仿照PageHelperAutoConfiguration實現了一次自動注入,這樣mybatis的自定義攔截器就不會因為自定義sqlSessionFactory而失效了。MybatisAutoConfiguration的sqlSessionFactory是下面這樣的