基於註解方式 springboot + mybatis + Druid多數據源(oracle+mysql)

基本架構

在這裏插入圖片描述

簡要原理:

 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
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章