2017-10-20 38 views
0

내가 엔티티 ParentChild 사이 매우 간단 @ManyToOne 양방향 매핑이왜 엔티티 초기화되지 않은 컬렉션은 현재 트랜잭션 이전에 지속 된 엔티티에 대해서만 자동으로 초기화됩니까?

(이 질문을 읽고 제목을 편집 주시기 바랍니다).

Collection<Child> childrenParent에있는 자식 목록은 초기화되지 않으므로 null이어야합니다.

이전에 대한 EntityManager.find(...)를 사용하여 Parent을 지속하고 해당 Parent에서 목록을 얻는 것은 ArrayList의 제공도 거기에는 아이들이 Parent와 아직 없습니다 그것은 괜찮습니다. 지속 또는 아이들의 동일한 트랜잭션 컬렉션에서 새로운 Parent를 병합하면 지속하더라도 null 될 것입니다 그러나 경우

은/ParentEntityManager.find(...) 다시 페치 합병했다.

그래서이 다른 동작을하고 내 환경에서만 발생하는 경우 궁금합니다.

엔티티의 캐싱과 관련이 있다고 가정합니다. 엔티티는 캐시에서 발견되며 db에서 가져 오는 대신 반환되며 빈 컬렉션의 초기화는 db에서 가져온 경우에만 발생합니다. 어쩌면 JPA 구현.

나의 가정은 진실에 가깝고 무엇이 그 이유입니까?

아래의 엔티티 및 테스트 사례. 테스트 환경이 태그에 나열됩니다.

// using lombok 
@Slf4j 
@RunWith(Arquillian.class) 
public class NoPersistTest { 

    @PersistenceContext 
    private EntityManager em; 

    @Deployment 
    public static final WebArchive deploy() { 
     WebArchive wa = ShrinkWrap.create(WebArchive.class, "test.war") 
       .addAsWebInfResource("test-persistence.xml", "persistence.xml").addClasses(Parent.class, Child.class); 
     return wa; 
    } 

    @Test 
    @Transactional 
    public void testWithPreviouslyPersistedParent() { 
     Parent parent = em.find(Parent.class, 1); // has no children in db 
                // before 
     Child child = new Child(); 
     child.setParent(parent); 
     parent.getChildren().add(child); 
     log.info("type of Collection<Child> is {}", parent.getChildren().getClass().getName()); 
     // above logs "type of Collection<Child> is 
     // org.apache.openjpa.util.java$util$ArrayList$proxy" 
    } 

    @Test(expected = NullPointerException.class) 
    @Transactional 
    public void testPersistingParentInSameTransaction() { 
     Parent parent = new Parent(); 
     em.persist(parent); 
     Parent parent2 = em.find(Parent.class, parent.getId()); 
     Child child = new Child(); 
     child.setParent(parent2); 
     log.info("Collection<Child> is {}", parent2.getChildren()); 
     // above logs Collection<Child> is null 
     parent2.getChildren().add(child); 
    } 

    @Test(expected = NullPointerException.class) 
    @Transactional 
    public void testMergingParentInSameTransaction() { 
     Parent parent = new Parent(); 
     parent = em.merge(parent); 
     Parent parent2 = em.find(Parent.class, parent.getId()); 
     Child child = new Child(); 
     child.setParent(parent2); 
     log.info("Collection<Child> is {}", parent2.getChildren()); 
     // logs Collection<Child> is null 
     parent2.getChildren().add(child); 
    } 

} 

@Entity @Getter @Setter 
public class Parent { 

    @Id @GeneratedValue(strategy=GenerationType.IDENTITY) 
    private Long id; 

    @OneToMany(mappedBy="parent", cascade=CascadeType.ALL, orphanRemoval=true) 
    private Collection<Child> children; 

    private Date created = new Date(); // just to have something to persist 

} 

@Entity @Getter @Setter 
public class Child { 

    @Id @GeneratedValue(strategy=GenerationType.IDENTITY) 
    private Long id; 

    private Date created = new Date(); // just to have something to persist 

    @ManyToOne(optional=false) 
    private Parent parent; 


} 

답변

1

부모를 만들면하지 않기 때문에 컬렉션이 초기화되지 않았습니다. 또한 JPA를 그대로 유지하면 컬렉션이 그대로 남아있게됩니다.

그러나 부모가 Hibernate를 읽을 때 toMany 관계가 LAZY를 가져오고 요청시이 프록시를 사용하여 컬렉션에 프록시가 포함됩니다.

NullPointerExceptions을 피하기 위해 항상 컬렉션을 초기화하는 것이 좋습니다. 좋은 프로그래밍 스타일입니다.

+0

다른 곳에서 NPE가 꽤 많이 있기 때문에 내가하는 일은 항상 좋습니다. :)하지만 우리가 openjpa가 최대 절전 모드와 동일한 작업을 수행한다고 가정하면 왜 두 가지 NPE에서 같은 방식으로 null 컬렉션을 초기화 할 수 없습니까? 작업 케이스에서와 마찬가지로 테스트 케이스? – pirho

+0

두 테스트 케이스에서 게으른 페칭이 필요 없기 때문에 콜렉션의 컨텐츠를 변경할 이유가 없기 때문입니다. –

+0

그리고 JPA가 실제로 동일한 프록시 부모/parent2를 반환하기 때문에 게으른 로딩을 할 이유가 없습니다. 데이터베이스에서 가져 오기를 시도하거나 (왜 느려지는지 미안 ...) 느리게하려고합니까? 이 "no reason"에 대한 자세한 내용으로 답변을 업데이트하면 해결할 수 있습니다. – pirho

1

아래 답변은 정확합니다. 다른 곳의 의견에 질문을 받았을 때 좀 더 자세한 정보를 추가하고 싶습니다.

JPA는 가능하면 데이터베이스 적중을 피하기 위해 캐싱을 사용하고 데이터베이스 히트가 필요한 곳에서는 캐싱을 통해 개체를 재 구축하는 비용을 피하고 Identity를 유지 관리 할 수 ​​있으므로 A-> B- > 순환 참조. 당신이 엔티티를 유지하면

, 당신은 관리 대상 항목으로 EntityManager의 캐시에 배치하는 - 당신이 그냥 통과 똑같은 인스턴스를 반환 것 EntityManager의에서 찾기를 호출

A initialA = new A(); 
    A managedA = em.persist(initialA); 
    managedA==initialA 

호출을 지속. 엔티티 내에서 아무 것도 변경하지 않습니다 (사전 할당을 사용할 수있는 시퀀스 인 경우 ID 제외). null 참조는 여전히 null입니다.

결국 트랜잭션이 커밋되고 공급자에 따라 엔터티를 두 번째 수준 캐시에 캐시 할 수 있습니다. 나는 당신이 간결함을 위해서 그것을 사용하지 않는다고 가정 할 것입니다; EM이이 인스턴스를 새로 고치도록 강요하지 않으면 (새로운 것이면 먼저 플러시!) 또는 별도의 EntityManager에서 읽으면 항상 null 참조로 같은 인스턴스를 돌려 받게됩니다.

새로 고치거나 다시로드해야하는 경우 JPA 공급자는 매핑에 따라 데이터베이스의 모든 내용을 개체에 설정해야합니다. null은 콜렉션 맵핑에 대한 지속 가능한 상태가 아니기 때문에 참조를 열망하게 가져 오거나, 게으른 관계로 프록시를 배치하여 빈 콜렉션을 찾을 수 있음을 의미합니다.