一、引入依賴
引入資料庫連線池的依賴——druid和麵向切面程式設計的依賴——aop,如下所示:
<!-- druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.21</version> </dependency> <!-- aop --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
二、建立資料庫
1、主資料庫
使用前文中已經建立的名為spring_boot_demo的資料庫。
spring_boot_demo中t_user資料如下:
2、輔資料庫
資料庫名為other_data,庫中建立資料表t_user,表結構與spring_boot_demo中的t_user一致。
實際專案中,大多是跨資料庫的資料來源切換,常用在同公司的多個不同系統中共用一個使用者資料庫,或者二次開發專案在原有資料庫基礎上做擴充,保留原有的資料連線。
這裡為了方便操作,就都在mysql下部署資料庫並且使表結構一致,方便形成資料對比。
other_data中插入資料如下:
三、修改資料庫連線配置資訊
在application.yml中,修改資料庫連線配置如下:
spring: application: name: spring-boot-demo datasource: type: com.alibaba.druid.pool.DruidDataSource druid: primary: url: jdbc:mysql://localhost:3306/spring_boot_demo?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8 username: root password: root driverClassName: com.mysql.jdbc.Driver second: url: jdbc:mysql://localhost:3306/other_data?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8 username: root password: root driverClassName: com.mysql.jdbc.Driver
四、編寫程式碼
結構如下:
1、列舉類DataSourceName:
該類用來存放資料來源的名稱,定義兩個資料來源名稱分別為PRIMARY和SECOND。
package com.example.demo.enums; /** * DataSource的name常量 * 便於切換 * @author 我命傾塵 */ public enum DataSourceName { /** * 主資料來源 spring_boot_demo */ PRIMARY("PRIMARY"), /** * 副資料來源other_data */ SECOND("SECOND"); private String dataSourceName; private DataSourceName(String dataSourceName){ this.dataSourceName=dataSourceName; } DataSourceName(){ } public String getDataSourceName(){ return this.dataSourceName; } }
2、配置類DynamicDataSourceConfig:
通過@ConfigurationProperties讀取配置檔案中的資料來源配置資訊,並通過DruidDataSourceBuilder.create().build()建立資料連線,將多個資料來源放入map,注入到IoC中:
package com.example.demo.config; import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; import com.example.demo.bean.DynamicDataSource; import com.example.demo.enums.DataSourceName; 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 javax.sql.DataSource; import java.util.HashMap; import java.util.Map; /** * @author 我命傾塵 */ @Configuration public class DynamicDataSourceConfig { /** * 建立DataSource Bean,將資料來源配置從配置檔案中讀出 */ @Bean @ConfigurationProperties("spring.datasource.druid.primary") public DataSource oneDataSource(){ return DruidDataSourceBuilder.create().build(); } @Bean @ConfigurationProperties("spring.datasource.druid.second") public DataSource twoDataSource(){ return DruidDataSourceBuilder.create().build(); } /** * 將資料來源放入到 這個map中,注入到IoC */ @Bean @Primary public DynamicDataSource dataSource(DataSource oneDataSource, DataSource twoDataSource){ Map<Object,Object> targetDataSources=new HashMap<>(2); targetDataSources.put(DataSourceName.PRIMARY.getDataSourceName(),oneDataSource); targetDataSources.put(DataSourceName.SECOND.getDataSourceName(),twoDataSource); return new DynamicDataSource(oneDataSource,targetDataSources); } }
3、動態資料來源DynamicDataSource:
通過繼承AbstractRoutingDataSource類,在建構函式中呼叫父類的方法,將配置類中放入map的資料來源集合定為備選資料來源,將傳來的oneDataSource作為預設資料來源:
package com.example.demo.bean; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import javax.sql.DataSource; import java.util.Map; /** * @author 我命傾塵 */ public class DynamicDataSource extends AbstractRoutingDataSource { private static final ThreadLocal<String> contextHolder=new ThreadLocal<>(); /** * 配置DataSource * 設定defaultTargetDataSource為主資料庫 */ public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object,Object> targetDataSources){ super.setDefaultTargetDataSource(defaultTargetDataSource); super.setTargetDataSources(targetDataSources); super.afterPropertiesSet(); } public static String getDataSource(){ return contextHolder.get(); } public static void setDataSource(String dataSource){ contextHolder.set(dataSource); } public static void clearDataSource(){ contextHolder.remove(); } @Override protected Object determineCurrentLookupKey() { return getDataSource(); } }
setTargetDataSources設定備選的資料來源集合,
setDefaultTargetDataSource設定預設資料來源,
determineCurrentLookupKey決定當前資料來源的對應的key。
4、自定義註釋類DataSource:
package com.example.demo.annotation; import com.example.demo.enums.DataSourceName; import java.lang.annotation.*; /** * @author 我命傾塵 */ @Documented @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface DataSource { DataSourceName value() default DataSourceName.PRIMARY; }
@Documented指定被標註的註解會包含在javadoc中,
@Target指定註釋可能出現在Java程式中的語法位置(ElementType.METHOD則說明註解可能出現在方法上),
@Retention指定註釋的保留時間(RetentionPolicy.RUNTIME則是在java檔案編譯成class類時也依舊儲存該註釋)。
5、切面類DataSourceAspect:
package com.example.demo.aspect; import com.example.demo.annotation.DataSource; import com.example.demo.bean.DynamicDataSource; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; import java.lang.reflect.Method; /** * @author 我命傾塵 */ @Aspect @Component public class DataSourceAspect implements Ordered { private Logger log= LoggerFactory.getLogger(DataSourceAspect.class); /** * 切點:所有配置DataSource註解的方法 */ @Pointcut("@annotation(com.example.demo.annotation.DataSource)") public void dataSourcePointCut(){ } @Around(value = "dataSourcePointCut()") public Object around(ProceedingJoinPoint point) throws Throwable{ Object result; MethodSignature signature=(MethodSignature)point.getSignature(); Method method=signature.getMethod(); DataSource ds=method.getAnnotation(DataSource.class); /** * 判斷DataSource的值 * 獲取當前方法應用的資料來源 */ DynamicDataSource.setDataSource(ds.value().getDataSourceName()); try{ result=point.proceed(); }finally { DynamicDataSource.clearDataSource(); } return result; } @Override public int getOrder() { return 1; } }
Spring框架有很多相同介面的實現類,提供了Ordered介面來處理相同介面實現類之間的優先順序問題。
通過環繞切面,對方法上的註釋進行了檢驗,如果獲取到有DataSource註釋,則會進行資料來源的切換,否則按預設資料來源進行處理。
6、引入配置類:
既然手動配置了動態切換資料連線池,就要在入口類中排除自動引入,並引入資料來源的配置類,以及開啟AOP:
package com.example.demo; import com.example.demo.config.DynamicDataSourceConfig; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.annotation.Import; @MapperScan("com.example.demo.mapper") @Import({DynamicDataSourceConfig.class}) @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) @EnableAspectJAutoProxy public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
通過@Import引入配置類,在@SpringBootApplication後進行自動引入的排除。
@EnableAspectJAutoProxy用來開啟AOP。
五、簡單測試
1、不使用註解:
UserController中的方法如下:
@RequestMapping("/user/age") public int getAgeOfUser(){ return userService.getAgeByUsername("springbootdemo"); }
所得到的結果如下:
這個結果是從主資料來源spring_boot_demo資料庫的表中得到的資料。
2、在方法前新增註解@DataSource(DataSourceName.SECOND):
UserController中的方法如下:
@RequestMapping("/user/age") @DataSource(DataSourceName.SECOND) public int getAgeOfUser(){ return userService.getAgeByUsername("springbootdemo"); }
結果如下:
這個結果則是從輔資料來源other_data中得到的資料。
前言:SpringBoot框架:使用mybatis連線mysql資料庫完成資料訪問(二)