我们在项目中经常会遇到配置多个数据的情况,之前我有写过通过配置mapper.xml的不同地址来实现同一个项目连接到多个数据库。如果用这种方式可以参考,下面一片文章。
https://blog.csdn.net/fajing_feiyue/article/details/100604300
这篇文档与上面一种方式相比,是通过注解来实现和切面来配置实现数据库,这种方式需要能够更深刻来理解mybatis与spring的结合,这里将这种方式展示出来供大家参考。
1、引入maven依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.0.4.RELEASE</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.12</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.12</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> <version>8.0.17</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> <version>2.0.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> <version>1.5.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency>
2、配置application.properties
server.port=8080 default.datasource.type=com.alibaba.druid.pool.DruidDataSource default.datasource.url=jdbc:mysql://127.0.0.1:3306/pingyougou?serverTimezone=UTC default.datasource.driver-class-name=com.mysql.cj.jdbc.Driver default.datasource.username=root default.datasource.password=123 custom.datasource.names=custom1 custom.datasource.custom1.type=com.alibaba.druid.pool.DruidDataSource custom.datasource.custom1.url=jdbc:mysql://127.0.0.1:3306/pinyougoudb?serverTimezone=UTC custom.datasource.custom1.driver-class-name=com.mysql.cj.jdbc.Driver custom.datasource.custom1.username=root custom.datasource.custom1.password=123 #配置初始化大小,最小,最大 common.datasource.initialSize=10 common.datasource.minIdle=10 common.datasource.maxActive=50 #配置连接等待超时时间 common.datasource.maxWait=15000 #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 common.datasource.timeBetweenEvictionRunsMillis=60000 #配置一个连接在池中最小生存时间,一个小时 common.datasource.minEvictableIdleTimeMillis=3600000 #连接空闲时是否进行有效性验证(即是否还和数据库连通的) common.datasource.testWhileIdle=true #取得连接时是否进行有效性验证(即是否还和数据库连通的)【防止取到连接不可用】 common.datasource.testOnBorrow=true #回连接时是否进行有效性验证(即是否还和数据库连通的) common.datasource.testOnReturn=false #配置提交方式,默认是true,可以不用配置 common.datasource.defultAutoCommint=true #logging.config=classpath:config/log4j2.xml #配置.xml文件路径 #mybatis.config-locations=classpath:mybatis/mybatis-config.xml mybatis.mapper-locations=classpath:mapper/*.xml mybatis.type-aliases-package=com.yin.dynamic_datasource.dto
3、通过实现ImportBeanDefinitionRegistrar动态注册DataSource,实现EnvironmentAware 来读取application.properties里面的内容。由自己定义DynamicDataSourceContextHolder来保存目前正在数据库的信息
和每个线程所使用的数据库。并且设置由自己定义的DynamicDataSource来切换数据库。
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware { private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class); private static final Object DATASOURCE_TYPE_DEFAULT = "com.alibaba.druid.pool.DruidDataSource"; private DataSource defaultDataSource; private Map<String, DataSource> customDataSources = new HashMap(); private static String DB_NAME = "names"; private static String DB_DEFAULT_VALUE = "default.datasource"; private static String DB_CUSTOM_VALUE = "custom.datasource"; private static String DB_COMMON_VALUE = "common.datasource"; public DynamicDataSourceRegister() { } @Override public void setEnvironment(Environment env) { initDefaultDataSource(env); initCustomDataSources(env); } private void initCustomDataSources(Environment env) { String dsPrefixs = env.getProperty(DB_CUSTOM_VALUE + "." + DB_NAME); String[] var3 = dsPrefixs.split(","); int var4 = var3.length; for(int var5 = 0; var5 < var4; ++var5) { String dsPrefix = var3[var5]; Map<String, Object> dsMap = new HashMap(); dsMap.put("type", env.getProperty(DB_CUSTOM_VALUE + "." + dsPrefix + ".type")); dsMap.put("driver-class-name", env.getProperty(DB_CUSTOM_VALUE + "." + dsPrefix + ".driver-class-name")); dsMap.put("url", env.getProperty(DB_CUSTOM_VALUE + "." + dsPrefix + ".url")); dsMap.put("username", env.getProperty(DB_CUSTOM_VALUE + "." + dsPrefix + ".username")); dsMap.put("password", env.getProperty(DB_CUSTOM_VALUE + "." + dsPrefix + ".password")); DataSource ds = this.buildDataSource(dsMap,env); this.customDataSources.put(dsPrefix, ds); } } private void initDefaultDataSource(Environment env) { HashMap<String, Object> dsMap = new HashMap<>(); dsMap.put("type", env.getProperty(DB_DEFAULT_VALUE + ".type")); dsMap.put("driver-class-name", env.getProperty(DB_DEFAULT_VALUE + ".driver-class-name")); dsMap.put("url", env.getProperty(DB_DEFAULT_VALUE + ".url")); dsMap.put("username", env.getProperty(DB_DEFAULT_VALUE + ".username")); dsMap.put("password", env.getProperty(DB_DEFAULT_VALUE + ".password")); //创建数据源 defaultDataSource = buildDataSource(dsMap, env); } private DataSource buildDataSource(Map<String, Object> dsMap,Environment env) { Object type = dsMap.get("type"); if (type == null) { type = DATASOURCE_TYPE_DEFAULT; } Class<? extends DataSource> dataSourceType; try { 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(); DruidDataSource dataSource = new DruidDataSource(); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); dataSource.setDriverClassName(driverClassName); dataSource.setInitialSize(Integer.valueOf(env.getProperty(DB_COMMON_VALUE + ".initialSize"))); dataSource.setMinIdle(Integer.valueOf(env.getProperty(DB_COMMON_VALUE + ".minIdle"))); dataSource.setMaxActive(Integer.valueOf(env.getProperty(DB_COMMON_VALUE + ".maxActive"))); dataSource.setMaxWait(Integer.valueOf(env.getProperty(DB_COMMON_VALUE + ".maxWait"))); dataSource.setTimeBetweenEvictionRunsMillis(Integer.valueOf(env.getProperty(DB_COMMON_VALUE + ".timeBetweenEvictionRunsMillis"))); dataSource.setMinEvictableIdleTimeMillis(Long.valueOf(env.getProperty(DB_COMMON_VALUE + ".minEvictableIdleTimeMillis"))); dataSource.setTestWhileIdle(Boolean.valueOf(env.getProperty(DB_COMMON_VALUE + ".testWhileIdle"))); dataSource.setTestOnBorrow(Boolean.valueOf(env.getProperty(DB_COMMON_VALUE + ".testOnBorrow"))); dataSource.setTestOnReturn(Boolean.valueOf(env.getProperty(DB_COMMON_VALUE + ".testOnReturn"))); dataSource.setDefaultAutoCommit(Boolean.valueOf(env.getProperty(DB_COMMON_VALUE + ".defultAutoCommint"))); dataSource.setValidationQuery("SELECT 1"); return dataSource; } catch (ClassNotFoundException ex) { logger.error(ex.getMessage(), ex); } return null; } @Override public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) { Map<Object, Object> targetDataSources = new HashMap(); targetDataSources.put("dataSource", this.defaultDataSource); DynamicDataSourceContextHolder.dataSourceIds.add("dataSource"); targetDataSources.putAll(this.customDataSources); Iterator var4 = this.customDataSources.keySet().iterator(); while(var4.hasNext()) { String key = (String)var4.next(); DynamicDataSourceContextHolder.dataSourceIds.add(key); } GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(DynamicDataSource.class); beanDefinition.setSynthetic(true); MutablePropertyValues mpv = beanDefinition.getPropertyValues(); mpv.addPropertyValue("defaultTargetDataSource", this.defaultDataSource); mpv.addPropertyValue("targetDataSources", targetDataSources); registry.registerBeanDefinition("dataSource", beanDefinition); logger.info("Dynamic DataSource Registry"); } }
4、将DynamicDataSourceRegister 注册类交由启动类加载
@SpringBootApplication @Import(DynamicDataSourceRegister.class) public class DynamicDatasourceApplication { public static void main(String[] args) { SpringApplication.run(DynamicDatasourceApplication.class, args); } }
5、定义DynamicDataSourceContextHolder类来保存所有用到数据库和每个线程所使用到数据库
public class DynamicDataSourceContextHolder { /** * 保存每个线程对应数据信息 */ private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); /** * 保存所有数据库的信息 */ public static List<String> dataSourceIds = new CopyOnWriteArrayList<>(); public static void setContextHolder(String dataSource){ contextHolder.set(dataSource); } public static String getContextHolder(){ return contextHolder.get(); } public static boolean isContainsDatasource(String dataSourceId){ return dataSourceIds.contains(dataSourceId); } //这里在使用完数据库进行.remove()防止内存泄漏从而导致内存溢出 public static void clearDataSourceType(){ contextHolder.remove(); } }
6、定义DynamicDataSource来切换数据库
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getContextHolder(); } }
7、定义注解TargetDataSource 来实现,对数据库指定
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface TargetDataSource { String name(); }
8、定义切面对使用到这个注解的方法前后对用DynamicDataSourceContextHolder 保存和移除每个线程所使用到的数据库信息。之所以要用完移除主要是防止内存泄漏从而导致的内存溢出。
@Aspect //保证该aop在@Transaction之前执行 @Order(-1) @Component public class DynamicDataSourceAspect { private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class); public DynamicDataSourceAspect() { } @Before("@annotation(ds)") public void changeDataSource(JoinPoint point, TargetDataSource ds) throws Throwable { String dsId = ds.name(); if (!DynamicDataSourceContextHolder.isContainsDatasource(dsId)) { logger.error("数据源[{}]不存在,使用默认数据源 > {}", ds.name(), point.getSignature()); } else { logger.debug("Use DataSource : {} > {}", dsId, point.getSignature()); DynamicDataSourceContextHolder.setContextHolder(dsId); } } @After("@annotation(ds)") public void restoreDataSource(JoinPoint point, TargetDataSource ds) { logger.debug("Revert DataSource : {} > {}", ds.name(), point.getSignature()); DynamicDataSourceContextHolder.clearDataSourceType(); } }
9、测试(以下为测试内容)
package com.yin.dynamic_datasource.controller; import com.yin.dynamic_datasource.dto.AreaDto; import com.yin.dynamic_datasource.service.DynamicService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.Mapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author yin * @Date 2020/1/28 10:35 * @Method */ @RestController @RequestMapping("/dynamic/test") public class DynamicController { @Autowired private DynamicService dynamicService; @GetMapping("/insert/default") public AreaDto insertDefault(){ AreaDto areaDto = new AreaDto(); areaDto.setArea("defaultArea1"); areaDto.setAreaId("defaultId1"); areaDto.setCityid("defaultCityId1"); dynamicService.insertDefaultArea(areaDto); return areaDto; } @GetMapping("/insert/custom1") public AreaDto insertCustom1(){ AreaDto areaDto = new AreaDto(); areaDto.setArea("custom1Area1"); areaDto.setAreaId("custom1Id1"); areaDto.setCityid("custom1CityId1"); dynamicService.insertCustom1Area(areaDto); return areaDto; } }
service
public interface DynamicService { void insertArea(AreaDto areaDto); void insertDefaultArea(AreaDto areaDto); void insertCustom1Area(AreaDto areaDto); }
serviceImpl
@Service public class DynamicServiceImpl implements DynamicService { @Autowired private DynamicMapper dynamicMapper; @Override public void insertArea(AreaDto areaDto) { dynamicMapper.insertArea(areaDto); } @Override public void insertDefaultArea(AreaDto areaDto) { dynamicMapper.insertArea(areaDto); } @Override @TargetDataSource(name = "custom1") public void insertCustom1Area(AreaDto areaDto) { dynamicMapper.insertArea(areaDto); } }
mapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.yin.dynamic_datasource.dao.DynamicMapper"> <!-- 插入记录--> <insert id="insertArea" useGeneratedKeys="true" keyProperty="id" parameterType="com.yin.dynamic_datasource.dto.AreaDto"> INSERT INTO tb_areas(areaid, area, cityid) values (#{areaId}, #{area}, #{cityid}) </insert> </mapper>