2012-02-08 30 views
5

Tôi có một ứng dụng sử dụng Hibernate/JPA, với Spring và Jersey. Trong bối cảnh ứng dụng của tôi, tôi thiết lập nguồn dữ liệu, xác định nhà máy quản lý thực thể, thiết lập trình quản lý giao dịch với nhà máy manger thực thể đó và có các phương thức dịch vụ khác nhau được chú thích bằng chú thích giao dịch, vì vậy tôi cũng có định nghĩa tx: annotation-driven trong quản lý giao dịch của tôi khi cần. Thiết lập này hoạt động tốt, tôi đã có thể đọc và viết tốt. Tôi muốn chuyển sang thiết lập DB, nơi tôi có một Master với nhiều nô lệ (MySQL). Vì vậy, tôi muốn tất cả các phương thức được chú thích với giao dịch để sử dụng một nguồn dữ liệu trỏ đến máy chủ db chủ và tất cả các phương thức khác để sử dụng một nhóm kết nối của các nô lệ.Spring JPA Read Write splitting - có giao dịch sử dụng ghi nguồn dữ liệu

Tôi đã thử tạo hai nguồn dữ liệu khác nhau, với hai nhà máy quản lý thực thể khác nhau và hai đơn vị liên tục khác nhau - xấu xí để nói ít nhất. Tôi đã thử một Proxy MySQL nhưng chúng tôi đã có nhiều vấn đề với điều đó thì chúng tôi cần. Kết nối tổng hợp được xử lý trong thùng chứa servlet rồi. Tôi có thể thực hiện một cái gì đó trong Tomcat mà đọc giao dịch và hướng nó đến máy chủ cơ sở dữ liệu đúng không, hoặc có cách nào tôi có thể nhận tất cả các phương thức được chú thích với chú thích giao dịch để sử dụng một nguồn dữ liệu cụ thể không?

Trả lời

7

Đây là những gì tôi đã kết thúc và nó hoạt động khá tốt. Trình quản lý thực thể chỉ có thể có một bean để sử dụng làm nguồn dữ liệu. Vì vậy, những gì tôi phải làm là tạo ra một hạt đậu giữa hai nơi cần thiết. Đó là một trong những ben tôi đã sử dụng cho người quản lý thực thể JPA.

Tôi thiết lập hai nguồn dữ liệu khác nhau trong tomcat. Trong tệp tin server.xml, tôi đã tạo hai tài nguyên (nguồn dữ liệu).

<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" /> 

Bạn có thể có các bảng cơ sở dữ liệu trên cùng một máy chủ, trong trường hợp các địa chỉ ip, lĩnh vực sẽ là như nhau, chỉ khác nhau dbs - bạn sẽ có được jist.

Sau đó tôi đã thêm liên kết tài nguyên vào tệp context.xml trong tomcat tham chiếu các tài nguyên này.

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

Các liên kết tài nguyên này là những gì mùa xuân đọc trong ngữ cảnh ứng dụng. Trong bối cảnh ứng dụng, tôi đã thêm định nghĩa bean cho mỗi liên kết tài nguyên và thêm một định nghĩa bean bổ sung tham chiếu đến một bean Router Datasource mà tôi đã tạo trong bản đồ (enum) của hai bean đã được tạo trước đó (định nghĩa bean).

<!-- 
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> 

Định nghĩa bean của người quản lý thực thể sau đó tham chiếu bean dataSource.

<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> 

Tôi đã xác định một số thuộc tính trong tệp thuộc tính nhưng bạn có thể thay thế giá trị $ {} bằng các giá trị cụ thể của riêng bạn. Vì vậy, bây giờ tôi có một bean sử dụng hai bean khác đại diện cho hai nguồn dữ liệu của tôi. Một trong những đậu là một trong tôi sử dụng cho JPA. Đó là không biết gì về bất kỳ định tuyến xảy ra.

Bây giờ, hạt định tuyến.

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(); 
    } 

} 

Phương thức ghi đè được gọi bởi người quản lý thực thể để xác định nguồn dữ liệu về cơ bản. DatasourceProvider có một thuộc tính cục bộ (thread safe) với phương thức getter và setter cũng như phương thức nguồn dữ liệu rõ ràng để làm sạch.

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(); 
    } 

} 

Tôi có một thực hiện DAO chung với các phương pháp tôi sử dụng để xử lý các cuộc gọi JPA thói quen khác nhau (getReference, kiên trì, createNamedQUery & getResultList, vv). Trước khi nó thực hiện cuộc gọi đến thực thểManager để làm bất cứ điều gì nó cần phải làm tôi thiết lập nguồn dữ liệu DatasourceProvider để đọc hoặc ghi. Phương thức này có thể xử lý giá trị đó cũng được truyền vào để làm cho nó trở nên năng động hơn một chút. Đây là một phương pháp ví dụ.

@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 là một enum với READ hoặc WRITE, tham chiếu nguồn dữ liệu thích hợp. Bạn có thể thấy rằng trong bản đồ được định nghĩa trong bean của tôi trên ngữ cảnh ứng dụng.

+0

Ồ, và bạn cần đảm bảo rằng JAR MySQL nằm trong Tomcat, nếu không nguồn dữ liệu (tài nguyên) sẽ không hoạt động. – Elrond

+0

Cảm ơn! Đó là một ý tưởng hay, tôi sẽ có một thử. – Stony

+1

Dưới đây là một số cải tiến của phương pháp này bằng cách sử dụng chú thích tùy chỉnh: http://fedulov.website/2015/10/14/dynamic-datasource-routing-with-spring/ –

1

Tôi có cùng một loại nhu cầu: định tuyến kết nối giữa cơ sở dữ liệu chỉ đọc và ghi chú bằng cách sử dụng MASTER/SLAVE cổ điển để chia tỷ lệ đọc.

Tôi kết thúc bằng giải pháp gọn gàng, sử dụng lớp cơ sở AbstractRoutingDataSource từ mùa xuân. Nó cho phép bạn tiêm một nguồn dữ liệu mà các tuyến đường đến một số nguồn dữ liệu dựa trên một số điều kiện mà bạn viết.

<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> 

Và router của tôi chỉ đơn giản là trông giống như sau:

public class ReadWriteDataSourceRouter extends AbstractRoutingDataSource { 

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

Tôi tìm thấy điều này khá thanh lịch, nhưng vấn đề ở đây là mùa xuân dường như thiết lập các giao dịch để readonly sau khi tiêm các nguồn dữ liệu, vì vậy nó không hoạt động. Kiểm thử đơn giản của tôi là kiểm tra kết quả của TransactionSynchronizationManager.isCurrentTransactionReadOnly() trong các phương thức chỉ đọc của tôi (nó là đúng), và trong phương thức defineCurrentLookupKey() trong đó nó sai trên cùng một cuộc gọi.

Nếu bạn có ý tưởng ... Dù sao bạn có thể căn cứ vào bất kỳ điều gì khác ngoài TransactionSynchronizationManager và điều này sẽ hoạt động tốt.

Hope this helps, Christophe

+0

bạn đã thiết lập và chạy nó chưa? Tôi có cùng một vấn đề. –

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"/> 

Các vấn đề liên quan