自己根绝视频知识整理,项目地址:https://gitee.com/zhangjunqing/spring-boot/tree/master/springboot-mybatis-sample
1 准备知识
1.1 spring的@Import标签可以将外部类引入到容器生成bean对象,spring 4.2后支持普通java类的引入
1.2 spring可以通过实现ImportBeanDefinitionRegistrar接口的registerBeanDefinitions方法来注册bean
1.3 spring可以通过实现EnvironmentAware接口的setEnvironment方法来获取配置信息
1.4 spring的数据源主要存在 AbstractRoutingDataSource当中,并且可以通过实现此抽象类来动态切换
解决思路:
(一):通过@import实现数据源动态切换的类引入到spring 容器当中
(二):通过实现EnvironmentAware接口来读取配置文件中配置的数据源配置信息,然后生成默认数据源和其他数据源
(三):通过实现ImportBeanDefinitionRegistrar接口,将生成的默认数据源和其他数据源注入到spring容器当中
(四):spring容器启动后,默认数据源对象和其他数据源会注入到你实现的AbstractRoutingDataSource的类的对象中
(五):通过切面技术通过你定义的自定义注解的含义,通过AbstractRoutingDataSource实现类,来进行数据源的动态选择
2 整合mybatis
2.1 启动类,通过@Import引入类,生成对象。
package com.springboot; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Import; import com.springboot.dynamicdatasource.DynamicDataSourceRegister; //@EnableTransactionManagement @MapperScan("com.springboot.mapper")//mybatis的映射接口 @Import(value = {DynamicDataSourceRegister.class}) //@Import({ImportTest.class}) @SpringBootApplication //注意此类的包地址应该是最大的包,这样他就可以自动扫描其下包的所有类.否则也可以(scanBasePackages={"com.springboot.controller"})指定要扫描的包 public class App { public static void main(String[] args) throws Exception { SpringApplication.run(App.class, args); } }
2.2 查看DynamicDataSourceRegister类,并且实现EnvironmentAware和ImportBeanDefinitionRegistrar接口
下面代码注意点:
由于实现了 EnvironmentAware 接口。setEnvironment方法会在初始化调用读取数据源配置信息,读取完后根据配置信息生成数据源
由于实现了ImportBeanDefinitionRegistrar接口。registerBeanDefinitions方法会调用,在此方法中可以将生成的数据源注入到容器当中(特别注意,注入defaultTargetDataSource 和targetDataSources变量名必须是这个,后面会解释原因)。
package com.springboot.dynamicdatasource; import java.util.HashMap; import java.util.Map; import javax.sql.DataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotationMetadata; public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware { private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class); // 如配置文件中未指定数据源类型,使用该默认值 private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource"; // 数据源 private DataSource defaultDataSource; private Map<String, DataSource> customDataSources = new HashMap<String, DataSource>(); /** * 用于动态注入数据 */ @Override public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { Map<Object, Object> targetDataSources = new HashMap<Object, Object>(); // 将主数据源添加到更多数据源中 targetDataSources.put("dataSource", defaultDataSource); DynamicDataSourceContextHolder.dataSourceIds.add("dataSource"); // 添加更多数据源 targetDataSources.putAll(customDataSources); for (String key : customDataSources.keySet()) { DynamicDataSourceContextHolder.dataSourceIds.add(key); } // 创建DynamicDataSource GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(DynamicDataSource.class); beanDefinition.setSynthetic(true); MutablePropertyValues mpv = beanDefinition.getPropertyValues(); mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource); mpv.addPropertyValue("targetDataSources", targetDataSources); registry.registerBeanDefinition("dataSource", beanDefinition); logger.info("Dynamic DataSource Registry"); } /** * 加载多数据源配置 */ @Override public void setEnvironment(Environment env) { initDefaultDataSource(env); initCustomDataSources(env); } /** * 创建DataSource * * @param type * @param driverClassName * @param url * @param username * @param password * @return * @author SHANHY * @create 2016年1月24日 */ @SuppressWarnings("unchecked") public DataSource buildDataSource(Map<String, Object> dsMap) { try { Object type = dsMap.get("type"); if (type == null) type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource Class<? extends DataSource> dataSourceType; dataSourceType = (Class<? extends DataSource>)Class.forName((String)type); String driverClassName = dsMap.get("driver-class-name").toString(); String url = dsMap.get("url").toString(); String username = dsMap.get("username").toString(); String password = dsMap.get("password").toString(); DataSourceBuilder factory = DataSourceBuilder.create() .driverClassName(driverClassName) .url(url) .username(username) .password(password) .type(dataSourceType); return factory.build(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return null; } /** * 初始化主数据源 * * @author SHANHY * @create 2016年1月24日 */ private void initDefaultDataSource(Environment env) { // 读取主数据源 RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver( env, "spring.datasource."); Map<String, Object> dsMap = new HashMap<String, Object>(); dsMap.put("type", propertyResolver.getProperty("type")); dsMap.put("driver-class-name", propertyResolver.getProperty("driver-class-name")); dsMap.put("url", propertyResolver.getProperty("url")); dsMap.put("username", propertyResolver.getProperty("username")); dsMap.put("password", propertyResolver.getProperty("password")); defaultDataSource = buildDataSource(dsMap); } /** * 初始化更多数据源 * * @author SHANHY * @create 2016年1月24日 */ private void initCustomDataSources(Environment env) { // 读取配置文件获取更多数据源,也可以通过defaultDataSource读取数据库获取更多数据源 RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver( env, "custom.datasource."); String dsPrefixs = propertyResolver.getProperty("names"); for (String dsPrefix : dsPrefixs.split(",")) {// 多个数据源 Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix + "."); DataSource ds = buildDataSource(dsMap); customDataSources.put(dsPrefix, ds); } } }
2.3 定义注解,主要用来标识使用那个数据源
package com.springboot.dynamicdatasource; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface TargetDataSource1 { String name(); }
2.4 在需要数据源的service方法中增加TargetDataSource1 上面注解来标识需要那个数据源
@Service @Transactional(propagation=Propagation.REQUIRED) public class UserServiceImpl implements UserService{ @Resource private UserMapper userMapper; @Override public void insert(User user) { userMapper.insert(user); } @Override @TargetDataSource1(name="ds1") public void insertdb1(User user) { userMapper.insert(user); } @Override @TargetDataSource1(name="ds2") public void insertdb2(User user) { userMapper.insert(user); } }
2.5 DynamicDataSourceContextHolder 用来存储当前线程所需要的数据源名称
package com.springboot.dynamicdatasource; import java.util.ArrayList; import java.util.List; public class DynamicDataSourceContextHolder { /** * 线程安全,用来存储本次执行需要数据源的名称 */ private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); public static List<String> dataSourceIds = new ArrayList<String>(); public static void setDataSourceType(String dataSourceType) { contextHolder.set(dataSourceType); } public static String getDataSourceType() { return contextHolder.get(); } public static void clearDataSourceType() { contextHolder.remove(); } /** * 判断指定DataSrouce当前是否存在 * * @param dataSourceId * @return * @author SHANHY * @create 2016年1月24日 */ public static boolean containsDataSource(String dataSourceId) { return dataSourceIds.contains(dataSourceId); } }
2.6 定义切面,读取service上的注解,然后获取注解指定的数据源名称,将其存储到DynamicDataSourceContextHolder中,方法执行完之后再清空此数据。
注意:必须指定执行顺序,防止出现问题
package com.springboot.dynamicdatasource; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @Aspect //保证该AOP在@Transactional之前执行 @Order(-1) @Component public class DynamicDataSourceAspect { private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class); /** * @Description 在方法执行之前执行 @annotation(ds) 会拦截有ds这个注解的方法即有 TargetDataSource这个注解的 * @param @param point * @param @param ds * @param @throws Throwable 参数 * @return void 返回类型 * @throws */ @Before("@annotation(targetDataSource)") public void changeDataSource(JoinPoint point, TargetDataSource1 targetDataSource) throws Throwable { String dsId = targetDataSource.name(); if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) { logger.error("数据源[{}]不存在,使用默认数据源 > {}", targetDataSource.name(), point.getSignature()); } else { logger.debug("Use DataSource : {} > {}", targetDataSource.name(), point.getSignature()); DynamicDataSourceContextHolder.setDataSourceType(targetDataSource.name()); } } /** * @Description 在方法执行之后执行 @annotation(ds) 会拦截有ds这个注解的方法即有 TargetDataSource这个注解的 * @param @param point * @param @param ds 参数 * @return void 返回类型 * @throws */ @After("@annotation(targetDataSource)") public void restoreDataSource(JoinPoint point, TargetDataSource1 targetDataSource) { logger.debug("Revert DataSource : {} > {}", targetDataSource.name(), point.getSignature()); DynamicDataSourceContextHolder.clearDataSourceType(); } }
2.7 通过实现DynamicDataSource抽象类,并且根据DynamicDataSourceContextHolder存储的数据源名称来进行数据源的选取。
特别注意:AbstractRoutingDataSource 是用来做数据源管理用的,其中 targetDataSources,defaultTargetDataSource变量,其实就是上面DynamicDataSourceRegister类中注入到spring容器中的数据源,故需要DynamicDataSourceRegister注入这两个对象时名称必须和这两个变量一致
package com.springboot.dynamicdatasource; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * 用于动态数据源的获取 * @author 76524 * */ public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { System.out.println(DynamicDataSourceContextHolder.getDataSourceType()); // TODO Auto-generated method stub return DynamicDataSourceContextHolder.getDataSourceType(); } }