복붙노트

[SPRING] Hibernate가 다른 데이터 소스를 읽거나 쓰도록 설정하는 방법은 무엇입니까?

SPRING

Hibernate가 다른 데이터 소스를 읽거나 쓰도록 설정하는 방법은 무엇입니까?

Spring과 Hibernate를 사용하여 하나의 MySQL 마스터 데이터베이스에 쓰고 클라우드 기반 Java 웹 애플리케이션에서 복제 된 하나 이상의 슬레이브를 읽고 싶다.

응용 프로그램 코드에 투명성이있는 솔루션을 찾을 수 없습니다. 다른 DAQ을 변경하여 다른 SessionFactory를 관리하고 싶지는 않습니다. 실제로는 지저분 해 보인 코드와 특정 서버 아키텍처를 결합합니다.

Hibernate에게 자동적으로 CREATE / UPDATE 쿼리를 하나의 데이터 소스로 라우팅하고 다른 데이터 소스로 SELECT하도록하는 방법이 있습니까? 샤딩이나 객체 유형에 기반한 어떤 작업도하고 싶지 않습니다. 다른 데이터 소스에 다른 유형의 쿼리를 전달하기 만하면됩니다.

해결법

  1. ==============================

    1.이에 대한 예는 https://github.com/afedulov/routing-data-source에서 확인할 수 있습니다.

    이에 대한 예는 https://github.com/afedulov/routing-data-source에서 확인할 수 있습니다.

    Spring은 AbstractRoutingDatasource 라 불리는 DataSource의 변형을 제공한다. 표준의 DataSource 구현 대신에 사용할 수있어 실행시에 각 조작에 사용하는 구체적인 DataSource를 결정하는기구를 가능하게합니다. 당신이해야 할 일은 그것을 확장하고 추상 decideCurrentLookupKey 메소드의 구현을 제공하기 만하면됩니다. 이것은 구체적인 데이터 소스를 결정하기 위해 사용자 정의 로직을 구현하는 장소입니다. 반환 된 객체는 조회 키 역할을합니다. 일반적으로 String 또는 Enum이며 Spring 구성의 한정자로 사용됩니다 (자세한 내용은 뒤에서 설명합니다).

    package website.fedulov.routing.RoutingDataSource
    
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    public class RoutingDataSource extends AbstractRoutingDataSource {
        @Override
        protected Object determineCurrentLookupKey() {
            return DbContextHolder.getDbType();
        }
    }
    

    이 DbContextHolder 객체가 무엇인지, 그리고 반환 할 DataSource 식별자를 어떻게 알 수 있습니까? TransactionSManager가 연결을 요청할 때마다 decideCurrentLookupKey 메소드가 호출된다는 점에 유의하십시오. 각 트랜잭션은 별도의 스레드와 "연관"함을 기억하는 것이 중요합니다. 보다 정확하게, TransactionsManager는 Connection을 현재 스레드에 바인드합니다. 따라서 서로 다른 트랜잭션을 다른 대상 데이터 소스로 보내려면 모든 스레드가 어떤 DataSource를 사용할 것인지 확실하게 확인할 수 있어야합니다. 이렇게하면 특정 DataSource를 Thread에 바인딩하고 Transaction에 바인딩하는 데 ThreadLocal 변수를 사용하는 것이 자연 스럽습니다. 이것이 완료된 방법입니다.

    public enum DbType {
       MASTER,
       REPLICA1,
    }
    
    public class DbContextHolder {
    
       private static final ThreadLocal<DbType> contextHolder = new ThreadLocal<DbType>();
    
       public static void setDbType(DbType dbType) {
           if(dbType == null){
               throw new NullPointerException();
           }
          contextHolder.set(dbType);
       }
    
       public static DbType getDbType() {
          return (DbType) contextHolder.get();
       }
    
       public static void clearDbType() {
          contextHolder.remove();
       }
    }
    

    보시다시피 열거 형을 키로 사용할 수 있으며 Spring은 이름을 기반으로 열거 형을 올바르게 처리합니다. 연관된 DataSource 구성 및 키는 다음과 같습니다.

      ....
    <bean id="dataSource" class="website.fedulov.routing.RoutingDataSource">
     <property name="targetDataSources">
       <map key-type="com.sabienzia.routing.DbType">
         <entry key="MASTER" value-ref="dataSourceMaster"/>
         <entry key="REPLICA1" value-ref="dataSourceReplica"/>
       </map>
     </property>
     <property name="defaultTargetDataSource" ref="dataSourceMaster"/>
    </bean>
    
    <bean id="dataSourceMaster" class="org.apache.commons.dbcp.BasicDataSource">
      <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
      <property name="url" value="${db.master.url}"/>
      <property name="username" value="${db.username}"/>
      <property name="password" value="${db.password}"/>
    </bean>
    <bean id="dataSourceReplica" class="org.apache.commons.dbcp.BasicDataSource">
      <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
      <property name="url" value="${db.replica.url}"/>
      <property name="username" value="${db.username}"/>
      <property name="password" value="${db.password}"/>
    </bean>
    

    이 시점에서 다음과 같은 일을 할 수 있습니다.

    @Service
    public class BookService {
    
      private final BookRepository bookRepository;
      private final Mapper               mapper;
    
      @Inject
      public BookService(BookRepository bookRepository, Mapper mapper) {
        this.bookRepository = bookRepository;
        this.mapper = mapper;
      }
    
      @Transactional(readOnly = true)
      public Page<BookDTO> getBooks(Pageable p) {
        DbContextHolder.setDbType(DbType.REPLICA1);   // <----- set ThreadLocal DataSource lookup key
                                                      // all connection from here will go to REPLICA1
        Page<Book> booksPage = callActionRepo.findAll(p);
        List<BookDTO> pContent = CollectionMapper.map(mapper, callActionsPage.getContent(), BookDTO.class);
        DbContextHolder.clearDbType();               // <----- clear ThreadLocal setting
        return new PageImpl<BookDTO>(pContent, p, callActionsPage.getTotalElements());
      }
    
      ...//other methods
    

    이제 우리는 어떤 DataSource가 사용될 것인지를 제어 할 수 있고 요청을 전달할 수 있습니다. 좋은데!

    ... 아니면 그것을합니까? 우선, 마술적인 DbContextHolder에 대한 정적 메서드 호출이 실제로 발생합니다. 그들은 비즈니스 논리에 속하지 않는 것처럼 보입니다. 그리고 그들은 그렇지 않습니다. 그들은 목적을 전달할뿐만 아니라 깨지기 쉽고 오류가 발생하기 쉽습니다 (dbType 정리를 잊어 버리는 방법). 그리고 setDbType과 cleanDbType 사이에 예외가 발생하면 어떻게 될까요? 우리는 그것을 무시할 수 없습니다. 우리는 dbType을 다시 설정해야합니다. 그렇지 않으면 ThreadPool에 반환 된 스레드가 다음 호출에서 복제본에 쓰려고 시도하는 "깨진"상태 일 수 있습니다. 그래서 우리는 이것을 필요로합니다 :

      @Transactional(readOnly = true)
      public Page<BookDTO> getBooks(Pageable p) {
        try{
          DbContextHolder.setDbType(DbType.REPLICA1);   // <----- set ThreadLocal DataSource lookup key
                                                        // all connection from here will go to REPLICA1
          Page<Book> booksPage = callActionRepo.findAll(p);
          List<BookDTO> pContent = CollectionMapper.map(mapper, callActionsPage.getContent(), BookDTO.class);
           DbContextHolder.clearDbType();               // <----- clear ThreadLocal setting
        } catch (Exception e){
          throw new RuntimeException(e);
        } finally {
           DbContextHolder.clearDbType();               // <----- make sure ThreadLocal setting is cleared         
        }
        return new PageImpl<BookDTO>(pContent, p, callActionsPage.getTotalElements());
      }
    

    Yikes> _

    불행히도이 게시물은 이미 사용자 정의 측면의 주제를 다루기에는 너무 길었습니다. 이 링크를 사용하여 측면 사용에 대한 세부 정보를 확인할 수 있습니다.

  2. ==============================

    2.SELECT가 하나의 DB (하나의 슬레이브)로 가고 CREATE / UPDATES가 다른 하나 (마스터)로 가야한다는 결정은 매우 좋은 결정이라고 생각하지 않습니다. 이유는 다음과 같습니다.

    SELECT가 하나의 DB (하나의 슬레이브)로 가고 CREATE / UPDATES가 다른 하나 (마스터)로 가야한다는 결정은 매우 좋은 결정이라고 생각하지 않습니다. 이유는 다음과 같습니다.

    필자는 모든 WRITE 플로우에 대해 마스터 DB를 사용할 것을 권고합니다 (SELECT, UPDATE 또는 INSERT). 그런 다음, 읽기 전용 흐름을 다루는 응용 프로그램은 슬레이브 DB에서 읽을 수 있습니다.

    또한 각각의 메서드가있는 별도의 DAO를 사용하는 것이 좋습니다. 그러면 읽기 전용 흐름과 쓰기 / 업데이트 흐름을 명확하게 구분할 수 있습니다.

  3. ==============================

    3.당신은 2 개의 세션 팩토리를 생성 할 수 있고 2 개의 팩토리 (또는 그것을 사용한다면 2 개의 hibernateTemplates)를 래핑하는 BaseDao를 hava하고, 팩토리와 getOnUpdate 메소드를 다른 메소드와 함께 사용하는 get 메소드를 사용할 수있다.

    당신은 2 개의 세션 팩토리를 생성 할 수 있고 2 개의 팩토리 (또는 그것을 사용한다면 2 개의 hibernateTemplates)를 래핑하는 BaseDao를 hava하고, 팩토리와 getOnUpdate 메소드를 다른 메소드와 함께 사용하는 get 메소드를 사용할 수있다.

  4. ==============================

    4.다음과 같이 시도하십시오 : https://github.com/kwon37xi/replication-datasource

    다음과 같이 시도하십시오 : https://github.com/kwon37xi/replication-datasource

    추가 주석이나 코드없이 구현이 가능하고 매우 쉽습니다. @Transactional 만 필요합니다 (readOnly = true | false).

    나는이 솔루션을 Hibernate (JPA), Spring JDBC Template, iBatis와 함께 사용 해왔다.

  5. ==============================

    5.DDAL을 사용하여 Writing master 데이터베이스를 구현하고 Daos를 수정하지 않고 DefaultDDRDataSource에서 슬레이브 데이터베이스를 읽을 수 있습니다. DDL은 다중 슬레이브 데이터베이스에 대한 로딩 밸런스를 제공합니다. 그것은 봄이나 동면에 의존하지 않습니다. 그것을 사용하는 방법을 보여주는 데모 프로젝트가 있습니다 : https://github.com/hellojavaer/ddal-demos 그리고 데모 1은 여러분이 묘사 한 장면입니다.

    DDAL을 사용하여 Writing master 데이터베이스를 구현하고 Daos를 수정하지 않고 DefaultDDRDataSource에서 슬레이브 데이터베이스를 읽을 수 있습니다. DDL은 다중 슬레이브 데이터베이스에 대한 로딩 밸런스를 제공합니다. 그것은 봄이나 동면에 의존하지 않습니다. 그것을 사용하는 방법을 보여주는 데모 프로젝트가 있습니다 : https://github.com/hellojavaer/ddal-demos 그리고 데모 1은 여러분이 묘사 한 장면입니다.

  6. from https://stackoverflow.com/questions/4386130/how-to-setup-hibernate-to-read-write-to-different-datasources by cc-by-sa and MIT license