使用场景
在实际开发中,可能遇到多数据源的场景。
- 业务复杂(数据量大)
数据分布在不同的数据库中,对业务数据进行垂直拆分。 可以拆分为微服务架构,依赖的业务可以通过远程调用的方式来是实现,那么这种方式是不存在多数据源的情况。但是对于性能要求不高,以实现功能为目的,则可以使用多数据源。 - 读写分离
为了解决数据库读性能瓶颈(读比写的性能要高,但是写锁会影响阻塞,从而影响读性能) 主从架构:一台主数据库对外提供增删改业务,其他从数据库进行读操作。 - 这里还有读取上传的业务。
自定义多数据源实现类
关键类解析
DynamicDataSource 需要编写这个类并继承 AbstractRoutingDataSource
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return null;
}
}
AbstractRoutingDataSource解析
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;
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);
}
}
}
示例:
- 创建SpringBoot程序,并编写配置文件。配置好两个数据源。这里我的叫db1,db2
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
- 新建数据源配置类 DataSourceConfig.java(包含了数据源配置)
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")
@ConditionalOnProperty(
prefix = "spring.datasource.db2",
name = "enable",
havingValue = "true"
)
public DataSource db2() {
return DataSourceBuilder.create().build();
}
@Primary
@Bean
public DynamicDataSource dynamicDB(@Qualifier("db1") DataSource db1) {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("db1", db1);
targetDataSources.put("db2", db2());
dynamicDataSource.setDefaultTargetDataSource(db1);
dynamicDataSource.setTargetDataSources(targetDataSources);
dynamicDataSource.afterPropertiesSet();
return dynamicDataSource;
}
}
- 新建动态数据源类 DynamicDataSource.java(继承AbstractRoutingDataSource)
package com.hx.multiDB.config;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;
import java.util.Map;
public class DynamicDataSource extends AbstractRoutingDataSource {
@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();
}
}
利用Aop和注解来自动切换数据源
- 新建数据源注解 DataSource.java
package com.hx.multiDB.config;
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
String value();
}
- 创建数据源AOP DataSourceAop.java
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("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...");
}
}
}
测试
- 编写TestMapper1.java和TestMapper2.java
@Mapper
@Repository
public interface TestMapper1 {
@Select(" SELECT * FROM TEST")
List<Test> getAll();
}
@Mapper
@Repository
public interface TestMapper2 {
@Select(" SELECT * FROM TEST2 ")
List<Test> getAll();
}
- 新建测试 TestController.java
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();
}
}
TreadLocal原理
官方描述: ThreadLocal类用来提供线程内部的局部变量,这种变量在多线程环境下访问(通过get和set方法访问)时,能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例通常是private static类型的,用于关联线程和线程的上下文。
作用: 提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少一个线程内多个函数或组件之间一些公共变量传递的复杂度。
总结: 线程并发 :在多线程并发的场景下使用 传递数据 :我们可以通过ThreadLocal在同一线程,不同组件中传递公共变量 线程隔离 :每个线程的变量都是独立的,不会互相影响
| synchronized | ThreadLocal |
---|
原理 | 同步机制采用以时间换空间的方式,只提供了一份变量,让不同的线程排队访问 | ThreadLocal采用以空间换时间的方式,为每一个线程都提供了一份变量的副本,从而实现同时访问而相互不干扰 | 侧重点 | 多个线程之间访问资源的同步 | 多线程中让每个线程之间的数据相互隔离 |
ThreadLocal设计对比JDK8前后
static class Entry extends WeakReference<ThreadLocal<?>> {
}
Memory overflow: 内存溢出,没有足够的内存提供申请者使用 Memory leak: 内存泄漏,是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度缓慢甚至系统前遗等严重后果。内存泄漏的堆积终将导致内存溢出 java中的引用有4种类型:强、软、弱、虚。当前这个问题主要涉及到强引用和弱引用 StrongReference: 强引用,就是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能证明对象还"活若”,垃圾回收器就不会回收这种对象, WeakReference: 弱引用,垃圾回收器一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
致谢:B站UP主【我觉得吧__】 视频地址:852196951
|