1,動態數據源類
public class DynamicDataSource extends AbstractRoutingDataSource{
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceHolder.getDbType();//負載均衡,需要在這里進行從庫的key輪詢。
}
}
2,使用本地ThreadLocal,存儲當前線程的數據源。
threadlocal將具體的值保存在線程自身的threadLocalMap中
public abstract class DynamicDataSourceHolder {
public static final String master = "master";
public static final String slave = "slave";
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();//定義全局ThreadLocal對象,不同線程使用的是同一個對象。
public static void setDbType(String dbTypeName){
DynamicDataSourceHolder.threadLocal.set(dbTypeName);
}
public static String getDbType(){
return DynamicDataSourceHolder.threadLocal.get();
}
public static void clearDbType(){
DynamicDataSourceHolder.threadLocal.remove();
}
}
3,配置多個數據源
@Value("${datasource.username2}")
private String username2;
@Value("${datasource.password2}")
private String password2;
@Value("${datasource.url2}")
private String url2;
@Value("${datasource.username}")
private String username;
@Value("${datasource.password}")
private String password;
@Value("${datasource.url}")
private String url;
@Bean//配置主數據庫源
public DataSource master(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return getDataSource(druidDataSource);
}
@Bean//配置從數據庫源
public DataSource slave(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl(url2);
druidDataSource.setUsername(username2);
druidDataSource.setPassword(password2);
return getDataSource(druidDataSource);
}
@Bean//配置動態數據源
public DynamicDataSource dynamicDataSource(){
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setDefaultTargetDataSource(master());//default數據源
Map<Object, Object> map = Maps.newHashMap();
map.put(DynamicDataSourceHolder.master, master());//key - value
map.put(DynamicDataSourceHolder.slave, slave());
dynamicDataSource.setTargetDataSources(map);
return dynamicDataSource;
}
4,自定義注解,標識使用哪個數據源
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Order(Ordered.HIGHEST_PRECEDENCE)
public @interface DynamicSourceAno {
/**
* 默認使用的數據源
* @return
*/
String value() default DynamicDataSourceHolder.master;
}
5,實現切面。
設置spring事務aop的執行順序
@EnableTransactionManagement(order = 2) <tx:annotation-driven order="2"/>
@Aspect
@Component
@Order(1)//service層有@Transactional,事務也是aop實現的,這里定義order,要在事務開啟的aop執行前,進行數據源的切換,事務的aop結束后,進行數據源的恢復默認。
public class DynamicSourceAspect {
private static Logger logger = LoggerFactory.getLogger(DynamicSourceAspect.class);
@Before("within(@org.springframework.stereotype.Service *) && @annotation(dynamicSourceAno)")
//在service的所有方法執行前,并且帶有注解DynamicSourceAno
public void beforeDynamicSource(DynamicSourceAno dynamicSourceAno){
try {
DynamicDataSourceHolder.setDbType(dynamicSource.value());
} catch (Throwable t) {
logger.info(t.getMessage(), t);
}
}
@After("within(@org.springframework.stereotype.Service *) && @annotation(dynamicSource)")
public void afterDynamicSource(DynamicSource dynamicSource){
try {
DynamicDataSourceHolder.clearDbType();
} catch (Throwable t) {
logger.info(t.getMessage(), t);
}
}
}
6,ThreadLocal與Thread
1)Thread類。定義了兩個TheadLocalMap類型屬性
(可以用來保存對象)
,通過ThreadLocal類來維護的set,get,remove等操作
。
image.png
2)ThreadLocal類。ThreadLocalMap是ThreadLocal的靜態內部類。
Entry類中value屬性用來保存和線程關聯的具體對象,key是ThreadLocal類型
image.png
3)ThreadLocal類操作當前線程的ThreadLocalMap。
//保存value到當前線程
public void set(T value) {
Thread t = Thread.currentThread();//得到當前線程
ThreadLocalMap map = getMap(t);//得到當前線程的ThreadLocalMap對象。
if (map != null)
map.set(this, value);//將自身對象作為key。每個線程保存這個對象,都是使用相同的key,因為該類型的ThreadLocal對象只new了一個。
else
createMap(t, value);
}
//當第一次調用set方法時,會創建一個ThreadLocalMap對象。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//從當前線程的threadlocals中去除指定key對應的value
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//不同的線程使用相同的key來取值,得到的各自線程保存的對象。
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}