SpringBoot+Mybatis plus實現多數據源整合的實踐
SpringBoot 版本為1.5.10.RELEASE,Mybatis plus 版本為2.1.8。
第一步:填寫配置信息:
spring: aop: proxy-target-class: true auto: true datasource: druid: # 數據庫 1 db1: url: jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true username: root password: root driver-class-name: com.mysql.jdbc.Driver initialSize: 5 minIdle: 5 maxActive: 20 # 數據庫 2 db2: url: jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true username: root password: root driver-class-name: com.mysql.jdbc.Driver initialSize: 5 minIdle: 5 maxActive: 20
第二步: 數據源配置:
@Configuration @MapperScan({"com.warm.system.mapper*"}) public class MybatisPlusConfig { /** * mybatis-plus分頁插件<br> * 文檔:http://mp.baomidou.com<br> */ @Bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); //paginationInterceptor.setLocalPage(true);// 開啟 PageHelper 的支持 return paginationInterceptor; } /** * mybatis-plus SQL執行效率插件【生產環境可以關閉】 */ @Bean public PerformanceInterceptor performanceInterceptor() { return new 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 ) { DynamicDataSource dynamicDataSource = new DynamicDataSource(); Map< Object, Object > targetDataSources = new HashMap<>(); targetDataSources.put(DBTypeEnum.db1.getValue(), db1 ); targetDataSources.put(DBTypeEnum.db2.getValue(), db2); dynamicDataSource.setTargetDataSources(targetDataSources); dynamicDataSource.setDefaultTargetDataSource(db1); return dynamicDataSource; } @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; } }
第三步:利用AOP進行數據源的動態切換:
@Component @Aspect @Order(-100) //這是為瞭保證AOP在事務註解之前生效,Order的值越小,優先級越高 @Slf4j public class DataSourceSwitchAspect { @Pointcut("execution(* com.warm.system.service.db1..*.*(..))") private void db1Aspect() { } @Pointcut("execution(* com.warm.system.service.db2..*.*(..))") private void db2Aspect() { } @Before( "db1Aspect()" ) public void db1() { log.info("切換到db1 數據源..."); DbContextHolder.setDbType(DBTypeEnum.db1); } @Before("db2Aspect()" ) public void db2 () { log.info("切換到db2 數據源..."); DbContextHolder.setDbType(DBTypeEnum.db2); } } public class DbContextHolder { private static final ThreadLocal contextHolder = new ThreadLocal<>(); /** * 設置數據源 * @param dbTypeEnum */ public static void setDbType(DBTypeEnum dbTypeEnum) { contextHolder.set(dbTypeEnum.getValue()); } /** * 取得當前數據源 * @return */ public static String getDbType() { return (String) contextHolder.get(); } /** * 清除上下文數據 */ public static void clearDbType() { contextHolder.remove(); } } public enum DBTypeEnum { db1("db1"), db2("db2"); private String value; DBTypeEnum(String value) { this.value = value; } public String getValue() { return value; } } public class DynamicDataSource extends AbstractRoutingDataSource { /** * 取得當前使用哪個數據源 * @return */ @Override protected Object determineCurrentLookupKey() { return DbContextHolder.getDbType(); } }
OK!寫個單元測試來驗證一下:
@SpringBootTest @RunWith(SpringJUnit4ClassRunner.class) public class DataTest { @Autowired private UserService userService; @Autowired private OrderService orderService; @Test public void test() { userService.getUserList().stream().forEach(item -> System.out.println(item)); orderService.getOrderList().stream().forEach(item -> System.out.println(item)); } }
如圖所示,證明數據源能動態切換瞭。
具體項目結構和代碼參考Github。
踩坑記錄:
直接調用Mybatis plus 的service方法AOP不會生效,即數據源不會動態切換,解決方法:在自己的service層中封裝一下,調用自定義的service方法AOP即能正常生效瞭,如下所示:
@Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { @Override public List<User> getUserList() { return selectList(null); } }
application.yml 定義的mybatis plus 配置信息不生效,如:
#MyBatis mybatis-plus: mapper-locations: classpath:/mapper/*/*Mapper.xml #實體掃描,多個package用逗號或者分號分隔 typeAliasesPackage: com.jinhuatuo.edu.sys.entity global-config: #主鍵類型 0:"數據庫ID自增", 1:"用戶輸入ID",2:"全局唯一ID (數字類型唯一ID)", 3:"全局唯一ID UUID"; id-type: 0 #字段策略 0:"忽略判斷",1:"非 NULL 判斷"),2:"非空判斷" field-strategy: 2 #駝峰下劃線轉換 db-column-underline: true #刷新mapper 調試神器 refresh-mapper: true #數據庫大寫下劃線轉換 #capital-mode: true #序列接口實現類配置 #key-generator: com.baomidou.springboot.xxx #邏輯刪除配置 #logic-delete-value: 0 #logic-not-delete-value: 1 #自定義填充策略接口實現 meta-object-handler: com.jinhuatuo.edu.config.mybatis.MyMetaObjectHandler #自定義SQL註入器 #sql-injector: com.baomidou.springboot.xxx configuration: map-underscore-to-camel-case: true cache-enabled: false
解決方法: 所有這些配置在MybatisPlusConfig 類中用代碼的方式進行配置,分頁插件亦是如此,否則統計列表總數的數據會拿不到,參考代碼即可。
在application.yml配置
logging: level: debug
控制臺也不會打印Mybatis 執行的SQL語句,解決方法:自定義日志輸出方案,如在classpath下直接引入日志配置文件如logback-spring.xml即可,同時application.yml無需再配置日志信息。
logback-spring.xml配置參考:
<?xml version="1.0" encoding="UTF-8"?> <!-- scan:配置文件如果發生改變,將會重新加載,默認值為true --> <configuration scan="true" scanPeriod="10 seconds"> <!-- <include resource="org/springframework/boot/logging/logback/base.xml"/> --> <!-- 日志文件路徑 --> <!-- <springProperty name="logFilePath" source="logging.path"/> --> <property resource="application.yml" /> <substitutionProperty name="LOG_HOME" value="${logging.path}" /> <substitutionProperty name="PROJECT_NAME" value="${spring.application.name}" /> <!--<substitutionProperty name="CUR_NODE" value="${info.node}" />--> <!-- 日志數據庫路徑 --> <!-- <springProperty name="logDbPath" source="spring.datasource.one.url"/> <springProperty name="logDbDriver" source="spring.datasource.one.driver-class-name"/> <springProperty name="logDbUser" source="spring.datasource.one.username"/> <springProperty name="logDbPwd" source="spring.datasource.one.password"/> --> <!-- 將日志文件 --> <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender"> <append>true</append> <encoder> <pattern> [ %-5level] [%date{yyyy-MM-dd HH:mm:ss}] %logger{96} [%line] - %msg%n </pattern> <charset>utf-8</charset> </encoder> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_HOME}/${PROJECT_NAME}-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>50 MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <!--最多保留30天log--> <maxHistory>30</maxHistory> </rollingPolicy> </appender> <!-- 將日志錯誤文件--> <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender"> <append>true</append> <encoder> <pattern> [ %-5level] [%date{yyyy-MM-dd HH:mm:ss}] %logger{96} [%line] - %msg%n </pattern> <charset>utf-8</charset> </encoder> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_HOME}/${PROJECT_NAME}-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>50 MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <!--最多保留30天log--> <maxHistory>30</maxHistory> </rollingPolicy> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>ERROR</level> <!-- <onMatch>ACCEPT</onMatch> <onMismatch>DENY </onMismatch> --> </filter> </appender> <!-- 將日志寫入控制臺 --> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern> [ %-5level] [%date{yyyy-MM-dd HH:mm:ss}] %logger{96} [%line] - %msg%n </pattern> <!--<charset>utf-8</charset>--> </encoder> </appender> <!-- 將日志寫入數據庫 --> <!-- <appender name="DB-CLASSIC-MYSQL-POOL" class="ch.qos.logback.classic.db.DBAppender"> <connectionSource class="ch.qos.logback.core.db.DataSourceConnectionSource"> <dataSource class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <driverClassName>${logDbDriver}</driverClassName> <url>${logDbPath}</url> <username>${logDbUser}</username> <password>${logDbPwd}</password> </dataSource> </connectionSource> </appender> --> <!-- spring擴展,分環境配置log信息 --> <springProfile name="dev"> <!-- <logger name="sand.dao" level="DEBUG"/> --> <!-- <logger name="org.springframework.web" level="INFO"/> --> <logger name="org.springboot.sample" level="TRACE" /> <logger name="org.springframework.cloud" level="INFO" /> <logger name="com.netflix" level="INFO"></logger> <logger name="org.springframework.boot" level="INFO"></logger> <logger name="org.springframework.web" level="INFO"/> <logger name="jdbc.sqltiming" level="debug"/> <logger name="com.ibatis" level="debug" /> <logger name="com.ibatis.common.jdbc.SimpleDataSource" level="debug" /> <logger name="com.ibatis.common.jdbc.ScriptRunner" level="debug" /> <logger name="com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate" level="debug" /> <logger name="java.sql.Connection" level="debug" /> <logger name="java.sql.Statement" level="debug" /> <logger name="java.sql.PreparedStatement" level="debug" /> <logger name="java.sql.ResultSet" level="debug" /> <logger name="com.warm" level="debug"/> <root level="DEBUG"> <appender-ref ref="console" /> <appender-ref ref="file" /> </root> <root level="ERROR"> <appender-ref ref="file_error" /> <!-- <appender-ref ref="DB-CLASSIC-MYSQL-POOL" /> --> </root> </springProfile> <springProfile name="test"> <logger name="org.springboot.sample" level="TRACE" /> <logger name="org.springframework.cloud" level="INFO" /> <logger name="com.netflix" level="INFO"></logger> <logger name="org.springframework.boot" level="INFO"></logger> <logger name="org.springframework.web" level="INFO"/> <logger name="jdbc.sqltiming" level="debug"/> <logger name="com.ibatis" level="debug" /> <logger name="com.ibatis.common.jdbc.SimpleDataSource" level="debug" /> <logger name="com.ibatis.common.jdbc.ScriptRunner" level="debug" /> <logger name="com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate" level="debug" /> <logger name="java.sql.Connection" level="debug" /> <logger name="java.sql.Statement" level="debug" /> <logger name="java.sql.PreparedStatement" level="debug" /> <logger name="java.sql.ResultSet" level="debug" /> <logger name="com.warm" level="DEBUG"/> <root level="DEBUG"> <!-- <appender-ref ref="console" /> --> <appender-ref ref="file" /> </root> <root level="ERROR"> <appender-ref ref="file_error" /> <!-- <appender-ref ref="DB-CLASSIC-MYSQL-POOL" /> --> </root> </springProfile> <springProfile name="prod"> <logger name="org.springboot.sample" level="TRACE" /> <logger name="org.springframework.cloud" level="INFO" /> <logger name="com.netflix" level="INFO"></logger> <logger name="org.springframework.boot" level="INFO"></logger> <logger name="org.springframework.web" level="INFO"/> <logger name="jdbc.sqltiming" level="debug"/> <logger name="com.ibatis" level="debug" /> <logger name="com.ibatis.common.jdbc.SimpleDataSource" level="debug" /> <logger name="com.ibatis.common.jdbc.ScriptRunner" level="debug" /> <logger name="com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate" level="debug" /> <logger name="java.sql.Connection" level="debug" /> <logger name="java.sql.Statement" level="debug" /> <logger name="java.sql.PreparedStatement" level="debug" /> <logger name="java.sql.ResultSet" level="debug" /> <logger name="com.warm" level="info"/> <root level="DEBUG"> <!-- <appender-ref ref="console" /> --> <appender-ref ref="file" /> </root> <root level="ERROR"> <appender-ref ref="file_error" /> <!-- <appender-ref ref="DB-CLASSIC-MYSQL-POOL" /> --> </root> </springProfile> </configuration>
到此這篇關於SpringBoot+Mybatis plus實現多數據源整合的實踐的文章就介紹到這瞭,更多相關SpringBoot+Mybatis plus多數據源內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- springboot如何去除debug日志
- Java logback日志的簡單使用
- springboot實現將自定義日志格式存儲到mongodb中
- Springboot允許logger.debug輸出日志方式
- 集成apollo動態日志取締logback-spring.xml配置