赞
踩
本文章展示如何使用hikari建立多数据源,发现网上的资料不全或者不对,这里展示如何
配置hikari多数据源附带连接池。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
这里仅需要这两个包即可,后面的springboot版本默认使用的hikari连接池不需要额外引入包。
spring.datasource.test1.hikari.name=test1 spring.datasource.test1.hikari.driverClassName=com.mysql.cj.jdbc.Driver spring.datasource.test1.hikari.jdbcurl=jdbc:mysql://localhost:3306/test1?allowMultiQueries=true&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL=false spring.datasource.test1.hikari.username=root spring.datasource.test1.hikari.password=123456 #客户端向池子请求连接的超时时间,超出则抛出一个SQLException。 #最小值:250毫秒。 #默认值:30000毫秒 单位ms spring.datasource.test1.hikari.connection-timeout=30000 #控制池允许达到的最大大小,包括两者空闲和正在使用的连接 spring.datasource.test1.hikari.maximum-pool-size=2 #控制HikariCP试图维护的空闲连接的最小数量 spring.datasource.test1.hikari.minimum-idle=2 #控制连接在池中允许空闲的最长时间。单位ms spring.datasource.test1.hikari.idle-timeout=600000 #控制连接池中连接的最长生命周期。使用中的连接永远不会被弃用,只有当它被关闭时才会被移除。针对每个连接,应用轻微的负面衰减,以避免连接池的大规模灭绝。 #我们强烈建议设置此值,应该比数据库或基础设施强制实施的连接时间限制短几秒钟。如果将其设置为0,则表示没有最大生命周期(无限生命周期),当然还要考虑idleTimeout设置。 #最小允许值为30000ms(30秒)。 #默认值为1800000(30分钟) spring.datasource.test1.hikari.max-lifetime=1800000 #控制HikariCP尝试保持连接的频率 适用于空闲连接 5分钟检测一次 spring.datasource.test1.hikari.keep-aliveTime =300000 # 检测连接存活 spring.datasource.test1.hikari.connection-test-query=SELECT 1 FROM DUAL spring.datasource.test1.hikari.pool-name =test1pool spring.datasource.test2.hikari.name=test2 spring.datasource.test2.hikari.driverClassName=com.mysql.cj.jdbc.Driver spring.datasource.test2.hikari.jdbcurl=jdbc:mysql://localhost:3306/test2?allowMultiQueries=true&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL=false spring.datasource.test2.hikari.username=root spring.datasource.test2.hikari.password=123456 #客户端向池子请求连接的超时时间,超出则抛出一个SQLException。 #最小值:250毫秒。 #默认值:30000毫秒 单位ms spring.datasource.test2.hikari.connection-timeout=30000 #控制池允许达到的最大大小,包括两者空闲和正在使用的连接 spring.datasource.test2.hikari.maximum-pool-size=2 #控制HikariCP试图维护的空闲连接的最小数量 spring.datasource.test2.hikari.minimum-idle=2 #控制连接在池中允许空闲的最长时间。单位ms spring.datasource.test2.hikari.idle-timeout=600000 #控制连接池中连接的最长生命周期。使用中的连接永远不会被弃用,只有当它被关闭时才会被移除。针对每个连接,应用轻微的负面衰减,以避免连接池的大规模灭绝。 #我们强烈建议设置此值,应该比数据库或基础设施强制实施的连接时间限制短几秒钟。如果将其设置为0,则表示没有最大生命周期(无限生命周期),当然还要考虑idleTimeout设置。 #最小允许值为30000ms(30秒)。 #默认值为1800000(30分钟) spring.datasource.test2.hikari.max-lifetime=1800000 #控制HikariCP尝试保持连接的频率 适用于空闲连接 5分钟检测一次 spring.datasource.test2.hikari.keep-aliveTime =300000 # 检测连接存活 spring.datasource.test2.hikari.connection-test-query=SELECT 1 FROM DUAL spring.datasource.test2.hikari.pool-name =test2pool mybatis.mapperlocations=classpath:com/example/springdemo/demo/dao/*.xml
通用的配置如上面的,具体的含义如上面也做了解释,够一般互联网公司使用了,可以自己再微调参数,根据实际情况配置。
该类用来保存线程使用的数据源是哪一个。
package com.example.springdemo.demo.conifg.initDataSourceConfig; /** * @author * @version 1.0 * @className com.framework.web.config.initDataSourceConfig * @description 动态数据源上下文管理:设置数据源,获取数据源,清除数据源 */ public class DataSourceContextHolder { // 存放当前线程使用的数据源类型 private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<String>(); /** * @param type 1 值 * @titel 设置数据源 * @description 设置数据源 * @author */ public static void setDataSource(String type) { CONTEXT_HOLDER.set(type); } /** * @return java.lang.String * @titel 获取数据源 * @description 获取数据源 * @author */ public static String getDataSource() { return CONTEXT_HOLDER.get(); } /** * @titel 清除数据源 * @description 清除数据源 * @author */ public static void clearDataSource() { CONTEXT_HOLDER.remove(); } }
获取当前数据源
package com.example.springdemo.demo.conifg.initDataSourceConfig; import com.example.springdemo.demo.conifg.annotation.TargetDataSource; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * @author * @version 1.0 * @className com.framework.web.config.initDataSourceConfig * @description 动态数据源,每执行一次数据库,动态获取数据源 */ public class DynamicDataSource extends AbstractRoutingDataSource { /** * @return java.lang.Object 值 * @titel 获取当前数据源 * @description 获取当前数据源 * @author */ @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDataSource(); } }
package com.example.springdemo.demo.conifg.initDataSourceConfig; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.util.StringUtils; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; /** * @author * @version 1.0 * @className com.framework.web.config.initDataSourceConfig * @description 数据源初始化类 * @datetime 2019/10/11 */ @Configuration //@EnableTransactionManagement @MapperScan(value = "com.example.springdemo.demo.dao.**") public class DataSourceConfig { // 扫描XML路径注入 @Value("${mybatis.mapper-locations}") private String mapperLocations; @Bean(name = "test1Config") @ConfigurationProperties(prefix = "spring.datasource.test1.hikari") public HikariConfig test1Config(){ return new HikariConfig(); } /** * @return javax.sql.DataSource 返回数据源 * @title 只读数据源 * @description 只读数据源 * @author * @datetime 2019/12/27 17:25 */ @Bean(name = "test1Datasource") @Primary public HikariDataSource readDataSource() { return new HikariDataSource(test1Config()); } @Bean(name = "test2Config") @ConfigurationProperties(prefix = "spring.datasource.test2.hikari") public HikariConfig test2Config(){ return new HikariConfig(); } /** * @return javax.sql.DataSource 返回数据源 * @title 只写数据源 * @description 只写数据源 * @author * @datetime 2019/12/27 17:25 */ @Bean(name = "test2Datasource") public HikariDataSource setWriteDataSource() { return new HikariDataSource(test2Config()); } /** * @param dataSource 1 数据源 * @return org.mybatis.spring.SqlSessionFactoryBean sql * @title 根据对应的数据源配置Mapper所有xml路径 * @description 根据对应的数据源配置Mapper所有xml路径 * @author * @datetime 2019/12/27 17:18 */ private SqlSessionFactoryBean getSqlSessionFactoryBean(DataSource dataSource) throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSource); PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); // 配置mapper文件位置 sqlSessionFactoryBean.setMapperLocations(resolver.getResources(mapperLocations)); return sqlSessionFactoryBean; } /** * @param dynamicDataSource 1 数据源 * @return org.apache.ibatis.session.SqlSessionFactory * @titel 根据对应的数据源配置Mapper所有xml路径 * @description 根据对应的数据源配置Mapper所有xml路径 * @author * @datetime 2019/10/11 */ @Bean public SqlSessionFactory sqlSessionFactory(DynamicDataSource dynamicDataSource) throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dynamicDataSource); // 设置mapper.xml的位置路径 // 配置mapper文件位置 PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); factoryBean.setMapperLocations(resolver.getResources(mapperLocations)); return factoryBean.getObject(); } /** * @param dynamicDataSource 1 动态数据源 * @return org.springframework.transaction.PlatformTransactionManager 返回事务配置 * @titel 事务配置 * @description 事务配置 * @author * @datetime 2019/10/11 */ @Bean public PlatformTransactionManager transactionManager(DynamicDataSource dynamicDataSource) { return new DataSourceTransactionManager(dynamicDataSource); } /** * @param writeDataSource 2 只写数据源 * @return javax.sql.DataSource 返回设置数据源对象 * @titel 初始化SQL会话模板 * @description 初始化SQL会话模板 * @author */ @Bean public DynamicDataSource dynamicDataSource(@Qualifier("test1Datasource") DataSource writeDataSource, @Qualifier("test2Datasource") DataSource patentDataDataSource) throws Exception { DynamicDataSource dynamicDataSource = new DynamicDataSource(); Map<Object, Object> targetSqlSessionFactorys = new HashMap<Object, Object>(); targetSqlSessionFactorys.put("test1Datasource", writeDataSource); targetSqlSessionFactorys.put("test2Datasource", patentDataDataSource); dynamicDataSource.setTargetDataSources(targetSqlSessionFactorys); //设置默认数据源 dynamicDataSource.setDefaultTargetDataSource(writeDataSource); return dynamicDataSource; } }
这里需要注意的是使用带配置的DataSource创建方式,也是官方推荐的新建数据源配置方式,可以提前检测出数据库连接池是否能正常建立。
该注解用于作用在mapper的方法上,通过aop切面的方式去切换数据源
package com.example.springdemo.demo.conifg.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @Author * @ClassName com.framework.common.annotation * @Description 数据源注解类 * @Version 1.0 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface TargetDataSource { //默认连接为只读 String dataSource() default ""; }
package com.example.springdemo.demo.conifg.initDataSourceConfig; import com.example.springdemo.demo.conifg.annotation.TargetDataSource; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; /** * @author * @version 1.0 * @className com.framework.web.config.initDataSourceConfig * @description service事物拦截类,用于动态切换数据源 * @datetime 2019/10/11 */ @Aspect //@Order(-1)//必须加上这个不然会DynamicDataSource比DataSourceAspect先执行 @Component public class DataSourceAspect { ///@Before是在所拦截方法执行之前执行一段逻辑。 // @After 是在所拦截方法执行之后执行一段逻辑。 // @Around是可以同时在所拦截方法的前后执行一段逻辑。+ // 作为@Pointcut的参数,用以定义连接点 /** * @titel 配置切面目录 * @description 配置切面目录 * @author */ @Pointcut("execution(* com.example.springdemo.demo.dao.*.*(..))") public void pointcut() { } /** * @param ds 1 数据源注解 * @titel 执行方法前更换数据源 * @description 执行方法前更换数据源 * @author */ @Before("pointcut() && @annotation(ds)") public void beforeDataSource(TargetDataSource ds) { String value = ds.dataSource(); DataSourceContextHolder.setDataSource(value); } /** * @param ds 1 数据源注解 * @titel 执行方法后清除数据源设置 * @description 执行方法后清除数据源设置 * @author */ @After("pointcut() && @annotation(ds)") public void afterDataSource(TargetDataSource ds) { DataSourceContextHolder.clearDataSource(); } }
如果需要切换数据库的方法,如下加上注解即可切换数据源,否则就是使用默认的数据源,我这里默认数据源为test1source
package com.example.springdemo.demo.dao; import com.example.springdemo.demo.conifg.annotation.TargetDataSource; import com.example.springdemo.demo.model.User; import com.example.springdemo.demo.model.User1; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface UserDao2 { @TargetDataSource(dataSource="test2Datasource") User getUser(); }
启动项目前先看看数据库连接有多少
日志出现以下的日志时候,说明连接池已经建立了。
2023-12-03 16:28:47.354 INFO 7452 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2023-12-03 16:28:47.354 INFO 7452 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1347 ms
2023-12-03 16:28:47.471 INFO 7452 --- [ main] com.zaxxer.hikari.HikariDataSource : test1pool - Starting...
2023-12-03 16:28:47.627 INFO 7452 --- [ main] com.zaxxer.hikari.HikariDataSource : test1pool - Start completed.
2023-12-03 16:28:47.652 INFO 7452 --- [ main] com.zaxxer.hikari.HikariDataSource : test2pool - Starting...
2023-12-03 16:28:47.656 INFO 7452 --- [ main] com.zaxxer.hikari.HikariDataSource : test2pool - Start completed.
minimum-idle 这里我两个测试库都是最小连接是2,启动项目后,数据库有4个连接,对得上数量。
通过debug到dao这层的请求看代理类的属性,可以看到连接池的属性是否和你配置的一致,只有一致才是读到了配置。
test1测试结果如下
test2测试结果如下
结果是正确切换了数据库并且读取到了数据,测试完毕。
1.这里就不展示如何配置dao了,网上的例子很多,也是该掌握后再看本篇文章。
2.使用切面的方式动态切换数据源,否则就是使用默认的数据源。
3.这里的hikari版本如下,版本变动不大,应该都能一样的使用。
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>3.4.5</version>
<scope>compile</scope>
</dependency>
4.这里需要注意的是如果方法加上了@Transactional 一个方法里面使用了两个数据源,会导致切换数据源失败,因为
事务的控制也是通过数据源的控制实现的不能同时使用两个数据源,要使用多数据源并且支持事务,只能是使用分布式事务控制了,例如seata等。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。