I'm working on a java spring mvc application with hibernate. I have two Entities Acl and AclGroupand These two entities have Many to Many relationship with a join table. But, when I save an AclGroup object, hibernate doesn't insert any record in join table and just inserts into AclGroup table. Here is structure of my classes:
Acl.java:
public class Acl implements Serializable{
...
#JoinTable(name = "acl_group_acl", joinColumns = {
#JoinColumn(name = "acl_id", referencedColumnName = "id")}, inverseJoinColumns = {
#JoinColumn(name = "acl_group_id", referencedColumnName = "id")})
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Collection<AclGroup> aclGroupCollection;
public Collection<AclGroup> getAclGroupCollection() {
return aclGroupCollection;
}
public void setAclGroupCollection(Collection<AclGroup> aclGroupCollection) {
this.aclGroupCollection = aclGroupCollection;
}
...
}
AclGroup.java:
public class AclGroup implements Serializable{
...
#ManyToMany(mappedBy = "aclGroupCollection",fetch = FetchType.LAZY)
private Collection<Acl> aclCollection;
public Collection<Acl> getAclCollection() {
return aclCollection;
}
public void setAclCollection(Collection<Acl> aclCollection) {
this.aclCollection = aclCollection;
}
}
Here is how I save my object:
AclGroup aclGroup = new AclGroup();
List<Acl> acls = new ArrayList<>();
/*
add some elements to acls
*/
aclGroup.setAclCollection(acls);
/*
hibernate config and opening a session
*/
session.save(aclGroup); //session.persist also did not work
Could anyone help me to solve this problem? Thank you for your attention.
The owner side of the association is Acl. AclGroup is the inverse side (since it has the mappedBy attribute). Hibernate only cares about the owner side.
So make sure to add the group to the acl when you add the acl to the group: that will work whatever the owner side is, and will make your graph coherent. Or, if you absolutely don't want to do that, put the mapping annotations on AclGroup, and make Acl the inverse side.
Related
The Problem
I have a 1:n relation, but the n side shouldnt rely on constraints. So i actually wanna insert a EntityPojo via its future id, when its not saved yet ( Lets ignore that its a bad practice ). This looks kinda like this.
var relation = new RelationshipPojo();
.
.
.
relation.targets.add(session.getReference(futureID, EntityPojo.class));
session.save(relation);
// A few frames later
session.save(theEntityPojoWithTheSpecificId);
Cascading is not possible here, i only have its future ID, not a reference to the object i wanna save. Only its id it will have in the future.
#Entity
#Table(name = "relationship")
#Access(AccessType.FIELD)
public class RelationshipPojo {
.
.
.
#ManyToMany(cascade = {}, fetch = FetchType.EAGER)
public Set<EntityPojo> targets = new LinkedHashSet<>();
}
Question
How do we tell hibernate that it should ignore the constraints for this 1:n "target" relation ? It should just insert the given ID into the database, ignoring if that EntityPojo really exists yet.
Glad for any help on this topic, thanks !
For a much simpler solution, see the EDIT below
If the goal is to insert rows into the join table, without affecting the ENTITY_POJO table, you could model the many-to-many association as an entity itself:
#Entity
#Table(name = "relationship")
#Access(AccessType.FIELD)
public class RelationshipPojo {
#OneToMany(cascade = PERSIST, fetch = EAGER, mappedBy = "relationship")
public Set<RelationShipEntityPojo> targets = new LinkedHashSet<>();
}
#Entity
public class RelationShipEntityPojo {
#Column(name = "entity_id")
private Long entityId;
#ManyToOne
private RelationshipPojo relationship;
#ManyToOne
#NotFound(action = IGNORE)
#JoinColumn(insertable = false, updatable = false)
private EntityPojo entity;
}
This way, you'll be able to set a value to the entityId property to a non-existent id, and if an EntityPojo by that id is later inserted, Hibernate will know how to populate relationship properly. The caveat is a more complicated domain model, and the fact that you will need to control the association between RelationshipEntityPojo and EntityPojo using the entityId property, not entity.
EDIT Actually, disregard the above answer, it's overly complicated. Turing85 is right in that you should simply remove the constraint. You can prevent Hibernate from generating it in the first place using:
#ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
#JoinTable(inverseJoinColumns = #JoinColumn(name = "target_id", foreignKey = #ForeignKey(name = "none", value = ConstraintMode.NO_CONSTRAINT)))
public Set<EntityPojo> targets = new LinkedHashSet<>();
The only caveat is that when you try to load RelationshipPojo.targets before inserting the missing EntityPojo, Hibernate will complain about the missing entity, as apparently #NotFound is ignored for #ManyToMany.
I have a situation where an entity could use another entity, and it could be used by another, so i have defined a ManyToMany relation that reference the same entity, so i could have listUse and listUsedBy, and both are persisted in the same table entity_usage :
#ManyToMany
#JoinTable(name = "entity_usage",
joinColumns = {
#JoinColumn(name = "id_use", referencedColumnName = "id")},
inverseJoinColumns = {
#JoinColumn(name = "id_used_by", referencedColumnName = "id")})
private List<Entity> listUse;
#ManyToMany
#JoinTable(name = "entity_usage",
joinColumns = {
#JoinColumn(name = "id_use_by", referencedColumnName = "id")},
inverseJoinColumns = {
#JoinColumn(name = "id_use", referencedColumnName = "id")})
private List<Entity> listUsedBy;
Exemple : Entity A could use Entity B and C, so Entity B and C are used by A.
Now my problem is when i add B and C to listUse, they are persisted in entity_usage, but when try to display listUsedBy i have to redeploy my project, otherwise listUsedBy remains empty, is there a way to refresh listUsedBy when persist my entity without having to redeploy my project.
This is the general approach:
#Entity
public class SomeEntity
{
#ManyToMany
#JoinTable(name = "entity_usage",
joinColumns = #JoinColumn(name = "using_id"),
inverseJoinColumns = #JoinColumn(name = "used_by_id"))
private Set<SomeEntity> using = new LinkedHashSet<>();
#ManyToMany(mappedBy = "using")
private Set<SomeEntity> usedBy = new LinkedHashSet<>();
public void addUsing(SomeEntity entity)
{
this.using.add(entity);
entity.usedBy.add(this);
}
public void addUsedBy(SomeEntity entity)
{
this.usedBy.add(entity);
entity.using.add(this);
}
}
and it's used:
public void someMethod(long parentEntityId, long childEntityId)
{
EntityManager em = getSomeEntityManager();
SomeEntity parentEntity = em.find(SomeEntity.class, parentEntityId);
SomeEntity childEntity = em.find(SomeEntity.class, childEntityId);
parentEntity.addUsing(childEntity);
}
typically this is a transactional EJB method.
Note that there's no need to em.merge anything, since entities are already managed by em.find.
Anyway, whichever method you'll use to manage your entities (query, find, persist, merge), remember that's important to call addUsing/addUsedBy only when both entities are managed.
This is one of the main incoherences that ORM logic cannot handle by its own: you have to inform both entities (parent and child) of their relation. It's not sufficient to set the relation only on one side - if you only say that A is parent of B, B still doesn't know who is its parent.
However, there exists alternative approaches, like setting only the owning side of the relation (parent.getChildren().add(child)), flush, and refresh the child.
Nevertheless (as I experienced very well on my skin) the alternatives are very hard to handle in real world complex applications.
As a side note, I'd use Set instead of List for the relation, unless you need some kind of insertion-order.
I'm using tenant by schema and i have the following entities:
#Entity
#Multitenant(MultitenantType.TABLE_PER_TENANT)
#TenantTableDiscriminator(type = TenantTableDiscriminatorType.SCHEMA)
public class Person {
#OneToOne(mappedBy = "person", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private CTPS ctps;
}
#Entity
#Table(name = "CTPS")
#Multitenant(MultitenantType.TABLE_PER_TENANT)
#TenantTableDiscriminator(type = TenantTableDiscriminatorType.SCHEMA)
public class CTPS {
#OneToOne
#JoinTable(name = "PERSON_CTPS", joinColumns = #JoinColumn(name = "CTPS_ID"), inverseJoinColumns = #JoinColumn(name = "PERSON_ID"))
private Person person;
}
During an update at the same time using two differents tenants, occurs key violation error in one of requests, because tenant_a is trying to execute an insert in person_ctps table using tenant_b.
I'm using:
postgresql-9.4.5-3
wildfly-8.2.0
EclispeLink 2.6.3 with patchs of issues 410870 and 493235.
Anyone knows how to fix this?
I found the problem. The object that maintain relation tables is not cloned in EclipseLink.
With the attachment patch of issue 498891, the problem is solved
My app has 2 java pojo classes linked via ManyToMany relationship User & Season:
#Entity
#Table(name = "user")
public class User implements Serializable {
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "user_season", joinColumns = { #JoinColumn(name = "user_id") }, inverseJoinColumns = { #JoinColumn(name = "season_id") })
private Set<Season> followingSeason;
Set<Season> getSeasonsWhichTheUserFollows(){
return this.followingSeason;
}
}
Season class
#Entity
#Table(name = "season")
public class Season implements Serializable{
#ManyToMany(mappedBy = "followingSeason", fetch = FetchType.EAGER)
private Set<User> user;
}
When a user unfollows a season unfollowedSeason object I remove it from the set of season which the user follows.
Set<Season> seasonSet = user.getSeasonsWhichTheUserFollows();
seasonSet.remove(unfollowedSeason);
user.setFollowingSeason(seasonSet );
this.userService.update(user);
well this removes the entry from the user_season bridge table, everything is fine. But at the same time I also want to update some fields of the Season entity in the db for an instance decrementing the count of users following by 1. Is there a way I can do that within the same call? Or do I have to run a separate query to update the season entity?
Not sure if i got that right, but why can't you just put something in there like unfollowedSeason.setCount(unfollowedSeason.getCount() +1 ) and then just update the season?
EDIT AFTER DISCUSSION IN COMMENTS:
What you want to do is not possible because
you can't do a update and a remove in the same SQL Statement(as over9k stated)
I have two entities, User and Event. Each event can have multiple users associated with it, so its a one to many between Event and User.
The way its being stored in the database, is that I have 3 tables, user, event, and event_user. event_user contains 3 fields, id, eventId, userId. So I can do a query like select userId from event_user where eventId = ? to get all the users which are associated with the event.
My question is, how can I map this relationship between the events and users in Hibernate, to get it to auto save/load the users associated with the event? I want to have the following field in the Event class:
Set<User> users = new HashSet<>();
and have hibernate auto load / save the users to this set.
How can I map this (using annotations)?
Use the #ManyToMany annotation.
class Event{
#ManyToMany(cascade=CascadeType.ALL)
#JoinTable(name = "EVENT_USER",
joinColumns = { #JoinColumn(name = "EVENT_ID") },
inverseJoinColumns = { #JoinColumn(name = "USER_ID") })
private Set<Users> users = new HashSet<Users>();
}
For more information on many to many associations in JPA check out this video tutorial at my blog.
Hibernate doc on the Bidirectional mapping using annotations should help
Basically you need to do something like this
#Entity
public class User implements Serializable {
#ManyToMany(
targetEntity=org.hibernate.test.metadata.manytomany.Event.class,
cascade={CascadeType.ALL}
)
#JoinTable(
name="USER_EVENT",
joinColumns=#JoinColumn(name="USER_ID"),
inverseJoinColumns=#JoinColumn(name="EVENT_ID")
)
public Set<Event> getEvents() {
return events;
}
...
}
#Entity
public class Event implements Serializable {
#ManyToMany(
cascade = {CascadeType.ALL},
mappedBy = "events",
targetEntity = User.class
)
public Set<User> getUsers() {
return users;
}
}