HttpMessageNotWritableException when serializing object with bidirectional Many to Many association using JsonIdentityInfo - java

I have two tables in my database and a join table. In order to prevent infinite recursion errors when requesting the server from the client, I use the JsonIdentityInfo annotation on the 2 entities that model both tables:
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property="id")
The association is indicated with a #ManyToMany annotation like the following:
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name = "join_table",
joinColumns = {
#JoinColumn(name = "first_table_id", referencedColumnName = "first_table_id")
},
inverseJoinColumns = {
#JoinColumn(name = "second_table_id", referencedColumnName = "second_table_id")
}
)
private List<SecondDTO> secondDTOs = new ArrayList<SecondDTO>();
And on the other side of the relationship:
#ManyToMany(mappedBy = "zonas")
private List<FirstDTO> firstDTOs = new ArrayList<FirstDTO>();
when performing a GET request to the server, a 500 HTTP error occurs and when looking a the server's console, an exception is shown:
WARN 94190 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Can not write a number, expecting field name (context: Object)]
Although I could remove the JsonIdentityInfo annotation and just use JsonIgnore for read requests, since I need the information of the relationship between the entities I would have to make a workaround to get such data from the server to the client, so I wanted to know if there is a way to solve this problem without ignoring the referenced objects.
When the List of referenced objects is empty, there is no error. Looking at the database, it only appears when a FK isn't unique in the join table, so it only works for OneToOne relations instead of ManyToMany.

Related

Duplicate items in a list attribute of JPA and Hibernate entity

The problem which i am trying to solve is avoid duplicate items inside a list attribute in hibernate.
Consider the below domain.
public class Account
{
#OneToMany(fetch = FetchType.LAZY)
#JoinTable(name = "FI_COMPANY_ACCOUNT", joinColumns = #JoinColumn(name = "ACCOUNT_ID", referencedColumnName = "ID"), inverseJoinColumns = #JoinColumn(name = "COMPANY_ID", referencedColumnName = "ID"))
private List<Company> companies;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "account", cascade = CascadeType.ALL, orphanRemoval = true)
private List<AccountDesc> accountDescList;
}
public class Company {}
public class AccountDesc
{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PARENT_ID", referencedColumnName = "ID")
private Account account;
}
I use a Criteria API to fetch Account. In the query i perform fetch using left join for companies and inner join for accountDescList attribute. This help me to get both attributes in first select, and which avoid further selects.
Root<Account> root = criteriaQuery.from(Account.class);
root.fetch("companies", JoinType.LEFT);
root.fetch("accountDescList");
I know the root entity (here Account) can be repeated in the results. I can solve the issue using multiple ways like,
http://in.relation.to/2016/08/04/introducing-distinct-pass-through-query-hint/
https://howtoprogramwithjava.com/how-to-fix-duplicate-data-from-hibernate-queries/
But issue i face is the attribute companies inside the Account has also duplicate entities. This happen if we have more than one entry for accountDescList.
To solve the issue of duplicates in the attribute companies, I feel only solution is to use Set. Could you please clarify on the below questions.
Is there a way other than using Set (for the attribute companies), to solve this issue.
Even if i use can i instruct hibernate to use OrderedSetType (which uses LinkedHashSet). So that i can retain the order of the items as it returned from database. Unfortunately I do not have a attribute to use in OrderBy. I need the whatever default order returned by database.
Thanks in advance.
But the issue I face is the attribute companies inside the Account has also duplicate entities.
That shouldn't happen unless you have duplicate Company entities assigned to the same account.
Using DISTINCT in the Criteria API query will remove root duplicates. However, in your case, it's not worth using JOIN FETCH on both #OneToMany relations since this will cause a Cartesian Product.
You should fetch at most one collection at a time, and maybe use #Subselect fetching for the second collection.
I think that it is much better use Set because a set doesn't allow elements duplicated, also you can overwrite equals method of Company and put it on what fields will be validated when two elements are equals.
The other way would be in setCompanies(List companies) method you can make something logic before this.companies = companies.stream().distinct().collect(Collectors.toList()); or
this.companies = new ArrayList<>(new HashSet(companies)) ;

ORA-02292: integrity constraint (xxx) violated - child record found

How do i delete all the entries using hibernate deleteAll() ?
I have a class with multiple #oneToMany relationships (having like +5000 child entities) and when i try to do deleteAll i get the title error
oracle.jdbc.OracleDatabaseException: ORA-02292: integrity constraint (xxx) violated - child record found
I've tried adding
cascade = {CascadeType.ALL}
and
orphanRemoval=true
to #OneToMany relationship class, but no help.
It's a bidirectional relationship with following classes
#OneToMany(targetEntity = XXX.class, fetch = FetchType.LAZY, cascade = {CascadeType.ALL}, orphanRemoval=true, mappedBy = "zzz")
#Fetch(FetchMode.SELECT)
#JsonManagedReference
private List<XXX> xxx;
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany(targetEntity = YYY.class, fetch = FetchType.LAZY, orphanRemoval=true, cascade = {CascadeType.ALL}, mappedBy = "zzz")
#Fetch(FetchMode.SELECT)
#JsonManagedReference
private List<YYY> yyy;
with child elements like
#ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
#JoinColumn(name = "XXX", nullable=false)
#JsonBackReference
private XXX zzz;
i also tried HQL DELETE query but that dosent get me anywhere either.
How on earth do i delete all these entities consistently?
So far i've manually droped the tables since this problem started (all entities were deleted fine just few days ago) but thats starting to really annoy me, but i cant figure how to do this.
Thanks!
You have set CascadeType.ALL on your parent and the best way to delete should be call one single delete on parent entity
If you try to delete a child, it can be hibernate will propagate delete on a parent that has still children not still deleted.
Last resort with this king of problem is:
Enable logs on Spring Boot Application
Run sql query generated in SQL server
Find where the error happens evaluating the current database condition
Change JPA if necessary

Apply filter on linking table in Hibernate

I've got a classic case of Category <-> Category_Product <-> Product relation.
CategoryDTO class has Set<ProductDTO> member that defined as:
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(
name = "Category_Product",
joinColumns = #JoinColumn(name = "CAT_ID"),
inverseJoinColumns = #JoinColumn(name = "PROD_ID")
)
public Set<Product> getProducts() {
return products;
}
The problem is that I want to have Category->Product relation if and only if the Category_Product.ENABLED is '1'.
I tried to use #FilterJoinTable annotation but, as I understand, it works on entities only (not on linking table), so it doesn't help here.
I hope that there is an elegant solution that uses Hibernate built-in features.
Hibernate filters documentation is clear regarding #FilterJoinTable:
When the collection use an association table as a relational
representation, you might want to apply the filter condition to the
association table itself or to the target entity table. To apply the
constraint on the target entity, use the regular #Filter annotation.
However, if you want to target the association table, use the
#FilterJoinTable annotation.
So, it should work.

Adding an entity into an large Many-To-Many relationship in JPA

I have a Group entity that has a list of User entities in a many to many relationship. It is mapped by a typical join table containing the two IDs. This list may be very large, a million or more users in a group.
I need to add a new user to the group, typically that will be something like
group.getUsers().add(user);
user.getGroups().add(group);
em.merge(group);
em.merge(user);
If I understand typical JPA operation, will this require pulling down the entire list of 1 million+ users into the collection in order to add the new user and then save? That doesn't sound very scalable to me.
Should I simply not be defining this relationship in JPA? Should I be manipulating the join table entries directly in a case like this?
Please forgive the loose syntax, I'm actually using Spring Data JPA so I don't directly use the entity manager directly very often, but the question seems to be general to JPA so I wanted to pose it that way.
Design your models like this and play with UserGroup for associations.
#Entity
public class User {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user",fetch = FetchType.LAZY)
#OnDelete(action = OnDeleteAction.CASCADE)
private Set<UserGroup> userGroups = new HashSet<UserGroup>();
}
#Entity
#Table(name="user_group",
uniqueConstraints = {#UniqueConstraint(columnNames = {"user_id", "group_id"})})
public class UserGroup {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
#ForeignKey(name = "usergroup_user_fkey")
private User user;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "group_id", nullable = false)
#ForeignKey(name = "usergroup_group_fkey")
private Group group;
}
#Entity
public class Group {
#OneToMany(cascade = CascadeType.ALL, mappedBy="group", fetch = FetchType.LAZY )
#OnDelete(action = OnDeleteAction.CASCADE)
private Set<UserGroup> userGroups = new HashSet<UserGroup>();
}
Do like this.
User user = findUserId(id); //All groups wont be loaded they are marked lazy
Group group = findGroupId(id); //All users wont be loaded they are marked lazy
UserGroup userGroup = new UserGroup();
userGroup.setUser(user);
userGroup.setGroup(group);
em.save(userGroup);
Using the ManyToMany mapping effectively is caching the collection in the entity, so you might not want to do this for large collections, as displaying it or passing the entity around with it triggered will kill performance.
Instead you might remove the mapping on both sides, and create an entity for the relation table that you can use in queries when you do need to access the relationship. Using an intermediate entity will allow you to use paging and cursors, so that you can limit the data that might be brought back into usable chunks, and you can insert a new entity to represent new relationships with ease.
EclipseLink's attribute change tracking though does allow adding to collections without the need to trigger the relationship, as well as other performance enhancements. This is enabled with weaving and available on collection types that do not maintain order.
The collection classes returned by getUsers() and getGroups() don't have to have their contents resident in memory, and if you have lazy fetching turned on, as I assume you do for such a large relationship, the persistence provider should be smart enough to recognize that you're not trying to read the contents but just adding a value. (Similarly, calling size() on the collection will typically cause a SQL COUNT query rather than actually loading and counting the elements.)

How do I create an index on join tables using Hibernate annotations?

I have a relationship as follows using Hibernate annotations, this is what I tried:
public class Job{
...
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "jobs_resource_locations")
#ForeignKey(name = "job_inputs_fk")
#Index(name="job_inputs_fk")
private List<FileSystemLocation> inputs;
This sort of thing works nicely on ManyToOne like so:
#ManyToOne
#JoinColumn(name = "service_call_id", referencedColumnName = "id")
#ForeignKey(name = "job_service_call_fk")
#Index(name = "job_service_call_fk")
private ServiceCall serviceCall;
I wanted to ensure that the foreign key gets indexed on PostgreSQL and that the schema looks similar on MySQL, hence the #ForeignKey and #Index with the same name (MySQL always creates an index with the same name as the FK).
I cannot create the index on the inverse side because FileSystemLocation is unaware of the relationship. Hence the JoinTable.
The former example fails since Hibernate finds no column in Job to index:
org.hibernate.MappingException: Unable to find logical column name from physical name null in table jobs
Does anyone know how to create indices on JoinTable foreign keys using Hibernate?
It's not exactly the answer you would like to receive, but this is the expected behavior. In other words: this is not supported. See the following JIRA for more details:
https://hibernate.atlassian.net/browse/HHH-4263

Categories