Hibernate #OneToMany - mapping to multiple join tables - java

Consider the following domain model:
Issue
- id
- List<Comment>
Entry
- id
- List<Comment>
Comment
-id
-comment
In my design, I was attempting to create two join tables to manage the associations; issue_comments, and entry_comments. I assumed #OneToMany on Issue & Entry, but how do you map the multiple join tables? Using hibernate annotations, how can this be mapped?

If you can change your domain model, take a look at answer given by cletus. You'll only have one table to update so it'll provide better performance.
If you cannot change your domain model, you can map your comment collections via join tables:
// ENTRY
#OneToMany
#JoinTable(
name="ENTRY_COMMENTS",
joinColumns = #JoinColumn( name="entry_id"),
inverseJoinColumns = #JoinColumn( name="comment_id")
)
public List<Comment> getComments()
// ISSUE
#OneToMany
#JoinTable(
name="ISSUE_COMMENTS",
joinColumns = #JoinColumn( name="issue_id"),
inverseJoinColumns = #JoinColumn( name="comment_id")
)
public List<Comment> getComments()
Your comments would still be in the same table for both issues and entries; only join tables will be different. Note that this is a uni-directional relationship. Details are here

This is what's known as an exclusive arc. Don't do it. Instead do this:
Post (id, List<Comment>)
Issue extends Post
Entry extends Post
Comment (id, comment)
In other words, create a common superclass for Issue and Entry and have the comments on that.

Related

Hibernate Second level cache doesn't work for OneToOne associations

I am trying to enable Hibernate's 2nd level cache but cannot avoid multiple queries being issued for OneToOne relations.
My models are:
#Entity
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Business {
#OneToOne(mappedBy = "business", cascade = {CascadeType.REMOVE}, fetch = FetchType.EAGER)
private Address address;
}
#Entity
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Address {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "business_id", unique = true, nullable = false, foreignKey = #ForeignKey(name = "fk_business_id"))
private Business business;
}
When I run session.get(Business.class, id) with the Business with id id in the cache, no query is issued for loading Business but it does for Address.
I understand that Address is the relation owner and that in the Business cache entry there's no Address.id information, but wouldn't it be possible to solve this problem by applying the same mechanism as *ToMany relations does, creating a new cache region for each field? Assuming Business 1 is related to Address 2, there would be the following regions and entries in my cache after a first load:
Business
Business#1 -> [business model]
Business.address
Business.address#1 -> [2]
Address
Address#2 -> [address model]
I have tried to make it work by annotating Address.business with #NaturalId and the Address class with #NaturalIdCache. The cache region is created and populated but session.get(Business.class, id) does not use it.
My Business model has many more OneToOne relations whose foreign key is on the other side (not the Business) and we must list several at a time so the database server has to process dozens of queries per HTTP request.
I have read the Hibernate's User Guide, Vlad Mihalcea's explanation on 2LC and its in-memory dehydrated format, Baeldung's explanation and several other StackOverflow answers and cannot find a way to solve this.

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

Caching an entity with spring data jpa

I have defined roles in my database role_template.
#Entity
#Table(name = "role_template")
#Cacheable
public class Role {
#Id
private int id;
private String name;
#Transient
private final int identity = new Random().nextInt(1000000) + 1;
}
I have one role at this moment with id=1 and name="admin"
My entity User has a list of roles defined as follow
#Entity
#Table(name = "app_user")
public class User {
[...]
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "role_assign",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id"))
private Set<Role> roles;
}
Roles are joined to users with my association table
[Table `role_assign`]
int user_id
int role_id
My problem is predictable, #Cacheable does not work.
I tried with 2 users, they have the same Role template, but not the same instance. The transient variable identity isn't equals for the role of the two users.. My app configuration is good, I think I forgot something to make it working for #JoinTable
Is this the javax.persistence.Cacheable annotation? Because it should.
I think your understanding of how caching works with JPA is wrong and your observations is not sufficient to decide if caching takes place or not.
#Cacheable is about the 2nd level cache. If an entity is pulled from the cache it is instantiated from information stored in the cache, and not actually the same instance. The latter wouldn't work. Entities can always only be attached to a single session, but the 2nd level cache lives across sessions.
Two representations of an entity should be the same instance exactly if they belong to the same session.
In order to decide if the cache is used or not you have two good options:
Log the SQL statements issued against the database and see if the data for the entity is selected over and over again, or only once.
Log the cache interaction and see what is going on directly.
How you do that depends on the JPA provider you use. Here are instructions for Hibernate.

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.

ManyToMany properties

This may be a stupid question or may be asked, But i didnt find which helped me.
Can anyone of you guys tell me what these properties described in ManyToMany relation do. And if there are any which i missed and should be used in ManyToMany mapping please help me with that too.
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "customer_service",
joinColumns = {#JoinColumn(name = "customer_id")},
inverseJoinColumns = #JoinColumn(name = "service_id"))
This is description of a many-to-many relationship between (I am guessing) Customer and Service tables. Technically all attributes are optional so:
#ManyToMany
private Set<Customer> services;
in Service would be fine (similar annotation on Customer side if the relationship is bidirectional).
Now assuming you have a some basic relational databases design knowledge:
#JoinTable describes a join table, the one used to store relationships between customer and service. Each row contains one reference to customer and one to service. This table will be named customer_service
joinColumns - definition of column(s) referencing the second side of the relationship - customer_id
inverseJoinColumns - definition of column(s) referencing back to the starting side - service_id in this example.
Join- and inverse join columns are symmetric - if you define the many-to-many relationship on the other side you switch them. Otherwise everything will work from Java perspective but Hibernate will create a foreign key from customer_id to Service table and vice-versa and will insert values accordingly.

Categories