Cascade.Type = ALL does not delete all "child rows" before trying to delete its own row - spring

I'm trying to delete a user but every row in the database that references the user does NOT get deleted before hibernate tries to remove the user.
I have the same structure for every other entity in the application and it works just fine, all the child rows get deleted first and then the row itself get deleted, but as you see below this is not the case when trying to delete a user. Hibernate goes to this statement :
Hibernate: delete from users where user_id=?
before all comment_votes are deleted. (Posts should also be deleted before as well but I guess the comment_votes error shows up first).
This sequence of sql statements are executed before the error according to the console:
Hibernate: delete from comment_vote where vote_id=?
Hibernate: delete from comment where comment_id=?
Hibernate: delete from comment_vote where vote_id=?
Hibernate: delete from comment where comment_id=?
Hibernate: delete from comment_vote where vote_id=?
Hibernate: delete from comment where comment_id=?
Hibernate: delete from users where user_id=?
This is the error I'm getting:
org.postgresql.util.PSQLException: ERROR: update or delete on table "users" violates foreign key constraint "fkjf73ixvt1jv3wdv4ah0hkpewf" on table "comment_vote"
Detail: Key (user_id)=(2) is still referenced from table "comment_vote".
User.java :
#Entity
#Table(name = "users") // because User is a keyword in some DBs
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id", columnDefinition = "serial")
private Long id;
#NotEmpty
#Column(unique = true)
private String username;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
#JsonIgnore
private List<Post> posts = new ArrayList<>();
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
#JsonIgnore
private List<Comment> comments = new ArrayList<>();
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
#JsonIgnore
private List<CommentVote> comment_votes = new ArrayList<>();
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
#JsonIgnore
private List<PostVote> post_votes = new ArrayList<>();
// getters and setters
}
This is CommentVote.java :
#Entity
#Table(name = "comment_vote")
public class CommentVote {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "vote_id", columnDefinition = "serial")
private Long id;
#Min(value = -1, message = "A vote can not be less than -1")
#Max(value = 1, message = "A vote can not be greater than 1")
#Column(name = "actual_vote")
private int actualVote;
#ManyToOne()
#JoinColumn(name="user_id", nullable=false)
#JsonIgnore
private User user;
// getters and setters
}
I tried with orphanRemoval = true on every child field in User.java but that does not seem to change anything.

You can try to use #OnDelete. As it's stated in the documentation:
... the #OnDelete cascade is a DDL-level FK feature which allows you to remove a child record whenever the parent row is deleted.
So, when annotating the #ManyToOne association with #OnDelete(action = OnDeleteAction.CASCADE), the automatic schema generator will apply the ON DELETE CASCADE SQL directive to the Foreign Key declaration.
Taken this in mind, you can correct your mapping in the following way:
#Entity
#Table(name = "comment_vote")
public class CommentVote {
// ...
#ManyToOne()
#JoinColumn(name="user_id", nullable=false)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonIgnore
private User user;
// ...
}

Related

Soft delete but cannot prevent delete cascade with foreign key table

My entity user with #SQLDelete
#Data
#Entity
#Table(name = "`user`")
#SQLDelete(sql = "UPDATE `user` SET status = 0 WHERE id = ?")
public class User {
private Integer id;
private String username;
private String password;
#ManyToMany
#JoinTable(
name = "`user_role`",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private List<Role> roles = new ArrayList<>();
}
When I delete by method userRepository.deleteAllById(ids). My row user_role table also deleted. I only want soft delete a row at user table and no effect to user_role

Referential integrity constraint violation with Cascade type ALL

I need your help with the following problem: there is a spring boot project, it has two entities:
Bank and CreditDetails, the bank refers to the details as OneToMany, the details as ManyToOne.
In the Bank entity, the cascade type is ALL, but when I try to delete the bank, I get an error, what could be the problem?
Bank:
#Entity
#Table(name = "banks")
#Getter
#Setter
#NoArgsConstructor
public class Bank {
#Id
#Column(name = "bank_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#OneToMany(orphanRemoval = true,cascade = CascadeType.ALL,
mappedBy = "bank", fetch = FetchType.LAZY)
private List<CreditDetails> creditDetails = new ArrayList<>();
#OneToMany(orphanRemoval = true,cascade = CascadeType.ALL,
mappedBy = "bank", fetch = FetchType.LAZY)
private List<Client> clients = new ArrayList<>();
}
Credit Details:
#Entity
#Table(name = "credit_details")
#Getter
#Setter
#NoArgsConstructor
public class CreditDetails {
#Id
#Column(name = "credit_details_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Column(name = "credit_limit")
private BigDecimal creditLimit;
#Column(name = "credit_percent")
private BigDecimal creditPercent;
#ManyToOne(targetEntity = Bank.class, cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch = FetchType.EAGER)
#JoinColumn(name = "bank_id")
private Bank bank;
#OneToMany(cascade = CascadeType.ALL,
mappedBy = "creditDetails")
List<CreditOffer> creditOffers;
}
Entities in DB(H2)
create table banks
(
bank_id uuid primary key
);
create table credit_details
(
credit_details_id uuid primary key,
credit_limit bigint,
credit_percent numeric(5, 2),
bank_id uuid references banks (bank_id),
primary key (credit_details_id)
);
Stacktrace:
Referential integrity constraint violation: "CONSTRAINT_8: PUBLIC.CREDIT_DETAILS FOREIGN KEY(BANK_ID) REFERENCES PUBLIC.BANKS(BANK_ID) ('ae1ce5c1-b1eb-4ee7-a1a2-63d831b0fd0a')";
I reconstructed your setup using spring, hibernate, an H2 database and a Postgres database. For me everything worked as intended.
To test the entities I used a BankRepository:
public interface BankRepository extends CrudRepository<Bank, UUID> {}
and a very simple RestController:
private final BankRepository bankRepository;
#DeleteMapping
public void removeBank(#RequestParam String uuid) {
bankRepository.deleteById(UUID.fromString(uuid));
}
#PostMapping("/add")
public Bank addBank() {
var bank = new Bank();
var creditDetails = new CreditDetails();
creditDetails.setBank(bank);
bank.setCreditDetails(List.of(creditDetails));
bankRepository.save(bank);
return bank;
}
You might geht the error if you try to delete the Bank entity via some SQL directly or if you manually delete it from your database. Could you set
spring.jpa.show-sql: true
and post the generated JPA Queries? Mine looked like this:
Hibernate: select bank0_.bank_id as bank_id1_1_0_ from banks bank0_ where bank0_.bank_id=?
Hibernate: select creditdeta0_.bank_id as bank_id4_2_0_, creditdeta0_.credit_details_id as credit_d1_2_0_, creditdeta0_.credit_details_id as credit_d1_2_1_, creditdeta0_.bank_id as bank_id4_2_1_, creditdeta0_.credit_limit as credit_l2_2_1_, creditdeta0_.credit_percent as credit_p3_2_1_ from credit_details creditdeta0_ where creditdeta0_.bank_id=?
Hibernate: delete from credit_details where credit_details_id=?
Hibernate: delete from banks where bank_id=?

Foreign key constraint #ManyToMany relationship preventing deletion

I've three associated records (Conference, SubmissionRecord, SubmissionAuthorRecord). Every SubmissionRecord has a Conference object and has a List<SubmissionAuthorRecord>.
When I delete a Conference record if the SubmissionRecord is associated with that Conference, it should cascade and delete as well. However, I keep getting a java.sql.SQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`viz`.`submission_record_author_set`, CONSTRAINT `FKgnqq52l26bitkmojk1oiuaki1`
FOREIGN KEY (`submission_record_s_id`) REFERENCES `submission_record` (`s_id`)) error message.
The table submission_record_author_set is create automatically and I have no entity that maps to it.
I understand the issue lies in the fact that the submission_record_author_set rows are preventing the SubmissionRecord from being deleted and have tried the #PreRemove method described here (How to remove entity with ManyToMany relationship in JPA (and corresponding join table rows)?) but to no avail. Maybe there's an issue with the ManyToMany annotation? Cause I do not see the equivalent annotation in the SubmissionAuthorRecord either.
#Entity
public class SubmissionRecord {
#Id
#GenericGenerator(name = "UseExistingIdOtherwiseGenerateUsingIdentity", strategy = "xyz")
#GeneratedValue(generator = "UseExistingIdOtherwiseGenerateUsingIdentity")
#JsonSerialize(using = ToStringSerializer.class)
#Column(name = "s_id")
private Long id;
#Exportable(name = "Submission Id", nameInDB = "s_submission_id")
#Column(name = "s_submission_id")
private String submissionId;
// internal set of authors of the associated
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JsonIgnore
private List<SubmissionAuthorRecord> authorSet;
#JoinColumn(name="conference_id")
#ManyToOne(fetch = FetchType.EAGER)
#OnDelete(action = OnDeleteAction.CASCADE)
private Conference conference;
//...
}
#Entity
public class Conference {
#Id
#GenericGenerator(name = "UseExistingIdOtherwiseGenerateUsingIdentity", strategy = "xyz")
#GeneratedValue(generator = "UseExistingIdOtherwiseGenerateUsingIdentity")
#JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String creatorIdentifier;
private String conferenceName;
private String conferenceYear;
}
#Entity
public class SubmissionAuthorRecord {
#Id
#GenericGenerator(name = "UseExistingIdOtherwiseGenerateUsingIdentity", strategy = "xyz")
#GeneratedValue(generator = "UseExistingIdOtherwiseGenerateUsingIdentity")
#JsonSerialize(using = ToStringSerializer.class)
#Column(name = "s_author_id")
private Long id;
private String dataSet;
#Column(name = "s_author_name")
private String name;
}
The submission_author_record_set table looks like the following:

Could not fetch the SequenceInformation from the database ERROR but still everything works

I've created user and userRole tables
user entity
#Entity
#Table(name = "USERS")
public class User {
#Id
#Column(name = "USERNAME", nullable = false, unique = true)
private String username;
#Column(name = "PASSWORD", nullable = false)
private String password;
#Column(name = "ENABLED", nullable = false)
private boolean enabled = true;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user", fetch = FetchType.EAGER)
private Set<UserRole> userRole = new HashSet<>();
userRole entity
#Entity
#Table(name = "USER_ROLES", uniqueConstraints = #UniqueConstraint(
columnNames = { "ROLE", "USERNAME" }))
public class UserRole {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "user_role_id",
unique = true, nullable = false)
private Integer userRoleId;
#Column(name = "ROLE")
private String role;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "USERNAME")
private User user;
When i launch my app i get an Error and this stack trace:
ERROR JdbcEnvironmentImpl:420 - Could not fetch the SequenceInformation from the database
org.h2.jdbc.JdbcSQLException: Column "start_with" not found [42122-197]
But i don't have any 'start_with' columns. Before my UserRole entity was without userRoleId column and everything worked fine but then i added it to do 'role' column not unique and then this happened. But still everything works fine, i just disturbed by this error, what can be the couse of it?
I suggest checking your Hibernate dialect.
I had a similar error because of a start_value column that Hibernate was looking for in sequences of my PostgresQL database. This field name is a default value in Hibernate's SequenceInformationExtractorLegacyImpl class which has many subclasses, each depending on your precise database server and its version. Hibernate loads the right class according to the dialect you specify.
In my case, I was using the org.hibernate.dialect.PostgreSQLDialect dialect, a (deprecated) class meant to be used with a PostgesQL 8.2 version. I switched to org.hibernate.dialect.PostgreSQL9Dialect since my database was hosted on a PostgresQL 9 server. And the issue was gone.

OneToMany does not return values saved from other entity

I have entity structure:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = { CascadeType.ALL })
List<UserAgreement> userAgreements= new ArrayList<>();
}
#Entity
#Table(name = "user_agreements")
public class UserAgreement {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#ManyToOne(cascade = CascadeType.REFRESH)
#JoinColumn(name = "user_id")
private User user;
#ManyToOne(cascade = CascadeType.REFRESH)
#JoinColumn(name = "agreement_id")
private Agreement agreement;
}
#Entity
#Table(name = "agreements")
public class Agreement {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "agreement", cascade = { CascadeType.ALL })
List<UserAgreement> userAgreements = new ArrayList<>();
}
I am using Spring Boot with JpaRepository. When I use AgreementRepository extends JpaRepository<Agreement, Long> to save Agreement and related UserAgreement, it works well and cascades necessary fields to DB:
agreement.getUserAgreements().add(new UserAgreement(user, agreement, status));
agreementRepository.save(agreement);
However, after save, if try to retrieve user.getActiveUserAgreements(), I get empty list. It does not refresh.
How to force User entity to get List<UserAgreement> which was saved from other side?
From the Wikibooks: OneToMany
The relationship is bi-directional so, as the application updates one
side of the relationship, the other side should also get updated, and
be in sync. In JPA, as in Java in general it is the responsibility of
the application, or the object model to maintain relationships. If
your application adds to one side of a relationship, then it must add
to the other side.
That means you need to assign the UserAgreement to the User when you create the relation.
It looks like many-to-many association. You might probably drop UserAgreement class. Anyway, to support it you have to write helper methods addAgreement(), removeAgreement() etc. See more details here https://vladmihalcea.com/the-best-way-to-use-the-manytomany-annotation-with-jpa-and-hibernate/

Resources