2012-02-08 85 views
5

我有一个应用程序使用Spring和Jersey使用Hibernate/JPA。在我的应用程序环境中,我设置了数据源,定义了一个实体管理器工厂,使用该实体管理器工厂设置了事务管理器,并且使用事务注释标注了各种服务方法,所以我还有tx:annotation驱动的定义在我的交易经理需要的地方。这个设置很好,我已经能够读写。我想迁移到一个数据库设置,我拥有一个拥有多个从服务器(MySQL)的主服务器。所以我希望所有使用事务注释的方法使用指向主数据库服务器的数据源,以及所有其他使用连接池的从服务器。Spring JPA Read写入拆分 - 事务使用写入数据源

我试着创建两个不同的数据源,有两个不同的实体管理器工厂,和两个不同的持久单元 - 丑陋至少可以说。我尝试了一个MySQL代理,但我们遇到了更多的问题,那么我们需要。连接池已经在servlet容器中处理了。我可以在Tomcat中实现一些读取事务并将其指向正确的数据库服务器的东西,或者是否有办法让所有使用事务注释注释的方法使用特定的数据源?

回答

7

这是我最终做的,它工作得很好。实体管理器只能有一个bean用作数据源。所以我必须做的是在必要时创建一个在两者之间路由的bean。那一个是我用于JPA实体经理的那个。

我在tomcat中设置了两个不同的数据源。在server.xml中创建了两个资源(数据源)。

<Resource name="readConnection" auth="Container" type="javax.sql.DataSource" 
      username="readuser" password="readpass" 
      url="jdbc:mysql://readipaddress:3306/readdbname" 
      driverClassName="com.mysql.jdbc.Driver" 
      initialSize="5" maxWait="5000" 
      maxActive="120" maxIdle="5" 
      validationQuery="select 1" 
      poolPreparedStatements="true" 
      removeAbandoned="true" /> 
<Resource name="writeConnection" auth="Container" type="javax.sql.DataSource" 
      username="writeuser" password="writepass" 
      url="jdbc:mysql://writeipaddress:3306/writedbname" 
      driverClassName="com.mysql.jdbc.Driver" 
      initialSize="5" maxWait="5000" 
      maxActive="120" maxIdle="5" 
      validationQuery="select 1" 
      poolPreparedStatements="true" 
      removeAbandoned="true" /> 

你可以在同一台服务器上的数据库表,在这种情况下,IP地址或域名将是一样的,只是不同的DBS - 你的JIST。

然后,我在tomcat中的context.xml文件中添加了一个资源链接,它将这些资源引用到资源中。

<ResourceLink name="readConnection" global="readConnection" type="javax.sql.DataSource"/> 
<ResourceLink name="writeConnection" global="writeConnection" type="javax.sql.DataSource"/> 

这些资源链接是弹簧在应用程序上下文中读取的内容。

在应用程序上下文中,我为每个资源链接添加了一个bean定义,并添加了一个额外的bean定义,该定义引用了我创建的数据源路由器bean,该bean在先前创建的两个bean(bean定义)的映射(enum)中。

<!-- 
Data sources representing master (write) and slaves (read). 
--> 
<bean id="readDataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> 
    <property name="jndiName" value="readConnection" /> 
    <property name="resourceRef" value="true" /> 
    <property name="lookupOnStartup" value="true" /> 
    <property name="cache" value="true" /> 
    <property name="proxyInterface" value="javax.sql.DataSource" /> 
</bean> 

<bean id="writeDataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> 
    <property name="jndiName" value="writeConnection" /> 
    <property name="resourceRef" value="true" /> 
    <property name="lookupOnStartup" value="true" /> 
    <property name="cache" value="true" /> 
    <property name="proxyInterface" value="javax.sql.DataSource" /> 
</bean> 

<!-- 
Provider of available (master and slave) data sources. 
--> 
<bean id="dataSource" class="com.myapp.dao.DatasourceRouter"> 
    <property name="targetDataSources"> 
     <map key-type="com.myapp.api.util.AvailableDataSources"> 
     <entry key="READ" value-ref="readDataSource"/> 
     <entry key="WRITE" value-ref="writeDataSource"/> 
     </map> 
    </property> 
    <property name="defaultTargetDataSource" ref="writeDataSource"/> 
</bean> 

实体管理器bean定义然后引用dataSource bean。

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> 
    <property name="dataSource" ref="dataSource" /> 
    <property name="persistenceUnitName" value="${jpa.persistenceUnitName}" /> 
    <property name="jpaVendorAdapter"> 
     <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> 
      <property name="databasePlatform" value="${jpa.dialect}"/> 
      <property name="showSql" value="${jpa.showSQL}" /> 
     </bean> 
    </property> 
</bean> 

我在属性文件中定义了一些属性,但是您可以用您自己的特定值替换$ {}值。所以现在我有一个bean使用两个代表我的两个数据源的bean。这个bean是我用于JPA的那个bean。它没有任何路由发生。

所以现在路由bean。

public class DatasourceRouter extends AbstractRoutingDataSource{ 

    @Override 
    public Logger getParentLogger() throws SQLFeatureNotSupportedException{ 
    // TODO Auto-generated method stub 
    return null; 
    } 

    @Override 
    protected Object determineCurrentLookupKey(){ 
    return DatasourceProvider.getDatasource(); 
    } 

} 

重写的方法由实体管理器调用以基本确定数据源。 DatasourceProvider具有一个带有getter和setter方法的线程本地(线程安全)属性,以及用于清理的清除数据源方法。

public class DatasourceProvider{ 
    private static final ThreadLocal<AvailableDataSources> datasourceHolder = new ThreadLocal<AvailableDataSources>(); 

    public static void setDatasource(final AvailableDataSources customerType){ 
    datasourceHolder.set(customerType); 
    } 

    public static AvailableDataSources getDatasource(){ 
    return (AvailableDataSources) datasourceHolder.get(); 
    } 

    public static void clearDatasource(){ 
    datasourceHolder.remove(); 
    } 

} 

我有我用它来处理各种日常JPA电话(getReference,坚持,createNamedQUery & getResultList等)方法的通用DAO实现。在调用entityManager来完成它需要做的事情之前,我将DatasourceProvider的数据源设置为读或写。该方法可以处理传入的值,并使其更具动态性。这是一个示例方法。

@Override 
public List<T> findByNamedQuery(final String queryName, final Map<String, Object> properties, final int... rowStartIdxAndCount) 
{ 
DatasourceProvider.setDatasource(AvailableDataSources.READ); 
final TypedQuery<T> query = entityManager.createNamedQuery(queryName, persistentClass); 
if (!properties.isEmpty()) 
{ 
    bindNamedQueryParameters(query, properties); 
} 
appyRowLimits(query, rowStartIdxAndCount); 

return query.getResultList(); 
} 

的AvailableDataSources与READ或WRITE,它引用适当的数据源的枚举。您可以在应用程序上下文中的bean中定义的映射中看到。

+0

哦,你需要确保MySQL JAR在Tomcat中,否则数据源(资源)将无法工作。 – Elrond 2012-02-21 17:23:37

+0

谢谢!这是个好主意,我会试一试。 – Stony 2015-10-11 09:11:05

+1

以下是使用自定义注释的此方法的一些增强功能:http://fedulov.website/2015/10/14/dynamic-datasource-routing-with-spring/ – 2015-10-16 10:43:06

1

我有同样的需求:使用传统的MASTER/SLAVE来路由只读和唯一数据库之间的连接来缩放读取。

我最终得到了一个精益解决方案,使用Spring的AbstractRoutingDataSource基类。它允许您根据您编写的某些条件注入一个数据源,该数据源可以路由到多个数据源。

<bean id="commentsDataSource" class="com.nextep.proto.spring.ReadWriteDataSourceRouter"> 
    <property name="targetDataSources"> 
     <map key-type="java.lang.String"> 
      <entry key="READ" value="java:comp/env/jdbc/readdb"/> 
      <entry key="WRITE" value="java:comp/env/jdbc/writedb"/> 
     </map> 
    </property> 
    <property name="defaultTargetDataSource" value="java:comp/env/jdbc/readdb"/> 
</bean> 

而且我的路由器只需如下所示:

public class ReadWriteDataSourceRouter extends AbstractRoutingDataSource { 

@Override 
protected Object determineCurrentLookupKey() { 
    return TransactionSynchronizationManager.isCurrentTransactionReadOnly() ? "READ" 
      : "WRITE"; 
} 
} 

我觉得这很优雅,但这里的问题是,春天似乎来设置事务注入数据源后,以只读的,所以它不起作用。我的简单测试是在我的只读方法(它是true)中检查TransactionSynchronizationManager.isCurrentTransactionReadOnly()的结果,以及在同一个调用中它为false的determineCurrentLookupKey()方法中检查结果。

如果您有想法...无论如何,您可以将测试基于除TransactionSynchronizationManager以外的其他任何东西,这将工作正常。

希望这有助于 克里斯托夫

+0

您是否启动并运行?我有同样的问题。 – 2017-10-02 17:24:27

0
<bean id="entityManagerFactory" 
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> 
    <property name="persistenceUnitName" value="filerp-pcflows" /> 
    <property name="dataSource" ref="pooledDS" /> 
    <property name="persistenceXmlLocation" value="classpath:powercenterCPCPersistence.xml" /> 
    <property name="jpaVendorAdapter"> 
     <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> 
      <property name="showSql" value="true" /> 
      <!--<property name="formatSql" value="true" /> 
      --><property name="generateDdl" value="false" /> 
      <property name="database" value="DB2" /> 
     </bean> 
    </property> 
</bean> 

- >

<bean id="pool" autowire-candidate="false" class="org.apache.commons.pool.impl.GenericObjectPool" destroy-method="close"> 
    <property name="minEvictableIdleTimeMillis" value="300000"/> 
    <property name="timeBetweenEvictionRunsMillis" value="60000"/> 
    <property name="maxIdle" value="2"/> 
    <property name="minIdle" value="0"/> 
    <property name="maxActive" value="8"/> 
    <property name="testOnBorrow" value="true"/> 
</bean> 

<bean id="dsConnectionFactory" class="org.apache.commons.dbcp.DataSourceConnectionFactory"> 
    <constructor-arg><ref bean="dataSource" /></constructor-arg> 
</bean> 
<bean id="poolableConnectionFactory" class="org.apache.commons.dbcp.PoolableConnectionFactory"> 
    <constructor-arg index="0"><ref bean="dsConnectionFactory" /></constructor-arg> 
    <constructor-arg index="1"><ref bean="pool" /></constructor-arg> 
    <constructor-arg index="2"><null /></constructor-arg> 
    <constructor-arg index="3"><value>select 1 from ${cnx.db2.database.creator}.TPROFILE</value></constructor-arg> 
    <constructor-arg index="4"><value>false</value></constructor-arg> 
    <constructor-arg index="5"><value>true</value></constructor-arg> 
</bean> 

<bean id="pooledDS" class="org.apache.commons.dbcp.PoolingDataSource" 
    depends-on="poolableConnectionFactory"> 
    <constructor-arg> 
     <ref bean="pool" /> 
    </constructor-arg> 
</bean> 
<import resource="powercenterCPCBeans.xml"/>