Spring 引导:使用 dblink 跨多个数据库对同一实体建模

Spring Boot: modeling same entity across multiple databases with dblink

我有一个数据库 MAIN_DB,它有到其他数据库的 dblink:EMP_DB1EMP_DB2EMP_DB3EMP_DBx。为简单起见,我们假设另外两个数据库。

EMP_DB1EMP_DB2 都有 table EMPLOYEE 和相同的 table 结构:

create table employee(
id number primary key,
first_name varchar2(40),
last_name varchar2(40));

MAIN_DB 可以通过 dblink:

查询 EMP_DB1EMP_DB2
select * from employee@emp_db1;
1  John  Smith

select * from employee@emp_db2;
2  Jane  Doe

我想让我的 RestController 监听 /employee/{id} 并根据 id.

查询相应的 EMP_DBx

当前工作(不理想)示例:

com.apitest.repository.PersonRepository

package com.apitest.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.NoRepositoryBean;

@NoRepositoryBean
interface PersonRepository<T> extends JpaRepository<T, Long> {
}

com.apitest.repository.EmployeeRepository

package com.apitest.repository;

import com.apitest.model.Employee;
import org.springframework.stereotype.Repository;

@Repository
public interface EmployeeRepository extends PersonRepository<Employee> {
    Employee findById(Long id);
}

com.apitest.repository.Employee2Repository

package com.apitest.repository;

import com.apitest.model.Employee2;
import org.springframework.stereotype.Repository;

@Repository
public interface Employee2Repository extends PersonRepository<Employee2> {
    Employee2 findById(Long id);
}

com.apitest.model.Person

package com.apitest.model;

...

@MappedSuperclass
public abstract class Person {
    private long id;
    private String firstName;
    private String lastName;

    @Id
    @Column(name = "ID")
    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    @Basic
    @Column(name = "FIRST_NAME")
    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    @Basic
    @Column(name = "LAST_NAME")
    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String toString() {
        return "id: " + id + ", " +
                "first_name: " + firstName + ", " +
                "last_name: " + lastName;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person employee = (Person) o;
        return id == employee.id &&
                Objects.equals(firstName, employee.firstName) &&
                Objects.equals(lastName, employee.lastName);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, firstName, lastName);
    }
}

com.apitest.model.Employee

package com.apitest.model;

import javax.persistence.Entity;

@Entity(name = "EMPLOYEE@EMP_DB1")
public class Employee extends Person {

}

com.apitest.model.Employee2

package com.apitest.model;

import javax.persistence.Entity;

@Entity(name = "EMPLOYEE@EMP_DB2")
public class Employee2 extends Person {

}

com.apitest.controller.EmployeeController

package com.apitest.controller;

...

@RestController
@RequestMapping("/employee")
public class EmployeeController {
    private final EmployeeRepository employeeRepository;
    private final Employee2Repository employee2Repository;

    @Autowired
    public EmployeeController(EmployeeRepository employeeRepository, Employee2Repository employee2Repository) {
        this.employeeRepository = employeeRepository;
        this.employee2Repository = employee2Repository;
    }

    // logic will be different, this is just an example
    @RequestMapping(path = "/{id}", method = RequestMethod.GET)
    public ResponseEntity<?> getByUserid(@PathVariable("id") Long id) {
        if (id % 2 == 1) {
            Employee employee = employeeRepository.findById(id);
            return new ResponseEntity<>(ResponseEntity.ok(employee), HttpStatus.OK);
        }
        else {
            Employee2 employee2 = employee2Repository.findById(id);
            return new ResponseEntity<>(ResponseEntity.ok(employee2), HttpStatus.OK);
        }
    }
}

有没有办法参数化 Entity 名称?

@Entity(name = "EMPLOYEE@{db_link_name}")
public class Employee extends Person

如果没有,我如何使用 @Query 查询 dblink?

EmployeeRepository:

public interface EmployeeRepository extends JpaRepository<Employee, Long> {
    @Query("select e from employee@emp_dbx e e.id = ?1")
    Employee findById(Long id);
}

Spring 不喜欢查询字符串中的 @dblink_name。我可以创建同义词来解决这个问题,但我无法参数化同义词名称:

create or replace synonym employee_emp_db1 for employee@emp_db1;
create or replace synonym employee_emp_db2 for employee@emp_db2;

public interface EmployeeRepository extends JpaRepository<Employee, Long> {
    @Query("select e from ?1 e e.id = ?2")
    Employee findById(String dblinkName, Long id);
}

QuerySyntaxException: unexpected token: ? near line 1, column 15 [select e from ?1 e e.id = ?2]

期望的行为:

GET /employee/1
{
    "headers": {},
    "body": {
        "id": 1,
        "firstName": "John",
        "lastName": "Smith"
    },
    "statusCode": "OK",
    "statusCodeValue": 200
}

GET /employee/2
{
    "headers": {},
    "body": {
        "id": 2,
        "firstName": "Jane",
        "lastName": "Doe"
    },
    "statusCode": "OK",
    "statusCodeValue": 200
}

我的问题归结为 Spring 最好的设计是什么,我怎样才能做到这一点?

您应该实施多租户架构。它将是用于您的用例的正确设计模式。 看看这个 link 让你的 spring/spring-boot 应用多租户。 https://fizzylogic.nl/2016/01/24/make-your-spring-boot-application-multi-tenant-aware-in-2-steps/