ManyToMany properties - java

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.

Related

Owner side with JoinColumns annotation

I have the following scenario:
I'm having troubles figuring out what the owner side is and why ?
Please can you help ?
public class Basket
{
...
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumns({#JoinColumn(name="BASKET", referencedColumnName="ID")})
public Set<Product> getProductList() {
return this.productList;
}
}
public class Product
{
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
#JoinColumns({#JoinColumn(name="BASKET", referencedColumnName="ID")})
public Basket getBasket() {
return this.basket;
}
}
Basket is the "One" side of the relationship and there are many products identified as Baskets. So the "one" is usually the parent code and the child records reference the parent. Usually the easiest way to understand these is to look at the database foreign key constraint (hopefully there is one) that connects the backing tables.
One client has One basket and One basket has One owner, so:
class Owner has basket in relation #OneToOne.
One basket has many products, so:
class Basket has List of products in relation #OneToMany.
Many Products has One basket so:
class Product has relation in #ManyToOne.
The extract from the JPA 2.0 specification might help you to understand the issue:
2.10.2Bidirectional ManyToOne / OneToMany Relationships
Assuming that:
Entity A references a single instance of Entity B.
Entity B references a collection of Entity A[21].
Entity A must be the owner of the relationship.
The following mapping defaults apply:
Entity A is mapped to a table named A.
Entity B is mapped to a table named B.
Table A contains a foreign key to table B. The foreign key column name is formed as the concatenation of the following: the name of the relationship property or field of entity A; "_"; the name of the primary key column in table B. The foreign key column has the same type as the
primary key of table B.
And in section 11.1.21 the following is stated:
If there is more than one join column, a JoinColumn annotation must be specified for each join column using the JoinColumns annotation. Both the name and the referencedColumnName elements must be specified in each such JoinColumn annotation.
In your case, there is only one join column. So you don't need to the #JoinColumns annotation. Just use#JoinColumn. The #JoinColumn annotation is used always on the owning side of the relationship, that is the #ManyToOne side in this case.

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.

org.hibernate.AnnotationException: referencedColumnNames referencing not mapped to a single property

I ran into the below exception while mapping a one-to-one between 2 entities. The 1 first entity has embedded composite key. The second entity also has embedded composite key. The tables are part of legacy system. Data is flat, relations are not well defined. Please help.
Caused by: org.hibernate.AnnotationException: referencedColumnNames(FLAG_NAME) of net.javabeat.spring.model.ReferralsM.mnEditFlag referencing net.javabeat.spring.model.MnEditFlag not mapped to a single property
at org.hibernate.cfg.BinderHelper.createSyntheticPropertyReference(BinderHelper.java:205)
at org.hibernate.cfg.ToOneFkSecondPass.doSecondPass(ToOneFkSecondPass.java:116)
at org.hibernate.cfg.Configuration.processEndOfQueue(Configuration.java:1515)
at org.hibernate.cfg.Configuration.processFkSecondPassInOrder(Configuration.java:1440)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1358)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1727)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1778)
at org.springframework.orm.hibernate4.LocalSessionFactoryBuilder.buildSessionFactory(LocalSessionFactoryBuilder.java:247)
at org.springframework.orm.hibernate4.LocalSessionFactoryBean.buildSessionFactory(LocalSessionFactoryBean.java:373)
at org.springframework.orm.hibernate4.LocalSessionFactoryBean.afterPropertiesSet(LocalSessionFactoryBean.java:358)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1571)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1509)
... 34 more
Here is my one to one mapping in the main/parent table.
#OneToOne(targetEntity = MnEditFlag.class, fetch = FetchType.LAZY)
#JoinColumn(name = "REFFLG", referencedColumnName = "FLAG_NAME", insertable = false, updatable = false)
MnEditFlag mnEditFlag;
The cause of the issue is that you are trying to use a single join column, while the identity of the referenced entity is defined by multiple columns. You simply have to define all the needed join colums and you are good to go:
#JoinColumns({
#JoinColumn(name = "REFFLG", referencedColumnName = "FLAG_NAME"),
#JoinColumn(name = "OTHER_KEY", referencedColumnName = "SOME_OTHER_NAME"))
...
})
MnEditFlag mnEditFlag;
OT: you should not need the targetEntity attribute on the OneToOne annotation. This is already defined by the type of the target entity: MnEditFlag. You probably need targetEntity only for untyped Collections.
EDIT: If there is a single join column, which is only part of the PK and you cannot change the existing tables, perhaps you can define a new join table with all necessary columns.
Then you define the join table to be used for the relationship:
#JoinTable(name="ReferralsM_MnEditFlag",
joinColumns={
#JoinColumn(name="REFERRALS_ID1", referencedColumnName="ID1"),
#JoinColumn(name="REFERRALS_ID2", referencedColumnName="ID2")
}, inverseJoinColumns={
#JoinColumn(name="REFFLG", referencedColumnName="FLAG_NAME"),
#JoinColumn(name="REFFLG2", referencedColumnName="FLAG_NAME2")
})
MnEditFlag mnEditFlag;
You would have to migrate the data to the new join table programmatically or by queries.
Unfortunately you cannot define a relationship with a partial PK with vanilla JPA, perhaps Hibernate has such a feature, like one-to-one by query, but I cannot confirm it.
EDIT2: The join table should contain all PK columns for both entities to be fully functional. That is why I have defined two join columns for each side in my example. The number of columns and their names are purely exemplary.
Extracting only the one join column you already have in your table would not add any value.
The optimal solution would be to change the entity tables so they define a proper relationship between the entities. It is sufficient to change only one of the tables and define it as the owning side as you did, but with all FK columns. This would require a migration effort, since you would need to add the data for the missing FK columns like described above.
EDIT3: The strategies I recommended were based on the assubmption that you want to have complete CRUD functionality. If you just want to pull the data for display or reporting, a view is perfectly fine. You can define the columns you need and map the whole view to a single entity. However, as it is a view, you will not be able to change the data or migrate it.
You could use targetEntity on #ManyToOne. The targetEntity should be equal to the parent (probably abstract) class.
The temporary solution is here depending to your class hierarchy:
https://hibernate.atlassian.net/browse/HHH-4975

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

Hibernate #OneToMany - mapping to multiple join tables

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.

Categories