在实际开发中,可能遇到多数据源的场景。
/**
* 继承抽象路由数据源类AbstractRoutingDataSource
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 每次查询数据库都会执行该方法
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
// 返回AbstractRoutingDataSource类targetDataSources集合的key。
return null;
}
}
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
@Nullable
private Map<Object, Object> targetDataSources;
@Nullable
private Object defaultTargetDataSource;
private boolean lenientFallback = true;
private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
@Nullable
private Map<Object, DataSource> resolvedDataSources;
@Nullable
private DataSource resolvedDefaultDataSource;
// 这个类中有四个需要传入的非空属性。其中我们只需要传入上面两个即可,下面的两个会通过调用默认的afterPropertiesSet()方法来赋值。
public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
} else {
this.resolvedDataSources = new HashMap(this.targetDataSources.size());
this.targetDataSources.forEach((key, value) -> {
Object lookupKey = this.resolveSpecifiedLookupKey(key);
DataSource dataSource = this.resolveSpecifiedDataSource(value);
this.resolvedDataSources.put(lookupKey, dataSource);
});
if (this.defaultTargetDataSource != null) {
this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
}
}
// 这里省略
}
server:
port: 8888
spring:
application:
name: MultiDatasource
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
name: defaultDataSource
url: jdbc:mysql://localhost:3306/blue?serverTimezone=UTC
username: 'root'
password: '123456'
type: com.zaxxer.hikari.HikariDataSource
db1:
url: jdbc:mysql://localhost:3306/blue?serverTimezone=UTC
username: 'root'
password: '123456'
db2:
url: jdbc:mysql://localhost:3306/blue?serverTimezone=UTC
username: 'root'
password: '123456'
mybatis:
mapper-locations: classpath:mappers/*xml
type-aliases-package: com.hx.multiDB.entity
configuration.map-underscore-to-camel-case: true
package com.hx.multiDB.config;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DataSourceConfig {
@Bean(name = "db1")
@ConfigurationProperties("spring.datasource.db1")
public DataSource db1DataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.db2")
// 配置当存在enable属性值,且为true时才注入容器
@ConditionalOnProperty(
prefix = "spring.datasource.db2",
name = "enable",
havingValue = "true"
)
public DataSource db2() {
return DataSourceBuilder.create().build();
}
/**
* 通过AOP在不同的数据库之间进行切换。
*
* @param db1
* @return
*/
@Primary //告诉spring这个是主数据源
@Bean
// 注意这个类名不可以
public DynamicDataSource dynamicDB(@Qualifier("db1") DataSource db1) {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
// 这里有两种方法把上面的配置放入map集合中,1.以参数传入,2.调用方法
targetDataSources.put("db1", db1);
targetDataSources.put("db2", db2());
dynamicDataSource.setDefaultTargetDataSource(db1);
dynamicDataSource.setTargetDataSources(targetDataSources);
dynamicDataSource.afterPropertiesSet();
return dynamicDataSource;
}
}
package com.hx.multiDB.config;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 继承抽象路由数据源类AbstractRoutingDataSource
* 这里使用的是AbstractRoutingDataSource来切换配置,
* 我们也可以使用mybatis中的SqlSessionTemplate,
* 继承他的getSqlSessionFactory来切换数据源
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 每次查询数据库都会执行该方法
*
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceHandler.getDB();
}
}
package com.hx.multiDB.config;
import lombok.extern.slf4j.Slf4j;
/**
* 动态数据源处理器
*/
@Slf4j
public class DynamicDataSourceHandler {
// 必须要线程变量的集合。不能用线程不安全的,否则在查询的时候并发切换,会导致错误。
private static final ThreadLocal<String> DATASOURCE_HOLDER = new ThreadLocal<>();
public static void setDB(String dbName){
log.info("切换数据源为:{}",dbName);
DATASOURCE_HOLDER.set(dbName);
}
public static String getDB(){
return DATASOURCE_HOLDER.get();
}
// 每次使用完毕都要移除,否则会导致内存泄露
public static void clear(){
DATASOURCE_HOLDER.remove();
}
}
package com.hx.multiDB.config;
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
String value();
}
package com.hx.multiDB.config;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.Joinpoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
@Aspect
@Component
@Slf4j
public class DataSourceAop {
// 通过参数来拿到注解
// @Around("@annotation(dataSource)")
// public Object around( DataSource dataSource){
// dataSource.value();
// }
// 通过切点对象来获取注解
// @Around("@annotation(com.hx.multiDB.config.DataSource)")
// 切dao中的所有方法。
@Around("execution(* com.hx.multiDB.mapper.*Mapper*.*(..))")
public Object around(ProceedingJoinPoint joinpoint) {
log.info("进入环绕通知...");
MethodSignature signature = (MethodSignature) joinpoint.getSignature();
Method method = signature.getMethod();
// 获取方法上的注解
DataSource annotation = method.getAnnotation(DataSource.class);
if(annotation == null){
// 通过签名拿到类
Class type = signature.getDeclaringType();
// 拿到类上的注解
Annotation methodAnno = type.getAnnotation(DataSource.class);
if(methodAnno instanceof DataSource){
annotation = (DataSource) methodAnno;
}
}
if (annotation != null) {
String value = annotation.value();
// 设置数据源
DynamicDataSourceHandler.setDB(value);
}
try {
return joinpoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}finally {
// 清空,避免内存泄露
DynamicDataSourceHandler.clear();
log.info("清空数据源Holder...");
}
}
}
/**
* 实际开发可能是读写分离等场景。
*/
@Mapper
@Repository
public interface TestMapper1 {
@Select(" SELECT * FROM TEST")
List<Test> getAll();
}
/***** 以下是TestMapper2.java *****/
@Mapper
@Repository
public interface TestMapper2 {
@Select(" SELECT * FROM TEST2 ")
List<Test> getAll();
}
package com.hx.multiDB.controller;
import com.hx.multiDB.config.DataSource;
import com.hx.multiDB.mapper.TestMapper1;
import com.hx.multiDB.mapper.TestMapper2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(path="/test",method={RequestMethod.GET,RequestMethod.POST})
public class TestController {
@Autowired
private TestMapper1 mapper1;
@Autowired
private TestMapper2 mapper2;
@GetMapping("/select1")
public Object select1(){
return mapper1.getAll();
}
@GetMapping("/select2")
public Object select2(){
return mapper2.getAll();
}
}
官方描述: ThreadLocal类用来提供线程内部的局部变量,这种变量在多线程环境下访问(通过get和set方法访问)时,能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例通常是private static类型的,用于关联线程和线程的上下文。
作用: 提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少一个线程内多个函数或组件之间一些公共变量传递的复杂度。
总结:
线程并发
:在多线程并发的场景下使用
传递数据
:我们可以通过ThreadLocal在同一线程,不同组件中传递公共变量
线程隔离
:每个线程的变量都是独立的,不会互相影响
synchronized | ThreadLocal | |
---|---|---|
原理 | 同步机制采用以时间换空间的方式,只提供了一份变量,让不同的线程排队访问 | ThreadLocal采用以空间换时间的方式,为每一个线程都提供了一份变量的副本,从而实现同时访问而相互不干扰 |
侧重点 | 多个线程之间访问资源的同步 | 多线程中让每个线程之间的数据相互隔离 |
ThreadLocal设计对比JDK8前后
// JDK8的ThreadLocal下的Entry类通过继承WeakReference采用弱引用。
static class Entry extends WeakReference<ThreadLocal<?>> {
}
Memory overflow: 内存溢出,没有足够的内存提供申请者使用
Memory leak: 内存泄漏,是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度缓慢甚至系统前遗等严重后果。内存泄漏的堆积终将导致内存溢出
java中的引用有4种类型:强、软、弱、虚。当前这个问题主要涉及到强引用和弱引用
StrongReference: 强引用,就是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能证明对象还"活若”,垃圾回收器就不会回收这种对象,
WeakReference: 弱引用,垃圾回收器一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。