springboot2多数据源之JNDI方式,使用AOP切换数据源,tomcat的context.xml维护数据源信息

环境:

springboot2

tomcat8

jdk1.8

驱动及加密:com.alibaba.druid

多数据源

1、数据源枚举定义

public interface DatasourceEnum {
    String DATA_SOURCE = "dataSource";            //主数据源

    String DATA_SOURCE_2 = "dataSource2";            //其他业务的数据源
    String DATA_SOURCE_3 = "dataSource3";            //其他业务的数据源
}

2、定义数据源实体

import com.alibaba.druid.pool.DruidDataSource;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class MutiDataSourceProperties {

    private String dataSourceName;

    private String url;

    private String username;

    private String password;

    private String driverClassName;

    private String validationQuery = "SELECT 'x'";


    public void config(DruidDataSource dataSource) {
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setDriverClassName(driverClassName);
        dataSource.setValidationQuery(validationQuery);
    }

}

 

 3、自定义注解

import java.lang.annotation.*;

/**
 * @author keeps
 * @Title: DataSource
 * @ProjectName aifaceboot
 * @Description: 多数据源标识
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {

    String name() default DatasourceEnum.DATA_SOURCE;

}

 4、数据源基础配置类

import lombok.Getter;
import lombok.Setter;

/**
 * @author keeps
 * @Title: DataConnectionProperties
 * @ProjectName aifaceboot
 * @Description: 数据源基础配置类
 */
@Setter
@Getter
public class DataSourceSchool {

    private String dataSourceName;

    private String dialectType;

}

5、定义数据源帮助类,共享上下文线程

/**
 * @author keeps
 * @Title: DataSourceContextHolder
 * @ProjectName aifaceboot
 * @Description: 动态切换数据源datasource的上下文
 */
public class DataSourceContextHolder {

    private static final ThreadLocal contextHolder = new ThreadLocal();


    /**
     * 设置数据源类型
     *
     * @param dataSourceThread 数据库名,数据源类型
     */
    public static void setDataSourceSchool(DataSourceSchool dataSourceThread) {

        contextHolder.set(dataSourceThread);
    }

    public static void setDataSourceType(String dataSourceName) {
        DataSourceSchool dataSourceThread = new DataSourceSchool();
        String dialectType = DataSourceDialect.dataSourceNameDialect.get(dataSourceName);
        dataSourceThread.setDialectType(dialectType);
        dataSourceThread.setDataSourceName(dataSourceName);
        contextHolder.set(dataSourceThread);
    }
    /**
     * 获取数据源类型
     */
    public static DataSourceSchool getDataSourceSchool() {
        if(contextHolder.get()==null){
            return new DataSourceSchool();
        }
        return contextHolder.get();
    }


    public static String getDialectType() {
        DataSourceSchool dataSourceSchool = contextHolder.get();
        if (dataSourceSchool!=null && dataSourceSchool.getDialectType() != null){
            return dataSourceSchool.getDialectType();
        }
        return null;
    }

    public static String getSQLDialect(String sql, int offset,int limit) {
        String dialectType = getDialectType();
        if (dialectType == null){
            return sql;
        }
        if (dialectType.toUpperCase().contains(Dialect.Type.MYSQL.name().toUpperCase())){
            return new MysqlDialect().getLimitSql(sql, offset, limit);
        } else if (dialectType.toUpperCase().contains(Dialect.Type.ORACLE.name().toUpperCase())) {
            return new OracleDialect().getLimitSql(sql, offset, limit);
        } else if (dialectType.toUpperCase().contains(Dialect.Type.SQLSERVER.name().toUpperCase())) {
            return new SqlServerDialect().getLimitSql(sql, offset, limit);
        }
        return sql;
    }

    /**
     * 清除数据源类型
     */
    public static void clearDataSourceSchool() {
        contextHolder.remove();
    }

}

 

6、创建动态数据源路由

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * @author keeps
 * @Title: DynamicDataSource
 * @ProjectName aifaceboot
 * @Description: 动态数据源路由
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    public DynamicDataSource() {
    }

    /*
     * DynamicDataSourceContextHolder代码中使用setDataSourceType
     * 设置当前的数据源,在路由类中使用getDataSourceType进行获取,
     *  交给AbstractRoutingDataSource进行注入使用。
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSourceSchool().getDataSourceName();
    }

7、定义切面,环绕

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;

import java.lang.reflect.Method;

/**
 * @author keeps
 * @Title: MultiSourceExAop
 * @ProjectName aifaceboot
 * @Description: 多数据源切换的aop
 */
@Slf4j
@Aspect
public class MultiSourceAop implements Ordered {

    @Autowired
    private MutiDataSourceProperties mutiDataSourceProperties;


    @Pointcut("@annotation(com.newcap.xy.common.mutidatasource.annotion.DataSource)")
    private void cut()
    {
    }

    @Around("cut()")
    public Object around(ProceedingJoinPoint point)throws Throwable{
        //jdbcCoordinator.close();
        Signature signature = point.getSignature();
        MethodSignature methodSignature = null;
        if (!(signature instanceof MethodSignature)) {
            throw new IllegalArgumentException("该注解只能用于方法");
        }
        methodSignature = (MethodSignature)signature;

        Object target = point.getTarget();
        Method currentMethod = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());

        DataSource datasource = (DataSource)currentMethod.getAnnotation(DataSource.class);
        DataSourceSchool dataSourceSchool = new DataSourceSchool();
        String dialectType = DataSourceDialect.dataSourceNameDialect.get(datasource.name());
        dataSourceSchool.setDataSourceName(datasource.name());
        dataSourceSchool.setDialectType(dialectType);
        DataSourceContextHolder.setDataSourceSchool(dataSourceSchool);
        if (datasource != null) {
            this.log.debug("设置数据源为:" + datasource.name());
        } else {
           /* String dialectType = DataSourceDialect.dataSourceNameDialect.get(datasource.name());
            dataSourceSchool.setDataSourceName(datasource.name());
            dataSourceSchool.setDialectType(dialectType);
            DataSourceContextHolder.setDataSourceSchool(dataSourceSchool);*/
            this.log.debug("设置数据源为:dataSourceCurrent");
        }
        try
        {
            return point.proceed();
        } finally {
            this.log.debug("清空数据源信息!");
            DataSourceContextHolder.clearDataSourceSchool();
        }
    }

    public int getOrder()
    {
        return -1;
    }
}

 

8、植入springboot

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author keeps
 * @Title: DataSourceAopConfig
 * @Description: 初版生成 AOP
 */
@Configuration
public class DataSourceAopConfig {

    /**
     * 多数据源切换的aop
     */
    @Bean
    public MultiSourceAop multiSourceExAop() {
        return new MultiSourceAop();
    }

}

 

9、创建数据源分类map方便维护

import java.util.HashMap;
import java.util.Map;

/**
 * @author keeps
 * @Title: DataSourceD
 * @ProjectName newcapboot
 * @Description: 数据源分类
 */
public class DataSourceDialect {

    public static Map dataSourceNameDialect = new HashMap<>();

}

 

10、创建连接池优化实体

 

import com.alibaba.druid.pool.DruidDataSource;

import java.sql.SQLException;

/**
 * @author keeps
 * @Title: DruidProperties
 * @ProjectName aifaceboot
 * @Description: Druid Properties
 */
public class DruidProperties {

    private String url;

    private String username;

    private String password;

    private String driverClassName;

    private Integer initialSize = 2;

    private Integer minIdle = 1;

    private Integer maxActive = 20;

    private Integer maxWait = 60000;

    private Integer timeBetweenEvictionRunsMillis = 60000;

    private Integer minEvictableIdleTimeMillis = 300000;

    private String validationQuery = " select 1 FROM DUAL ";

    private Boolean testWhileIdle = true;

    private Boolean testOnBorrow = false;

    private Boolean testOnReturn = false;

    private Boolean poolPreparedStatements = true;

    private Integer maxPoolPreparedStatementPerConnectionSize = 20;

    private String filters = "stat";

    public void config(DruidDataSource dataSource) {

        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);

        dataSource.setDriverClassName(driverClassName);
        dataSource.setInitialSize(initialSize);     //定义初始连接数
        dataSource.setMinIdle(minIdle);             //最小空闲
        dataSource.setMaxActive(maxActive);         //定义最大连接数
        dataSource.setMaxWait(maxWait);             //最长等待时间

        // 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);

        // 配置一个连接在池中最小生存的时间,单位是毫秒
        dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        dataSource.setValidationQuery(validationQuery);
        dataSource.setTestWhileIdle(testWhileIdle);
        dataSource.setTestOnBorrow(testOnBorrow);
        dataSource.setTestOnReturn(testOnReturn);

        // 打开PSCache,并且指定每个连接上PSCache的大小
        dataSource.setPoolPreparedStatements(poolPreparedStatements);
        dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);

        try {
            dataSource.setFilters(filters);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getDriverClassName() {
        return driverClassName;
    }

    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }

    public Integer getInitialSize() {
        return initialSize;
    }

    public void setInitialSize(Integer initialSize) {
        this.initialSize = initialSize;
    }

    public Integer getMinIdle() {
        return minIdle;
    }

    public void setMinIdle(Integer minIdle) {
        this.minIdle = minIdle;
    }

    public Integer getMaxActive() {
        return maxActive;
    }

    public void setMaxActive(Integer maxActive) {
        this.maxActive = maxActive;
    }

    public Integer getMaxWait() {
        return maxWait;
    }

    public void setMaxWait(Integer maxWait) {
        this.maxWait = maxWait;
    }

    public Integer getTimeBetweenEvictionRunsMillis() {
        return timeBetweenEvictionRunsMillis;
    }

    public void setTimeBetweenEvictionRunsMillis(Integer timeBetweenEvictionRunsMillis) {
        this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
    }

    public Integer getMinEvictableIdleTimeMillis() {
        return minEvictableIdleTimeMillis;
    }

    public void setMinEvictableIdleTimeMillis(Integer minEvictableIdleTimeMillis) {
        this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
    }

    public String getValidationQuery() {
        return validationQuery;
    }

    public void setValidationQuery(String validationQuery) {
        this.validationQuery = validationQuery;
    }

    public Boolean getTestWhileIdle() {
        return testWhileIdle;
    }

    public void setTestWhileIdle(Boolean testWhileIdle) {
        this.testWhileIdle = testWhileIdle;
    }

    public Boolean getTestOnBorrow() {
        return testOnBorrow;
    }

    public void setTestOnBorrow(Boolean testOnBorrow) {
        this.testOnBorrow = testOnBorrow;
    }

    public Boolean getTestOnReturn() {
        return testOnReturn;
    }

    public void setTestOnReturn(Boolean testOnReturn) {
        this.testOnReturn = testOnReturn;
    }

    public Boolean getPoolPreparedStatements() {
        return poolPreparedStatements;
    }

    public void setPoolPreparedStatements(Boolean poolPreparedStatements) {
        this.poolPreparedStatements = poolPreparedStatements;
    }

    public Integer getMaxPoolPreparedStatementPerConnectionSize() {
        return maxPoolPreparedStatementPerConnectionSize;
    }

    public void setMaxPoolPreparedStatementPerConnectionSize(Integer maxPoolPreparedStatementPerConnectionSize) {
        this.maxPoolPreparedStatementPerConnectionSize = maxPoolPreparedStatementPerConnectionSize;
    }

    public String getFilters() {
        return filters;
    }

    public void setFilters(String filters) {
        this.filters = filters;
    }
} 

 以上为多数据源所需AOP相关各个类。下面是指定数据源类型,这里写JNDI

JNDI

1、创建JNDI自定义配置基础映射类

import lombok.Getter;
import lombok.Setter;

/**
 * @author keeps
 * @Title: JndiProperties
 * @Description: 初版生成 JNDI数据配置项
 */
@Setter
@Getter
public class JNDIProperties {

    private String dataSourceName;
    private String jndiName;
    private String dialect;

}

2、创建多数据源容器类

@Getter
@Setter
public class JNDIExtraProperties {

    private List jndiNameList;

}

 

3、创建JNDI数据源管理类

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.util.HashMap;

/**
 * @author keeps
 * @Title: JndiDataSourceConfig
 * @ProjectName newcapboot
 * @Description: JDNI读取多数据源
 */
@ConditionalOnProperty(prefix = "newcap.jndi-datasource", name = "open", havingValue = "true")
@Configuration
@EnableTransactionManagement(order = 2)
@Slf4j
public class JndiDataSourceConfig {
    @Bean
    @ConfigurationProperties(prefix = "newcap.jndi-datasource")
    public JNDIExtraProperties jndiProperties() {
        JNDIExtraProperties jndiProperties = new JNDIExtraProperties();
        return jndiProperties;
    }

    @Bean(name = "dataSource")
    public DynamicDataSource dataSource() {
        HashMap hashMap = new HashMap<>();

        JNDIExtraProperties jndiProperties = jndiProperties();

        for (JNDIProperties properties : jndiProperties.getJndiNameList()){
            DataSourceDialect.dataSourceNameDialect.put(properties.getDataSourceName(), properties.getDataSourceName());

            JndiDataSourceLookup lookup = new JndiDataSourceLookup();
            lookup.setResourceRef(true);
            DataSource dataSource = lookup.getDataSource(properties.getJndiName());
            hashMap.put(properties.getDataSourceName(), dataSource);
        }

        DynamicDataSource dynamicDataSource = new DynamicDataSource();

        dynamicDataSource.setTargetDataSources(hashMap);
        //设置主数据源
        dynamicDataSource.setDefaultTargetDataSource(hashMap.get(DatasourceEnum.DATA_SOURCE));
        return dynamicDataSource;
    }

}

 

以上为JNDI代码部分,下面写配置。

maven引入依赖,略过

yml配置

springboot yml配置

server:
  port: 8080 #配置项目端口号

log:
  path: aiface-logs

#自定义项目基础配置
newcap:
  jndi-datasource: #jndi数据源的配置
    open: true #是否开启jndi配置
    jndiNameList: #开启jndi配置时,配置如下信息,spring.datasource不在生效
    - dataSourceName: dataSource
      dialect: oracle
      jndiName: java:comp/env/oracleJDBC
    - dataSourceName: dataSource2
      dialect: mysql
      jndiName: java:comp/env/mysqlJDBC

 

tomcat配置

路径为tomcat下conf下context.xml文件,context标签体内增加:

     connectionProperties="config.decrypt=true;config.decrypt.key=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALpU95CZIs6Ub7ap1vCI5veVLzArVfVOO5a+baOl2eOLD2EXV7DQ7Y3TVkbsDAx4u2Lcu8scUQwYFWaRw42T6O0CAwEAAQ==" 
     name="oracleJDBC" 
     type="com.alibaba.druid.pool.DruidDataSource"
     factory="com.alibaba.druid.pool.DruidDataSourceFactory" 
     driverClassName="oracle.jdbc.OracleDriver" 
     url="jdbc:oracle:thin:@(DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = 127.0.0.1)(PORT = 8080))(LOAD_BALANCE = yes)(FAILOVER = ON) (CONNECT_DATA =(SERVER = DEDICATED) (SERVICE_NAME = test1) (FAILOVER_MODE=(TYPE = SELECT)(METHOD = BASIC)(RETIRES = 20)(DELAY = 15))))" 
     username="test" 
     password="oyJRemEF/0wWaOEXVSByOHCKAMvrDeldTSh5oyCX3Oj0lmZKtCZm2TnT+2pcIy3f0WtaUitvbzi8uCydqMzzlg==" 
     validationQuery="select 1 FROM DUAL"
     maxActive="400"
     initialSize="50"
     maxWait="60000"
     minIdle="10"
     filters="config"
     timeBetweenEvictionRunsMillis="60000"
     testWhileIdle="true"
     testOnBorrow="false"
     testOnReturn="false"
     minEvictableIdleTimeMillis="300000"
     maxOpenPreparedStatements="100"
     poolPreparedStatements="true"
     maxPoolPreparedStatementPerConnectionSize="20"
 
    />
         type="javax.sql.DataSource" 
     factory="com.alibaba.druid.pool.DruidDataSourceFactory" 
     driverClassName="com.mysql.cj.jdbc.Driver" 
     url="jdbc:mysql://127.0.0.1:3306/test?useUnicode=true;characterEncoding=UTF-8;serverTimezone=Asia/Shanghai;useSSL=false" 
     username="root" 
     password="123456" 
     validationQuery="select 1 "
     maxActive="400"
     initialSize="50"
     maxWait="60000"
     minIdle="10"
     timeBetweenEvictionRunsMillis="60000"
     minEvictableIdleTimeMillis="300000"
     testWhileIdle="true"
     testOnBorrow="false"
     testOnReturn="false"
     maxOpenPreparedStatements="100"
     poolPreparedStatements="true"
     maxPoolPreparedStatementPerConnectionSize="20"
     filters="config"
    
    />

 说明:这密码使用了druid进行加密,也可以不使用,直接写明文,并且把connectionProperties属性删除,其中name属性与yml里对应。

注意:tomcat1.8的时候xml文件中name属性不能待斜杠("/");

代码使用数据源切换

在DAO层的任意类的方法上添加注解:

@DataSource(name = DatasourceEnum.DATA_SOURCE)

可直接切换数据源。

这是使用JNDI交给tomcat维护的方式接下来会写一个springboot   yml文件处理的方式

 

你可能感兴趣的:(springBoot,多数据源,JNDI,AOP,springboot)