springboot主从数据库
是从springmvc的思路上来做的,主要就是配置主、从DataSource,
再继承AbstractRoutingDataSource,重写determineCurrentLookupKey
方法,通过Context结合 aop 进行数据主、从库的切换。
上代码:
路由,即实现多数据库的切换源
/* * 重写的函数决定了最后选择的DataSource * 因为AbstractRoutingDataSource中获取连接方法为: @Override public Connection getConnection(String username, String password) throws SQLException { return determineTargetDataSource().getConnection(username, password); } */ public class MultiRouteDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDataSource(); } }
注解,即用以标识选择主还是从数据库
@Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface TargetDataSource { String value(); }
常规配置项,具体主从继承并通过
@ConfigurationProperties(prefix = "master.datasource") 进行配置读取
public class BaseDataSourceConfig { private String url; private String username; private String password; private String driverClassName; // 添加上getter、setter方法 }
多数据源设置
@Configuration public class DataSourceComponent { @Resource MasterDataSourceConfig masterDataSourceConfig; @Resource FirstDataSourceConfig firstDataSourceConfig; @Resource SecondDataSourceConfig secondDataSourceConfig; /* * 一开始以为springboot的自动配置还是会生效,直接加了@Resource DataSource dataSource; * 显示是不work的,会报create bean 错误 */ public DataSource masterDataSource() { DataSource dataSource = new DataSource(); dataSource.setUrl(masterDataSourceConfig.getUrl()); dataSource.setUsername(masterDataSourceConfig.getUsername()); dataSource.setPassword(masterDataSourceConfig.getPassword()); dataSource.setDriverClassName(masterDataSourceConfig.getDriverClassName()); return dataSource; } /* * 一开始在这里加了@Bean的注解,当然secondDataSource()也加了 * 会导致springboot识别的时候,发现有多个 * 所以,其实都不要加@Bean,最终有效的的DataSource就只需要一个multiDataSource即可 */ public DataSource firstDataSource() { DataSource dataSource = new DataSource(); dataSource.setUrl(firstDataSourceConfig.getUrl()); dataSource.setUsername(firstDataSourceConfig.getUsername()); dataSource.setPassword(firstDataSourceConfig.getPassword()); dataSource.setDriverClassName(firstDataSourceConfig.getDriverClassName()); return dataSource; } public DataSource secondDataSource() { DataSource dataSource = new DataSource(); dataSource.setUrl(secondDataSourceConfig.getUrl()); dataSource.setUsername(secondDataSourceConfig.getUsername()); dataSource.setPassword(secondDataSourceConfig.getPassword()); dataSource.setDriverClassName(secondDataSourceConfig.getDriverClassName()); return dataSource; } @Bean(name = "multiDataSource") public MultiRouteDataSource exampleRouteDataSource() { MultiRouteDataSource multiDataSource = new MultiRouteDataSource(); Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put("master", masterDataSource()); targetDataSources.put("first", firstDataSource()); targetDataSources.put("second", secondDataSource()); multiDataSource.setTargetDataSources(targetDataSources); multiDataSource.setDefaultTargetDataSource(masterDataSource()); return multiDataSource; } @Bean(name = "transactionManager") public DataSourceTransactionManager dataSourceTransactionManager() { DataSourceTransactionManager manager = new DataSourceTransactionManager(); manager.setDataSource(exampleRouteDataSource()); return manager; } @Bean(name = "sqlSessionFactory") public SqlSessionFactoryBean sqlSessionFactory() { SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean(); sessionFactoryBean.setDataSource(exampleRouteDataSource()); return sessionFactoryBean; } }
当然少不了DataSourceContextHolder,用以保持当前线程的数据源选择。
public class DataSourceContextHolder { private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); public static void setDataSource(String value) { contextHolder.set(value); } public static String getDataSource() { return contextHolder.get(); } public static void clearDataSource() { contextHolder.remove(); } }
最后,自然就是AOP+注解实现数据源切换啦
@Aspect @Component public class DynamicDataSourceAspect { @Around("execution(public * com.wdm.example.service..*.*(..))") public Object around(ProceedingJoinPoint pjp) throws Throwable { MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); Method targetMethod = methodSignature.getMethod(); if(targetMethod.isAnnotationPresent(TargetDataSource.class)){ String targetDataSource = targetMethod.getAnnotation(TargetDataSource.class).value() ; DataSourceContextHolder.setDataSource(targetDataSource); } Object result = pjp.proceed(); DataSourceContextHolder.clearDataSource(); return result; } }
那用法就是如下了:
package com.wdm.example.service; import java.util.Date; import javax.annotation.Resource; import org.springframework.stereotype.Service; import com.wdm.example.dao.UserDao; import com.wdm.example.datasource.TargetDataSource; import com.wdm.example.model.User; import com.wdm.example.service.UserService; /* * @author wdmyong * 20170416 */ @Service public class UserService { @Resource UserDao userDao; public User getById(Integer id) { return userDao.getById(id); } @TargetDataSource("master") public User getById0(Integer id) { return userDao.getById(id); } @TargetDataSource("first") public User getById1(Integer id) { return userDao.getById(id); } @TargetDataSource("second") public User getById2(Integer id) { return userDao.getById(id); } public void insert(User user) { Date now = new Date(); user.setCreateTime(now); user.setModifyTime(now); userDao.insert(user); } public void update(User user) { user.setModifyTime(new Date()); userDao.update(user); } }
自己在网上找的时候不是全的,包括上文注释中提到的出现的问题,也是根据错误提示多个DataSource目标,以及没设置就是没有DataSource了。
PS:其实之前一直以为DataSource听起来挺悬乎,没去细想,当然主要由于自己是半路出家的Java、web开发,本身也没那么熟悉,所以没理解哈,
现在想想DataSource其实就是保存了些配置,说白了是url和账号密码,就是连接数据库的,相当于你用命令行连接了数据库进行了操作一样,各种
数据库DataSource的实现高功能多半应该是做了些连接池的管理,以及连接的打开关闭之类,其实实质上我觉得应该就是说最后用的就是那个url加
上账号密码就能连接并操作了。这样的话,多数据源的切换就好理解了,结合 aop 在函数入口之前设置好当前线程数据源,以及根据路由数据库类
AbstractRoutingDataSource将选择数据源留给子类实现的方法
determineCurrentLookupKey,从而在service方法入口设置数据源,在使用时取到数据源。
大PS:这应该算是我写的最全的一次Java的博客了!!!