基于注解方式 springboot + mybatis + Druid多数据源(oracle+mysql)

基本架构

基于注解方式 springboot + mybatis + Druid多数据源(oracle+mysql)_第1张图片

简要原理:

 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 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 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 getAllOrclUsers();
   
   @Select("select userId as id,realName as name  from sys_user")
   public List 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> getAllOrclUsers();
  public Result> 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> getAllOrclUsers() {
		List users = userMapper.getAllOrclUsers();
		return new Result>(ResultEnum.SUCCESS,users);
	}
	@Override
	@DataBase("db2")
	public Result> getAllMySQLUsers() {
		List users = userMapper.getAllMySQLUsers();
		return new Result>(ResultEnum.SUCCESS,users);
	}

}

测试

基于注解方式 springboot + mybatis + Druid多数据源(oracle+mysql)_第2张图片
读取oracle
基于注解方式 springboot + mybatis + Druid多数据源(oracle+mysql)_第3张图片
读取mysql
基于注解方式 springboot + mybatis + Druid多数据源(oracle+mysql)_第4张图片

你可能感兴趣的:(springboot)