SpringBootTest not resolving parent child relation - failed to lazy initialize - java

I'm using springBootTest to test a service I've created. In the before each function i create a parententity using the repository directly.
parentEntity = parentEntityRepository.saveAndFlush(ObjectMother.getParentEntityBuilder().string3("s3").build());
Within my test i create a childentity
childEntity = childEntityRepository.saveAndFlush(ObjectMother.getChildEntityBuilder().parentEntity(parentEntity).build());
The children relation is defined as follows
#Getter #Setter
#OneToMany(orphanRemoval = true)
#JoinColumn(name="PARENTENTITY_ID", updatable = false)
private Set<ChildEntity> children;
This is called within the test
parentEntity = parentEntityService.read(requestContext, parentEntity.getId());
parentEntity.getChildren().forEach(child -> Assert.assertNotNull(child.getText()));
It causes the following error
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.sap.icn.pi.test.data.mcp.ParentEntity.children, could not initialize proxy - no Session
If i add #Transactional to my test method i receive the following
java.lang.NullPointerException // for parentEntity.getChildren()
** Edit: Code Snippets **
#Test
public void get_children_through_parent() {
parentEntity = parentEntityService.read(requestContext, 1);
parentEntity.getChildren().forEach(child -> Assert.assertNotNull(child));
parentEntity.getChildren().forEach(child -> Assert.assertNull(child.getTooltip()));
}
ParentEntity Class
#Entity
#Table(name = "T_PARENTENTITY")
#SequenceGenerator(initialValue = 1, name = "idgen", sequenceName = "SEQ_PARENTENTITY")
#Builder #NoArgsConstructor #AllArgsConstructor
#Localized
public class ParentEntity extends BaseEntity{
... //props
#Getter #Setter
#OneToMany(orphanRemoval = true)
#JoinColumn(name="PARENTENTITY_ID", updatable = false)
private Set<ChildEntity> children;
}

This is a common JPA/Hibernate problem. Object was read is different Hibernate session or hibernate session doesn't exist anymore, so lazy loading can't do SQL query to retrieve lazy dependency. Reason of this situation can vary and you didn't provide enough context.
To fix that, you have various options:
Make sure that object is read and lazy dependency is loaded in same Hibernate session. Spring Automatically creates hibernate session per controller request, so it would be good to make sure that you object is not retrieved in servlet filter and lazy dependency in your controller/service. Or common problem is also passing that object into separate thread.
Change dependency to be EAGER:
#Getter #Setter
#OneToMany(orphanRemoval = true, fetch = FetchType.EAGER)
#JoinColumn(name="PARENTENTITY_ID", updatable = false)
private Set<ChildEntity> children;

Related

Why does the mappedBy parameter cause infinite recursion in spring boot?

These are two entities, each with more fields but these two are causing StackOverflow. When I'm using only #JsonMannagedReference and #JsonBackReference and not using mappedBy, infinite recursion doesn't exist. Still, in that case, I'm unable to create bidirectional relation, when I add a mappedBy parameter it causes a StackOverflow error.
#Entity
#Getter
#Setter
#ToString
#NoArgsConstructor
#AllArgsConstructor
public class Business {
#OneToMany(targetEntity = ProductCategory.class
, cascade = CascadeType.ALL
, fetch = FetchType.LAZY
, mappedBy = "business"
)
#JsonManagedReference
private List<ProductCategory> productCategories;
}
#Entity
#Getter
#Setter
#ToString
#NoArgsConstructor
#AllArgsConstructor
public class ProductCategory {
#ManyToOne(targetEntity = Business.class
, cascade = CascadeType.ALL
, fetch = FetchType.LAZY)
#JoinColumn(name = "business_id")
#JsonBackReference
private Business business;
}
We have a very similar setup and it works. I see two differences.
First, we do not set targetEntity, JPA should be able to figure that out based on field types.
Second, we excluded "business" fields from toString and equalsAndHashCode generated for ProductCategory class.
Try adding annotations
#ToString(exclude = {"business"})
#EqualsAndHashCode(exclude = {"business"})
to your ProductCategory class.
That should exclude cyclic calls in toString and equals/hashcode methods and take away your unwanted infinite recursion.

JPA Criteria Builder - getting an attribute that is a list

I'm trying to access an attribute in one of my entity classes: "products" that is a list:
#Entity
#Table(name = "TRANSACTION")
#Getter
#NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Transaction extends BaseTransaction {
...
#OneToMany(mappedBy="transaction)
private List<Product> products;
...
}
#Entity
#Table(name = "PRODUCT")
#Getter
#AllArgsConstructor
#NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Product {
....
#ManyToOne
#PrimaryKeyJoinColumn
#Getter
#NonNull
private Transaction transaction;
....
#Embedded
#AttributeOverrides({
#AttributeOverride(name = "name", column = #Column(name = "seller_name")),
#AttributeOverride(name = "country", column = #Column(name = "seller_country")) })
private NameAndCountry seller;
...
}
#Embeddable
#AllArgsConstructor
#Getter #Setter
#NoArgsConstructor(access = AccessLevel.PRIVATE)
public class NameAndCountry {
private String name;
private String country;
}
Given a string: "myName", and by using JPA criteria builder, I'm trying to retrieve the name of the seller of the transaction, and this is what have when I'm trying to build the predicate:
Join<Object, Object> transactionProductJoin = root.join("products");
Predicate predicate_ = criteriaBuilder.equal(transactionProductJoin.get("products").get("seller").get("name"), "myName");
However I'm facing an error which says:
Unable to locate Attribute with the the given name [products] on this ManagedType [work.my.domain.models.BaseTransaction]
Why is JPA criteria builder trying to retrieve the "products" attribute from the parent class of Transaction? What should be the correct way to construct the predicate?
Following is the example where we map Parent and Child entity classes using JPA Annotations.
#Entity
#Table(name = "Parent")
#Inheritance(strategy = InheritanceType.JOINED)
public class Parent {
// Getter and Setter methods
}
#Inheritance – Defines the inheritance strategy to be used for an entity class hierarchy. It is specified on the entity class that is the root of the entity class hierarchy.
#InheritanceType – Defines inheritance strategy options.
Single table per class hierarchy strategy: a single table hosts all the instances of a class hierarchy
Joined subclass strategy: one table per class and subclass is present and each table persist the properties specific to a given subclass. The state of the entity is then stored in its corresponding class table and all its superclasses
Table per class strategy: one table per concrete class and subclass is present and each table persist the properties of the class and its superclasses. The state of the entity is then stored entirely in the dedicated table for its class.
#Entity
#Table(name="Child")
public class Child extends Parent {
//Getter and Setter methods,
}
#Inheritance(strategy = InheritanceType.JOINED)
Should be added to the parent entity. (Depending on the InheritanceType required for your scenario.)
Check these links for reference:
Chapter 10. Inheritance mapping
5.1.6. Inheritance strategy
The issue is solved, the problem was in the construction of the predicate:
Wrong:
Predicate predicate_ = criteriaBuilder.equal(transactionProductJoin.get("products").get("seller").get("name"), "myName");
Correct:
Predicate predicate_ = criteriaBuilder.equal(transactionProductJoin.get("seller").get("name"), "myName");

#OrderBy Hibernate: ERROR: missing FROM-clause entry for table

So I have three entities. A FormCollection contains multiple Form. The Form is created from a template and thus has also a many-to-one relation to FormTemplate.
#Table(name = "form_collection", schema = "public")
public class FormCollectionDO extends BaseAuditableDO {
#OneToMany(mappedBy = "formCollection")
#OrderBy("formTemplate.templateId") //throws error
private List<FormDO> forms = new ArrayList<>();
}
#Table(name = "form", schema = "public")
public class FormDO extends BaseAuditableDO {
#NotNull
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "form_template_id")
private FormTemplateDO formTemplate;
}
#Table(name = "form_template", schema = "public")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class FormTemplateDO extends BaseDO {
#Column(name = "template_id", nullable = false)
#NotNull
private Long templateId;
}
#OrderBy("formTemplate.templateId") throws an error:
o.h.engine.jdbc.spi.SqlExceptionHelper : ERROR: missing FROM-clause entry for table "formtemplate"
#OrderBy("formTemplate.id") works fine. The id comes from the abstract class BaseDO. Why does it not work with any of the fields from the FormTemplateDO class?
Although I am not sure about this solution, What I am suspecting is this issue happens because the formTemplate.templateId isnt part of your query indeed.
I see you are using #OneToMany for defining the relationship, but in hibernate, the default FetchMode is SELECT which means your order by parameter isnt part of your query. To make this parameter part of your query, you will have to make a Join query.
Try this out -
#Fetch(value = FetchMode.JOIN)
#OneToMany(mappedBy = "formCollection")
#OrderBy("formTemplate.templateId") //throws error
private List<FormDO> forms = new ArrayList<>();
And propogate this join to further levels. It might solve your problem.
When you want to order by a collection of entities by a nested attribute, you can not use #OrderBy, because the nested attribute is not part of your query. You can only use #OrderBy for of first level attribute OR a nested attribute IF it is a collection of #Embeddable.
So for this case, you have to use #SortNatural or #SortComparator.
Similar issue : Hibernate - How to sort internal query lists (or List in List)?
More about #OrderBy vs #SortNatural: Sort vs OrderBy - performance impact

Spring data JPA repository returns entity of parent class

I have strange problem with spring data and inheritance, i have two classes:
#Getter
#Setter
#Entity
#Table(name = "a")
#Inheritance(strategy = InheritanceType.JOINED)
public class A {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "a_id_gen")
#SequenceGenerator(name = "a_id_gen", sequenceName = "a_id_seq", allocationSize = 50)
#Column(name = "id")
private Long id;
}
And class B
#Getter
#Setter
#Entity
#Table(name = "b")
public class B extends A {
#ManyToOne
#JoinColumn(name = "subject")
private Subject subject;
}
Also i have two simple interfaces which extends JpaRepo like this:
public interface ARepository extends JpaRepository<A, Long>
public interface BRepository extends JpaRepository<B, Long>
And then in code in #Transactional i use it like this:
A a = ARepository.findOne(someId);
if (some checks here) {
B b = BRepository.findOne(a.getId());
}
And a problem that B here is NULL, however in DB in table b it exists with same ID 100% sure. IF in debug i write
BRepository.getOne(a.getId());
it returns instance of A, same instance A as above from ARepository.
How i could make this work as i need? I think that problem in some hibernate managed cache or something. I also tried to change equals and hashcode like in this example http://docs.jboss.org/hibernate/orm/5.3/userguide/html_single/Hibernate_User_Guide.html#identifiers-composite-associations but no luck, problem still there.
Hibernate version is: 5.0.12.Final
Spring boot dependencies: 1.5.6.RELEASE
Ok i found out problem cause. It was query earlier in transaction. JOOK was used to create recursive sql request, and hibernate to map this request to entity. Because of entity have inheritance for mapping i have to add "clazz_" field in request with hard coded 0, after this request all entity was cached in first lvl hibernate cache somehow and cant be then reRequested from DB.
I add to my JOOK
.select(when(B.ID.isNotNull(), 1).otherwise(0).as("clazz_"))
And now all working as expected

Lazy initialization in Spring DataJPA

happy new year:)
I have a Spring MVC project using Hibernate and DataJPA.
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id", nullable = false)
private User user;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "restaurant_id", nullable = false)
#NotNull
private Restaurant restaurant;
As you can see, here is two fields with eager fetch. I want to make both a lazy. I need to user #NamedEntityGraph annotation asI made here:
#NamedQueries({
#NamedQuery(name = Restaurant.GET_BY_ID, query = "SELECT r FROM Restaurant r WHERE r.id=?1"),
})
#Entity
#NamedEntityGraph(name = Restaurant.GRAPH_WITH_MENU_HISTORY, attributeNodes = {#NamedAttributeNode("menuHistory")})
#Table(name = "restaurants")
public class Restaurant extends NamedEntity {
#OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.LAZY, mappedBy = "restaurant")
#OrderBy(value = "date DESC")
private List<Dish> menuHistory;
public static final String GRAPH_WITH_MENU_HISTORY = "Restaurant.withMenuHistory";
I want to know, if I'll write
#NamedEntityGraph(name = "G_NAME", attributeNodes = {#NamedAttributeNode("user", "restaurant")})
and if I'll request one of them, will the second load anyway or it will load only by request to him? May be, I need to user two graphs?
According to JPA 2.1 Spec 3.7.4:
The persistence provider is permitted to fetch additional entity state
beyond that specified by a fetch graph or load graph. It is required,
however, that the persistence provider fetch all state specified by
the fetch or load graph.
So actually the #NamedEntityGraph just guarantees what fields should be eagerly loaded, but not what fields should not be loaded.
So, if you make #NamedEntityGraph with user, your persistence provider (Hibernate for example) can load only user field or both user and restaurant fields eagerly. This is dependent on implementation and not guaranteed.
See this hibernate's issue.
But as far as I know, the Hibernate loads only simple fields in addition to specified in #NamedEntityGraph, and not loads lazy associations.
So if you use hibernate, it should work.
But of course you need two separate #NamedEntityGraphs for user and restaurant fields.
Or you can use ad-hoc spring-data-jpa's feature:
#Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {
#EntityGraph(attributePaths = { "members" })
GroupInfo getByGroupName(String name);
}
With this code you don't need explicitly declare #NamedEntityGraph anymore. You can just specify fields inline.

Categories

Resources