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

3

7

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:

enter image description here

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?

Landes answered 22/12, 2012 at 18:31 Comment(2)
Is Role an embeddable object?Abdomen
Role is just an enum. The ElementCollection is a new JPA2 feature if I recall corectly which is aimed for cases such as this one - where you need to hold a list of strings. Since this is a new feature I wouldn't be surprised if it is limited as far as the cascading generation option.Landes
L
2

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:

enter image description here

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

Landes answered 2/1, 2013 at 14:12 Comment(0)
A
4

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.
Abdomen answered 24/12, 2012 at 0:51 Comment(8)
Actually I'm using entity-manager (since I'm following JPA specifications) with HibernateJpaVendorAdapter that has a generateDdl property with the value of "true".Landes
Can you set the hibernate.hbm2ddl.auto property as part of the jpaProperties used to instantiate the jpa adapter?Abdomen
Where are you setting the OnDelete annotation, on the id member of UserDetails?Abdomen
When I tried to use the OnDelete annotation I put it above the userDetails member of the User class. I was sure it will do the job, but.. nope :(Landes
It should go on the UserDetails class. Also the UserDetails should define the relation to the User class, and annotate it as the Id and that should remove the need for the GenericGenerator you are using.Abdomen
Ok, I guess there are a few ways to use a onetoone relation with a shared id (is mine wrong?). Regarding the OnDelete - I'll try and use it as you suggestedLandes
Thank you! Using the OnDelete over the user memeber of the UserDetails class did the trick, but now I'm wondering about some other cases - Updating the question.Landes
Regarding your last reply - I agree, and it seems that the OnDelete is in the right place in this case. Regarding the "mappedby" option - I cannot use it since I use a join table (it should also be like that with a join column I guess). So I really don't get what is it that I'm missing here.Landes
L
2

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:

enter image description here

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

Landes answered 2/1, 2013 at 14:12 Comment(0)
M
0

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://mcmap.net/q/1624374/-ondelete-hibernate-annotation-does-not-generate-on-delete-cascade-for-mysql

Marje answered 25/3, 2019 at 15:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.