Reputation: 13
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:
could not initialize proxy - no Session
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);
}
Upvotes: 1
Views: 7591
Reputation: 608
You misunderstand relationships laziness in JPA. When you mark your OneToMany relationship as a lazy one, it's your Set that will actually become lazy rather than getChildren. In other words, you need to access Set's contents to trigger lazy relationship to be fetched, and you need a transaction for this. Following your questions:
Upvotes: 3