基本架構
簡要原理:
1. 創建用於數據源切換註解@DataBase
2. 創建Aspect切面類DataSourceAspect,用於完成在實際操作前根據註解內容動態切換數據源動作
3. DatabaseContextHolder是一個線程安全的DatabaseType容器,並提供了向其中設置和獲取DatabaseType的方法
4. DynamicDataSource繼承AbstractRoutingDataSource並重寫其中的方法determineCurrentLookupKey(),在該方法中使用DatabaseContextHolder獲取當前線程的DatabaseType
5. DruidConfig中生成2個數據源DataSource的bean---value
6. DruidConfig中將組成的key-value對寫入到DynamicDataSource動態數據源的targetDataSources屬性(同時也會設置2個數據源其中的一個爲DynamicDataSource的defaultTargetDataSource屬性中)
7. 將DynamicDataSource數據源注入到SqlSessionFactory的dataSource屬性中去,並且該dataSource作爲transactionManager的入參來構造DataSourceTransactionManager
8. 在serviceImpl層,在具體方法上加入@DataBase("dbType")註解
注意:在進行操作的時候,會先調用determineCurrentLookupKey()方法獲取一個數據源(獲取數據源:先根據設置去targetDataSources中去找,若沒有,則選擇defaultTargetDataSource),之後在進行數據庫操作。
具體實現
配置application.properties文件
在application.properties配置文件配置連個數據源。一個mysql一個oracle
# mysql
spring.datasource.db2.driverClassName=com.mysql.jdbc.Driver
spring.datasource.db2.url=jdbc:mysql://192.168.0.189:3306/demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false
spring.datasource.db2.username=root
spring.datasource.db2.password=898
# oracle
spring.datasource.db1.driverClassName: oracle.jdbc.driver.OracleDriver
spring.datasource.db1.url: jdbc:oracle:thin:@192.168.20.17:1521:ORCL
spring.datasource.db1.username: orcl
spring.datasource.db1.password: orcl
自定義註解DataBase
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @ClassName DataBase
* @Description TODO(註解)
* @author 尋找手藝人
* @Date 2019年5月6日 上午9:15:01
* @version 1.0.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface DataBase {
String value() default "db1";
}
定義切面類DataSourceAspect
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import edu.jliae.card.common.DatabaseContextHolder;
/**
* @ClassName DataSourceAspect
* @Description TODO(切面類)
* @author 尋找手藝人
* @Date 2019年5月6日 上午9:10:36
* @version 1.0.0
*/
@Aspect
@Component
public class DataSourceAspect implements Ordered{
@Pointcut("@annotation(edu.jliae.aspect.DataBase)")//注意:這裏的xxxx代表的是上面public @interface DataSource這個註解DataSource的包名
public void dataSourcePointCut() {
}
@SuppressWarnings("rawtypes")
@Before("dataSourcePointCut()")
public void beforeSwitchDS(JoinPoint point){
//獲得當前訪問的class
Class<?> className = point.getTarget().getClass();
//獲得訪問的方法名
String methodName = point.getSignature().getName();
//得到方法的參數的類型
Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
String dataSource = DatabaseContextHolder.DEFAULT_DS;
try {
// 得到訪問的方法對象
Method method = className.getMethod(methodName, argClass);
// 判斷是否存在@DateBase註解
if (method.isAnnotationPresent(DataBase.class)) {
DataBase annotation = method.getAnnotation(DataBase.class);
// 取出註解中的數據源名
dataSource = annotation.value();
}
} catch (Exception e) {
e.printStackTrace();
}
// 切換數據源
DatabaseContextHolder.setDatabaseType(dataSource);
}
@After("dataSourcePointCut()")
public void afterSwitchDS(JoinPoint point){
DatabaseContextHolder.clearDataSource();
}
@Override
public int getOrder() {
return 1;
}
}
定義線程安全類DatabaseContextHolder
/**
* @ClassName DatabaseContextHolder
* @Description TODO(保證線程安全的DatabaseType容器)
* @author 尋找手藝人
* @Date 2019年5月5日 上午11:32:38
* @version 1.0.0
*/
public class DatabaseContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
//默認數據源
public static final String DEFAULT_DS = "db1";
public static final String SECOND_DS = "db2";
public static void setDatabaseType(String type) {
contextHolder.set(type);
}
public static String getDatabaseType() {
return contextHolder.get();
}
public static void clearDataSource() {
contextHolder.remove();
}
}
定義DynamicDataSource類
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* @ClassName DynamicDataSource
* @Description TODO(重新determineCurrentLookupKey獲取方式方法)
* @author尋找手藝人
* @Date 2019年5月5日 上午11:33:25
* @version 1.0.0
*/
public class DynamicDataSource extends AbstractRoutingDataSource{
/**
* 動態數據源(需要繼承AbstractRoutingDataSource)
*/
@Override
protected Object determineCurrentLookupKey() {
return DatabaseContextHolder.getDatabaseType();
}
}
創建配置數據源DruidConfig類
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import com.google.common.collect.Lists;
import edu.jliae.card.common.DatabaseContextHolder;
import edu.jliae.card.common.DynamicDataSource;
/**
* @ClassName DruidConfig
* @Description TODO(druid監控配置)
* @author 尋找手藝人
* @Date 2019年4月23日 上午11:55:24
* @version 1.0.0
*/
@Configuration
public class DruidConfig {
@Autowired
private Environment env;
@Primary
@ConfigurationProperties(prefix="spring.datasource.db1")
@Bean(name = "datasource1")
public DataSource dataSourceDB1(Filter statFilter) throws SQLException{
DruidDataSource dataSource = new DruidDataSource();
dataSource.setProxyFilters(Lists.newArrayList(statFilter()));
return dataSource;
}
@ConfigurationProperties(prefix="spring.datasource.db2")
@Bean(name = "datasource2")
public DataSource dataSourceDB2(Filter statFilter) throws SQLException{
DruidDataSource dataSource = new DruidDataSource();
dataSource.setProxyFilters(Lists.newArrayList(statFilter()));
return dataSource;
}
/**
* @Primary 該註解表示在同一個接口有多個實現類可以注入的時候,默認選擇哪一個,而不是讓@autowire註解報錯
* @Qualifier 根據名稱進行注入,通常是在具有相同的多個類型的實例的一個注入(例如有多個DataSource類型的實例)
*/
@Bean(name = "dynamicDataSource")
public DynamicDataSource dataSource(@Qualifier("datasource1") DataSource datasource1,
@Qualifier("datasource2") DataSource datasource2) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DatabaseContextHolder.DEFAULT_DS, datasource1);
targetDataSources.put(DatabaseContextHolder.SECOND_DS, datasource2);
DynamicDataSource dataSource = new DynamicDataSource();
dataSource.setTargetDataSources(targetDataSources);// 該方法是AbstractRoutingDataSource的方法
dataSource.setDefaultTargetDataSource(datasource1);// 默認的datasource設置爲dataSourceDB1
return dataSource;
}
/**
* 根據數據源創建SqlSessionFactory
*/
@Bean
public SqlSessionFactory sqlSessionFactory(DynamicDataSource ds) throws Exception {
SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
fb.setDataSource(ds);// 指定數據源(這個必須有,否則報錯)
// 下邊兩句僅僅用於*.xml文件,如果整個持久層操作不需要使用到xml文件的話(只用註解就可以搞定),則不加
// fb.setTypeAliasesPackage(env.getProperty("mybatis.typeAliasesPackage"));// 指定基包
// fb.setMapperLocations(
// new PathMatchingResourcePatternResolver().getResources(env.getProperty("mybatis.mapperLocations")));//
return fb.getObject();
}
/**
* 配置事務管理器
*/
@Bean
public DataSourceTransactionManager transactionManager(DynamicDataSource dataSource) throws Exception {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public Filter statFilter(){
StatFilter filter = new StatFilter();
filter.setSlowSqlMillis(5000);
filter.setLogSlowSql(true);
filter.setMergeSql(true);
return filter;
}
@Bean
public ServletRegistrationBean servletRegistrationBean(){
//org.springframework.boot.context.embedded.ServletRegistrationBean提供類的進行註冊.
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(),"/druid/*");
//添加初始化參數:initParams
//白名單:
// servletRegistrationBean.addInitParameter("allow","127.0.0.1");
//IP黑名單 (存在共同時,deny優先於allow) : 如果滿足deny的話提示:Sorry, you are not permitted to view this page.
//servletRegistrationBean.addInitParameter("deny","192.168.1.73");
//登錄查看信息的賬號密碼.
servletRegistrationBean.addInitParameter("loginUsername","admin");
servletRegistrationBean.addInitParameter("loginPassword","123456");
//在日誌中打印執行慢的sql語句
servletRegistrationBean.addInitParameter("logSlowSql", "true");
//是否能夠重置數據.
servletRegistrationBean.addInitParameter("resetEnable","false");
return servletRegistrationBean;
}
@Bean
public FilterRegistrationBean druidStatFilter(){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new WebStatFilter());
filterRegistrationBean.addUrlPatterns("/*");
//過濾文件類型
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
//監控單個url調用的sql列表
filterRegistrationBean.addInitParameter("profileEnable", "true");
return filterRegistrationBean;
}
}
應用UserMapper類
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import edu.jliae.card.entry.User;
/**
* @ClassName UserMapper
* @Description TODO(這裏用一句話描述這個類的作用)
* @author 尋找手藝人
* @Date 2019年4月23日 上午11:09:04
* @version 1.0.0
*/
@Mapper
public interface UserMapper {
@Select("select id as id,name as name from m_base_dic")
public List<User> getAllOrclUsers();
@Select("select userId as id,realName as name from sys_user")
public List<User> getAllMySQLUsers();
}
應用UserService類
import java.util.List;
import edu.jliae.card.common.Result;
import edu.jliae.card.entry.User;
/**
* @ClassName UserService
* @Description TODO(這裏用一句話描述這個類的作用)
* @author 尋找手藝人
* @Date 2019年4月23日 上午11:22:13
* @version 1.0.0
*/
public interface UserService {
public Result<List<User>> getAllOrclUsers();
public Result<List<User>> getAllMySQLUsers();
}
應用重點UserServiceImpl
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import edu.jliae.aspect.DataBase;
import edu.jliae.card.common.Result;
import edu.jliae.card.common.ResultEnum;
import edu.jliae.card.entry.User;
import edu.jliae.card.mapper.UserMapper;
import edu.jliae.card.service.UserService;
/**
* @ClassName UserServiceImpl
* @Description TODO(這裏用一句話描述這個類的作用)
* @author 尋找手藝人
* @Date 2019年4月23日 上午11:22:56
* @version 1.0.0
*/
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
@Override
@DataBase("db1")
public Result<List<User>> getAllOrclUsers() {
List<User> users = userMapper.getAllOrclUsers();
return new Result<List<User>>(ResultEnum.SUCCESS,users);
}
@Override
@DataBase("db2")
public Result<List<User>> getAllMySQLUsers() {
List<User> users = userMapper.getAllMySQLUsers();
return new Result<List<User>>(ResultEnum.SUCCESS,users);
}
}
測試
讀取oracle
讀取mysql