JPA OpenSessionInViewFilter 적용하지 않고 구현하기

현재 Spring Data JPA(Hibernate4.2사용)를 쓰고 있지만, OpenSessionInViewFilter를 적용해서 Transaction이 끝나더래도 해당 쓰래드가 끝날때까지 Hibernate Session을 유지하여 Lazy로딩을 처리 하고 있습니다.

Lazy로딩을 사용하기 위한 간편한 전략이지만 몇몇 프로젝트가 고도화 되면서 항상 여러가지 문제가 발생하기 시작 하여 OSIV를 쓰지 않고 처리하는 방법을 고도화 하면 어떨까 하면 정리글을 남김니다.

우선 불편한점.

1. flush

Spring Data JPA를 쓰게 되면 repository.save(); 명령을 자주 쓰게 되는데 @Transaction안에 있어도 실제 Transaction으 끝날때 처리되는것이 아니라 쓰래드가 처리될때까지 flush를 하지 않는 문제가 있습니다. 물론 saveAndFlush를 해줘도 문제는 없지만 대부분의 경우 flush를 안해도 큰문제가 없지만, 간간히 flush를 해줘야 좀더 좋은 결과가 나옴으로 항상 유념해야 됩니다.

2. 쓰래드 안에서 다중 트랜잭션 영속성 Context 꼬임

간혹 한쓰래드에서 한번 이상 트랜잭션 처리를 해야할경우 트랜잭션마다 별도의 PersistenceContext를 가지고 있는데, 첫번째 트랜잭션에서 생성한 객체를 두번째 트랜잭션에서도 재사용이 가능 해짐으로 데이터 꼬임이 발생할수 있습니다.  자세히 적자하니 손꾸락이 아프네요……

결론적으로 개발자는 Transaction 밖에서도 Hibernate Session을 머리속에 담아두고 코딩을 해야합니다. 그래서 불편합니다.

해결책

  1. fetch mode EAGER
  2. Service에서  input, output은 Hibernate와 관계없는 객체 처리

두가지 입니다.

https://github.com/sonegy/donotuseosvf/blob/master/src/main/java/sonegy/sample/model/Article.java#L23

 @ManyToOne(optional = false)
 private Board board;

@ManyToOne은 fetch mode가 EAGER입니다. Article 객체를 얻어올때 select from inner join으로 가져옵니다.

Board입장에서는 Article이 @OneToMany로 설정될 수 있습니다. 그렇게 되면 LAZY모드라 EAGER로 고쳐봤자 성능 저하를 불로 옴으로 Article에서 꺼내쓰도록 합시다. @OneToMany금지!

https://github.com/sonegy/donotuseosvf/blob/master/src/main/java/sonegy/sample/model/Article.java#L29

 @ManyToOne(fetch = FetchType.LAZY)
 private Member createdBy;

Article에서 회원정보를 null을 받을수도 있습니다. 이때 EAGER로 설정되어 있다면 optional true라서 SQL query가 left outer join이 발생하게 됩니다.  DBA가 보면 한마디 하고 프로젝트 전체에서 left outer join을 지우라는 정책이 떨어질 수도 있습니다.  그래서 전 이런것들은 LAZY로 처리합니다. 물론 LazyInitializationException 이 발생할 수 있으니 처리해줘야 합니다.

https://github.com/sonegy/donotuseosvf/blob/master/src/main/java/sonegy/sample/service/ArticleService.java#L31

 @Transactional(readOnly = true)
 public ArticleResult findResult(Long id) {
     return ArticleResult.of(articleRepository.findOne(id));
 }

@Service의 메소드가 결과 값을 반환할때는 무조건 Entity객체를 변환 시킵니다. http://modelmapper.org/ 를 사용할 수도 있지만, 복잡하니 단순 변환 시킵니다.

 static ArticleResult of(Article article) {
 ArticleResult result = new ArticleResult();
 result.id = article.getId();
 result.board = BoardResult.of(article.getBoard());
 result.title = article.getTitle();
 result.createdBy = MemberResult.of(article.getCreatedBy());
 result.createdDate = article.getCreatedDate();
 result.modifiedBy = MemberResult.of(article.getModifiedBy());
 result.modifiedDate = article.getModifiedDate();
 return result;
 }

그리고 argument 값들도 Hibernate Entity객체를 쓰지않고 개발하는게 영속성문제를 고민하지 않고 개발하는 지름길같습니다. service안에서만 자유롭게 Entity객체를 사용하면 좀더 쉽게 JPA를 사용할수 있게 됩니다.

끝으로 LazyInitializationException test case를 추가합니다.

https://github.com/sonegy/donotuseosvf/blob/master/src/test/java/sonegy/sample/CoreConfigTest.java#L72

댓글 남기기