Spring Data JDBC Lookup Tables Cascading Deletes - java

I have a structure similar to below. I need to only retrieve the lookup data but not have it deleted/saved/updated when a parent/child is deleted/saved/updated. The data in the lookup table is static. I'm using Sprint Data JDBC with Java 11 with Postgres for the database. I understand this is a contrived example but I am not allowed to post the actual code.
#Data
#Table("parent")
public class ParentDTO {
#Id
private long parentId;
private Date createdAt;
#MappedCollection(idColumn="parent_id", keyColumn="parent_id")
private Set<ChildDTO> children;
String name;
}
#Data
#Table("child")
public class ChildDTO {
#Id
private long childId;
private Date createdAt;
private long parentId;
String name;
#MappedCollection(idColumn="lookup_id", keyColumn="lookup_id")
private LookupDTO lookupDTO;
}
#Data
#Table("lookup")
public class LookupDTO {
#Id
private long lookupId;
String name;
private Date createdAt;
}

Cascade is not an optional feature in JPA/Spring-Data that can be entirely removed. The entity relations will have a default cascade option, since it is a reflection of the underlying database schema.
In your example, if the ParentDTO is deleted, how can the ChildDTO that has a foreign key (ChildDTO.parentId) exist in the database? The Spring-Data JPA just reflects this design.
If you want to make sure nobody deletes the ParentDTO or other entities, then you can do several things:
Here are some ideas:
Change the Database access to these tables to be read-only for your JDBC user id. If they are static, this is how it should be.
Make the Repository read-only. Here is a good discussion on that - Creating a read-only repository with SpringData
Force your entity to be read-only. The top answer here is the best - How to make an Entity read-only? (Implement an EntityListener, or just remove all setter methods from your entities)

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.

How to store string lists in database without a join table?

I want to store a List<String> in a postgres DB.
#ElementCollection
private List<String> products;
Hibernate will therefore create a join table. Is it possible to prevent this?
One workaround would be to create an explicit class with bidirectional mapping as follows:
#Entity
public class MainEntity {
#OneToMany(mappedBy = "main")
private List<Product> products;
}
#Entity
public class Product {
#Id
private long id;
#ManyToOne
private MainEntity main;
private String text;
}
But I feel this is a bit over the top for just storing a string list, isn't it?
If you don't find anything better, try this:
Mark your List with #Transient so that it is never persisted directly.
Prepare additional field for persisting your list, of a type that is "persistable" directly by JPA (concatenated, delimetered String seems to be quite natural).
Use methods annotated with #PostLoad and #PrePersist to move data between those two fields, converting from List to String and back.
i'm not sure but could you remove :
#ManyToOne
private MainEntity main;
in class product.
I think it might works properly without this.
Do you want to handle your list from MainEntity or from Product?

index not updating after external entity changes

I'm currently working on a project to persist data with JPA 2.1 and to search entities using hibernate search 4.5.0.final.
After mapping classes and indexing, the searching works fine.
However, when I changed the value description of classB from "someStr" to "anotherStr". The database was updated accordingly, but when I checked the index using Luke, classA.classB.description in the index wasn't updated, and the data cannot be searchable by keyword "anotherStr", but can be searchable by keyword "someStr".
After I reindex the whole database, it's updated finally.
According to Hibernate search website,
The short answer is that indexing is automatic: Hibernate Search will transparently index every entity persisted, updated or removed through Hibernate ORM. Its mission is to keep the index and your database in sync, allowing you to forget about this problem.
But it's not working in my case. I'm not sure if I missed some details or I need to handle it myself for this kind of issues.
I also tried to add annotation #Indexed on classB as suggested by this one, but it's still not solving my problem.
As far as I know, the solution would be to reindex the database periodically. But reindexing would disable the search functionality and that's not an option in most of the cases.
Could anyone give some suggestions? Thanks.
I have a class which embedded some other classes by using #IndexedEmbedded annotation. Here is a simplified version of my class mapping.
Class A
#Entity(name = "classA")
#Indexed
public class classA extends Model {
private int id;
private String name;
private ClassB place;
...
some constructors
...
#Id
#GeneratedValue
#DocumentId
public int getId() {
return id;
}
#Column(name = "name")
#Field(analyze = Analyze.NO, store = Store.YES) // only used for sorting
public String getName() {
return name;
}
#IndexedEmbedded
#ManyToOne
#JoinColumn(name = "place_id")
public ClassB getPlace() {
return place;
}
...
}
Class B
#Entity(name = "classB")
public class classB extends Model {
private int id;
private String description;
...
some constructors
...
#Id
#GeneratedValue
public int getId() {
return id;
}
#Fields({
#Field,
#Field(name = "description_sort", analyze = Analyze.NO, store = Store.YES)
})
#ContainedIn
#Column(name = "description")
public String getDescription() {
return description;
}
...
}
And the indexing methods is as follows:
fullTextEntityManager.createIndexer()
.purgeAllOnStart(true)
.optimizeAfterPurge(true)
.optimizeOnFinish(true)
.batchSizeToLoadObjects(25)
.threadsToLoadObjects(8)
.startAndWait();
You placed ContainedIn annotation incorrectly. According the Hibernate Search documentation:
Be careful. Because the data is denormalized in the Lucene index when using the #IndexedEmbedded technique, Hibernate Search needs to be aware of any change in the Place object and any change in the Address object to keep the index up to date. To make sure the Place Lucene document is updated when it's Address changes, you need to mark the other side of the bidirectional relationship with #ContainedIn.
In your example, you need to:
Make the relationship between classes bidirectional
Mark the relationship in ClassB as ContainedIn
In your case:
ClassB {
private Set<ClassA> linkedObjects;
....
#OneToMany(mappedBy="place")
#ContainedIn
public Set<ClassA> getLinkedObjects() {
return linkedObjects;
}
....
}
I had a similar problem but already with correct annotations. In my case, I have added forced flush both to the database and to index and refreshed it afterward:
myEm.flush();
Search.getFullTextEntityManager(myEm).flushToIndexes();
myEm.refresh(updatedObject);
hmmm, add #ContainedIn doesn't work for me.
I put the sample project here
https://github.com/yhjhoo/princeSSH
Update department object is not able to update person index

Can I configure Hibernate/JPA to update an entity record when only non-timestamp fields have been modified?

At the moment I have an Hibernate entity class as follows:
#Entity
#Table(name = "entity")
public class Entity implements Serializable {
private static final long serialVersionUID = 2040757598327793105L;
#Id
#Column
private int id;
#Column
private String data;
#Column(name = "last_modified")
#Temporal(TemporalType.TIMESTAMP)
private Date lastModified;
}
I've found that even when the non-timestamp fields are not modified (i.e. the data field) a call to merge still updates the timestamp. I would like the timestamp to only update when other data fields have changed.
Is there anyway I can prevent calls to merge making a SQL UPDATE when all other data fields are not modified, or do I have to explicitly check for this myself in the code?
Update (thanks to comment):
Since v4 of Hibernate #Entity annotation is deprecated and for allowing dynamic updates you should use #DynamicUpdate(true) (in conjunction with #SelectBeforeUpdate(true))
If you want to prevent unmodified fields to be included in UPDATE queries, add this on your entity:
#org.hibernate.annotations.Entity(dynamicUpdate=true) // update only changed fields
public class ...

Hibernate and JPA: how to make a foreign key constraint on a String

I am using Hibernate and JPA. If I have two simple entities:
#Entity
#Table(name = "container")
public class Container {
#Id
#Column(name="guid")
private String guid;
}
#Entity
#Table(name="item")
public class Item {
#Id
#Column(name="guid")
private String guid;
#Column(name="container_guid")
private String containerGuid;
}
and I want to insure that inserting an Item fails if the referenced Container does not exist. I would prefer not to have a Container object populated inside the item object (ManyToOne), how would I do this if it is possible to do?
You can declare arbitrary constraint using columnDefinition attribute:
#Column(name="container_guid",
columnDefinition = "VARCHAR(255) REFERENCES container(guid)")
private String containerGuid;
Note, however, that Hibernate doesn't know anything about this constraint, so that, for example, it may not perform inserts in proper order with respect of it and so on.
Therefore it would be better to create a #ManyToOne relationship. If you are afraid of extra SQL query for Container needed to set this property, you can use Session.load()/EntityManager.getReference() to get a proxy without issuing actulal query.
Try using below relationship mapping
RelationShip Mapping
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#ManyToOne()
#ManyToMany()
<>
#JoinColumn(name="<>")

Categories

Resources