Hibernate EntityGraph not loading entity graph as declared [Second level] - java

I was trying to use EntityGraph to load Entity.
I have Employee entity with all associations mapped as LAZY loading associations. At one use case, I want to fetch Employee with its single value associations eagerly, so I was trying to use EntityGraph for this.
[I know Eager associations can not be loaded as LAZY using EntityGraph, but in my case, I am trying to load LAZY association EAGERly using Entity graph, which I think should work]
Employee
#NamedEntityGraphs({
#NamedEntityGraph(name = "Employee.graph1", attributeNodes = {
#NamedAttributeNode(value = "firstName") ,
#NamedAttributeNode(value = "lastName"),
#NamedAttributeNode(value = "manager", subgraph = "managerSubGraph"),
#NamedAttributeNode(value = "department")
}, subgraphs = {
#NamedSubgraph(name = "managerSubGraph", attributeNodes = {
#NamedAttributeNode(value = "department"),
#NamedAttributeNode(value = "manager")}
)}
)}
)
public class Employee {
#Column(name = "id")
#Basic
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "department_id")
private Department department;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "manager_id")
private Employee manager;
}
Department
#Entity
#Table(name = "department")
public class Department {
#Column(name = "id")
#Basic
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Basic
#Column(name = "name")
private String name;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "department_id")
private Set<Employee> employees;
public Department(String name) {
this.name = name;
}
}
Caller
#Transactional(readOnly = true)
public void entityGraphDemo() {
final EntityGraph<?> entityGraph = em.getEntityGraph("Employee.graph1");
Map<String, Object> properties = new HashMap<>();
properties.put("javax.persistence.fetchgraph", entityGraph);
final Employee employee = em.find(Employee.class, 2, properties);
// all of these get loaded EAGERly with no issue
final PersistenceUnitUtil persistenceUnitUtil = entityManagerFactory.getPersistenceUnitUtil();
System.out.println("isLoaded(employee, \"department\") = " + persistenceUnitUtil.isLoaded(employee, "department"));
System.out.println("isLoaded(employee, \"manager\") = " + persistenceUnitUtil.isLoaded(employee, "manager"));
final Employee employeeManager = employee.getManager();
// these return false, means these nested associations Employee -> Manager -> Department, Employee -> Manager -> Manager are not loaded (which should be)
System.out.println("isLoaded(employeeManager, \"department\") = " + persistenceUnitUtil.isLoaded(employeeManager, "department"));
System.out.println("isLoaded(employeeManager, \"manager\") = " + persistenceUnitUtil.isLoaded(employeeManager, "manager"));
}
First level of entity graph get loaded correctly as expecred, but second level of graph is not getting loaded as expected.
Issue is, department and manager associations on employeeManager (Second level subgraph) are not getting loaded eagerly.
Last two Syso prints false. Data is available for both associations and when I call getters on employeeManager instance, hibernate execute two sql to load manager and department, so both associations seem to load lazily.
So, is there limitations on levels subgraph supported by hibernate? JPA spec does not define any such a limitation. Or am I missing something here?

Related

Exclude table using CrudRepository in Java

i have two tables Person and PersonType and there is a relation "ManyToMany" between these tables. During loading my application i am getting all the PersonTypes, but when i create new Person, i have an exception
org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "person_type_person_type_name_key"
Detail: Key (person_type_name)=(TYPE1) already exists.
person_type_person_type_name_key is my table where i should store the relations between Person and PersonType. When i create a new Person i DO NOT want to insert into PersonType table because the person type already exists. What should i do, not to insert into DB ? I am using personService.save(person); which is trying to insert also in person_type table into DB.
#Table(name = "person")
public class Person {
#Id
#Column(name = "id")
#GeneratedValue(generator = "person_id_seq")
#SequenceGenerator(sequenceName = "person_id_seq", name = "person_id_seq", schema = "manager", allocationSize = 1, initialValue = 1)
private Integer id;
#Column(name = "name")
private String name;
#Column(name = "password")
private String password;
#ManyToMany(cascade = {CascadeType.ALL})
#JoinTable(
name = "person_person_types",
joinColumns = #JoinColumn(name = "person_fk"),
inverseJoinColumns = #JoinColumn(name = "person_type_fk"))
private List<PersonType> personTypes;
}
#Entity
#Table(name = "person_type")
public class PersonType {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "person_type_name", unique=true)
private String personType;
#ManyToMany(mappedBy = "personTypes", cascade = {CascadeType.ALL})
private Set<Person> persons;
}```
Maybe the problem is with inserting the PersonType. Ensure that you put PersonType with the same ID and same name into the DB. Also change CascadeType.ALL to be CascadeType.MERGE

JPA: Reference column in the child entity is null when using unidirectional #OneToMany

I have two entity classes.
Order.java
#Entity
#Table(name = "order_table")
public class Order implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "order_id", referencedColumnName = "id", nullable = false, insertable=false, updatable=false)
private Set<Item> items;
// getters & setters & toString
Item.java
#Entity
#Table(name = "item")
public class Item implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "order_id", nullable = false)
private Long orderId;
// getters & setters && toString
I created a test class like this:
#Test
public void createOrderWithItems() {
Item item = new Item();
item.setName("Iron Man");
Order order = new Order();
order.setName("Toy");
order.getItems().add(item);
Order created = service.createOrder(order);
Order orderById = service.getOrderById(order.getId());
System.out.println("Created Order: " + orderById);
Item itemById = service.getItemById(item.getId());
System.out.println("Created item: " + itemById);
Assert.notNull(created.getId(), "Order ID is Null");
}
Test is green but if you check output, you'll see that orderId field in the Item class is null.
Created Order: Order{id=1, name='Toy', items=[Item{id=2, name='Iron Man', orderId=null}]}
Created item: Item{id=2, name='Iron Man', orderId=null}
Does JPA not update this column in the db automatically? Is this column is redundant? If so, how can I retrieve this information from test code?
You need to set orderId explicitly.
item.setOrderId(order.getId());
order.getItems().add(item);
You can create a method addItem(Item item) in your Order class and hide this logic within it.
Cascading will create an entry in db but it won't initialize field. JPA annotations just indicate to JPA provider how to perform mapping between entity and table.
Moreover, check your annotations. #JoinColumn should be used in the entity which owns the relationship (the corresponding table has column as a foreign key). Check the top answer for this question for detailed explanations: What's the difference between #JoinColumn and mappedBy when using a JPA #OneToMany association

Hibernate entity related to two other entities causes NPE when trying to query database

Apologies if there is an obvious problem, I'm new to Hibernate. I've spent the last 3 hours scouring the googles, it's very likely I'm not phrasing my question correctly. I think I might have a circular reference, but I'm not sure.
I have 3 entities. The relationships I am trying to materialize are:
An Institution can have many courses
An Institution can be a sponsor
A course can have many sponsors
A sponsor can have many courses
My unit test creates these entities, saves them, and then queries for them to make sure I can read & write to the DB correctly and maintain the relationships.
If I only add an institution (BMC) to a course, everything is fine.
If I only add sponsors (BMC, HKU) to a course, everything is fine.
If I add an institution (BMC) and sponsors (BMC) to a course, when I try to get the course back, I get an empty object, which ultimately causes an NPE
I'm representing the many-to-many relationship in the third entity, CourseDetails. Ultimately, the CourseDetails will have several many-to-many relationships. However, only institution is used in multiple places.
Any help would be greatly appreciated.
** EDIT ** I can confirm that the data is being saved correctly in the database when I look at in Toad. The course table has the correct institution_id and the courseDetails_institution has the correct mapping of course_id and institution_id
Institution
#Entity
#Table(name="institution")
public class Institution {
#Id
#GeneratedValue(strategy = GenerationType.TABLE, generator="table-generator")
#TableGenerator(name = "table-generator", pkColumnValue = "institution_id")
#Column(name = "institution_id")
private int id;
#Column(name = "sponsor")
private boolean sponsor;
#ManyToMany(mappedBy = "sponsors", fetch = FetchType.EAGER)
protected Set<CourseDetails> courseDetails = new HashSet<>();
#OneToMany(mappedBy = "institution")
#Cascade({CascadeType.SAVE_UPDATE})
protected Set<Course> courses = new HashSet<>();
protected Institution() {
// no-op constructor for hibernate, cannot be private
}
public Institution(String name, String abbreviation) {
super(name, abbreviation);
this.sponsor = false;
}
// Setters, getters, equals, hashcode omitted for brevity.
Course
#Entity
#Table(name = "course")
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.TABLE, generator="table-generator")
#TableGenerator(name = "table-generator", pkColumnValue = "course_id")
#Column(name = "course_id")
private int id;
#ManyToOne
private Institution institution;
#ManyToMany(mappedBy = "courses", fetch = FetchType.EAGER)
protected Set<CourseDetails> courseDetails = new HashSet<>();
// Setters, getters, equals, hashcode omitted for brevity.
CourseDetails
#Entity
#Table(name="courseDetails")
public class CourseDetails {
#Id
#GeneratedValue(strategy = GenerationType.TABLE, generator = "table-generator")
#TableGenerator(name = "table-generator", pkColumnValue = "courseDetails_id")
#Column(name = "courseDetails_id")
private int id;
#ManyToMany(fetch = FetchType.LAZY)
#Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})
protected Set<Course> courses = new HashSet<>();
#ManyToMany(fetch = FetchType.LAZY)
#Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})
protected Set<Institution> sponsors = new HashSet<>();
// Setters, getters, equals, hashcode omitted for brevity.
Relevant methods for querying
public <T> List getAllEntities(final Class<T> clazz) {
CriteriaBuilder builder = sessionFactory.getCriteriaBuilder();
CriteriaQuery<T> criteria = builder.createQuery(clazz);
Root<T> root = criteria.from(clazz);
criteria.select(root);
final Session session = startTransaction();
List<T> results = session.createQuery(criteria).getResultList();
endTransaction(session);
return results;
}
public Session startTransaction() {
Session session = sessionFactory.openSession();
session.beginTransaction();
activeSessions.add(session);
return session;
}
public void endTransaction(final Session session) {
session.getTransaction().commit();
session.close();
activeSessions.remove(session);
}
Jules' comments have answered my question. I needed to either have the institution be a many-to-many relationship or create two entities that map to the same table. This is what I ended up doing:
BaseInstitution
#Entity
#Table(name="institution")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorFormula(
"CASE WHEN sponsor " +
"THEN 'SPONSOR' " +
"ELSE 'INSTITUTION' " +
"END "
)
public class BaseInstitution {
#Id
#GeneratedValue(strategy = GenerationType.TABLE, generator="table-generator")
#TableGenerator(name = "table-generator", pkColumnValue = "institution_id")
#Column(name = "institution_id")
private int id;
#Column(name = "sponsor")
private boolean sponsor = false;
Sponsor
#Entity
#DiscriminatorValue("SPONSOR")
public class Sponsor extends BaseInstitution {
#ManyToMany(mappedBy = "sponsors", fetch = FetchType.EAGER)
protected Set<CourseDetails> courseDetails = new HashSet<>();
Institution
#Entity
#DiscriminatorValue("INSTITUTION")
public class Institution extends BaseInstitution {
#OneToMany(mappedBy = "institution")
#Cascade({CascadeType.SAVE_UPDATE})
protected Set<Course> courses = new HashSet<>();
Course
#Entity
#Table(name = "course")
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.TABLE, generator="table-generator")
#TableGenerator(name = "table-generator", pkColumnValue = "course_id")
#Column(name = "course_id")
private int id;
#ManyToOne
#Cascade({CascadeType.SAVE_UPDATE})
private Institution institution;
#ManyToMany(mappedBy = "courses", fetch = FetchType.EAGER)
protected Set<CourseDetails> courseDetails = new HashSet<>();
CourseDetails
#Entity
#Table(name="courseDetails")
public class CourseDetails {
#Id
#GeneratedValue(strategy = GenerationType.TABLE, generator = "table-generator")
#TableGenerator(name = "table-generator", pkColumnValue = "courseDetails_id")
#Column(name = "courseDetails_id")
private int id;
#ManyToMany(fetch = FetchType.LAZY)
#Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})
protected Set<Course> courses = new HashSet<>();
#ManyToMany(fetch = FetchType.EAGER)
#Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})
protected Set<Sponsor> sponsors = new HashSet<>();

What hibernate / jpa annotation is required

I am trying to create a new User(entity1) - it has reference to a Group (entity2) via a link table Member (entity3)
A user has a Set of groups as a class variable.
When i create my user object i want to say this user will be a member of group n (there are pre defined users that are linked to by id (1,2,3,4,5,6...) each group has some associated data in the table.
Whenever I create my user object as follows;
User user = new User();
user.setActive(1);
user.setCrby("me");
user.setUsername("username");
user.setCrdate("2016-06-20 12:42:53.610");
user.setCrwsref("...");
user.setModby("...");
user.setModdate("2016-06-20 12:42:53.610");
user.setModswref("..");
user.setBackground("Y");
user.setPassword("password");
user.setFullName("me");
Group group = new Group();
group.setId(1);
Group group2 = new Group();
group2.setId(2);
Set<Group> sets = new HashSet<Group>();
sets.add(group);
sets.add(group2);
user.setGroups(sets);
userDao.addUser(user);
I keep getting errors telling me that certain columns cannot be null. What I actually want to happen here is not to be doing an insert in to the group table but associating a user to a line in the group table. Is there a particular way I can prevent the columns in the group table being modified? I think I need to modify the mappings between the link table - this is how much pojos link right now
User
#Entity
#Table(name = "user")
public class User
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "zmember", joinColumns = {#JoinColumn(name = "username")}, inverseJoinColumns = {#JoinColumn(name = "id")})
private Set<Group> groups = new HashSet<Group>(0);
Member link table
#Entity
#Table(name = "member")
public class Member implements Serializable
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Id
#Column(name = "sgpid")
private int sgpid;
#Column(name = "username")
private String memberUsername;
Group
#Entity
#Table(name = "group")
public class Group
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
What is happening is there is no association to the link Member table so ideally should User have a set of member objects rather than a set of groups?
Thanks - this was quite hard to explain so sorry if it is hard to understand
This is a typical case for the #ManyToMany annotation. See for example:
https://dzone.com/tutorials/java/hibernate/hibernate-example/hibernate-mapping-many-to-many-using-annotations-1.html
The relationship from User to Group is essentially ManyToMany. You could model this is using the #ManyToMany annotation however one drawback with this approach is you cannot save additional information about the group in the join table such as 'date_joined'.
See: https://en.wikibooks.org/wiki/Java_Persistence/ManyToMany#ManyToMany
Using this approach you would not need the Join entity Member and the relationship on User would look like:
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "zmember", joinColumns = {#JoinColumn(name = "member_id", referencedColumnName = "id")}, inverseJoinColumns = {#JoinColumn(name = "group_id", referencedColumnName = "id")})
private Set<Group> groups = new HashSet<Group>(0);
The alternative to using #ManyToMany is to use a Join entity Member(ship) as you have done. This would allow you to save additional data about the relationship (by defining additional field mappings in the Join entity).
In this case the mappings would look like:
User:
public class User{
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Membership> memberships = new HashSet<Membership>(0);
//if required, you can 'hide' the join entity from client code by
//encapsulating add remove operations etc.
public void addToGroup(Group group){
Membership membershup = new Membership();
membership.setUser(this);
membership.setGroup(group);
memberships.add(membership);
)
public Set<Groupp> getGroups(){
//iterate memberships and build collection of groups
}
}
Membership:
public class Membership{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#ManyToOne
#JoinColumn(name = "user_id")
private Member member;
#ManyToOne
#JoinColumn(name = "group_id")
private Group group;
}
Group:
#Entity
#Table(name = "group")
public class Group
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#OneToMany(mappedBy = "group", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Membership> memberships = new HashSet<Membership>(0);
}

How to properly delete a complex object using Hibernate?

Here is the problem. When I'm trying to delete a Catalog object from database, Hibernate also removing all Catalog objects with associated Type and Genre Ids. For example, if I’m removing Catalog with Type.id=1 and Genre.id=1 Hibernate delete every Catalogs with such Ids. Any ideas how to fix it? I need to delete only one Catalog object without deleting Type and Genre objects with id=1.
#Entity
#Table(name = "catalog", catalog = "media_store_db")
public class Catalog implements Serializable {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name = "product_name", length = 100)
private String productName;
#ManyToOne(cascade = {CascadeType.ALL})
#JoinColumn(name = "genre_id", referencedColumnName = "genre_id")
private Genre genre;
#ManyToOne(cascade = {CascadeType.ALL})
#JoinColumn(name = "type_id", referencedColumnName = "type_id")
private Type type;
#Entity
#Table(name = "genres", catalog = "media_store_db")
public class Genre implements Serializable {
#Id
#Column(name = "genre_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name = "genre_name")
private String name;
#OneToMany(mappedBy = "genre", cascade = CascadeType.ALL)
Collection<Catalog> catalogs = new ArrayList<Catalog>();
#Entity
#Table(name = "types", catalog = "media_store_db")
public class Type implements Serializable {
#Id
#Column(name = "type_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name = "type_name")
private String name;
#OneToMany(mappedBy = "type", cascade = CascadeType.ALL)
Collection<Catalog> catalogs = new ArrayList<Catalog>();
My method which delete a Catalog object
public void deleteCatalog(Integer catalogId) {
Session session = config.getSession();
Transaction tx = session.beginTransaction();
session.delete(session.get(Catalog.class, catalogId));
tx.commit();
session.close();
}
This is because of Cascade.ALL. If you delete a parent if would also delete all related child if you are using Cascade.ALL.
Instead ALL choose only what you need from the below
CascadeType.PERSIST: cascades the persist (create) operation to associated entities if persist() is called or if the entity is managed
CascadeType.MERGE: cascades the merge operation to associated entities if merge() is called or if the entity is managed
CascadeType.REMOVE: cascades the remove operation to associated entities if delete() is called
CascadeType.REFRESH: cascades the refresh operation to associated entities if refresh() is called
CascadeType.DETACH: cascades the detach operation to associated entities if detach() is called
CascadeType.ALL: all of the above

Categories