这里用到了Spring Boot + Mybatis + DynamicDataSource配置动态双数据源,可以动态切换数据源实现数据库的读写分离。
添加依赖
加入Mybatis启动器,这里添加了Druid连接池、Oracle数据库驱动为例。
1<dependency> 2 <groupId>org.mybatis.spring.boot</groupId> 3 <artifactId>mybatis-spring-boot-starter</artifactId> 4</dependency> 5 6<dependency> 7 <groupId>com.alibaba</groupId> 8 <artifactId>druid</artifactId> 9</dependency> 10 11<dependency> 12 <groupId>com.oracle</groupId> 13 <artifactId>ojdbc6</artifactId> 14</dependency> 15
添加启动类
1@EnableMybatis 2@EnableTransactionManagement 3@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) 4public class Application { 5 6 public static void main(String[] args) { 7 SpringApplication.run(ServiceApplication.class, args); 8 } 9 10}
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }): 这里用到了双数据源,需要排除数据源的自动配置,如果只有一个数据源用Spring Boot的自动配置就行。
@EnableTransactionManagement:开启事务支持。
@EnableMybatis:开启Mybatis功能
1@Target(ElementType.TYPE) 2@Retention(RetentionPolicy.RUNTIME) 3@Documented 4@Import(MybatisConfig.class) 5public @interface EnableMybatis { 6 7}
Mybatis配置类
1@Configuration 2@MapperScan(basePackages = DSConfig.BASE_PACKAGES) 3public class MybatisConfig implements DSConfig { 4 5 @Primary 6 @Bean 7 public DynamicDataSource dynamicDataSource(@Qualifier(DB_MASTER) DataSource master, 8 @Qualifier(DB_SLAVE) DataSource slave) { 9 Map<Object, Object> dsMap = new HashMap<>(); 10 dsMap.put(DB_MASTER, master); 11 dsMap.put(DB_MASTER, slave); 12 13 DynamicDataSource dynamicDataSource = new DynamicDataSource(); 14 dynamicDataSource.setDefaultTargetDataSource(master); 15 dynamicDataSource.setTargetDataSources(dsMap); 16 return dynamicDataSource; 17 } 18 19 @Bean 20 public PlatformTransactionManager transactionManager(DynamicDataSource dynamicDataSource) { 21 return new DataSourceTransactionManager(dynamicDataSource); 22 } 23 24 @Bean 25 public SqlSessionFactory sqlSessionFactory(DynamicDataSource dynamicDataSource) 26 throws Exception { 27 SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); 28 sessionFactory.setDataSource(dynamicDataSource); 29 sessionFactory.setMapperLocations( 30 ((ResourcePatternResolver) new PathMatchingResourcePatternResolver()) 31 .getResources(DSConfig.MAPPER_LOCATIONS)); 32 return sessionFactory.getObject(); 33 } 34 35}
DSConfig常量类:
1public interface DSConfig { 2 3 String DS_PREFIX = "spring.datasource"; 4 String DS_ACTIVE = "active"; 5 6 String DB_MASTER = "db-master"; 7 String DB_SLAVE = "db-slave"; 8 9 String DRUID = "druid"; 10 11 String DRUID_MONITOR_USERNAME = "spring.druid.username"; 12 String DRUID_MONITOR_PASSWORD = "spring.druid.password"; 13 String DRUID_MONITOR_URL = "/druid/*"; 14 String DRUID_FILTER_EXCLUSIONS = "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"; 15 String DRUID_FILTER_URL = "/*"; 16 17 String BASE_PACKAGES = "com.example.**.mapper"; 18 String MAPPER_LOCATIONS = "mapper/**/*.xml"; 19 20}
连接池配置类
Druid连接池的自动配置类:
1@Configuration 2@Import({ PropertiesConfig.class }) 3@ConditionalOnClass(DruidDataSource.class) 4@ConditionalOnProperty(prefix = DSConfig.DS_PREFIX, value = DSConfig.DS_ACTIVE, havingValue = DSConfig.DRUID) 5public class DruidAutoConfig implements DSConfig { 6 7 private Logger logger = LoggerUtils.getLogger(this); 8 9 @Bean(name = DB_MASTER, initMethod = "init", destroyMethod = "close") 10 public DataSource dataSourceMaster(DruidMasterProperties masterProperties) throws SQLException { 11 logger.debug("master properties: {}", masterProperties.toString()); 12 13 DruidDataSource dds = new DruidDataSource(); 14 dds.setDriverClassName(masterProperties.getDriverClassName()); 15 dds.setUrl(masterProperties.getUrl()); 16 dds.setUsername(masterProperties.getUsername()); 17 dds.setPassword(masterProperties.getPassword()); 18 dds.setInitialSize(masterProperties.getInitialSize()); 19 dds.setMinIdle(masterProperties.getMinIdle()); 20 dds.setMaxActive(masterProperties.getMaxActive()); 21 dds.setMaxWait(masterProperties.getMaxWait()); 22 dds.setTimeBetweenEvictionRunsMillis(masterProperties.getTimeBetweenEvictionRunsMillis()); 23 dds.setMinEvictableIdleTimeMillis(masterProperties.getMinEvictableIdleTimeMillis()); 24 dds.setValidationQuery(masterProperties.getValidationQuery()); 25 dds.setTestOnBorrow(masterProperties.isTestOnBorrow()); 26 dds.setTestWhileIdle(masterProperties.isTestWhileIdle()); 27 dds.setTestOnReturn(masterProperties.isTestOnReturn()); 28 dds.setPoolPreparedStatements(masterProperties.isPoolPreparedStatements()); 29 dds.setMaxPoolPreparedStatementPerConnectionSize( 30 masterProperties.getMaxPoolPreparedStatementPerConnectionSize()); 31 dds.setFilters(masterProperties.getFilters()); 32 33 return dds; 34 } 35 36 @Bean(name = DB_SLAVE, initMethod = "init", destroyMethod = "close") 37 public DataSource dataSourceSlave(DruidSlaveProperties slaveProperties) throws SQLException { 38 logger.debug("slave properties: {}", slaveProperties.toString()); 39 40 DruidDataSource dds = new DruidDataSource(); 41 dds.setDriverClassName(slaveProperties.getDriverClassName()); 42 dds.setUrl(slaveProperties.getUrl()); 43 dds.setUsername(slaveProperties.getUsername()); 44 dds.setPassword(slaveProperties.getPassword()); 45 dds.setInitialSize(slaveProperties.getInitialSize()); 46 dds.setMinIdle(slaveProperties.getMinIdle()); 47 dds.setMaxActive(slaveProperties.getMaxActive()); 48 dds.setMaxWait(slaveProperties.getMaxWait()); 49 dds.setTimeBetweenEvictionRunsMillis(slaveProperties.getTimeBetweenEvictionRunsMillis()); 50 dds.setMinEvictableIdleTimeMillis(slaveProperties.getMinEvictableIdleTimeMillis()); 51 dds.setValidationQuery(slaveProperties.getValidationQuery()); 52 dds.setTestOnBorrow(slaveProperties.isTestOnBorrow()); 53 dds.setTestWhileIdle(slaveProperties.isTestWhileIdle()); 54 dds.setTestOnReturn(slaveProperties.isTestOnReturn()); 55 dds.setPoolPreparedStatements(slaveProperties.isPoolPreparedStatements()); 56 dds.setMaxPoolPreparedStatementPerConnectionSize( 57 slaveProperties.getMaxPoolPreparedStatementPerConnectionSize()); 58 dds.setFilters(slaveProperties.getFilters()); 59 60 return dds; 61 } 62 63 @Bean 64 public ServletRegistrationBean druidServletRegistrationBean(EnvConfig env) { 65 String username = env.getStringValue(DSConfig.DRUID_MONITOR_USERNAME); 66 String password = env.getStringValue(DSConfig.DRUID_MONITOR_PASSWORD); 67 return new ServletRegistrationBean(new DruidStatViewServlet(username, password), 68 DSConfig.DRUID_MONITOR_URL); 69 } 70 71 @Bean 72 public FilterRegistrationBean druidFilterRegistrationBean() { 73 WebStatFilter wsf = new WebStatFilter(); 74 FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); 75 filterRegistrationBean.setFilter(wsf); 76 filterRegistrationBean.setUrlPatterns(Arrays.asList(DSConfig.DRUID_FILTER_URL)); 77 filterRegistrationBean.setInitParameters( 78 Collections.singletonMap("exclusions", DSConfig.DRUID_FILTER_EXCLUSIONS)); 79 return filterRegistrationBean; 80 } 81 82}
根据类路径下有DruidDataSource这个类即有Druid这个jar包和配置文件中spring.datasource.active=druid才开启对Druid连接池的自动配置。
导入的配置文件:
1@Configuration 2@ComponentScan(basePackages = "com.example.common.config.properties") 3public class PropertiesConfig { 4 5}
DruidMasterProperties、DruidSlaveProperties属性文件读取的配置省略。
连接池监控配置类:
1public class DruidStatViewServlet extends StatViewServlet { 2 3 private static final long serialVersionUID = 1L; 4 5 private String username; 6 private String password; 7 8 @Override 9 public String getInitParameter(String name) { 10 if ("loginUsername".equals(name)) { 11 return username; 12 } 13 14 if ("loginPassword".equals(name)) { 15 return password; 16 } 17 18 return super.getInitParameter(name); 19 } 20 21 public DruidStatViewServlet(String username, String password) { 22 super(); 23 this.username = username; 24 this.password = password; 25 } 26 27 public String getUsername() { 28 return username; 29 } 30 31 public String getPassword() { 32 return password; 33 } 34 35}
在META-INF/spring.factories中加入Druid自动配置映射:
1org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2com.example.common.config.ds.DruidAutoConfig
切换数据源
切换数据源注解:
1@Target(ElementType.METHOD) 2@Retention(RetentionPolicy.RUNTIME) 3@Documented 4public @interface DS { 5 String value() default DSConfig.DB_MASTER; 6}
动态数据源类:
1public class DynamicDataSource extends AbstractRoutingDataSource { 2 3 private final Logger logger = LoggerUtils.getLogger(this); 4 5 @Override 6 protected Object determineCurrentLookupKey() { 7 logger.debug("当前数据源为{}", DataSourceContextHolder.getDS()); 8 return DataSourceContextHolder.getDS(); 9 } 10 11}
动态数据源AOP实现类:
1@Aspect 2@Component 3public class DynamicDataSourceAspect { 4 5 @Before("@annotation(DS)") 6 public void beforeSwitchDS(JoinPoint point) { 7 Class<?> className = point.getTarget().getClass(); 8 String methodName = point.getSignature().getName(); 9 Class<?>[] argClass = ((MethodSignature) point.getSignature()).getParameterTypes(); 10 String dataSource = DataSourceContextHolder.DEFAULT_DS; 11 12 try { 13 Method method = className.getMethod(methodName, argClass); 14 if (method.isAnnotationPresent(DS.class)) { 15 DS annotation = method.getAnnotation(DS.class); 16 dataSource = annotation.value(); 17 } 18 } catch (Exception e) { 19 e.printStackTrace(); 20 } 21 DataSourceContextHolder.setDS(dataSource); 22 } 23 24 @After("@annotation(DS)") 25 public void afterSwitchDS(JoinPoint point) { 26 DataSourceContextHolder.clearDS(); 27 } 28 29}
绑定当前线程数据源类:
1public class DataSourceContextHolder { 2 3 public static final String DEFAULT_DS = DSConfig.DB_MASTER; 4 5 private static final ThreadLocal<String> DS_HOLDER = new ThreadLocal<>(); 6 7 public static void setDS(String dbType) { 8 DS_HOLDER.set(dbType); 9 } 10 11 public static String getDS() { 12 return (DS_HOLDER.get()); 13 } 14 15 public static void clearDS() { 16 DS_HOLDER.remove(); 17 } 18}
扫描关注我们的微信公众号,干货每天更新。