【问题标题】:Is it possible to use the same Spring Data Repository to hit Two Different Databases (data sources)?是否可以使用相同的 Spring Data Repository 来访问两个不同的数据库(数据源)?
【发布时间】:2019-12-10 21:59:05
【问题描述】:

我有两个这样的配置文件:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
    basePackages = { "repo" },
    entityManagerFactoryRef = "db1",
    transactionManagerRef = "JpaTxnManager_db1")
public class RepositoryConfigSpringDataDb1 {
}

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
    basePackages = { "repo" },
    entityManagerFactoryRef = "db2",
    transactionManagerRef = "JpaTxnManager_db2")
public class RepositoryConfigSpringDataDb2 {
}

我有一个 dao 类,它有很多方法。现在在 dao 类中,我认为我可以使用事务指定的 @Transactional 注释来命中特定的数据库。

调用 db1 的一些示例方法是:

@Transactional(transactionManager="JpaTxnManager_db1")
public List<EntityOne> getAllEntitiesById(String id) { 
    return entityOneRepo.findById(id);
}

调用 db2 的其他方法可能是:

@Transactional(transactionManager="JpaTxnManager_db2")
public List<EntityOne> getAllEntitiesById(String id) { 
    return entityOneRepo.findById(id);
}

repo 的定义如下:

@org.springframework.stereotype.Repository
public interface EntityOneRepository extends PagingAndSortingRepository<EntityOne, String> {
    // ommitted for brevity 

-- 我已经为这些定义了不同的数据源,但是我定义的第二个数据源没有被命中。

知道我错过了什么吗?

是否可以根据 transactionManagerRef 和 entityManagerFactoryRef 使用扩展 PagingAndSortingRepository 的相同 EntityOneRepository 来访问 2 个不同的数据库?

【问题讨论】:

  • 对不起,我想帮忙:-)

标签: java spring spring-boot spring-data-jpa spring-data


【解决方案1】:

我之前多次遇到过这个问题,并使用 Spring 的 DelegatingDataSource 解决了它,它允许您定义多个 DataSource 对象并通过某种类型的查找键委托给所需的正确目标数据源。 TransactionAwareDataSourceProxy 这可能是您在帖子中显示的代码的一个不错选择的子类,正如 JavaDoc 描述的第一句话所述:

目标 JDBC 数据源的代理,增加 Spring 管理事务的意识。类似于 Java EE 服务器提供的事务性 JNDI 数据源。

我通常总是在任何给定线程中使用相同的目标数据源,所以我倾向于将我的查找键放入 ThreadLocal 对象中,并让代理数据源读取它以在调用 @ 时找到实际的目标数据源987654325@制作完成。

如果您创建这样一个委托(代理)数据源,您的EntityManagerFactory 可以将其用作其底层 JDBC 数据源,并在任何给定时间根据需要委托给正确的目标数据源。

多年来,我一直在 JPA 代码中使用这种类型的方法,我需要使用相同的持久性单元访问多个数据源,这对我来说非常有用。也应该与 Spring JPA 数据存储库一起正常工作。

以下是我之前参与的一个项目的一些实现代码。代码属于我,所以随意复制你喜欢的任何内容,并随心所欲地使用它。

这里是代理DataSource 类,它委托给实际目标 JDBC DataSource。它确实不扩展了 Spring 的 DelegatingDataSource,如上所述,但它做的事情完全相同。如果您不熟悉 OSGI 声明式服务及其注释(我想大多数人不熟悉),@Component(property = {"osgi.jndi.service.name=jdbc/customation"} 就是将DataSource 放入 JNDI 注册表的原因,以便它可以通过持久性单元描述符( persistence.xml) 显示在下面。

package com.custsoft.client.ds;

import com.custsoft.client.ClientXrefHolder;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import static com.custsoft.Constants.CLIENT;

/**
 * Proxy data source that delegates to an actual JDBC data source. 
 * There is one target JDBC data source per client.
 * 
 * Created by eric on 9/29/15.
 */
@Component(property = {"osgi.jndi.service.name=jdbc/customation"},
        service = DataSource.class)
public class ClientDelegatingDataSource implements DataSource {

    private static final Logger logger = LoggerFactory.getLogger(ClientDelegatingDataSource.class);

    private String DEFAULT_CLIENT_XREF = "customation";

    private Map<String, DataSource> clientDataSources = new HashMap<>();

    @Reference(target = "(client=*)",
            cardinality = ReferenceCardinality.MULTIPLE,
            policy = ReferencePolicy.DYNAMIC)
    protected void addDataSource(DataSource dataSource, Map<String, Object> properties) {
        final String clientId = getClientId(properties);
        clientDataSources.put(clientId, dataSource);
    }

    protected void removeDataSource(DataSource dataSource, Map<String, Object> properties) {
        final String clientId = getClientId(properties);
        clientDataSources.remove(clientId);
    }

    private String getClientId(Map<String, Object> properties) {
        return Objects.toString(properties.get(CLIENT), null);
    }

    @Override
    public Connection getConnection() throws SQLException {
        return determineTargetDataSource().getConnection();
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return determineTargetDataSource().getConnection(username, password);
    }

    private DataSource determineTargetDataSource() {

        String clientId = ClientXrefHolder.getClientXref();
        if (clientId == null) {
            clientId = DEFAULT_CLIENT_XREF;
        }

        DataSource dataSource = clientDataSources.get(clientId);
        if (dataSource == null) {
            final String message = String.format(
                    "Couldn't find data source for client \"%s\".", clientId);
            throw new IllegalStateException(message);
        }

        return dataSource;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return determineTargetDataSource().unwrap(iface);
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return determineTargetDataSource().isWrapperFor(iface);
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return determineTargetDataSource().getLogWriter();
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {
        determineTargetDataSource().setLogWriter(out);
    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {
        determineTargetDataSource().setLoginTimeout(seconds);
    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return determineTargetDataSource().getLoginTimeout();
    }

    @Override
    public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return determineTargetDataSource().getParentLogger();
    }

}

这是在ThreadLocal 中保存查找键的类:

package com.custsoft.client;

/**
 * Holds the client ID in the current thread. It is
 * generally placed there by a REST filter that reads
 * it from a "client" HTTP header.
 *
 * Created by eric on 8/25/15.
 */
public class ClientXrefHolder {

    private static final ThreadLocal<String> CLIENT_XREF_HOLDER = new ThreadLocal<>();

    public static String getClientXref() {
        return CLIENT_XREF_HOLDER.get();
    }

    public static void setClientXref(final String clientXref) {
        CLIENT_XREF_HOLDER.set(clientXref);
    }
}

persistence.xml:

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0"
             xmlns="http://java.sun.com/xml/ns/persistence"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
             http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">

    <persistence-unit name="customation" transaction-type="JTA">

        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

        <!-- Only used when transaction-type=JTA -->
        <jta-data-source>osgi:service/javax.sql.DataSource/(osgi.jndi.service.name=jdbc/customation)</jta-data-source>

        <!-- Only used when transaction-type=RESOURCE_LOCAL -->
        <non-jta-data-source>osgi:service/javax.sql.DataSource/(osgi.jndi.service.name=jdbc/customation)</non-jta-data-source>

        <class>com.custsoft.model.AccessToken</class>
        <class>com.custsoft.model.JpaModel</class>
        <class>com.custsoft.model.Role</class>
        <class>com.custsoft.model.stats.Stat</class>
        <class>com.custsoft.model.stats.StatDefinition</class>
        <class>com.custsoft.model.User</class>
        <class>com.custsoft.model.UserProperty</class>

        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
            <property name="hibernate.hbm2ddl.auto" value="validate"/>
        </properties>

    </persistence-unit>

</persistence>

【讨论】:

  • 感谢您的意见。原来我采取的方法对我有用。目前,它很好地解决了这个问题。如果以后情况有变,我会参考您的建议。
  • 很高兴您解决了自己的问题!如果您对我如何实施我在回答中所说的内容有任何其他问题,请告诉我。
  • - Eric - 就您的实施提出一些问题的最佳方式是什么?
  • 我在帖子中添加了一些相关的实现代码。否则问任何你想知道的。如果它会是一个相当长的,我们可以把它聊天。我很乐意尽我所能提供帮助。
  • - 谢谢。您是否有一个 Spring Data 的存储库配置类,它利用了这个?还是这能解决所有问题?
【解决方案2】:

我上面描述的解决方案对我有用。

当你有一个 Repository 类并且你希望它能够访问多个数据源时要指出的主要事情,一种简单的方法是使用 @Transactional注释方法> 注释。这将确保命中将进入相应的数据库。

但是,我认为根据您的需求,Eric Green 的解决方案可能是技术上更合适的方法。

【讨论】:

    猜你喜欢
    • 2012-08-19
    • 2015-07-06
    • 1970-01-01
    • 2017-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多