I'm in the middle of learning Spring Data/Hibernate stuff and faced with known issue of lazy loading in Hibernate. I tried different approaches described in stackoverflow and other resources, but it looks like they don't work. I'm using hibernate in coupe with spring data annotation configuration. My approaches were:
- Plain loading of @OneToMany dependency with fetch = FetchType.LAZY. As expected I receive LazyInitializationException with message about
could not initialize proxy - no Session
- Using transactional repository. Same result as for the first point;
- Using Spring transaction template. Same result;
- Using @NamedEntityGraph of JPA 2.1. Here situation is more interesting. My test pass, however in SQL debug I can see data are not loaded lazy, but at the first time I querying repository. Another words child table joined to parent table first time I'm querying repository.
I pushed my test project to github, so it's possible to review it there https://github.com/megamaxskx/hibernate_lazy_fetch
UPDATED
Spring transaction template approach: Repository:
@Repository
public interface PlainParentRepository extends CrudRepository<PlainParent, Long> {
}
Configuration:
@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;
}
}
Child class:
@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;
}
}
Parent class:
@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;
}
}
Spring transaction template 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 approach:
Child:
@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);
}