2016-04-15 17 views
1

Ich bin in der Mitte des Lernens Spring Data/Hibernate Zeug und konfrontiert mit bekannten Problem des lazy Loading in Hibernate. Ich habe versucht verschiedene Ansätze in Stackoverflow und anderen Ressourcen beschrieben, aber es sieht so aus, als ob sie nicht funktionieren. Ich verwende Hibernate im Coupé mit der Konfiguration der Federdatenanmerkung. Meine Ansätze waren:Federdaten Hibernate Lazy Loading

  1. Plain Laden von @OneToMany Abhängigkeit mit holen = FetchType.LAZY. Wie erwartet erhalte ich LazyInitializationException mit Nachricht über

nicht Proxy initialisieren konnte - keine Session

  1. Verwendung von Transaktions Repository. Gleiches Ergebnis wie für den ersten Punkt;
  2. Verwenden der Spring-Transaktionsvorlage. Gleiches Ergebnis;
  3. Verwenden von @NamedEntityGraph von JPA 2.1. Hier ist die Situation interessanter. Mein Test-Durchlauf, jedoch in SQL-Debugging kann ich sehen, dass Daten nicht lazy geladen werden, aber bei der ersten Abfrage des Repository. Eine andere Wörter-Kind-Tabelle ist zum ersten Mal mit der Eltern-Tabelle verbunden, wenn ich das Repository abfrage.

schob ich mein Testprojekt Github, so ist es möglich, es gibt https://github.com/megamaxskx/hibernate_lazy_fetch

AKTUALISIERT

Frühling Transaktion Template Ansatz zu überprüfen: Repository:

@Repository 
public interface PlainParentRepository extends CrudRepository<PlainParent, Long> { 

} 

Konfiguration:

@Configuration 
@EnableTransactionManagement 
@EnableJpaRepositories(basePackages = REPOSITORY_LOCATION) 
public class DBConfig { 

    public static final String REPOSITORY_LOCATION = "com.lazyloadingtest"; 
    private static final String ENTITIES_LOCATION = "com.lazyloadingtest"; 

    @Bean 
    public DataSource dataSource() { 
     return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build(); 
    } 

    @Bean 
    public JpaTransactionManager transactionManager(EntityManagerFactory emf) { 
     return new JpaTransactionManager(emf); 
    } 

    @Bean 
    public JpaVendorAdapter jpaVendorAdapter() { 
     HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter(); 
     jpaVendorAdapter.setDatabase(Database.H2); 
     jpaVendorAdapter.setGenerateDdl(true); 
     jpaVendorAdapter.setShowSql(true); 
     return jpaVendorAdapter; 
    } 

    @Bean 
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() { 
     LocalContainerEntityManagerFactoryBean lemfb = new LocalContainerEntityManagerFactoryBean(); 
     lemfb.setDataSource(dataSource()); 
     lemfb.setJpaVendorAdapter(jpaVendorAdapter()); 
     lemfb.setPackagesToScan(ENTITIES_LOCATION); 
     lemfb.setJpaProperties(hibernateProperties()); 
     return lemfb; 
    } 

    protected Properties hibernateProperties() { 
     Properties properties = new Properties(); 
     properties.put("hibernate.format_sql", true); 
     return properties; 
    } 

} 

Kinderklasse:

@Entity 
public class EntityGraphChild implements Serializable { 

    public static long serialVersionUID = 1L; 

    @Id 
    @GeneratedValue 
    private long id; 

    private String name; 

    @ManyToOne 
    private PlainParent parent; 

    public long getId() { 
     return id; 
    } 

    public void setId(long id) { 
     this.id = id; 
    } 

    public String getName() { 
     return name; 
    } 

    public void setName(String name) { 
     this.name = name; 
    } 

    public PlainParent getParent() { 
     return parent; 
    } 

    public void setParent(PlainParent parent) { 
     this.parent = parent; 
    } 
} 

Übergeordnete Klasse:

@Entity 
public class PlainParent implements Serializable { 

    public static long serialVersionUID = 1L; 

    @Id 
    @GeneratedValue 
    private long id; 

    private String name; 

    @OneToMany(targetEntity = PlainChild.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL) 
    private Set<PlainChild> children; 

    public long getId() { 
     return id; 
    } 

    public void setId(long id) { 
     this.id = id; 
    } 

    public String getName() { 
     return name; 
    } 

    public void setName(String name) { 
     this.name = name; 
    } 

    public Set<PlainChild> getChildren() { 
     return children; 
    } 

    public void setChildren(Set<PlainChild> children) { 
     this.children = children; 
    } 
} 

Frühling Transaktionsvorlage Test:

@RunWith(SpringJUnit4ClassRunner.class) 
@ContextConfiguration(classes = { 
     DBConfig.class, 
}) 
public class SpringTransactionTemplateTest { 

    @Autowired 
    private PlainParentRepository repository; 

    @Autowired 
    private JpaTransactionManager transactionManager; 

    @Test 
    public void testRepository() { 
     PlainChild child1 = new PlainChild(); 
     child1.setName("first child"); 

     PlainChild child2 = new PlainChild(); 
     child2.setName("second child"); 

     PlainParent parent = new PlainParent(); 
     parent.setId(1l); 
     HashSet<PlainChild> children = new HashSet<PlainChild>(Arrays.asList(child1, child2)); 
     parent.setChildren(children); 
     repository.save(parent); 

     TransactionTemplate txTemplate = new TransactionTemplate(); 
     txTemplate.setTransactionManager(transactionManager); 

     Set<PlainChild> fromDB = txTemplate.execute(new TransactionCallback<Set<PlainChild>>() { 
      public Set<PlainChild> doInTransaction(TransactionStatus transactionStatus) { 
       PlainParent fromDB = repository.findOne(1L); 
       return fromDB.getChildren(); 
      } 
     }); 

     assertEquals(2, fromDB.size()); 
    } 

} 

NamedEntityGraph Ansatz:

Kind:

@Entity 
public class EntityGraphChild implements Serializable { 

    public static long serialVersionUID = 1L; 

    @Id 
    @GeneratedValue 
    private long id; 

    private String name; 

    @ManyToOne 
    private EntityGraphParent parent; 

    public long getId() { 
     return id; 
    } 

    public void setId(long id) { 
     this.id = id; 
    } 

    public String getName() { 
     return name; 
    } 

    public void setName(String name) { 
     this.name = name; 
    } 

    public EntityGraphParent getParent() { 
     return parent; 
    } 

    public void setParent(EntityGraphParent parent) { 
     this.parent = parent; 
    } 

    @Override 
    public boolean equals(Object o) { 
     if (this == o) return true; 
     if (o == null || getClass() != o.getClass()) return false; 

     EntityGraphChild child = (EntityGraphChild) o; 

     if (id != child.id) return false; 
     return name != null ? name.equals(child.name) : child.name == null; 

    } 
} 

Parent:

@Entity 
@NamedEntityGraph(
     name = "graph.Parent.children", 
     attributeNodes = @NamedAttributeNode(value = "children") 
) 
public class EntityGraphParent implements Serializable { 

    public static long serialVersionUID = 1L; 

    @Id 
    @GeneratedValue 
    private long id; 

    private String name; 

    @OneToMany(targetEntity = EntityGraphChild.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL) 
    private Set<EntityGraphChild> children; 

    public long getId() { 
     return id; 
    } 

    public void setId(long id) { 
     this.id = id; 
    } 

    public String getName() { 
     return name; 
    } 

    public void setName(String name) { 
     this.name = name; 
    } 

    public Set<EntityGraphChild> getChildren() { 
     return children; 
    } 

    public void setChildren(Set<EntityGraphChild> children) { 
     this.children = children; 
    } 

    @Override 
    public boolean equals(Object o) { 
     if (this == o) return true; 
     if (o == null || getClass() != o.getClass()) return false; 

     EntityGraphParent parent = (EntityGraphParent) o; 

     if (id != parent.id) return false; 
     if (name != null ? !name.equals(parent.name) : parent.name != null) return false; 
     return children != null ? children.equals(parent.children) : parent.children == null; 

    } 

    @Override 
    public int hashCode() { 
     int result = (int) (id^(id >>> 32)); 
     result = 31 * result + (name != null ? name.hashCode() : 0); 
     result = 31 * result + (children != null ? children.hashCode() : 0); 
     return result; 
    } 
} 

Test:

@RunWith(SpringJUnit4ClassRunner.class) 
@ContextConfiguration(classes = { 
     DBConfig.class, 
}) 
public class EntityGraphParentRepositoryTest { 

    @Autowired 
    private EntityGraphParentRepository repository; 

    @Test 
    public void testRepository() { 
     EntityGraphChild child1 = new EntityGraphChild(); 
     child1.setName("first child"); 

     EntityGraphChild child2 = new EntityGraphChild(); 
     child2.setName("second child"); 

     EntityGraphParent parent = new EntityGraphParent(); 
     parent.setId(1l); 
     parent.setName("ParentGraph"); 
     HashSet<EntityGraphChild> children = new HashSet<EntityGraphChild>(Arrays.asList(child1, child2)); 
     parent.setChildren(children); 

     repository.save(parent); 

     System.out.println("--- Before querying repo"); 
     EntityGraphParent fromDB = repository.findByName("ParentGraph"); 
     System.out.println("--- After querying repo"); 
     assertEquals(2, fromDB.getChildren().size()); 
     System.out.println("--- Test finished"); 
    } 

} 

Repository:

@Repository 
public interface EntityGraphParentRepository extends CrudRepository<EntityGraphParent, Long> { 

    @EntityGraph(value = "graph.Parent.children", type = EntityGraph.EntityGraphType.LOAD) 
    public EntityGraphParent findByName(String name); 
} 
+2

Beitrag der minimal einen entsprechenden Code hier, verknüpfe nicht nur –

+0

sie das Beste, was Hibernate hat: sqlqueries –

+0

Zum einen ist die vollständige Stack-Trace – LearningPhase

Antwort

2

Sie Missverständnis stehen Beziehungen Faulheit in JPA.Wenn Sie Ihre OneToMany-Beziehung als faul markieren, wird Ihr Set eher faul als getChildren. Mit anderen Worten, Sie müssen auf den Inhalt von Set zugreifen, um auszulösen, dass eine faule Beziehung abgerufen wird, und Sie benötigen dafür eine Transaktion. Befolgen Sie Ihre Fragen:

  1. Da Ihr äußerer Code nicht transaktional ist, wird die Transaktion selbst am Ende des Repository-Methoden-Aufrufs festgeschrieben. Auf das Session-Objekt kann nicht mehr zugegriffen werden, da es an eine Transaktion gebunden ist und Sie den Fehler erhalten.
  2. In Ihrer Transaktionsvorlage greifen Sie auf getChildren, nicht auf den Inhalt des untergeordneten Sets zu. Davon rede ich im kurzen Teil.
  3. EntityGraphs sollen Beziehungsabrufstrategien überschreiben, die in Zuordnungen definiert sind. Da Sie das Feld Parent.children markiert haben, das in Ihrem Diagramm abgerufen werden soll, muss dieses Feld jetzt unbedingt bei jeder Datenbankinteraktion abgerufen werden, auf die dieses Diagramm angewendet wird.
+0

Vielen Dank für die Erklärungen. Jetzt wird es viel klarer. Noch ein paar Worte, um meinen Ansatz 2 und 3 zu verstehen. Ich muss @Transactional zu meiner Testmethode hinzufügen. –