如何实现一个多租户的Spring Boot应用程序(每个用户有自己的数据库)

8
我正在使用Spring Boot构建REST API,并希望实现多租户结构来处理数据。
我想要一个名为Main的数据库,其中包含一个User表,该表将包含有关用户的数据(用户名,密码等),并且具有一个database字段,用于指定该用户被指派的数据库。每当用户注册时,将创建他们相应的数据库(这是我遇到困难的地方之一)。
我已经阅读了不同的教程,它们都在application.properties文件中预定义了Datasource。显然,这里并非如此,因为每个用户的数据库将会动态创建或者访问已经创建的数据库。
工作流程如下(尽可能简单地解释):
1. 用户注册 2. 应用程序创建用户实体并将其保存到Main数据库,然后为用户创建相应的数据库。 3. 应用程序对每个调用进行身份验证,如果通过身份验证,则从用户的数据库中获取数据。
然后有许多问题涉及自动创建数据库时如何填充数据库。但首先是基础问题:)
我的技术栈:POSTGRESQL,Spring Boot。
提前感谢您的帮助。
2个回答

7

按以下步骤可以实现多租户。

  1. 添加2个配置类,一个用于共享数据库,另一个用于租户数据库,这些配置类应配置LocalContainerEntityManagerFactoryBean。该bean应为LocalContainerEntityManagerFactoryBean设置所需的多租户属性,例如:
 Map<String, Object> properties = hibernateProperties.determineHibernateProperties(
        this.properties.getProperties(), new HibernateSettings());


    properties.put(Environment.MULTI_TENANT, MultiTenancyStrategy.SCHEMA);
    properties.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, this.connectionProvider);
    properties.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, this.resolver);
    properties.put(Environment.DIALECT, "org.hibernate.dialect.MySQLDialect");

这个类还应该为每种类型实现一个命名的bean transactionManager,例如:

 @Bean(name = "tenantTransactionManager")
  public PlatformTransactionManager transactionManager() {
    JpaTransactionManager tm = new JpaTransactionManager();
    tm.setEntityManagerFactory(this.entityManagerFactory().getObject());
    return tm;
  }

  1. 实现接口CurrentTenantIdentifierResolver和方法resolveCurrentTenantIdentifier。这应该根据当前已登录的用户返回租户的数据库名称,或者如果没有用户登录,则返回默认数据库名称。

  2. 一个线程安全的上下文持有器,用于记住当前的租户名称。

  3. 使用@Transactional注解为实体类的服务实现进行注释,并传递相应的实体管理器的bean名称,例如:

@Transactional("tenantTransactionManager") // for tenant database

并且

@Transactional("transactionManager") // for shared database.

  1. 当新用户注册时,设置一个数据库模式创建方法,并在共享模式的用户表中维护租户数据库名称作为其中一列。

  2. 如果您使用Spring Security,请实现UserDetailsService接口,并实现loadUserByUsername方法,以便返回TenantUser类对象,该对象包含有关登录用户的附加信息(租户数据库名称)。

public class TenantUser extends org.springframework.security.core.userdetails.User {
 

  /** The tenand id. */
  private String tenantId;

希望这些步骤能帮助您实现所需的目标。有很多文章可以详细解释所有这些步骤。我的实现深度嵌入在我的项目中,因此它不处于可以共享作为工作示例的状态。
欢迎进一步提出问题。

首先,感谢您抽出时间回复。 1)既然我正在使用boot,为什么要使用LocalContainerEntityManagerFactoryBean,而不是使用application.properties文件来处理不同的数据源?(附注:我的方言是Postgres) 2)这很清楚,它只是一个检查用户是否已登录的类,如果是,则给我相应的数据库,否则返回与主数据库对应的字符串。 5)如何使用Spring boot和Postgresql“即时”创建数据库?没有像MySql中的?createIfExists=true这样的选项。 - Renis1235
此外,这显示了很多内容,但当数据库连接更改时的步骤对我来说不太清楚。如果您能进一步解释,我将非常感激。 再次感谢您。@Milind Barve - Renis1235
为了动态创建数据库,我使用了以下步骤:Statement stmt = null; stmt = connection.createStatement(); stmt.executeUpdate("CREATE DATABASE IF NOT EXISTS " + tenantId + " CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;");并且我使用Liquibase进行模式管理。因此,我使用Liquibase方法在新创建的数据库中填充模式(表等)。 - Milind Barve
我会尽快分享我的代码,但在此之前,您可以参考https://tech.asimio.net/2017/01/17/Multitenant-applications-using-Spring-Boot-JPA-Hibernate-and-Postgres.html,这篇文章有可能会回答您的问题。 - Milind Barve
我很感激你想要帮助我, 请保持更新。 与此同时, 我会查看上面的链接。 谢谢 :) - Renis1235
你的想法和我找到的解决方案是一样的,谢谢。 你能给我展示一些好的资源,让我学习如何像使用Liquibase一样填充这些新数据库的模式吗?谢谢。 - Renis1235

4

我在这里找到了解决我的问题的完整方案:

使用Spring Data JPA管理多个数据源

非常感谢作者@Cepr0。

唯一缺少的是动态创建数据库。当我完成实现时,我会在此更新答案。

更新

我使用以下代码创建了数据库,这是@Milind Barve推荐的。所以谢谢你。

 Class.forName("org.postgresql.Driver");
 Connection con = DriverManager.getConnection("jdbc:postgresql://localhost:5432/","postgres", "password");
 Statement smt = con.createStatement();

 smt.executeUpdate("CREATE DATABASE [name_of_db_here] WITH OWNER DEFAULT");

更新: 为每个新创建的数据库初始化模式, 我创建了一个包含所有表创建的.sql文件,并使用FlyWay初始化每个新创建的数据库。
// INITIALIZE THE DB
            Flyway flyway = Flyway.configure()
                    .dataSource(dataSource)
                    .target(MigrationVersion.LATEST)
                    .load();

            flyway.migrate();

你如何使用Flyway进行初始化?你能分享一下代码吗? - KenobiBastila
1
@KenobiBastila 第二段代码片段展示了如何实现该功能。 - Renis1235

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接