JPA: Modelling m:n relationship & join-table values - java

having a m:n real-world relationship between some entities, e.g. user <---> group
Now I want to model this relationship and store additional information based on it, e.g. a field "quality".
I heard that I would have to create a new join table user_group as follows:
id | user_ref | group_ref | quality
----------------------------------
1 1 1 0.5
2 1 2 1.3
... ... ... ...
The corresponding entity has two related entities (private members) user and group, annotated with the #ManyToOne-annotation.
On the other hand, both, my user and my group have a set of related user_group-entities, both private members and declared with the #OneToMany-annotation.
I have three questions:
Is this the right way to model the problem of additional fields in JPA 2.0?
I not allowed to use both user and group in user_group as primary key
since they are not valid primary key types. Is it really necessary to declare a new primary.
Is this a common workflow with these join-tables/entities?
...
EntityManager em = ...
...
em.getTransaction().begin();
User u = new User("Pete");
Group g = new Group("Anonymous workaholics")
UserGroup ug = new UserGroup();
ug.addUser(u);
ug.addGroup(g);
em.persist(u); em.persist(g); em.persist(ug);
em.getTransaction().commit();
em.close()
Thanks a lot!

Yes, it's the good way to do it.
It's possible to have a composite primary key consisting of two ManyToOne associations, but it's much more complex, both in the mapping and in the rest of the application, to handle that, and it's also less efficient. You have an entity, so just do like for all the other entities and use an auto-generated, single-column primary key. The fact that this entity used to be a join table of a many-to-many association is irrelevant.
Yes, that seems OK, except the addUser() and addGroup() methods should be named setUser() and setGroup(): there's only one user and one group for a given UserGroup. I would also use another name for the entity itself. Something like "Membership" for example.

Related

Used only #mappedBy , not #JoinColumn - join column created in OneToMany relationship, what is then the use of JoinColumn?

I am new to Hibernate. I have a OneToMany relationship with bidirectional mapping between Account and Transaction. I am not using #JoinColumn in either class and using #mappedBy in the non owning Account class. And everything is working fine. Using H2 in memory database, new join column is created in Transaction table. Then what is the use of #JoinColumn in OneToMany relationships? Is it for unidirectional mapping only? Below is the code. I also read for reference JPA JoinColumn vs mappedBy
public class Account {
#OneToMany( mappedBy="account", cascade=CascadeType.ALL)
List<Transaction> list= new ArrayList<Transaction>();
}
public class Transaction {
#ManyToOne
Account account;
}
Application class :
Account a = new Account("savings");
Transaction t1 = new Transaction("shoe purchase", 45);
t1.setAccount(a);
a.getList().add(t1);
accountRepository.save(a);
output:
Transaction table has an entry with foreign key which is account number in that one row in Account table. ACCOUNT_ID column in created in Transaction table.
There are no extra tables created.
Jpa works on the idea of configuration by convention. So, it will perform configuration on your behalf whenever it can. Think of the #Column annotation, you don't have to apply it on every entity attribute, you would need it only when you have to change something about the attributes.
It's the same with #JoinColumn, when you added #ManyToOne, Jpa already knows that you will need the join column and thus was added for you and the default naming convention for the foreign key was applied (attributename_primarykeyoftheothertype).
Use of
mappedBy
is instruct framework to enable bi-directional relationship. Because of #ManyToOne on Transaction class you Your Transaction Table will have foreign key referring to Account table primary key. By default, Hibernate generates the name of the foreign key column based on the name of the relationship mapping attribute and the name of the primary key attribute. In this example, Hibernate would use a column with the name account_id to store the foreign key to the Account entity.
#JoinColum
can be used If you would like override default foreign key name like #JoinColum(name="acc_id")
MappedBy intructs Hibernate that the key used for the association is on the other side of the association.Like in this case ACCOUNT_ID is being created on Account table.
That means that although you associate two tables together, only one table is having foreign key constraint to the other one.
MappedBylets you to still associate from the table not having foreign key constraint to the other table.

JQPL Update Query to update entity without using the primary key

This may be a simple question, but I'm trying to find out if there is a way that I can create a JPQL update query that would allow me to update a single Persisted Entity using a unique column identifier that is not the primary key.
Say I have and entity like the following:
#Entity
public class Customer {
#ID
private Long id;
#Column
private String uniqueExternalID;
#Column
private String firstname;
....
}
Updating this entity with a Customer that has the id value set is easy, however, id like to update this customer entity using the uniqueExternalId without having to pre-query for the local entity and merge the changes in or manually construct a jpql query with all the fields in it manually.
Something like
UPDATE Customer c SET c = :customer WHERE c.uniqueExternalId = :externalId
Is something like this possible in JQPL?
You cannot do it in the exact way you describe - by passing an entity reference, but you can use bulk queries to achieve the same effect.
UPDATE Customer c SET c.name = :name WHERE c.uniqueExternalId = :externalId
Please note that you will have to explicitly define each updated attribute.
It is important to note that bulk queries bypass the persistence context. Entity instances that are managed within the persistence context will not reflect the changes to the records that are changed by the bulk update. Further, if you use optimistic locking, consider incrementing the #Version field of your entities with the bulk update:
UPDATE Customer c SET c.name = :name, c.version = c.version + 1 WHERE c.uniqueExternalId = :externalId
EDIT: The JPA 2.0 spec advises in ยง 4.10:
In general, bulk update and delete operations should only be performed
within a transaction in a new persistence context or before fetching
or accessing entities whose state might be affected by such
operations.

Hibernate one-to-one and one-to-many

I currently have a one-to-many relation between 2 table: I have:
Table A (id, ...)
Table B (tableAId, ...) (has no id of it's own).
In the mapping this is done using <list>.
The issue I am having, is that I have to add another one to one relationship from A to B. Something like this:
class A {
public B b; // new part
public List<B> bs; // already existing part.
}
I have a boolean column in the table by which I should differentiate between the one-to-one and one-to-many. I'm not sure how to write the hibernate mapping for these tables. Could any of you help me out with this?
I think you can do this by using 'mappedBy', have you tried that?
http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html_single/#entity-mapping-association

Hibernate and a Complex many-to-one on Two Columns

I have a BUSINESS table that looks like this:
BUSINESS_ID | BRN | CODE | PARENT_ID
A Business can have a parentBusiness, which is joined from child.parent_id->parent.brn and child.code->parent.code and can be represented by the query:
select * from business childbus left join business parentbus on childbus.parent_id=parentbus.brn and childbus.yppa_code=parentbus.yppa_code where childbus.business_id=?
How can I create that many-to-one relationship for business.parentBusiness? The following many-to-one mapping creates the link from PARENT_ID to BRN but how do I get the constraint from the CODE column in there?
<many-to-one name="parentBusiness" class="Business" column="PARENT_ID" property-ref="brn"/>
EDIT
It was suggested in an answer below that I try to use a formula. According to the docs the formula needs to return just the PK for the object and it will figure out how to populate itself. Here is where I'm at:
<many-to-one name="parentBusiness">
<formula>(select parentBusiness.business_id from business parentBusiness where parentBusiness.brn=parent_id and parentBusiness.code=code)</formula>
</many-to-one>
But this creates an sql error
...
from Business business0_
left outer join Business business1_ on
(select parentBusiness.business_id from business parentBusiness where parentBusiness.brn=business0_.parent_id and parentBusiness.code=business0_.code)=business1_.BUSINESS_ID
because, of course
ORA-01799: a column may not be outer-joined to a subquery
How should my formula be set up?
Many thanks in advance.
you could try to use the formaula attribute.
here is some documentation about how to use it...
I was never able to get this complex relationship working as stated in the original question. I was, however, able to convince management that this was a poor data model.

Can you specify date-based partition keys when joining to related entities?

I have two related entities being mapped by JPA annotations backed by hibernate. The entities both have sequence-backed identity columns in oracle. Our also have monthly partitions, represented by a column called ENTRY_DATE.
T_MASTER T_JOINED
--------- -----------
MASTER_ID JOINED_ID
ENTRY_DATE ENTRY_DATE
MASTER_ID(FK)
... ....
To gain the benefit of the partition key, I'd like Hibernate to join on both the identity IDs and the partition key, but when I use the following annotation in the Joined class:
#ManyToOne
#JoinColumns(value={
#JoinColumn(name="MASTER_ID"),
#JoinColumn(name="ENTRY_DATE")})
private Master master;
I get errors about having too many join columns. I am forced to use
#JoinColumn(name="MASTER_ID")
private Master master;
I'm a bit of a JPA/Hibernate noob. Is it possible to use a partition key in addition to the primary key when joining to related entities?
Thanks!
You probably need to declare both columns as parts of composite primary key in Master. Hibernate would not care that much about what is real PK in the database. Mapping wil be slightly more complex with #Embeddable but it should solve the problem.
yes.
Using native sql it looks as follows:
Query query = session.createSQLQuery( "SELECT {T_master.*}, {T_joined.*} FROM schema.t_master AS T_master OUTER LEFT JOIN schema.T_joined AS T_joined ON T_master.ENTRY_DATE = T_joined.ENTRY_DATE AND T_master.MASTER_ID = T_joined.JOINED ID " /*WHERE ... */ );
query.setEntity( "T_master", T_master.class);
query.setJoin( "T_joined", "T_master.joinedSet"); //joinedSet is the one-to-many mapping
query.setJoin( "T_master", "T_joined.master" );
Thus, you get to be very verbose on your query, and hibernate only acts as an object mapper.
Using Criteria or HQL, it is impossible to have OUTER LEFT JOIN ON some field other than primary keys. This is why if you have such an option, use native SQL code.

Categories