Cascade remove with Spring Data JPA (bi-directional) - spring

I've got the following bi-directional relationship between two entity classes:
User
#Entity
#Table(name = "USER")
public class User {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
#Cascade({CascadeType.REMOVE, CascadeType.SAVE_UPDATE})
private Set<Book> books;
}
Book
#Entity
#Table(name = "BOOKS")
public class Book {
#ManyToOne(optional = false)
#JoinColumns({
#JoinColumn(name = "ID", referencedColumnName = "ID"),
#JoinColumn(name = "NAME", referencedColumnName = "NAME"),
})
private User user;
}
I want to remove a User with the delete cascading down to all the books associated with the user. However, when I used a Spring Data CrudRepository:
myDAO.delete(String userId) // interface extending CrudRepository<User, UserPK>
I'm getting:
Caused by: org.hsqldb.HsqlException: integrity constraint violation: foreign key no action; FK_IN88FEHUXOOYQK0YE71USPIEP table: USER
I was trying to use orhpanRemoval, all kind of CascadeTypes both org.hibernate and javax.persistence. I don't want to implement native queries like #Query(Delete from User....) I'm wondering about exception statement foreign key no action whether I directly specified cascade option.

There is a many-to-one relationship from Book to User. Therefore, the Book table requires only a single column to capture the foreign key to the User table.
The code:
#ManyToOne(optional = false)
#JoinColumns({
#JoinColumn(name = "ID", referencedColumnName = "ID"),
#JoinColumn(name = "NAME", referencedColumnName = "NAME"),
})
private User user;
should simply be:
#ManyToOne(optional = false)
#JoinColumn(name = "user_id")
private User user;
When you use the Hibernate Schema Generation Tool (HBM2DDL), foreign key constraints are generated as follows:
ALTER TABLE
BOOK
ADD CONSTRAINT
FK_IN88FEHUXOOYQK0YE71USPIEP
FOREIGN KEY
(USER_ID) REFERENCES USER (ID)
ON UPDATE NO ACTION
ON DELETE NO ACTION;
Notice ON DELETE NO ACTION. This means, when a User instance is deleted, nothing should happen to Book instances associated with it (NO ACTION). This practically means that the User should not be allowed to be deleted.
If you want Book instances associated with a User to be deleted when the User is deleted, the DDL should be:
ALTER TABLE
BOOK
ADD CONSTRAINT
FK_IN88FEHUXOOYQK0YE71USPIEP
FOREIGN KEY
(USER_ID) REFERENCES USER (ID)
ON UPDATE NO ACTION
ON DELETE CASCADE;
This can be generated by adding a Hibernate-specific annotation to the domain model:
class User {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
#Cascade({CascadeType.REMOVE, CascadeType.SAVE_UPDATE})
#OnDelete(OnDeleteAction.CASCADE)
private Set<Book> books;
}

Related

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=?

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

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

Parent key not found with #MapsId

I am using Java8, SpringBoot 2.3.7 and JPA with Oracle
There is this legacy table (I cannot touch it, as it is in production with other applications)
TRANSFE (TRA_ID_PK NUMBER, ACCOUNT_ID NUMBER, .... )
CONSTRAINT "TRANSFE_UNIQUE" UNIQUE ("TRA_ID_PK")
In my new application some of the new Transfe will be TransfeIn, with only one field, so I created this table
TRANSFE_IN (ID NUMBER, ....)
CONSTRAINT "TRANSF_IN_PK" PRIMARY KEY ("ID")
Everything works fine with Jpa:
#Entity
#Table(name = "TRANSFE")
public class Transfe implements Serializable {
#Id
#Column(name = "TRA_ID_PK")
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SEQ_TRANSFES")
#SequenceGenerator(name="SEQ_SW_TRANSFERENCIAS", sequenceName="SEQ_TRANSFES", allocationSize=1)
private Long id;
#ManyToOne
#JoinColumn(name = "TRA_ID")
private Account account;
#OneToOne(cascade = {CascadeType.ALL})
#PrimaryKeyJoinColumn
private TransfeIn transfeIn;
and
#Entity
#Table(name = "TRANSFE_IN")
public class TransferenciaEntrant {
#Id
private Long id;
#MapsId
#OneToOne(cascade = {CascadeType.ALL}, mappedBy = "transfeIn")
#JoinColumn(name = "id")
private Transfe transfe;
The problem raises when I add this to TRANSFE_IN
CONSTRAINT "TRANSFE_IN_FK" FOREIGN KEY ("ID") REFERENCES "TRANSFE" ("TRA_ID_PK")
So when there is a new TransfeIn to be stored with
accountRepository.save(account)
(Account has a #OneToMany(mappedBy = "account", cascade = CascadeType.ALL) List transfes), I get
SQLIntegrityConstraintViolationException: ORA-02291: integrity constraint (TRANSFE_ENTR_FK) violated - parent key not found
In the logs, I can see how a new TRANSFE_IN row with the right ID (taken from SEQ_TRANSFES) is being inserted. But the table TRANSFE is empty, yet.
What am I doing wrong? Should I not use the FK? Or there is something in the annotations to be changed?

Get Hibernate single iteration values only in an entity

I need to get values from user table, in that user have an manager id,
Manager is an user so again manager id is mapped with user entity.
This will cause continuous iteration until the manager ID get null.
\
The entity get more on inside on it > User entity > manager id> --> user entity ....
Is there any possible to get single entity with single manager using hibernate query?
USER_NAME (PK)
CREATE_DATE
UPDATED_DATE
ROLE_ID
USER_ID
MANAGER_ID
REGION_ID
USER_GROUP
Created_By
User class
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "USER_ID", unique = true, nullable = false)
private Long userId;
#ManyToOne( targetEntity = User.class, cascade = CascadeType.ALL)
#JoinColumn(name = "MANAGER_ID", referencedColumnName = "USER_ID")
private User managerId;
You can relate to entity only managerId(I mean Long type), or you can mark Many to one relation with fetch = FetchType.LAZY and call this field only if needed.

Cannot delete or update parent row Hibernate

I have a design and scenario entity.
I'm getting an error when removing a Design that contains one or more scenarios.
The design entity looks like:
#Entity
public class Design {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(columnDefinition = "LONGBLOB")
private byte[] image;
#OneToMany(mappedBy = "design", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Scenario> ScenarioSet;
The scenario entity looks like:
#Entity
public class Scenario {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "design_ID")
private Design design;
As you can see A design can have more than one scenarios.
And Design is responsible for the relation.
My code to save a scenario:
Design design = this.designService.getDesignById(designID);
scenario.setDesign(design);
this.scenarioService.saveScenario(scenario);
Saving it isn't a problem. I'm saving it this way because the scenario doesn't have an ID at first.
The error i'm getting:
Cannot delete or update a parent row: a foreign key constraint fails (`db`.`scenario`, CONSTRAINT `FKqmttw6jic4aplswy08wtkj5r7` FOREIGN KEY (`design_id`) REFERENCES `design` (`id`)) 0.016 sec
This lets me think that It isn't cascading when I remove the Design.
Add orphanRemoval=true to your scenario list mapping in the Design entity:
#OneToMany(mappedBy = "design", cascade = CascadeType.ALL, orphanRemoval=true, fetch = FetchType.LAZY)
private Set<Scenario> ScenarioSet;
CascadeType.ALL (or precisely CascadeType.REMOVE) serve for cascading remove operation when you take an item from the collection and save the owning entity (Design in this case). To tell Hibernate to remove items in the collection when the owning entity (Design) is removed, you need to use the orphanRemovalattribute:
https://docs.oracle.com/cd/E19798-01/821-1841/giqxy/

Resources