sharding-datasource之JPA基于MultiTenant动态切换数据源

原创
2022/03/10 22:22
阅读数 106

介绍

基于sharding-datasource, jpa进行分库操作, 基于AbstractDataSourceBasedMultiTenantConnectionProviderImpl, CurrentTenantIdentifierResolver动态切换数据源

SaaS服务多租户介绍,每个租户的资源都是独立的,从入口到应用部署到数据,都是完全隔离的。每个租户相当于“托管”在提供服务的公司里面,公司做的其实是统一运维。好处在于,这个隔离非常彻底,基本不太会有相互影响;如果有特殊客户要求的定制化,也没啥处理难度。缺陷在于,很容易因为隔离和定制,导致版本不统一,资源也浪费比较大,因为是“代运维”的模式,因此服务成本也比较高。这个时候通常采用的数据库方式是分库

1. maven项目依赖

<dependencies>
	<dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
</dependencies>

2.application.yml配置

spring:
  application:
    name: jdbc-db-jpa-tenant
  profiles:
    include: jdbc
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: none
      naming:
        implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
        physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
    database-platform: org.hibernate.dialect.MySQL8Dialect
    properties:
      hibernate.enable_lazy_load_no_trans: true
      hibernate.format_sql: true
      hibernate.show_sql: true

logging:
  file:
    name: logs/${spring.application.name}.log
  level:
    org.springframework: info
    com.lance.sharding.tenant: debug

3.application-jdbc.yml配置

com:
  multi:
    db:
      bbs_1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/bbs_1?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
        password: li123456
        username: root
      bbs_2:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/bbs_2?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
        password: li123456
        username: root
      bbs_3:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/bbs_3?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
        password: li123456
        username: root

4.测试Sql脚本

CREATE TABLE `t_order`
(
    `order_id`    bigint NOT NULL AUTO_INCREMENT,
    `user_id`     int    NOT NULL,
    `address_id`  bigint NOT NULL,
    `status`      tinyint NULL DEFAULT NULL,
    `creator`     varchar(32) NULL DEFAULT NULL,
    `create_time` datetime NULL DEFAULT NULL,
    `updater`     varchar(32) NULL DEFAULT NULL,
    `update_time` datetime NULL DEFAULT NULL,
    PRIMARY KEY (`order_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

5.配置多数据源

public class DbMultiTenantConnectionProviderImpl extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl {
  private final Map<String, DataSource> dataSourceMap;

  @Override
  protected DataSource selectAnyDataSource() {
    return this.dataSourceMap.values().iterator().next();
  }

  @Override
  protected DataSource selectDataSource(String tenantIdentifier) {
    return this.dataSourceMap.get(tenantIdentifier);
  }
}

public class CustomCurrentTenantIdentifierResolver implements CurrentTenantIdentifierResolver {
  private final static String DEFAULT_TENANT_ID = "bbs_1";

  @Override
  public String resolveCurrentTenantIdentifier() {
    String currentTenantId = DbContextHolder.get();
    return StringUtils.hasText(currentTenantId) ? currentTenantId : DEFAULT_TENANT_ID;
  }

  @Override
  public boolean validateExistingCurrentSessions() {
    return false;
  }
}

@Configuration
@AllArgsConstructor
@EnableConfigurationProperties(CustomDbProperties.class)
@EnableJpaRepositories(basePackages = {"com.lance.sharding.tenant.repository"}, transactionManagerRef = "txManager")
public class MultiDbConfig {
  private final CustomDbProperties customDbProperties;
  private final JpaProperties jpaProperties;

  @Bean
  public MultiTenantConnectionProvider multiTenantConnectionProvider() {
    Map<String, DataSource> map = new HashMap<>(8);
    customDbProperties.getDb().forEach((k, cfg) -> map.put(k, new HikariDataSource(cfg)));
    return new DbMultiTenantConnectionProviderImpl(map);
  }

  @Bean
  public CurrentTenantIdentifierResolver currentTenantIdentifierResolver() {
    return new CustomCurrentTenantIdentifierResolver();
  }

  @Bean
  public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(
      MultiTenantConnectionProvider multiTenantConnectionProvider, CurrentTenantIdentifierResolver currentTenantIdentifierResolver) {

    Map<String, Object> hibernateProps = new LinkedHashMap<>(jpaProperties.getProperties());
    hibernateProps.put(Environment.MULTI_TENANT, MultiTenancyStrategy.DATABASE);
    hibernateProps.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProvider);
    hibernateProps.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver);
    hibernateProps.put(Environment.PHYSICAL_NAMING_STRATEGY, CamelCaseToUnderscoresNamingStrategy.class.getName());
    hibernateProps.put(Environment.IMPLICIT_NAMING_STRATEGY, SpringImplicitNamingStrategy.class.getName());

    // No dataSource is set to resulting entityManagerFactoryBean
    LocalContainerEntityManagerFactoryBean result = new LocalContainerEntityManagerFactoryBean();
    result.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
    result.setJpaPropertyMap(hibernateProps);
    result.setPackagesToScan("com.lance.sharding.tenant.entity");

    return result;
  }

  @Bean
  public EntityManagerFactory entityManagerFactory(LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
    return entityManagerFactoryBean.getObject();
  }

  @Bean
  public PlatformTransactionManager txManager(EntityManagerFactory entityManagerFactory) {
    JpaTransactionManager transactionManager = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(entityManagerFactory);
    return transactionManager;
  }
}

6.单元测试Test

class OrderRepositoryTests {
	@Autowired
	private OrderRepository orderRepository;

	@Test
	@Disabled
	void save() {
		try {
			//DbContextHolder.set("bbs_1");
			//DbContextHolder.set("bbs_2");
			DbContextHolder.set("bbs_3");
			ThreadLocalRandom random = ThreadLocalRandom.current();

			IntStream.range(0, 20).forEach(i -> {
				OrderEntity order = new OrderEntity();
				order.setOrderId(System.nanoTime() + i);
				order.setAddressId(i);
				order.setUserId(Math.abs(random.nextInt()));
				order.setCreator("user.0" + i);
				order.setCreateTime(new Date());
				order.setUpdater(order.getCreator());
				order.setUpdateTime(order.getCreateTime());
				orderRepository.save(order);
			});
		} finally {
			DbContextHolder.clear();
		}
	}

	@Test
	@Disabled
	void findAll() {
		try {
			//DbContextHolder.set("bbs_1");
			DbContextHolder.set("bbs_2");
			//DbContextHolder.set("bbs_1");
			List<OrderEntity> list = orderRepository.findAll();
			log.info("===>{}", list);
		} finally {
			DbContextHolder.clear();
		}
	}
}

7.项目完整地址

sharding-datasource之JPA基于MultiTenant动态切换数据源 Github 地址

sharding-datasource之JPA基于MultiTenant动态切换数据源 Gitee 地址

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
0 评论
0 收藏
0
分享
返回顶部
顶部