First
- 项目中经常会遇到使用多个数据源的情况。
- 这里是基于 JPA 来配置多个数据源。
- 使用了 注解 + AOP 的方式实现。
- 如果多个数据源的表结构大不相同,不推荐使用,会产生冗余空表。
- 上面问题也可以通过分包扫描实现
- 上上面问题也可以通过
EntityManager
(doc) 实现(操作类似于JpaRepository
,并且没有实体映射数据库的操作)。
- 上上上面那问题,在分包扫描方式中,对于不同数据库的相同表用注解实现跨数据库访问很方便。
- 基于 MySql 8.x
- alibaba Druid pool
优点
缺点
- 不能简单的跟据参数动态切换数据源,也就是说,启动打那一刻,该方法执行连接的数据源就确定了。
- 如果其它数据源的表在主数据源中没有,则会自动在主数据源中添加。需要另外添加解决方案(扫描包的方式配置数据源)。这是JPA在初始化 Table Bean 的时候,必须要映射到对应数据库中的 Table。
构建
添加依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.16</version> </dependency>
|
配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| spring: datasource: driverClassName: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/maxzhao_ittest?charset=utf8mb4&useSSL=false username: maxzhao password: maxzhao main: allow-bean-definition-overriding: true
jpa: database: MYSQL database-plinatform: org.hibernate.dialect.MySQL5InnoDBDialect show-sql: true generate-ddl: true open-in-view: false
hibernate: ddl-auto: update properties: hibernate: current_session_context_class: org.springframework.orm.hibernate5.SpringSessionContext dialect: org.hibernate.dialect.MySQL5Dialect
gt: maxzhao: boot: multiDatasourceOpen: true datasource[0]: dbName: second driverClassName: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/pos?charset=utf8mb4&useSSL=false username: maxzhao password: maxzhao datasource[1]: dbName: third driverClassName: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/biz?charset=utf8mb4&useSSL=false username: maxzhao password: maxzhao
|
添加注解类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package gt.maxzhao.boot.common.annotation;
import java.lang.annotation.*;
@Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface MultiDataSource { String name() default "main"; }
|
数据源配置映射 yml
配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
|
@Component @ConfigurationProperties(prefix = "spring.datasource") @Setter @Getter @Slf4j public class DruidProperties { public DruidProperties() { log.info("default 数据源加载"); }
private String dbName = "main";
private String url;
private String username;
private String password;
private String driverClassName = "com.mysql.cj.jdbc.Driver";
private Integer initialSize = 10;
private Integer minIdle = 3;
private Integer maxActive = 60;
private Integer maxWait = 60000;
private Boolean removeAbandoned = true;
private Integer removeAbandonedTimeout = 180;
private Integer timeBetweenEvictionRunsMillis = 60000;
private Integer minEvictableIdleTimeMillis = 300000;
private String validationQuery = "SELECT 'x'";
private Boolean testWhileIdle = true;
private Boolean testOnBorrow = false;
private Boolean testOnReturn = false;
private Boolean poolPreparedStatements = true;
private Integer maxPoolPreparedStatementPerConnectionSize = 50;
private String filters = "stat";
public DruidDataSource config() { DruidDataSource dataSource = new DruidDataSource(); return config(dataSource); }
public DruidDataSource config(DruidDataSource dataSource) { dataSource.setDbType(JdbcConstants.MYSQL); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); dataSource.setDriverClassName(driverClassName); dataSource.setInitialSize(initialSize); dataSource.setMinIdle(minIdle); dataSource.setMaxActive(maxActive); dataSource.setMaxWait(maxWait); dataSource.setRemoveAbandoned(removeAbandoned); dataSource.setRemoveAbandonedTimeout(removeAbandonedTimeout);
dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); dataSource.setValidationQuery(validationQuery); dataSource.setTestWhileIdle(testWhileIdle); dataSource.setTestOnBorrow(testOnBorrow); dataSource.setTestOnReturn(testOnReturn); dataSource.setPoolPreparedStatements(poolPreparedStatements); dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize); try { dataSource.setFilters(filters); } catch (SQLException e) { log.error("扩展插件失败.{}", e.getMessage()); } return dataSource; }
}
|
多数据源配置映射 yml
配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
@Configuration @ConfigurationProperties(prefix = "gt.maxzhao.boot") @Getter @Setter @Slf4j public class MultiDataSource { public MultiDataSource() { log.info("加载多数据源配置信息 --> {}", "gt.maxzhao.boot.datasource"); }
private List<DruidProperties> datasource; }
|
多数据源配置类
这里需要配置动态开启多数据源,如果不主动开启,配置了注解也不会生效。
这里也做了一个不必要的处理,如果多数据源中有处理失败或名称填写错误,默认使用主数据源。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
|
@Slf4j @Component public class MultiSourceConfig { @Autowired private DruidProperties druidProperties;
@Autowired private MultiDataSource multiDataSource;
@Bean @ConditionalOnProperty(name = "gt.maxzhao.boot.multiDatasourceOpen", havingValue = "false") public DruidDataSource singleDatasource() { log.error("singleDatasource"); return druidProperties.config(new DruidDataSource()); }
@Bean @ConditionalOnProperty(name = "gt.maxzhao.boot.multiDatasourceOpen", havingValue = "true") public DynamicDataSource mutiDataSource() { log.error("mutiDataSource");
HashMap<Object, Object> dbNameMap = new HashMap<>(); DruidDataSource mainDataSource = druidProperties.config(); dbNameMap.put("main", mainDataSource); if (multiDataSource.getDatasource() != null) { List<DruidDataSource> multiDataSourceList = multiDataSource.getDatasource().stream() .filter(dp -> !"".equals(Optional.ofNullable(dp.getDbName()).orElse(""))) .map(dp -> { DruidDataSource druidDataSource = dp.config(druidProperties.config()); dbNameMap.put(dp.getDbName(), druidDataSource); return druidDataSource; }) .collect(Collectors.toList());
try { mainDataSource.init(); for (DruidDataSource druidDataSource : multiDataSourceList) { druidDataSource.init(); } } catch (SQLException sql) { log.error("======================= 多数据源配置错误 =========================="); sql.printStackTrace(); } } DynamicDataSource dynamicDataSource = new DynamicDataSource(); dynamicDataSource.setTargetDataSources(dbNameMap); dynamicDataSource.setDefaultTargetDataSource(mainDataSource); return dynamicDataSource; }
}
|
DataSource 的 router
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setDataSourceDbName(String dataSourceDbName) { contextHolder.set(dataSourceDbName); }
public static String getDataSourceDbName() { return contextHolder.get(); }
public static void clearDataSourceDbName() { contextHolder.remove(); }
@Override protected Object determineCurrentLookupKey() { return getDataSourceDbName(); }
}
|
AOP配置
切点是自定义注解的包路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
|
@Aspect @Component @ConditionalOnProperty(prefix = "gt.maxzhao.boot", name = "multiDatasourceOpen", havingValue = "true") public class MultiDataSourceAop implements Ordered { private Logger log = LoggerFactory.getLogger(this.getClass());
public MultiDataSourceAop() { log.info("多数据源初始化 AOP "); }
@Pointcut(value = "@annotation(gt.maxzhao.boot.common.annotation.MultiDataSource)") private void cut() { }
@Around("cut()") public Object around(ProceedingJoinPoint point) throws Throwable {
Signature signature = point.getSignature(); MethodSignature methodSignature ; if (!(signature instanceof MethodSignature)) { throw new IllegalArgumentException("该注解只能用于方法"); } methodSignature = (MethodSignature) signature; Object target = point.getTarget(); Method currentMethod = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
MultiDataSource datasource = currentMethod.getAnnotation(MultiDataSource.class); if (datasource != null) { DynamicDataSource.setDataSourceDbName(datasource.name()); log.debug("设置数据源为:" + datasource.name()); } else { DynamicDataSource.setDataSourceDbName("main"); log.debug("设置数据源为:默认 --> main"); } try { return point.proceed(); } finally { log.debug("清空数据源信息!"); DynamicDataSource.clearDataSourceDbName(); } }
@Override public int getOrder() { return 1; } }
|
到这里构建结束
测试
model
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Accessors(chain = true) @Data @Entity @Table(name = "temp", schema = "", catalog = "") public class Temp implements Serializable { private static final long serialVersionUID = -1L;
@Id @Column(name = "ID",unique = true) @ApiModelProperty(value = "主键") private Long id; @Basic @Column(name = "NAME") @ApiModelProperty(value = "地区名称") private String name; }
|
service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @Service @Transactional public class TempServiceDemo {
@Autowired private TempRepository tempRepository;
public List<Temp> findAll() { return tempRepository.findAll(); }
@MultiDataSource(name = "second") public List<Temp> findAllSecond() { return tempRepository.findAll(); }
@MultiDataSource(name = "third") public List<Temp> findAllThird() { return tempRepository.findAll(); } }
|
dao
1 2 3
| @Repository("tempRepository") public interface TempRepository extends JpaRepository<Temp, Long> { }
|
Test
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @RunWith(SpringRunner.class )
@SpringBootTest(classes = BasicApplication.class) @Slf4j public class MultiDataSourceTest { @Resource private TempServiceDemo tempServiceDemo;
@Autowired private MultiDataSource multiDataSource;
@Test public void testMultiDataSource() { System.out.println("\r\n=================\r\n"); System.out.println(tempServiceDemo.findAllSecond()); System.out.println("\r\n=================\r\n"); System.out.println( tempServiceDemo.findAllThird()); System.out.println("\r\n=================\r\n"); } }
|
EntityManager
(doc) 方式
引入:
1 2
| @Autowired private EntityManager entityManager
|
使用:
后续更新。
本文地址: SpringBoot+JPA多数据源(注解方式)
gitee
推荐
IDEA好用的插件
JAVA自定义注解
本文地址: https://github.com/maxzhao-it/blog/post/40131/