SpringBoot + Mybatis plus 實現多資料來源整合
阿新 • • 發佈:2019-02-08
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>