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等操作
。
2)ThreadLocal类。ThreadLocalMap是ThreadLocal的静态内部类。
Entry类中value属性用来保存和线程关联的具体对象,key是ThreadLocal类型
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();
}