是否可以使用相同的 Spring 数据存储库来访问两个不同的数据库(数据源)?



我有两个配置文件,如下所示:

@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 类中,我认为我可以使用与事务指定的@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);
}

存储库的定义如下:

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

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

知道我错过了什么吗?

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

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

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

我通常总是在任何给定线程中使用相同的目标数据源,因此我倾向于将查找键塞入 ThreadLocal 对象中,并在调用 DataSource#getConnection 时让代理数据源读取此内容以查找实际的目标数据源。

如果创建此类委派(代理(数据源,则 EntityManagerFactory 可以将其用作其基础 JDBC 数据源,并在任何给定时间根据需要委派给正确的目标数据源。

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

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

下面是委托给实际目标 JDBCDataSource的代理DataSource类。它没有像上面提到的扩展 Spring 的委托数据源,但它正在做完全相同的事情。如果您不熟悉OSGI声明性服务及其注释(我想大多数人都不熟悉(,@Component(property = {"osgi.jndi.service.name=jdbc/customation"}是将DataSource放入JNDI注册表的原因,以便可以通过下面进一步显示的持久性单元描述符(持久性.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);
}
}

持久性.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>

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

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

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

最新更新