2012-02-08 9 views
5

Spring과 Jersey와 함께 Hibernate/JPA를 사용하는 응용 프로그램이 있습니다. 내 애플리케이션 컨텍스트에서 데이터 소스를 설정하고 엔티티 관리자 팩토리를 정의하고 해당 엔티티 관리자 팩토리로 트랜잭션 관리자를 설정하고 트랜잭션 주석이있는 다양한 서비스 메소드가 있으므로 tx : 주석 중심의 정의를 연결합니다. 필요한 경우 거래 관리자에서 이 설정은 훌륭하게 작동하고, 잘 읽고 쓸 수있었습니다. 여러 개의 슬레이브 (MySQL)가있는 마스터가있는 DB 설치로 이동하고 싶습니다. 따라서 transactional로 주석 처리 된 모든 메소드가 마스터 db 서버를 가리키는 데이터 소스를 사용하고 다른 모든 메소드는 슬레이브의 연결 풀을 사용하도록합니다.Spring JPA 읽기 쓰기 분할 - 트랜잭션 데이터 쓰기 소스 사용

두 개의 서로 다른 데이터 소스, 두 개의 다른 엔티티 관리자 팩터 리 및 두 개의 다른 영구 단위를 만들려고했습니다. 나는 MySQL 프록시를 시도했지만 우리는 그때 우리가 필요로하는 것에 더 많은 문제가있었습니다. 연결 풀링은 서블릿 컨테이너에서 이미 처리됩니다. Tomcat에서 트랜잭션을 읽고이를 올바른 데이터베이스 서버로 전달하는 무언가를 구현할 수 있습니까? 아니면 특정 데이터 소스를 사용하기 위해 트랜잭션 주석으로 주석 된 모든 메소드를 가져올 수있는 방법이 있습니까?

답변

7

내가 끝내었던 것은 이것이다. 그리고 그것은 아주 잘 돌아갔다. 엔티티 관리자는 데이터 소스로 사용할 빈 하나만 가질 수 있습니다. 그래서 내가해야만하는 것은 두 곳 사이에서 필요할 때마다 라우팅되는 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 정의)의 맵 (enum)을 취하는 데이터 소스 라우터 bean을 참조하는 추가 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> 

그러면 엔티티 관리자 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은 JPA에서 사용하는 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(); 
    } 

} 

은 내가 (의 getReference이 지속, createNamedQUery & getResultList 등) 다양한 일상 JPA의 통화를 처리하는 데 사용하는 방법과 일반적인 DAO 구현을 가지고있다. entityManager에 대한 호출을하기 전에 필요한 모든 작업을 수행하기 위해 DatasourceProvider의 데이터 소스를 읽기 또는 쓰기로 설정합니다. 이 메소드는 전달되는 값을 처리하여 좀 더 동적으로 처리 할 수 ​​있습니다. 다음은 예제 메서드입니다.

AvailableDataSources는 적절한 데이터 소스를 참조하는 READ 또는 WRITE가있는 열거 형입니다. 애플리케이션 컨텍스트에서 내 bean에 정의 된 맵에서이를 볼 수있다.

+0

아, MySQL JAR이 Tomcat에 있는지 확인해야합니다. 그렇지 않으면 데이터 소스 (자원)가 작동하지 않습니다. – Elrond

+0

감사! 좋은 생각인데 시험해 볼게. – Stony

+1

다음은 맞춤 주석을 사용하여이 메소드의 일부 향상된 기능입니다. http://fedulov.website/2015/10/14/dynamic-datasource-routing-with-spring/ –

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) 및 동일한 호출에서 false 인 decideCurrentLookupKey() 메서드에서 TransactionSynchronizationManager.isCurrentTransactionReadOnly()의 결과를 확인하는 것입니다.

아이디어가 있다면 ... 어쨌든 TransactionSynchronizationManager가 아닌 다른 테스트를 기반으로 할 수 있으며 정상적으로 작동합니다. 이 도움이

희망, 크리스토프

+0

실행하고 실행 했습니까? 나는 같은 문제가있다. –

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"/> 
,536가

관련 문제