JPA one-to-many delete and set foregn key to null - mysql

I use Spring Data, JPA, Hibernate and MySQL. I have one to many relation between Event and Category. Obviously one event can have only one category and one category can be assigned many Events. The problem appears when I try to remove Category, if any event hold a foreign key of this category then I get an error. I would like to set the foreign key in the Event table to null when a Category is deleted. At the moment I update all Events by setting the foreign key explicitly in the code by updating it to null before deletion of Category. Is there any way of doing it in use of annotations?
This is my Category:
#Entity
#Table(name = "category")
public class Category implements Serializable{
#OneToMany(mappedBy="category", targetEntity=Event.class, fetch=FetchType.LAZY, cascade=
{CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
public Set<Event> getEvents_category() {
return events_category;
}
}
And the Event class:
#Entity
#Table(name = "event")
public class Event implements Serializable{
#ManyToOne(cascade={CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST,
CascadeType.REFRESH})
#JoinColumn(name="events_dancestyle")
public DanceStyle getDanceStyle() {
return danceStyle;
}
}
I've seen that the topic was discussed many times but I haven't seen any solution to that.

Unfortunately it is not currently possible to do this using JPA/Hibernate annotations. See these related questions:
Have JPA/Hibernate to replicate the "ON DELETE SET NULL" functionality
On delete set null in hibernate in #OneToMany

1- the best way is to write cascade = CascadeType.DETACH
2- but you should write above Class Event
#SQLDelete(sql = "UPDATE event SET deleted=true WHERE id=?")
3- inside the Event Class
#Column(columnDefinition = "boolean default false")
private boolean deleted;
4- Then when you delete category
if CascadeType.ALl the "deleted" attribute of Event entity will affected by 1 (true).
if CascadeType.DETACH the "deleted" attribute of Event entity will not affected and still 0 (false).

Related

Why #JoinTable(name="user_role") not allowing multiple user_id with role_id

Am using springboot with hibernate,
My Entity classes looks like below :
#Entity
#Table(name="tbl_user")
public class User {
#Id #GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="user_Id")
private long userId;
#Column(name="userName")
private String userName;
#Column(name="passWord")
private String passWord;
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name="user_role")
private Collection<Role> roleList;
My second entity looks like below :
#Entity
#Table(name="tbl_role")
public class Role {
#Id #GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="role_Id")
private long roleId;
#Column(name="roleName")
private String roleName;
When I insert first user with role as manager(pkid=1), admin(pkid=2) its success but while I tried to insert 2nd user with role as Manager*pkid=1, admin(pkid=2, serviceUser(pkid=3) it's not allowing me to insert second user with below exception
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '2' for key 'UK_ixctfj5iq0enl7iktlpo7wxct'
Can somebody help me why this constraint is getting creating while generating tables, how can i insert 2nd user into DB ?
If you use OnetoMany on role_list you are effectively saying that a single User will point to many Roles and that a Role will point to only one User. This will be enforced with a unique key constraint placed on the join table. If you have the SQL statements printed out you will see it when the schema is created. Something along the lines of:
alter table user_role add constraint UK_ixctfj5iq0enl7iktlpo7wxct unique (role_id)
In your requirement, you also have a single Role used by many Users. Your admin role primary key is 2 and you want to be able to assign it to more than one user. Your relationship is a ManyToMany for the role_list.
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name="user_role")
private Collection<Role> roleList;
When you change the annotation, you will still have a join table, but no constraint will be added.

Using seperate OneToManys to create a ManyToMany Join table

I've got 2 models User and Exercise. Now any User can have any Exercise. It's a ManyToMany situation. I modeled it with #ManyToMany, but you can't have a duplicate entry in a ManyToMany. A User is likely to do multiple sets of one exercise so I duplicate entries are required. To get round this I created the join table separately called UserExerciseJoin. User and Exercise had ManyToOne relationships with the UserExerciseJoin model. Though this solved the multiple keys issue I now can't delete from the new table. I get an OptimisticLockException from some of the models associated to the Exercise.
My question is: Am I on the right path with the seperate table or is there something I can do to a standard #ManyToMany to make it accept duplicate entries?
If I understand it right in your model, then yes, it is probably not the case for #ManyToMany. It seems to me that you can be better off with a meaningful entity like UserExerciseOccurrence that reference both a User and an Exercise and means a concrete exercise session.
You can also benefit from this approach if you need to save more info about a particular exercise session (like duration, etc).
#Entity
class UserExerciseOccurrence {
#ManyToOne
User user;
#ManyToOne
Exercise exercise;
}
#Entity
class User {
#OneToMany(mappedBy="user", cascade=DELETE)
Set<UserExerciseOccurrence> exerciseOccurrences;
}
#Entity
class Exercise {
#OneToMany(mappedBy="exercise", cascade=DELETE)
Set<UserExerciseOccurrence> exerciseOccurrences;
}
You are on the right path. You should have #OneToMany relation from User class and from Excercise class to this new entity. And in UserExerciseJoin you should have #ManyToOne relations.
So this code should look like this:
#Entity
User {
#OneToMany(mappedBy="user")
private List<UserExercise> userExercises;
....
}
#Entity
Excercise {
#OneToMany(mappedBy="excercise")
private List<UserExercise> userExercises;
....
}
#Entity
UserExercise
{
#ManyToOne
private User user;
#ManyToOne
private Excercise excercise;
...
}
You had an error when deleting this new entity. You had in on some entity related to excercise. It seems that this is because of cascades. You probably set cascades on fields of UserExerciseJoin class. If it was CascadeType.DELETE or CascadeType.ALL cascade then it caused deletion of related entities. So you shouldn't set cascades in UserExercise class. Then deleting of such entity will not cause a problem.

How do I auto-generate IDs for an #ElementCollection when it is a java.util.Map?

I’m using MySQL 5.5.37, JPA 2.0, and Hibernate 4.1.0.Final (I’m willing to upgrade if it solves my problem). I have the following entity …
#Entity
#Table(name = "url")
public class Url implements Serializable
{
…
#ElementCollection(fetch=FetchType.EAGER)
#MapKeyColumn(name="property_name")
#Column(name="property_value")
#CollectionTable(name="url_property", joinColumns=#JoinColumn(name="url_id"))
private Map<String,String> properties;
The “url_property” table has an ID (primary key) column, and perhaps for this reason, when I create a new Url entity with multiple properties, I feet the exception
[ERROR]: org.hibernate.engine.jdbc.spi.SqlExceptionHelper - Duplicate entry '' for key 'PRIMARY'
upon saving. Does anyone know what I have to do to auto-generate IDs for my url_property table? I would prefer not to write a trigger, but rather do something JPA, or at least, Hibernate sanctioned.
Edit: Per the first suggestion in the answer, I tried
#ElementCollection(fetch=FetchType.EAGER)
#Column(name="property_value")
#CollectionTable(name="url_property", joinColumns=#JoinColumn(name="url_id"))
private Set<UrlProperty> properties;
but it resulted in the exception, "org.hibernate.MappingException: Foreign key (FK24E4A95BB0648B:url_property [properties_id])) must have same number of columns as the referenced primary key (url_property [url_id,properties_id])".
My UrlProperty entity is
#Entity
#Table(name = "url_property")
public class UrlProperty
{
#Id
#GeneratedValue(generator = "uuid-strategy")
private String id;
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name="URL_ID")
private SubdomainUrl url;
#Column(name="PROPERTY_NAME")
private String propertyName;
#Column(name="PROPERTY_VALUE")
private String propertyValue;
You have only told JPA about 3 fields in the table ("property_name","property_value" and "url_id"), so it has no way of knowing about the 4th field used as the pk. Since it is not an entity, it doesn't have an Identity that is maintained. Options are:
1) Map the "url_property" table to a Property entity, which would have an ID, value and reference to the Url. The Url would then have a 1:M reference to the Property class, and can still be keyed on the name. http://wiki.eclipse.org/EclipseLink/Examples/JPA/2.0/MapKeyColumns has an example
2) Change your table to remove the ID field, and instead use "property_name","property_value" and "url_id" as the primary key.
3) Set a trigger to populate the ID. Doesn't seem useful though since the application is never aware of the field anyway.

JPA doesn't t allow entity made of columns from multiple tables?

I know this makes none sense as many tutorials state that you can use SecondaryTable annotation, however it doesn't work in hibernate. I have schema like this:
#Entity
#Table(name="server")
#SecondaryTable(name="cluster", pkJoinColumns = { #PrimaryKeyJoinColumn(name = "uuid", referencedColumnName = "cluster_uuid") })
public class Server {
#Id
#Column(name = "uuid")
private String uuid;
#Column(name = "cluster_uuid")
private String clusterUuid;
#Column(name = "ip", table="cluster")
private String ip;
..... }
#Entity
#Table(name = "cluster")
public class Cluster {
#Id
#Column(name = "uuid")
private String uuid;
#Column(name = "ip")
private String ip;
.....
}
Server.clusterUuid is a foreign key to Cluster.uuid. I am hoping to get Server entity that fetches ip column from Cluster by joining Server.clusterUuid to Cluster.uuid.
Then I was greeted by a hibernate exception:
Caused by: org.hibernate.AnnotationException: SecondaryTable
JoinColumn cannot reference a non primary key
at org.hibernate.cfg.annotations.TableBinder.bindFk(TableBinder.java:402)
at org.hibernate.cfg.annotations.EntityBinder.bindJoinToPersistentClass(EntityBinder.java:620)
at org.hibernate.cfg.annotations.EntityBinder.createPrimaryColumnsToSecondaryTable(EntityBinder.java:612)
I see lots of people encountered this problem. But the first bug for this in Hibernate's bugzilla was 2010, I am surprised it's been there for over two years as this is supposed to be a basic feature. There is some post saying JPA spec only allows primary key to do the mapping, however, I get below from JPA wikibook
JPA allows multiple tables to be assigned to a single class. The
#SecondaryTable and SecondaryTables annotations or
elements can be used. By default the #Id column(s) are assumed to be
in both tables, such that the secondary table's #Id column(s) are the
primary key of the table and a foreign key to the first table. If
the first table's #Id column(s) are not named the same the
#PrimaryKeyJoinColumn or can be used to
define the foreign key join condition.
it's obviously OK for non-primary key. Then I am confused why Hibernate didn't fix this problem as it seems to be easy to implement by a join clause.
anybody knows how to overcome this problem? thank you.
I don't quite understand your setup.
#SecondaryTable is for storing a single entity in multiple tables, but in your case you have a many-to-one relationship between different entities (each one stored in its own table), and it should be mapped as such:
#Entity
#Table(name="server")
public class Server {
#ManyToOne
#JoinColumn(name = "cluster_uuid")
private Cluster cluster;
...
}

Hibernate #OneToOne mapping with reverse reference and CascadeType.ALL on owner does not persist the child

We have the following entity relation ship. User is the parent entity.DistrictUserDetail is the child.
#Entity
#Table(name="USERS")
public class User implements Serializable {
#Id
#Column(name="ID", nullable=false)
#GeneratedValue(generator="system-uuid")
#GenericGenerator(name="system-uuid", strategy = "uuid")
private String id;
#OneToOne(cascade= {CascadeType.ALL})
#PrimaryKeyJoinColumn(name="ID", referencedColumnName="USER_ID")
public DistrictUserDetail districtUserDetail;
... other properties..
This is the parent entity.
The child entities primary key is a foreign key to User entity, and we have defined its key properties as:
#Entity
#Table(name="DISTRICT_USER_DETAIL")
public class DistrictUserDetail implements Serializable{
#Id
#Column(name="USER_ID")
#GeneratedValue(generator="foreign")
#GenericGenerator(name="foreign", strategy = "foreign",
parameters={#Parameter(name="property",value="user")})
protected String userId;
#OneToOne(mappedBy="districtUserDetail")
User user;
... other properties
So, when we create a new User, and create a new DistrictUser, and set the DistrictUser to the User and save User, we expect that the User is saved and due to cascadeAll, DistrictUser is also saved.
This is our test:
public void testSaveUserWithDistrictUserDetails() throws Exception {
User user = new User();
user.setFirstName("John");
user.setLastName("Doe");
user.setUserName("U11111");
DistrictUserDetail userDetail = new DistrictUserDetail();
userDetail.setIsLoginAllowed(Boolean.TRUE);
userDetail.setIsSuperintendent(Boolean.TRUE);
user.setDistrictUserDetail(userDetail);
User newUser = dao.save(user);
assertTrue(newUser!=null);
assertTrue(newUser.getId()!=null);
}
But we encounter the following hibernate exception:
Caused by: org.hibernate.id.IdentifierGenerationException: attempted to assign id from null one-to-one property: user
at org.hibernate.id.ForeignGenerator.generate(ForeignGenerator.java:44)
at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:99)
at org.hibernate.event.def.DefaultMergeEventListener.mergeTransientEntity(DefaultMergeEventListener.java:315)
at org.hibernate.event.def.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:283)
at org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:238)
at org.hibernate.impl.SessionImpl.fireMerge(SessionImpl.java:688)
at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:670)
at org.hibernate.engine.CascadingAction$6.cascade(CascadingAction.java:245)
at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:269)
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:217)
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:170)
at org.hibernate.engine.Cascade.cascade(Cascade.java:131)
at org.hibernate.event.def.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:456)
at org.hibernate.event.def.DefaultMergeEventListener.mergeTransientEntity(DefaultMergeEventListener.java:352)
at org.hibernate.event.def.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:283)
at org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:238)
at org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:85)
at org.hibernate.impl.SessionImpl.fireMerge(SessionImpl.java:678)
at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:662)
at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:666)
at org.springframework.orm.hibernate3.HibernateTemplate$23.doInHibernate(HibernateTemplate.java:817)
at org.springframework.orm.hibernate3.HibernateTemplate.doExecute(HibernateTemplate.java:406)
... 49 more
If User entity can invoke the save of DistrictUserDetail, then why can't DistrictUserDetail see the parent User (and its new id) and use it to save it.
We are using the hibernate version:3.2.7.ga
Underlying database is : MySQL
Any help will be appreciated.
Thanks
userDeatil.user is null, which is what the exception is complaining about.
I would suggest removing the property and not having a bi-directional relationship. Otherwise, user.setDistrictUserDetail() also needs to set the user property.
You should set optional attribute of OneToOne to false like this:
#OneToOne(mappedBy="districtUserDetail", optional = false)
User user;
And also, you should set DistrictUserDetail.user to a not-null entity of course.