Attributes on Many-to-Many relationships (Hibernate) - java

I have entity classes A and C. They are mapping the tables tblA and tblC and have a many-to-many relationship between them, with tblB to map between them. tblB contains A_ID, C_ID and SetDate, the last one being the date it was set, thus an attribute to the relationship. My question is, how do I best map in this attribute? At the moment they're unmapped, like this:
A:
#ManyToMany(targetEntity=C.class, cascade={ CascadeType.PERSIST, CascadeType.MERGE } )
#JoinTable(name="tblB", joinColumns=#JoinColumn(name="A_ID"), inverseJoinColumns=#JoinColumn(name="C_ID") )
private Collection<C> Cs;
C:
#ManyToMany( cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "Cs", targetEntity = A.class )
private Collection<A> As;
How should I get tblB.SetDate out of this?
Cheers
Nik

From what I know, it is not possible to map it this way, you have to switch to One-To-Many and a Many-To-One relationships, with your B in the middle. Your date will be an attribute of B.
For this lack of evolutivity, the Hibernate documentation recommends to avoid the Many-To-Many in general, and use the two relationships from the beginning.

See
#ManyToMany Hibernate Question (can add extra field?)
And
how to make a composite primary key (java persistence annotation)
regards,

Related

#PrimaryKeyJoinColumn with name of foreign key

Under hibernate-jpa-api-2.0, can i specify the name of the foreign key using #PrimaryKeyJoinColumn with oneToOne relation ?
I tried the folowing but it doesn't seem to work.
#OneToOne(optional = false, fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn(name = "CARID")
#ForeignKey(name = "FK_CAR_CORP")
public CarEntity getCar() {
return car;
}
There are two #ForeignKey annotations can be used:
org.hibernate.annotations.ForeignKey (Hibernate annotation)
javax.persistence.ForeignKey (JPA annotation)
Hibernate 4 (even the last version) sometimes doesn't process, probably cause of bug, javax.persistence.ForeignKey annotation.
For example, with Hibernate 4.3.11 we should use org.hibernate.annotations.ForeignKey on the #OneToMany part of unidirectional association. And we can use javax.persistence.ForeignKey on the #ManyToOne part of bidirectional association.
So you can try to use Hibernate org.hibernate.annotations.ForeignKey annotation.
The mappedBy attribute is only necessary for a bidirectional relationship, this element can be omitted on the annotation. It is used on the source entity to point back to a field on the target entity that defines the relationship (contains #JoinColumn).
The #JoinColumn annotation should be placed upon the billSimpleEntry field to define the column that should be used to join the two tables. In the case of a OneToOne the following applies:
If the join is for a OneToOne or ManyToOne mapping using a foreign key mapping strategy, the foreign key column is in the table of the source entity or embeddable.
Here is a code example:
#OneToOne(cascade=CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name="id") // use actual column name
private ClassName className; // use class name
I don't think #ForeignKey will work for hibernate-jpa-api-2.0, as per doc it's release in 2.1
Since:
Java Persistence 2.1
here is doc

cascade type save update in Hibernate

I am using hibernate with JPA annotations for relationship mapping.
I have three entities in my code User Group & User_Group
User & Group are in a ManyToMany relationship.
User_Group is a kinda bridge table but with some additional fields. So here is the modified mapping code.
User
#Entity
#Table(name = "USERS")
public class User {
#OneToMany(mappedBy = "user")
private Set<UserGroup> userGroups
}
Group
#Entity
#Table(name = "GROUPS")
public class Group {
#OneToMany(mappedBy = "group")
private Set<UserGroup> userGroups
}
UserGroup
#Entity
#Table(name = "USERS_GROUPS")
public class UserGroup {
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "USER_ID")
private User user;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "GROUP_ID")
private Group group;
}
When I set the user & group object to the usergroup & save it.
User user = new User("tommy", "ymmot", "tommy#gmail.com");
Group group = new Group("Coders");
UserGroup userGroup = new UserGroup();
userGroup.setGroup(group);
userGroup.setUser(user);
userGroup.setActivated(true);
userGroup.setRegisteredDate(new Date());
session.save(userGroup);
Things work fine. With CascadeType.ALL the group object & user object are updated too. But when I delete the userGroup object. The child object are deleted too.
Deletion of child objects is a strict no no.
There is no CascadeType.SAVE-UPDATE in JPA, which just does save or update but no delete. How do I achieve this.
If I remove the CascadeType.ALL from the mapping the child objects don't get updated & I need them to be updated.
SAVE_UPDATE is for save(), update(), and saveOrUpdate(), which are 3 Hibernate-proprietary methods. JPA only has persist() and merge(). So, if you want to use cascading on Hibernate-proprietary methods, you'll need to use Hibernate-proprietary annotations. In this case, Cascade.
Or you could stop using the Hibernate Session, and use the standard JPA API instead.
CascadeType.ALL includes CascadeType.REMOVE too.
The solution is to use all CascadeType.* you need except CascadeType.REMOVE, like so:
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.MERGE}))
in your UserGroup definitions.
It's almost always a code smell when propagating from child to parent entity, it should be the other way round.
From Cascading best practices:
Cascading only makes sense only for Parent – Child associations (the
Parent entity state transition being cascaded to its Child entities).
Cascading from Child to Parent is not very useful and usually, it’s a
mapping code smell.
From Hibernate best practices:
Avoid cascade remove for huge relationships
Most developers (myself included) get a little nervous when they see a
CascadeType.REMOVE definition for a relationship. It tells Hibernate
to also delete the related entities when it deletes this one. There is
always the fear that the related entity also uses cascade remove for
some of its relationships and that Hibernate might delete more
database records than intended. During all the years I’ve worked with
Hibernate, this has never happened to me, and I don’t think it’s a
real issue. But cascade remove makes it incredibly hard to understand
what exactly happens if you delete an entity. And that’s something you
should always avoid. If you have a closer look at how Hibernate
deletes the related entities, you will find another reason to avoid
it. Hibernate performs 2 SQL statements for each related entity: 1
SELECT statement to fetch the entity from the database and 1 DELETE
statement to remove it. This might be OK, if there are only 1 or 2
related entities but creates performance issues if there are large
numbers of them.

How to avoid bidirectional insertion on ManyToMany relationship

I have two tables with a m:n relationship. This relationship should be bidirectional when I am extracting the data from the database, so, I need the #ManyToMany in both entity classes. But, at the same time, I need that the non owner class does not insert the owner class when I perform a persist or merge operation on it.
For example, I have the Gene class, that is the owner class, and I have the Ontology class. One Gene has many Ontologies and one Ontology has many Genes. Classical many-to-many relationship. If I persist or merge a Gene I want to insert its Ontologies too, but I don't want that this Ontology insertion insert all other Genes linked to it.
On other hand, if I insert an Ontology I don't want to insert the genes linked to that Ontology.
I have been trying with a lot of JPA tags on the #ManyToMany and nothing works on the way that I want.
Have any one an Idea to solve this problem?
The Gene class
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = GeneTables.GENEINFO_HAS_ONTOLOGY,
joinColumns =
#JoinColumn(name = "GeneInfo_WID", referencedColumnName = "WID"),
inverseJoinColumns =
#JoinColumn(name = "Ontology_WID", referencedColumnName = "WID"))
private Set<Ontology> ontology;
The Ontology class
#ManyToMany(cascade = CascadeType.REFRESH, mappedBy = "ontology")
private Set<GeneInfo> geneInfo;
I tried all the Cascade types and even without the cascade option. The result is the same.
I have this error when I execute this code
EntityManager em = getEntityManager();
m.getTransaction().begin();
em.persist(ontology);
em.getTransaction().commit();
I have this error:
[EL Warning]: 2012-08-29 14:52:13.013--UnitOfWork(544628019)--java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST
at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commitInternal(EntityTransactionImpl.java:102)
at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commit(EntityTransactionImpl.java:63)
at org.jbiowh.core.datasets.ontology.controller.OntologyJpaController.create(OntologyJpaController.java:41)
at org.jbiowh.tools.prototypes.Test.main(Test.java:65)
Solved
I solved the problem. I create controller classes to handle the special cascades.
The create method on the Ontology controller class will be:
if (loadGeneFlag && ontology.getGene() != null && !ontology.getGene().isEmpty()) {
Set<Gene> geneSet = new HashSet<>();
GeneJpaController gController = new GeneJpaController(emf);
for (Gene gene : ontology.getGene()) {
Gene geneOnDB = em.find(Gene.class, gene.getWid());
if (geneOnDB != null) {
geneSet.add(geneOnDB);
} else {
gController.create(gene);
geneSet.add(em.getReference(Gene.class, gene.getWid()));
}
}
ontology.setGene(geneSet);
}
This code will create all gene references using the Gene controller class and not following the cascade operation. This give me the possibility to handle the Gene cascades correctly on the Gene controller class. Now, I don't have any duplicate object neither exceptions.
Where do these Genes come from?
If they are new, then you need to either persist them, or set cascade persist on the genes relationship. If they are existing, then you need to find them in the context of the current EntityManager/transaction.
If you don't want then persisted, then don't add them to the genes collection.
Set cascade=CascadeType.REFRESH on both entities or remove cascade attributes on both entities.
You must use mappedBy on one side of a bidirectional relationship.
See,
http://en.wikibooks.org/wiki/Java_Persistence/ManyToMany#Bi-directional_Many_to_Many

Trouble with Hibernate entities relation

I have a problem with a relation on Hibernate:
I have two Entities --> Solicitud and DetalleAccesorio, and the relation is:
I have one Solicitud with Many DetalleAccesorio, and I need to save the Solicitud with the DetalleAccesorio, and both are diferents tables on the database.. this is the important code of Solicitud:
#Id
#GeneratedValue( strategy=GenerationType.IDENTITY )
#Column( name="num_solicitud" )
private Long numSolicitud;
#OneToMany( fetch=FetchType.LAZY, mappedBy="codDetalle", cascade={ CascadeType.ALL } )
private List<DetalleAccesorio> listaAccesorios; `
What I need to save the same primary key of Solicitud on DetalleAccesorio?
Basically you have to set the relation in the child entity (DetalleAccesorio) like:
#ManyToOne
Solicitud codDetalle
Take a look to this thread and also this documentation
Remove the mappedBy="codDetalle" from the relation. the mapped by create bidirectional relation . in bidorectional relation the side with mappedby (the onetomany) not control on the relation any more. this is the reason that hibernate dosnt recognize that there is a relation here.
If you want to have bidirectional than implement it as it need to be. means both side need to annotate and the important part - you have to maintain the reference in java both ways!
look on this.
as you can see there in bidirectional (and i dont think that you need , so remove the mapped by and it will be solved ) you need in code to maintain 2 directions:
Changes made only to the inverse end of the association are not
persisted.
In fact it has a very simple solution
If you are willing to have access to Solicitud from DetalleAccesorio:
In the DetalleAccesorio class you have to have this:
#ManyToOne(fetch=fetchType.EAGER)
Solicitud codDetalle
It was optional.
(It's not optional) In the Solicitud try this:
#OneToMany(fetch=FetchType.LAZY)
#JoinTable(name = "Solicitud_DetalleAccesorio_MAPPING", joinColumns = #JoinColumn(name = "DetalleAccessorio_ID"), inverseJoinColumns = #JoinColumn(name = "Solicitu_ID"))
List<ManagerDetails> managerDetails;
this is the code from the entity DetalleAccesorio
#Id
#Column( name="cod_detalle" )
private Long codDetalle;
#Column( name="cod_accesorio" )
private Integer codAccesorio;
And the id "codDetalle" has to be the same id from Solicitud to save it (numSolicitud)...

JPA: implicit cascades for relationships mapped as #ManyToMany #JoinTable?

I have the following mapping:
#Entity
#Table(name = "Prequalifications")
public class Prequalification implements Serializable
{
...
#ManyToMany
#JoinTable(name = "Partnerships", joinColumns = #JoinColumn(name = "prequalification_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "company_id", referencedColumnName = "id"))
private Set<Company> companies;
...
}
In a #ManyToMany + #JoinTable mapped relationship, isn't it kind of implicit that the association (link) entities (here Partnerships) are automatically persisted, removed, etc. even though
by default, relationships have an empty cascade set
? The above quote was taken from "Pro JPA 2, by Mike Keith".
Executing
em.merge(prequalification);
on the above entity does persist the associated partnerships without any cascade types specified.
Am I correct that this implicit cascade has to be performed? This isn't mentioned anywhere I looked...
The rows in the join table will be inserted/deleted as part of the owning Entity (if bi-directional the side without the mappedBy). So if you persist or remove or update the Prequalification the join table rows will also be inserted or deleted.
The target Company objects will not be cascaded to. So on remove() they will not be deleted, if the list is updated they will not be deleted unless orphanRemovla is set. Persist should also not be cascaded, but what happens when you have references to "detached" objects is somewhat of a grey area. Technically an error should be thrown, because the object is new and the relationship was not cascade persist. It may also try to insert and get a constraint error. It should not cascade the persist, although your object model is technically in an invalid state, so what occurs may depend on the provider.
Wanted to add a comment, but don't have enough rep for it.
I had the same question as #D-Dᴙum: "Where in the docs can we find a reference to this behaviour?"
I found it in the Hibernate docs (many-to-many).
If you scroll just a bit just below the code example there, you will find:
When an entity is removed from the #ManyToMany collection, Hibernate simply deletes the joining record in the link table. Unfortunately, this operation requires removing all entries associated with a given parent and recreating the ones that are listed in the current running persistent context.
Where the "link table" refers to the "join table".
Hope this helps.

Categories

Resources