springboot整合druid及多数据源配置
前言
本篇主要分两部分 ①springboot整合druid的代码配置,以及druid的监控页面演示;②对实际场景中多数据源的配置使用进行讲解。
一、springboot整合druid的演示demo
可以用idea快速生成一个可运行的demo工程,具体可以参考如何快速创建springboot项目
主要用到的依赖如下:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
- 配置数据库需要的配置文件application.yml(注意格式和空格)
spring: datasource: username: root password: root url: jdbc:mysql://localhost:3306/firsttest?serverTimezone=UTC&useUnicode=true@characterEncoding=utf-8 driver-class-name: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource poolPreparedStatements: true #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入 #如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority #则导入 log4j 依赖即可,Maven 地址: https://mvnrepository.com/artifact/log4j/log4j filters: stat,wall,log4j maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
- 编写durid的启动配置类,加载数据库信息,和druid监控页面的初始化工作
package com.czing.jdbcdemo.config; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.support.http.StatViewServlet; import com.alibaba.druid.support.http.WebStatFilter; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.servlet.Servlet; import javax.sql.DataSource; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @Configuration public class DruidConfig { @ConfigurationProperties(prefix = "spring.datasource") @Bean public DataSource druidDataSource(){ return new DruidDataSource(); } /** * @Author wangchengzhi * @Description * 向DruidConfig中添加代码,配置druid监控管理台的servlet * @Date 22:12 2023/5/10 * @Param * @return **/ @Bean public ServletRegistrationBean druidServletRegistrationBean(){ ServletRegistrationBean<Servlet> servletRegistrationBean = new ServletRegistrationBean<>(new StatViewServlet(),"/druid/*"); Map<String,String> initParams = new HashMap<>(); initParams.put("loginUsername","root"); initParams.put("loginPassword","root"); //后台允许谁可以访问 //initParams.put("allow", "localhost"):表示只有本机可以访问 //initParams.put("allow", ""):为空或者为null时,表示允许所有访问 initParams.put("allow",""); //deny:Druid 后台拒绝谁访问 //initParams.put("msb", "192.168.1.20");表示禁止此ip访问 servletRegistrationBean.setInitParameters(initParams); return servletRegistrationBean; } @Bean public FilterRegistrationBean webStatFilter() { FilterRegistrationBean bean = new FilterRegistrationBean(); bean.setFilter(new WebStatFilter()); //exclusions:设置哪些请求进行过滤排除掉,从而不进行统计 Map<String, String> initParams = new HashMap<>(); initParams.put("exclusions", "*.js,*.css,/druid/*"); bean.setInitParameters(initParams); //"/*" 表示过滤所有请求 bean.setUrlPatterns(Arrays.asList("/*")); return bean; } }
- 编写controller类进行测试(此处使用jdbcTemplate进行演示)
package com.czing.jdbcdemo.controller; import com.czing.jdbcdemo.mult.DataSource; import com.czing.jdbcdemo.mult.DataSourceType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; import java.util.Map; @RestController public class JDBCController { @Autowired JdbcTemplate jdbcTemplate; /** * @Author wangchengzhi * @Description * 使用springboot自带的jdbcTemplate实现数据库操作 * @Date 21:34 2023/5/10 * @Param * @return **/ @RequestMapping("/selectTest") public List<Map<String, Object>> getTestAccount(){ String sql ="select * from account"; List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql); return maps; } }
- 启动项目,演示效果
项目启动日志:
druid的监控页面访问 http://localhost:8080/druid/sql.html
二、druid多数据源的配置使用
-
修改application.yml文件
spring: datasource: local: username: root password: root #注意多数据源配置的时候此处为jdbc-url jdbc-url: jdbc:mysql://localhost:3306/firsttest?serverTimezone=UTC&useUnicode=true@characterEncoding=utf-8 driver-class-name: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource remote: username: root password: root jdbc-url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true@characterEncoding=utf-8 driver-class-name: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource
编写多数据源的配置类:
package com.czing.jdbcdemo.config; import com.czing.jdbcdemo.mult.DataSourceType; import com.czing.jdbcdemo.mult.DynamicDataSource; 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 javax.sql.DataSource; import java.util.HashMap; import java.util.Map; @Configuration public class DataSourceConfig { @Bean @ConfigurationProperties("spring.datasource.remote") public DataSource remoteDataSource() { return DataSourceBuilder.create().build(); } @Bean @ConfigurationProperties("spring.datasource.local") public DataSource localDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "dynamicDataSource") @Primary public DynamicDataSource dataSource(DataSource remoteDataSource, DataSource localDataSource) { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put(DataSourceType.REMOTE.name(), remoteDataSource); targetDataSources.put(DataSourceType.LOCAL.name(), localDataSource); return new DynamicDataSource(remoteDataSource, targetDataSources); } }
使用自定义注解,在查询方法的时候指定对应的数据源:
- 编写枚举类,用于指定数据源
package com.czing.jdbcdemo.mult; public enum DataSourceType { REMOTE, LOCAL }
- 数据源切换的处理类
package com.czing.jdbcdemo.mult; public class DynamicDataSourceContextHolder { /** * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本, * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 */ private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); /** * 设置数据源变量 * @param dataSourceType */ public static void setDataSourceType(String dataSourceType){ System.out.printf("切换到{%s}数据源", dataSourceType); CONTEXT_HOLDER.set(dataSourceType); } /** * 获取数据源变量 * @return */ public static String getDataSourceType(){ return CONTEXT_HOLDER.get(); } /** * 清空数据源变量 */ public static void clearDataSourceType(){ CONTEXT_HOLDER.remove(); } }
- 数据源信息加载的实现
package com.czing.jdbcdemo.mult; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import javax.sql.DataSource; import java.util.Map; public class DynamicDataSource extends AbstractRoutingDataSource { public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) { super.setDefaultTargetDataSource(defaultTargetDataSource); super.setTargetDataSources(targetDataSources); // afterPropertiesSet()方法调用时用来将targetDataSources的属性写入resolvedDataSources中的 super.afterPropertiesSet(); } /** * @Author wangchengzhi * @Description * 根据Key获取数据源的信息 * @Date 23:06 2023/5/10 * @Param * @return **/ @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceType(); } }
- 多数据源的配置启动类,注入数据库配置信息
package com.czing.jdbcdemo.config; import com.czing.jdbcdemo.mult.DataSourceType; import com.czing.jdbcdemo.mult.DynamicDataSource; 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 javax.sql.DataSource; import java.util.HashMap; import java.util.Map; @Configuration public class DataSourceConfig { @Bean @ConfigurationProperties("spring.datasource.remote") public DataSource remoteDataSource() { return DataSourceBuilder.create().build(); } @Bean @ConfigurationProperties("spring.datasource.local") public DataSource localDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "dynamicDataSource") @Primary public DynamicDataSource dataSource(DataSource remoteDataSource, DataSource localDataSource) { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put(DataSourceType.REMOTE.name(), remoteDataSource); targetDataSources.put(DataSourceType.LOCAL.name(), localDataSource); return new DynamicDataSource(remoteDataSource, targetDataSources); } }
- 自定义注解
package com.czing.jdbcdemo.mult; import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DataSource { /** * 切换数据源名称 */ DataSourceType value() default DataSourceType.REMOTE; }
- AOP拦截类的实现
package com.czing.jdbcdemo.mult; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.lang.reflect.Method; /** * @Description TODO * @Author wangchengzhi * @Date 2023/5/11 10:09 */ @Aspect @Order(1) @Component public class DataSourceAspect { @Pointcut("@annotation(com.czing.jdbcdemo.mult.DataSource)") public void dsPointCut() { } @Around("dsPointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); DataSource dataSource = method.getAnnotation(DataSource.class); if (dataSource != null) { DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name()); } try { return point.proceed(); } finally { // 销毁数据源 在执行方法之后 DynamicDataSourceContextHolder.clearDataSourceType(); } } }
- controller测试类的编写验证
package com.czing.jdbcdemo.controller; import com.czing.jdbcdemo.mult.DataSource; import com.czing.jdbcdemo.mult.DataSourceType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; import java.util.Map; @RestController public class JDBCController { @Autowired JdbcTemplate jdbcTemplate; /** * @Author wangchengzhi * @Description * 使用springboot自带的jdbcTemplate实现数据库操作 * @Date 21:34 2023/5/10 * @Param * @return **/ @RequestMapping("/selectTest") @DataSource(value=DataSourceType.LOCAL) public List<Map<String, Object>> getTestAccount(){ String sql ="select * from account"; List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql); return maps; } /** * @Author wangchengzhi * @Description * 使用springboot自带的jdbcTemplate实现数据库操作 * @Date 21:34 2023/5/10 * @Param * @return **/ @RequestMapping("/selectRemote") @DataSource(DataSourceType.REMOTE) public List<Map<String, Object>> getRemoteAccount(){ String sql ="select * from account"; List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql); return maps; } }
- 启动项目,分别访问两个接口查询不同的数据源
http://localhost:8080/selectTest 的访问数据:
http://localhost:8080/selectRemote 的访问数据
结语
以上为springboot实现druid配置查询的demo,并且演示了如何实现多数据源配置动态切换的代码实现,注意多数据源配置的时候url的配置为jdbc-url