spring boot 配置多数据源
记录一下spring boot集成mybatis plus,配置 alibaba druid 多数据源的步骤。
1. 引用mybatis plus,aop,mysql依赖
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis.plus.version}</version> </dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.driver.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 生成entity,mapper (略过)
3. 配置mapperlocation,application.yml 配置druid多数据源
server: port: 18801 mybatis-plus: mapper-locations: classpath:mapper/**.xml global-config: db-config: id-type: auto field-strategy: not_empty column-underline: true logic-delete-value: 0 logic-not-delete-value: 1 db-type: mysql refresh: false configuration: map-underscore-to-camel-case: true cache-enabled: false druid: type: com.alibaba.druid.pool.DruidDataSource master: url: jdbc:mysql://数据库ip:3306/数据库名称?useUnicode=true&characterEncoding=UTF-8&useSSL=false driver-class-name: com.mysql.jdbc.Driver username: 数据库账号 password: 数据库密码 slave: url: jdbc:mysql://数据库ip:3306/数据库名称?useUnicode=true&characterEncoding=UTF-8&useSSL=false driver-class-name: com.mysql.jdbc.Driver username: 数据库账号 password: 数据库密码
4.编写database.config
4.1注入masterDatasource bean和slaveDatasource Bean
package com.ming.project.config.database; import com.alibaba.druid.support.http.StatViewServlet; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.session.SqlSessionFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.sql.DataSource; import java.sql.SQLException; @Slf4j @Configuration @EnableTransactionManagement public class DataSourceConfiguration { @Value("${druid.type}") private Class<? extends DataSource> dataSourceType; @Bean(name = "masterDataSource") @Primary @ConfigurationProperties(prefix = "druid.master") public DataSource masterDataSource() throws SQLException { DataSource masterDataSource = DataSourceBuilder.create().type(dataSourceType).build(); log.info("------------------master datasource-------------"); return masterDataSource; } @Bean(name = "slaveDataSource") @ConfigurationProperties(prefix = "druid.slave") public DataSource slaveDataSource() throws SQLException { DataSource salveDataSource = DataSourceBuilder.create().type(dataSourceType).build(); log.info("------------------slave datasource-------------"); return salveDataSource; } @Bean public ServletRegistrationBean druidServlet() { ServletRegistrationBean reg = new ServletRegistrationBean(); reg.setServlet(new StatViewServlet()); reg.addUrlMappings("/druid/*"); reg.addInitParameter("allow","localhost"); reg.addInitParameter("deny","/deny"); return reg; } }
4.2 masterDataSource bean、slaveDataSource bean 和 sqlsessionfactory 产生关联,通过AbstractRoutingDataSource,编写实现类集成AbstractRoutingDataSource重写determineCurrentLookupKey方法,返回DatasourceContextHolder持有的数据源
package com.ming.project.config.database; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import javax.annotation.Resource; import javax.sql.DataSource; import java.util.HashMap; @Configuration @AutoConfigureAfter(DataSourceConfiguration.class) public class MybatisPlusConfig { @Resource(name = "masterDataSource") private DataSource masterDataSource; @Resource(name = "slaveDataSource") private DataSource slaveDataSource; @Bean(name = "sqlSessionFactory") public SqlSessionFactory sqlSessionFactory () throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(roudRobbinDataSourceProxy()); return sqlSessionFactoryBean.getObject(); } public AbstractRoutingDataSource roudRobbinDataSourceProxy() { ReadWriteRoutingDataSource proxy = new ReadWriteRoutingDataSource(); HashMap<Object, Object> map = new HashMap<>(); map.put(DatabaseContextHolder.DataBaseType.MASTER,masterDataSource); map.put(DatabaseContextHolder.DataBaseType.SLAVE,slaveDataSource); proxy.setTargetDataSources(map); proxy.setDefaultTargetDataSource(masterDataSource); return proxy; } }
package com.ming.project.config.database; import org.springframework.context.annotation.Bean; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import org.springframework.stereotype.Component; public class ReadWriteRoutingDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DatabaseContextHolder.getDataBaseType(); } }
4.3 DatasourceContextHolder封装threadlocal,管理数据源类型
package com.ming.project.config.database; public class DatabaseContextHolder { public enum DataBaseType { MASTER, SLAVE; } private static final ThreadLocal<DataBaseType> contextHolder = new ThreadLocal<>(); public static void setDataBaseType(DataBaseType dataBaseType) { contextHolder.set(dataBaseType); } public static DataBaseType getDataBaseType() { return contextHolder.get() == null ? DataBaseType.MASTER : contextHolder.get(); } public static void clear() { contextHolder.remove(); } }
4.4 编写aspect,对annotation进行拦截
package com.ming.project.config.database; 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) public @interface ReadOnlyConnection { }
package com.ming.project.config.database; import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; @Aspect @Component public class ReadOnlyConnectionInceptor implements Ordered { @Around("@annotation(readOnlyConnection)") public Object proceed(ProceedingJoinPoint proceedingJoinPoint, ReadOnlyConnection readOnlyConnection) throws Throwable { DatabaseContextHolder.setDataBaseType(DatabaseContextHolder.DataBaseType.SLAVE); Object result = proceedingJoinPoint.proceed(); return result; } @Override public int getOrder() { return 0; } }
5. 对使用只读数据源的service方法增加注解(略过)
总结:多数据源的套路是先在yml中配置多个连接源,接着在配置类中,把多个数据源datasource注入到容器中。mybatis plus通过SqlsessionFactory管理数据源连接数据库,所以要把数据源和SqlSessionFactory关联起来,SqlSessionFactory可以设置AbstractRoutingDataSource类型的数据源,AbstractRoutingDataSource可以把多个数据源放入其中,通过一个map结构,然后通过determineCurrentLookupKey方法返回map的key,找到具体要是用的数据源。所以自己要写一个类ReadWriteRoutingDataSource实现AbstractRoutingDataSource,重写determineCurrentLookupKey方法,至此,多数据源的配置就完成了。但是要使用,怎么使用呢,方法是自定义注解,通过aop的方式拦截有这个注解的方法,在方法执行前设置一下数据源。为了让多个线程使用的数据源隔离,用到了ThreadLocal来管理数据源类型,这里写了一个DatabaseContextHolder做了封装。 好了,现在从配置多数据源到使用都已经完成了。
有需要demo源码的童鞋可以点击这里下载:demo源码