[java] 최대 절전 모드 지연로드 애플리케이션 설계

저는 Spring 프레임 워크와 함께 Hibernate 를 사용하는 경향이 있으며 선언적 트랜잭션 경계 기능 (예 : @Transactional )입니다.

우리 모두가 알고 있듯이, 최대 절전 모드는 가능한 한 비 침습적 이고 투명 하도록 노력 하지만 관계를 사용할 때 조금 더 도전적lazy-loaded 입니다.


투명도 수준이 다른 여러 디자인 대안이 있습니다.

  1. 지연로드되지 않는 관계 만들기 (예 : fetchType=FetchType.EAGER)
    • 이것은 지연 로딩의 전체 아이디어를 위반합니다.
  2. 다음을 사용하여 컬렉션 초기화 Hibernate.initialize(proxyObj);
    • 이것은 DAO에 상대적으로 높은 결합을 의미합니다.
    • 를 사용하여 인터페이스를 정의 할 수 있지만 initialize다른 구현에서는 동등한 기능을 제공하지 않을 수도 있습니다.
  3. 영구 Model객체 자체에 트랜잭션 동작 추가 ( 동적 프록시 또는 사용 @Transactional)
    • @Transactional이 영구 개체 자체에서 작업하는 것 같지는 않았지만 동적 프록시 접근 방식을 시도하지 않았습니다. 아마도 최대 절전 모드로 인해 프록시에서 작동합니다.
    • 거래가 실제로 발생할 때 통제력 상실
  4. 모두 게으른 / 비 지연 API를 제공, 예를 들어, loadData()loadDataWithDeps()
    • 응용 프로그램이 어떤 루틴을 사용할지 알도록합니다.
    • 메서드 오버플로 loadDataWithA(),, ….,loadDataWithX()
  5. 예를 들어 byId()오퍼레이션
    만 제공하여 종속성에 대한 강제 조회

    • 비 객체 지향 루틴, 예를 들어, 많이 필요 findZzzById(zid)하고 getYyyIds(zid)대신z.getY()
    • 트랜잭션간에 처리 오버 헤드가 큰 경우 컬렉션의 각 개체를 하나씩 가져 오는 것이 유용 할 수 있습니다.
  6. DAO 대신 @Transactional 응용 프로그램의 일부를 만듭니다.
    • 중첩 된 트랜잭션의 가능한 고려 사항
    • 트랜잭션 관리에 적합한 루틴이 필요합니다 (예 : 충분히 작음)
    • 프로그래밍 방식의 영향은 적지 만 대규모 트랜잭션이 발생할 수 있음
  7. DAO에 동적 가져 오기 프로필을 제공합니다 . 예 :loadData(id, fetchProfile);
    • 응용 프로그램은 언제 사용할 프로필을 알아야합니다.
  8. AoP 유형의 트랜잭션 (예 : 작업을 가로 채고 필요한 경우 트랜잭션 수행)
    • 바이트 코드 조작 또는 프록시 사용 필요
    • 거래가 수행 될 때 통제력 상실
    • 언제나처럼 흑 마법 🙂

내가 놓친 옵션이 있습니까?


lazy-loaded애플리케이션 설계에서 관계 의 영향을 최소화하려고 할 때 선호하는 접근 방식은 무엇입니까?

(오, WoT 죄송합니다 )



답변

우리 모두가 알고 있듯이, 최대 절전 모드는 가능한 한 비 침습적이고 투명하도록 노력합니다.

나는 초기 가정이 틀렸다고 말할 것입니다. 애플리케이션은 항상 엔티티 수명주기와로드되는 개체 그래프의 크기를 관리해야하기 때문에 투명 지속성은 신화입니다.

Hibernate는 생각을 읽을 수 없으므로 특정 작업에 대한 특정 종속성 세트가 필요하다는 것을 알고 있다면 어떻게 든 Hibernate에 대한 의도를 표현해야합니다.

이러한 관점에서 이러한 의도를 명시 적으로 표현하는 솔루션 (즉, 2, 4 및 7)은 합리적으로 보이며 투명성이 부족하지 않습니다.


답변

어떤 문제 (게으름으로 인한)를 암시하고 있는지 잘 모르겠지만, 저에게 가장 큰 고통은 내 애플리케이션 캐시에서 세션 컨텍스트를 잃지 않는 것입니다. 일반적인 경우 :

  • 객체 foo가로드되고지도에 배치됩니다.
  • 다른 스레드는지도에서이 객체를 가져와 호출합니다 foo.getBar()(이전에 호출 된 적이없고 지연 평가 된 것).
  • 팔!

따라서이를 해결하기 위해 다음과 같은 여러 규칙이 있습니다.

  • 세션을 가능한 한 투명하게 래핑합니다 (예 : OpenSessionInViewFilter웹앱의 경우).
  • 스레드 / 스레드 풀에 대한 공통 API를 가지고 있으며, 여기서 db 세션 바인딩 / 바인딩 해제가 계층 구조의 상위 어딘가에서 수행 try/finally되므로 하위 클래스가 이에 대해 생각할 필요가 없습니다.
  • 스레드간에 개체를 전달할 때 개체 자체 대신 ID를 전달합니다. 수신 스레드는 필요한 경우 객체를로드 할 수 있습니다.
  • 개체를 캐시 할 때 개체를 캐시하지 않고 해당 ID 만 캐시합니다. DAO 또는 관리자 클래스에 추상 메서드를 사용하여 ID를 알고있을 때 2 단계 Hibernate 캐시에서 개체를로드합니다. 2 단계 Hibernate 캐시에서 객체를 검색하는 비용은 여전히 ​​DB로가는 것보다 훨씬 저렴합니다.

보시다시피 이것은 실제로 비 침습적이며 투명하지 않습니다 . 그러나 그 비용은 내가 eager loading에 지불해야하는 가격과 비교할 때 여전히 견딜 수 있습니다. 후자의 문제는 엔티티 컬렉션은 말할 것도없고 단일 참조 객체를로드 할 때 가끔 나비 효과로 이어진다는 것입니다. 메모리 소비, CPU 사용량 및 지연 시간도 훨씬 더 나쁘기 때문에 함께 살 수 있다고 생각합니다.


답변

매우 일반적인 패턴은 웹 애플리케이션을 빌드하는 경우 OpenEntityManagerInViewFilter 를 사용 하는 것입니다.

서비스를 빌드하는 경우 메서드가 여러 엔터티를 가져 오거나 업데이트해야하는 경우가 많으므로 DAO가 아닌 서비스의 공용 메서드에서 TX를 엽니 다.

이렇게하면 “Lazy Load 예외”가 해결됩니다. 성능 조정을 위해 더 고급이 필요한 경우 프로필 가져 오기가 좋은 방법이라고 생각합니다.


답변