How to persist an EnumMap in Hibernate/JPA? - java

I'm currently trying to persist data which I'd stored as a Map in Java.
I've gotten this to work using List already, however when I try to make this a Map instead using #MapKeyEnumerated and #MapKey it acts a bit unexpected.
ExampleData.java
#MapKey(name = "feature")
#MapKeyEnumerated(EnumType.STRING)
#OneToMany(targetEntity = ExampleFeature.class, mappedBy = "exampleData")
private Map<Feature, ExampleFeature> features;
ExampleFeature.java
#ManyToOne
#JoinColumn(name = "example_id", nullable = false)
private ExampleData exampleData;
#Enumerated(EnumType.STRING)
#Column(name = "feature", nullable = false)
private Feature feature;
I'm able to add rows to the database manually outside of runtime, and when running the application it's able to load those entities correctly. All other entities are also working as intended.
When persisting entities in from the application, the ExampleFeature does appear in the Map, but all properties in the instance are the default Java values, and after stepping-out
Dependencies
org.hibernate:hibernate-core:5.4.17.Final
org.hibernate:hibernate-c3p0:5.4.17.Final
Could anyone point me in the right direction as to what I might have wrong here?

The issue is that in the project, #save() was only called on ExampleData, but not on any of the relational properties under it. This means that Hibernate would try to persist the ExampleData instance, however not any nested properties such as the EnumMap.
The solution is to set cascade = CascadeType.ALL which will tell Hibernate to persist changes in relational entities such as ExampleFeature when persisting ExampleData.
ExampleData.java
#MapKey(name = "feature")
#MapKeyEnumerated(EnumType.STRING)
#OneToMany(targetEntity = ExampleFeature.class, mappedBy = "exampleData", cascade = CascadeType.ALL)
private Map<Feature, ExampleFeature> features;

Related

Hibernate FK reference issue

I have an entity with reference to another one by FK, at the same time I have a field mapped on the same column to have access right to the identifier, let's say
#Entity
#Table(name = "book")
public class Book {
#Id
#GeneratedValue
Long id;
#JoinColumn(name = "author_id")
#ManyToOne(fetch = FetchType.LAZY)
Author author;
#Column(name = "author_id", insertable = false, updatable = false)
Long authorId;
}
so, for now on select via JPA repository (findById for instance) the field of "authorId" is always null, but in actual database it exists and object of "author" fills correctly. Tested in the transaction and outside - result is the same.
About app - it is spring boot 2.2.8 with spring data
Are there any ideas where I can be wrong?
*Update: found the reason - all the found entities are cached somehow, after detaching them from persistence context all data loads as expected. Seems it's clear, but still cant get where interactions with these entities appear, obviously not in my tx - it is pretty small and simple. Never thought that neighboring transactions can affect cache this way =((

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)) ;

Hibernate: Make a ManyToOne relationship work with unmatched key

I have a many-to-one relationship between two objects: SomeProjectType and Work Orders. In SomeProjectType, I have:
#OneToMany(mappedBy = "project", fetch = FetchType.EAGER)
private Set<WorkOrder> workOrders;
SomeProjectType has a "ProjectKey" as the #id for it.
And in WorkOrder I have:
#ManyToOne
#JoinColumn(name = "WorkOrderProjectKey")
private SomeProjectType project;
The issue I am having is that sometimes in WorkOrder, the "WorkOrderProjectKey" has a project key that doesn't exist in SomeProjectType (I am not sure why, but it is by design).
My question is: Is there a way to have Hibernate still return back rows even if some do not match? I have tried "nullable=true" and "optional=true" but it still won't work.
try to this code because i have same problem then i will change code and work properly.
Primary Key Tables
#OneToMany(mappedBy = "project")
private List<WorkOrder> workOrders;
Foreign Key Table
#ManyToOne
#JoinColumn(name = "WorkOrderProjectKey")
private SomeProjectType project;
I got it to work! Under the #ManyToOne, I put the following and it gets everything.
#NotFound( action = NotFoundAction.IGNORE )
Got this from the answer here:
Hibernate chokes on missing rows when dealing with a legacy database

Automatically hibernate save relationship

Basically my question is why if I have an Hibernate relationship like this one.
#OneToMany(cascade = {CascadeType.ALL})
#JoinColumn(name = "candidacy_id", nullable = false)
#XmlElement
#JsonIgnore
#Getter
#Setter
private List<EvaluationSelectionCriteria> evaluationSelectionCriterias = new ArrayList<>();
#ManyToOne
#JoinColumn(name = "candidacy_id", nullable = false, insertable = false, updatable = false)
#XmlTransient
#Getter
#Setter
private Candidacy candidacy;
Why if I do this candidacy.setEvaluationSelectionCriteria(list) automatically this list is persisted in database?
I would like to use the EvaluationSelectionCriteria as a repository to render a list of "future" EvaluationSelectionCriteria
Could be because is not Lazy?
More detail explanation
So would be like I call method a, there I´m get from database entity A then I set a list into A and then I return A in the method but I´m not saving A, when I see the value of the list already have ids!!!
If you do not want the list to be saved when the parent entity is saved/merged, you should remove or restrict the cascade setting for the relationship:
#OneToMany
private List<EvaluationSelectionCriteria> evaluationSelectionCriterias
or
#OneToMany(cascade = CascadeType.REMOVE) // or other values from the enum
private List<EvaluationSelectionCriteria> evaluationSelectionCriterias
EDIT: If you want to fetch an entity in a transactional method and modify it, you can restrict the scope of the transaction to the fetching only. Then modify the entity outside the transactional method. Later, you can merge the detached entity if needed.
Since collection attributes are lazy per default you will either need to
access their content while still inside the transactional method - so the collection can be fetched from the DB. Please note that you will have to call a method on the collection that actualy requires it's content to be loaded, like getCriterias().size().
use LEFT JOIN FETCH to load the collection as a side effect of the query.
I would not modify the FlushMode for the session - while this would probably work, it feels like a kludge - it does not communicate your intent very well. Explicitly fetching the collection and modifying it outside the transaction expresses your intent better IMO.
I found the solution, I forgot to say that I´m using Spring, so finally I add the #Transactional(readOnly=true) into my method instead in the service class level.

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.)

Categories