一、引入依賴
引入數據庫連接池的依賴——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數據庫完成數據訪問(二)