Hibernate annotation Cascade problem - java

I have a Java class that contains a list of another class.
#Entity public class Country {
private Long id;
private List<Hotel> hotels;
public void setId(Long id) {
this.id = id;
}
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="COUNTRY_SEQ")
#SequenceGenerator(name="COUNTRY_SEQ", sequenceName="COUNTRY_SEQ", allocationSize=1)
public Long getId() {
return id;
}
public void setHotels(List<Hotel> hotels) {
this.hotels = hotels;
}
#OneToMany
#JoinTable(
name="COUNTRY_HOTELS",
joinColumns=#JoinColumn(name="COUNTRY_ID"),
inverseJoinColumns=#JoinColumn(name="HOTEL_ID")
)
public List<Hotel> getHotels() {
return hotels;
}
}
When I try to delete a Country, I get "ORA-02292: integrity constraint (HOT.fk1a1e72aaf2b226a) violated - child record found" because it can't delete a Country when its children (=Hotels) still exist.
However, it is MEANT to be like this! I don't want to my Hotels deleted when I delete a Country.
I tried without any #Cascade-annotation but it failed. I also tried with SAVE_UPDATE, still failed.
So which #Cascade-annotation do I need (or is there another solution?):
PERSIST
MERGE
REMOVE
REFRESH
DELETE
SAVE_UPDATE
REPLICATE
DELETE_ORPHAN
LOCK
EVICT
Bart

You have to remove the hotels from the list of hotels of the country before deleting the country, in order to tell Hibernate that the hotels don't have a country anymore.

Unfortunately Hibernate doesn't support ON DELETE SET NULL type of cascade yet.
So you have to delete references manually first and only then delete child entity.
There is a future request for Hibernate to support it.

It looks like you use Oracle. It allows you to implement cascade delete at the database level by defining foreign key COUNTRY_ID with ON DELETE CASCADE (http://www.techonthenet.com/oracle/foreign_keys/foreign_delete.php).

Related

How do I prevent deleting reference entities/tables using the JpaRepository?

I'm creating a delete api endpoint for my spring boot application. I tried using the delete() and deleteById() methods provided by the JpaRepository. However, whenever I try to delete a concert, using the ConcertEntity or the concertId, the venue entry associated is deleted from the Venues table. How do I prevent deleting reference entities/tables using the JpaRepository?
My current solution is to set the venue to null before deleting the concert entity. My concertRepositroy extends to JpaRepository.
Current Solution in Service Impl
public void deleteConcert(ConcertEntity e){
e.setVenue(null);
this.concertRepository.delete(e);
}
Concert Entity
#Entity
#Table(name = "CONCERTS")
public class ConcertEntity{
#Id
private UUID concertId;
#Column(name = "ARTIST")
String artist;
#Column(name = "VENUE_ID")
VenueEntity venue;
/*Getters && Setters here...*/
}
Use the proper annotation to define the relationship (#ManyToOne or #OneToOne)
#ManyToOne(optional = true)
#JoinColumn(name = "VENUE_ID")
private VenueEntity venue;
That should not trigger any cascade deletion by default, but you can add the cascade parameter to the #ManyToOne or #OneToOne annotation if you want to customize the behavior.

JPA/Hibernate How to add duplicate values to a List<> with a #OneToMany mapping

I am getting an error every time I try to add duplicate products to the Client class.
org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "uk_8opk6otadh2pkvi9bghguwp89"
Detail: Key (products_id)=(500010) already exists.
My entities
#Entity
public class Client {
#Id
private String id;
#OneToMany
private List<Product> products;
}
#Entity
public class Product {
#Id
private String id;
private Boolean foreign;
private Integer price;
}
When I call .save() hibernate is trying to re-save an already existing Product. I believe this is due to the how it cascades things.
public Client addNewProduct(Client client, Product newProduct) {
List<Product> products = client.getProducts();
products.add(newProduct);
client.setProducts(products);
return clientRepository.save(client);
}
I just want to be able to add duplicates to a List<>. I believe this is & should be a uni-directional relationship.
This does not work with your current relationship, I don't understand why you would want to add duplicates, but if you have to, then you'd have to create a new entity for that. One example would be something like this:
#Entity
public class ProductBatch {
#Id
private String id;
#OneToOne
private Product product;
private Integer count;
// getter & setter
}
and then you change your Client like this:
#Entity
public class Client {
#Id
private String id;
#OneToMany
private List<ProductBatch> products;
}
this makes something like this possible for your addNewProduct function:
public Client addNewProduct(Client client, Product newProduct) {
List<ProductBatch> products = client.getProducts();
boolean exists = false;
for(ProductBatch product : products) {
if(product.getProduct().equals(newProduct)) {
product.setCount(product.getCount() + 1);
exists = true;
}
}
if(!exists) {
BatchProduct product = new BatchProduct();
product.setProduct(newProduct);
product.setCount(0);
products.add(product);
}
client.setProducts(products);
return clientRepository.save(client);
}
That has nothing to do with JPA or Hibernate. A constraint is a Database constraint and it is right. If a Foreign Key is already part of the table, trying to add it again, violates this constraint. An ID is unique and it should stay that way. So adding duplicates (at least with the same ID) will not work.

Hibernate delete referencing records after update of the owning record in a #ManyToOne relationship

I need some advice on how to properly configure a unidirectional many-to-one relationship with with JPA.
I have an entity called ScheduleEntry. Schedule entries need to know their parent schedule entry, but a parent schedule entry doesn't need to know its child entries. For that reason ScheduleEntry looks like this:
#Entity
#Data
public class ScheduleEntry {
[...]
#ManyToOne
#JoinColumn(name = "parent_id", nullable = true)
private ScheduleEntry parent;
#ElementCollection
#CollectionTable(name = "schedule_entry", joinColumns = #JoinColumn(name = "parent_id"))
#Column(name = "recurrenceNumber")
#EqualsAndHashCode.Exclude
private Set<Integer> recurrences;
}
There is no OneToMany side of this relationship.
This works fine when creating entries, setting their parent entry and fetching entries with parent entries.
However, whenever I update a parent ScheduleEntry Hibernate executes a DELETE statement and delete all child entries of the updated ScheduleEntry:
org.hibernate.SQL: update schedule_entry set active=?, cancelled=?, capacity=?, description=?, end_time=?, parent_id=?, recurrence_number=?, recurs_until_time=?, start_time=?, title=? where id=?
org.hibernate.SQL: delete from schedule_entry where parent_id=?
And that's not what I want. I want that child entries keep their reference to the updated parent entry and don't get deleted. I.e. I want to prevent HIbernate from executing the DELETE statement. Any ideas how to achieve that?
PS: The code that executes the update:
public void update(DTO dto) {
jpaRepository.save(mapper.dtoToModel(dto));
}
called from within a REST controller that manages the transaction scope with Spring's #Transactional:
#Transactional
#PutMapping("/{id}")
public ResponseEntity<?> update(#PathVariable("id") Long id, #RequestBody ScheduleEntryDTO dto) {
return super.update(id, dto);
}

Using a view as a join table with Hibernate

I've got two entities which I want to join via a common String.
I've created a view which I want to use as the join table. This all works fine except for when I try to delete an entity. Hibernate then tries to delete from that view which of course fails. The database used is MySQL.
So I've got
#Entity
public class Event {
...
String productId;
Date eventDatetime;
...
}
#Entity
public class Stock {
...
String productId;
...
}
I've created a view in MySQL
DROP VIEW IF EXISTS EVENT_STOCK_VIEW;
create view EVENT_STOCK_VIEW AS
SELECT EVENT.EVENT_ID, STOCK.STOCK_ID
FROM EVENT, STOCK
where STOCK.PRODUCT_ID = EVENT.PRODUCT_ID;
in Event I've added:
#ManyToOne(fetch=FetchType.LAZY)
#JoinTable(name="EVENT_STOCK_VIEW",
joinColumns=#JoinColumn(name="EVENT_ID"),
inverseJoinColumns=#JoinColumn(name="STOCK_ID",updatable=false,insertable=false))
public Stock getStock(){
return this.stock;
}
and in Stock:
#OneToMany(fetch=FetchType.LAZY)
#JoinTable(name="EVENT_STOCK_VIEW",
joinColumns=#JoinColumn(name="STOCK_ID",updatable=false,insertable=false), inverseJoinColumns=#JoinColumn(name="EVENT_ID",updatable=false,insertable=false))
#OrderBy("eventDatetime DESC")
public List<Event> getEvents(){
return events;
}
I've googled a bit and found this site.
But the solution isn't really that nice (you have to use entity in between stock and event).
Are there any other solutions?
I could use a Hibernate Interceptor and override onPrepareStatement(String sql) and check whether the SQL string contains delete from EVENT_STOCK_VIEW and return an dummy command. Clearly a hack which I try to avoid.
Can't you do it without join table at all? As far as I understand, your relationship is effectively read-only, so that the following approach should work fine:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "productId",
referencedColumnName = "productId", insertable = false, updateable = false)
public Stock getStock(){
return this.stock;
}
...
#OneToMany(fetch=FetchType.LAZY, mappedBy = "stock")
#OrderBy("eventDatetime DESC")
public List<Event> getEvents(){
return events;
}

Hibernate Annotations with a collection

I am trying to implement my model using hibernate annotations. I have 3 classes, image, person, and tags. Tags is a a table consisting of 4 fields, an id, personId, imageId, and a createdDate. Person has the fields name, id, birthdate, etc. My image class is defined as follows:
#Entity
#Table(name="Image")
public class Image {
private Integer imageId;
private Set<Person> persons = new HashSet<Person>();
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
public Integer getImageId() {
return imageId;
}
public void setImageId(Integer imageId) {
this.imageId = imageId;
}
#ManyToMany
#JoinTable(name="Tags",
joinColumns = {#JoinColumn(name="imageId", nullable=false)},
inverseJoinColumns = {#JoinColumn(name="personId", nullable=false)})
public Set<Person> getPersons() {
return persons;
}
public void setPersons(Set<Person> persons) {
this.persons = persons;
}
If I remove the annotations on the getPersons() method I can use the classes and add and remove records. I want to fetch all the tags with the image and I am trying to use a set. I keep getting the following error:
org.hibernate.LazyInitializationException - failed to lazily initialize a collection of role: com.exmaple.persons, no session or session was closed
Can someone please help me and let me know what I am doing wrong?
Thank you
This error message - which actually has nothing to do with your association mapping strategy or annotations - means that you have attempted to access a lazy-loaded collection on one of your domain objects after the Session was closed.
The solution is to either disable lazy-loading for this collection, explicitly load the collection before the Session is closed (for example, by calling foo.getBars().size()), or making sure that the Session stays open until it is no longer needed.
If you are not sure what lazy-loading is, here is the section in the Hibernate manual.
Thanks for the response matt. I am confused now. My query to retrieve the image looks like this:
public Image findByImageId(Integer imageId) {
#SuppressWarnings("unchecked")
List<Image> images = hibernateTemplate.find(
"from Image where imageId=?", imageId);
return (Image)images.get(0);
}
I thought that I can call the single hql query and if my mappings are correct it will bring back the associated data.
I was looking at this example at this link hibernate mappings:
2.2.5.3.1.3. Unidirectional with join table
A unidirectional one to many with join table is much preferred. This association is described through an #JoinTable.
#Entity
public class Trainer {
#OneToMany
#JoinTable(
name="TrainedMonkeys",
joinColumns = #JoinColumn( name="trainer_id"),
inverseJoinColumns = #JoinColumn( name="monkey_id")
)
public Set<Monkey> getTrainedMonkeys() {
...
}
#Entity
public class Monkey {
... //no bidir
} Trainer describes a unidirectional relationship with Monkey using the join table TrainedMonkeys, with a foreign key trainer_id to Trainer (joinColumns) and a foreign key monkey_id to Monkey (inversejoinColumns).

Categories

Resources