环境:
springboot2
tomcat8
jdk1.8
驱动及加密:com.alibaba.druid
public interface DatasourceEnum { String DATA_SOURCE = "dataSource"; //主数据源 String DATA_SOURCE_2 = "dataSource2"; //其他业务的数据源 String DATA_SOURCE_3 = "dataSource3"; //其他业务的数据源 }
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); } }
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; }
import lombok.Getter; import lombok.Setter; /** * @author keeps * @Title: DataConnectionProperties * @ProjectName aifaceboot * @Description: 数据源基础配置类 */ @Setter @Getter public class DataSourceSchool { private String dataSourceName; private String dialectType; }
/** * @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(); } }
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(); }
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; } }
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(); } }
import java.util.HashMap; import java.util.Map; /** * @author keeps * @Title: DataSourceD * @ProjectName newcapboot * @Description: 数据源分类 */ public class DataSourceDialect { public static Map
dataSourceNameDialect = new HashMap<>(); }
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
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; }
@Getter @Setter public class JNDIExtraProperties { private List
jndiNameList; }
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
以上为JNDI代码部分,下面写配置。
maven引入依赖,略过
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下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文件处理的方式