#OnDelete Hibernate annotation does not generate ON DELETE CASCADE for MySql - mysql

I have two entities parent and child with a unidirectional relationship
class Parent {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
}
and
class Child {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
#ManyToOne(optional = false)
#JoinColumn(name = "parent_id")
#OnDelete(action = OnDeleteAction.CASCADE)
private Parent parent;
}
In the application.properties file I have configured
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.database-platform = org.hibernate.dialect.MySQL5InnoDBDialect
But when I run the application the following statement is created
...
alter table child add constraint FKi62eg01ijyk2kya7eil2gafmx foreign key (parent_id) references parent (id)
...
So there is no ON CASCADE DELETE as there should be. The tables are created each time I run the application and I checked if the method
org.hibernate.dialect.MySQL5InnoDBDialect#supportsCascadeDelete()
is really called (it is). I am using spring-boot-parent version 1.4.3, which uses Hibernate 5.11. Any ideas? I do not want to use a bi-directional relationship by the way.
Edit
Thanks to #AlanHay I discovered that I omitted an important part. There actually is a third class involved
class Kindergarten {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
#OneToMany(mappedBy = "kindergarten", fetch = FetchType.EAGER)
#MapKeyJoinColumn(name = "parent_id") // parent_id causes the problem!
private Map<Parent, Child> children;
}
and Child with Kindergarten looks actually like this
class Child {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
#ManyToOne(optional = false)
#JoinColumn(name = "parent_id")
#OnDelete(action = OnDeleteAction.CASCADE)
private Parent parent;
#ManyToOne
#JoinColumn(name = "kindergarten_id")
private Kindergarten kindergarten;
}
and that is why the problem occurs. If you change "parent_id" in the MapKeyJoinColumn annotation to something not existing in Child as a column e.g. "map_id" the ON DELETE CASCADE is added to the foreign key parent_id in Child. If the parameter is the Child's column "parent_id" the ON DELETE CASCADE part will not be appended. Unfortunately the reason for this is not yet clear to me. Changing the parameter is no option because I want to use the existing link to parent of the child object.

Maybe a little late, but since it is one of the top posts when searching for 'hibernate ondelete generate cascade':
For some reason putting #OnDelete on the ManyToOne side in Mysql did not work for me, but it worked on the OneToMany side. So if you are unlucky, try it on the other side.

In my case I also had to place #OnDelete on the OneToMany side, next to that I also had to change the JpaVendorAdapter bean method. The adapter it returns must be set to org.hibernate.dialect.MySQL5InnoDBDialect like so:
adapter.setDatabasePlatform("org.hibernate.dialect.MySQL5InnoDBDialect");

Related

Hibernate create relation without relational table

Is it possible to map hibernate entities without using another table that is mapping them ?
When I create #OneToMany and #ManyToOne relation between 2 entities, hibernate always creates another table in the database to map the relation, I would like to map 2 entities directly using column in the mapped entity like this:
"InstalledApp" entity:
#OneToMany (fetch = FetchType.EAGER, targetEntity=InstalledAppPort.class, mappedBy = "id")
private List<InstalledAppPort> ports;
"InstalledAppPort" entity:
#ManyToOne (targetEntity=InstalledApp.class, fetch=FetchType.LAZY)
#JoinColumn(name = "iappId")
private InstalledApp iapp;
When using the code above, the list is always empty and I do not know why. Technically this mapping should work but it is not. No exception thrown.
Solved by using:
#OneToMany (fetch = FetchType.EAGER, targetEntity=InstalledAppPort.class)
#JoinColumn(name = "iappId", referencedColumnName = "id")
private List<InstalledAppPort> ports;

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.

Parent has List of child. Single record of child in database. But on debug it shows 2 records with same id

I have a parent entity ParentClass. In this parent Entity I have a list of child classes as shown below:-
public class BaseClass{
#Id
private Long id;
}
#Entity
public class ParentClass extends BaseClass{
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<ChildClassA> childClassAList;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<ChildClassB> childClassBList;
}
In my database I only have one record for ChildClassA mapped to ParentClass(say my parent_class record has id 25001 and their is only one child mapped to this parent[from ChildClassA entity] say 45001) in my third table. But still when I am debugging parentClass.getchildClassAList(), It shows me 2 items in this list(and that too both with same ID) instead of one single record as shown below:-
ParentClass parentRecord = myDao.find(ParentClass.class,25001L); //shows only one record which is expected.
List<ChildClassA> allChildsOfParent = parentRecord.getchildClassAList(); //shows 2 items in list,each with same id 45001 even though the third table in db carries one single record corresponding to this parent with id 25001.
What could be the possible reason for this issue? Any help would be appreciated.
Try using Set instead of List for your #OneToMany relationship container

ON DELETE CASCADE option not in generated when using ddl schema generation on Mysql

In a Maven-Spring-Hibernate-MySql running on Tomcat web app I'm using hibernate ddl to generate my DB schema with MySQL5InnoDBDialect.
The schema is generated just fine except the cascade option for foreign-keys. For example I have this structure:
A user object that holds user-details object, both sharing the same key:
#Entity
#Table(name = "Users")
public class User implements Serializable {
private static final long serialVersionUID = -359364426541408141L;
/*--- Members ---*/
/**
* The unique generated ID of the entity.
*/
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "User_Id")
protected long id;
#Getter
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "user", optional = true)
protected UserDetails userDetails;
...
}
And the user-details:
#Entity
#Table(name = "UserDetails")
public class UserDetails implements Serializable {
private static final long serialVersionUID = 957231221603878419L;
/*--- Members ---*/
/**
* Shared Key
*/
#Id
#GeneratedValue(generator = "User-Primary-Key")
#GenericGenerator(name = "User-Primary-Key", strategy = "foreign", parameters = { #Parameter(name = "property", value = "user") })
#Column(name = "User_Id")
protected long id;
#Getter
#Setter
#OneToOne(optional = false, fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn
private User user;
...
}
When generating the schema, the foreign-key from users-details table to users table is missing the cascading.
Here is the schema creation of the user-details:
CREATE TABLE `userdetails` (
`User_Id` bigint(20) NOT NULL,
`Creation_Time` bigint(20) NOT NULL,
`EMail` varchar(128) DEFAULT NULL,
`Enabled` bit(1) NOT NULL,
`First_Name` varchar(15) DEFAULT NULL,
`Last_Name` varchar(25) DEFAULT NULL,
`Password` varchar(64) NOT NULL,
`User_Name` varchar(15) NOT NULL,
PRIMARY KEY (`User_Id`),
UNIQUE KEY `User_Name` (`User_Name`),
UNIQUE KEY `EMail` (`EMail`),
KEY `FKAE447BD7BF9006F5` (`User_Id`),
CONSTRAINT `FKAE447BD7BF9006F5` FOREIGN KEY (`User_Id`) REFERENCES `users` (`User_Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8$$
As you can see there isn't any "ON DELETE CASCADE" written there in the "FOREIGN KEY" section.
This issue is also described here and here as well.
So I tried adding the #OnDelete annotation above the userDetails member with no luck..
I then created my own dialect overriding the supportsCascadeDelete:
public class MySql5Dialect extends MySQL5InnoDBDialect {
public MySql5Dialect() {
super();
}
#Override
public String getTableTypeString() {
return " ENGINE=InnoDB DEFAULT CHARSET=utf8";
}
#Override
public boolean supportsCascadeDelete() {
return true;
}
}
But still with no change. My foreign key cascading option still set to "RESTRICT" after generating the schema:
Is there a way to resolve this issue (non-manually of course)?
UPDATE
Following Angel Villalain's suggestion I put the #OnDelete annotation above the "user" member of the UserDetails class and this did the trick for the OneToOne relation, the delete is cascaded, but the OnUpdate is set to restrict (still), which leads me to my first question - what is the meaning of that? I mean "OnDelete" is pretty straight forward - when I delete the parent delete the child as well, but what is the meaning of the "OnUpdate" option? How does it affect my app when it set to restrict/cascade?
My second question refers to cascading with a OneToMany relation. My User class hols many UserProviders.
The following code is from the User class:
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinTable(name = "Users_Providers", joinColumns = #JoinColumn(name = "User_Id"), inverseJoinColumns = #JoinColumn(name = "Provider_Id"))
protected Set<UserProvider> userProviders = new HashSet<>(0);
And this is the inverse relation, from the UserProvider class:
#ManyToOne(fetch = FetchType.LAZY)
#JoinTable(name = "Users_Providers", joinColumns = #JoinColumn(name = "Provider_Id", insertable = false, updatable = false), inverseJoinColumns = #JoinColumn(name = "User_Id"))
#OnDelete(action = OnDeleteAction.CASCADE)
protected User user;
So after using the #OnDelete annotation I expected to see the onDelete option with cascade in the join table, but it isn't :( Have I used it correctly?
Last question - What about unidirectional relation such as #ElementCollection?
My UserDetails class holds an ElementCollection of roles (each user can be assigned with one or more roles):
#ElementCollection(fetch = FetchType.EAGER, targetClass = Role.class)
#CollectionTable(name = "Users_Roles", joinColumns = #JoinColumn(name = "User_Id", referencedColumnName = "User_Id"))
#Column(name = "Role")
protected Set<Role> roles = new HashSet<Enums.Role>(0);
A Role is just an enum, not an entity hence I cannot point back from a role to the parent entity. In this case, is there a way to cascade the onDelete?
With the OnDelete annotation the DDL should be right. Could you check how are you configuring the SessionFactory, in specific which value are you using for the hbm2ddl.auto parameter.
UPDATE
Regarding your issue with the UserProvider class. First the mapping seems to be bidirectional, but one side must be the owner side and the other must be the inverse side. Meaning the one that owns the relation is the one that persists the relation into the join table, and the other must be mapped with the mappedBy parameter and do not controls the relation. So the OneToMany with the mappedBy pointing to the user member of the UserProperty will be the inverse side, And UserProperty will be the owner side, and there should be the OnDelete annotation. But let me test it tomorrow to be sure, I am not in front of my dev station.
After investigating the issue I came up with the following methods to deal with DB schema generation (assuming you are using Hibernate as your JPA provider):
Using the ddl schema generation, you can generate your DB schema. Using this option the schema will be created/updated while you start your web-server. If you use this method, in order to make sure your onDelete option is set to cascade you can use the OnDelete annotation. This worked for me just fine for a OneToOne relation (thanks to Angel Villalain) but for some reason it didn't work for a OneToMany relation. To workaround this gap I used Spring's ResourceDatabasePopulator:
The db-additions.sql file contains the queries that adapt my DB, in my case create the Ondelete Cascade. For instance:
ALTER TABLE `buysmartdb`.`users_providers` DROP FOREIGN KEY `FKB4152EEBBF9006F5` ;
ALTER TABLE `buysmartdb`.`users_providers`
ADD CONSTRAINT `FKB4152EEBBF9006F5`
FOREIGN KEY (`User_Id` )
REFERENCES `buysmartdb`.`users` (`User_Id` )
ON DELETE CASCADE
ON UPDATE CASCADE;
Notice that the scripts triggered by the ResourceDatabasePopulator applies after the schema is generated by Hibernate ddl, which is good. I know that sinply due to the final result, I couldn't really make sure it is guaranteed.
A second method is to generate the schema using maven, in compile time. There are a few ways of doing that, such as this one or that one.
I hope this will help someone..
For some reason putting #OnDelete on the #ManyToOne side in Mysql and in PostgreSql did not work for me, but it worked on the #OneToMany side.
https://stackoverflow.com/a/44988100/4609353

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;
...
}