How do I check that an entity is unreferenced in JPA? - java

I have the following model
#Entity
class Element {
#Id
int id;
#Version
int version;
#ManyToOne
Type type;
}
#Entity
class Type {
#Id
int id;
#Version
int version;
#OneToMany(mappedBy="type")
Collection<Element> elements;
#Basic(optional=false)
boolean disabled;
}
and would like to allow Type.disabled = true only if Type.elements is empty. Is there a way to do it atomically?
I would like to prevent an insertion of an Element in a transaction while the corresponding Type is being disabled by an other transaction.
Update: sorry I didn't make myself clear. I'm not asking how to trigger the check, but how to prevent a sequence like this:
Transaction 1 checks that Type.elements is empty
Transaction 2 checks that Type.disabled = false
Transaction 1 updates Type and sets disabled = true
Transaction 2 persists a new Element
Transaction 2 commits
Transaction 1 commits
I then have a situation where Type.elements is not empty and Type.disabled = true (broken invariant). How can I avoid this situation? In native SQL, I would use pessimistic locking, but JPA 1.0 does not support this. Can the problem be solved using optimistic locking?

I would like to prevent an insertion of an Element in a transaction while the corresponding Type is being disabled by an other transaction.
I would use the Bean Validation API (JSR 303) and a custom constraint for this purpose. If you are not familiar with Bean Validation, I suggest reading the following entries:
Bean Validation Sneak Peek part I
Bean Validation Sneak Peek part II: custom constraints
Bean Validation Sneak Peek part III: groups and partial validation

while Bean Validation as suggested by Pascal is more elegant, a quick and easy solution would be entity lifecycle listener methods inside the entity class:
#PrePersist #PreUpdate
protected void validate(){
if(this.disabled && !this.elements.isEmpty()){
throw new IllegalArgumentException(
"Only types without elements may be disabled");
}
}
or better yet, have an Assert class (as in JUnit or Spring that encapsulates the exception throwing):
#PrePersist #PreUpdate
protected void validate(){
Assert.same( // but this implies a bi-directional constraint
this.disabled, this.elements.isEmpty(),
"Only types without elements may be disabled and vice-versa"
);
}

Related

How to save entities with manually assigned identifiers using Spring Data JPA?

I'm updating an existing code that handles the copy or raw data from one table into multiple objects within the same database.
Previously, every kind of object had a generated PK using a sequence for each table.
Something like that :
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
In order to reuse existing IDs from the import table, we removed GeneratedValue for some entities, like that :
#Id
#Column(name = "id")
private Integer id;
For this entity, I did not change my JpaRepository, looking like this :
public interface EntityRepository extends JpaRepository<Entity, Integer> {
<S extends Entity> S save(S entity);
}
Now I'm struggling to understand the following behaviour, within a spring transaction (#Transactional) with the default propagation and isolation level :
With the #GeneratedValue on the entity, when I call entityRepository.save(entity) I can see with Hibernate show sql activated that an insert request is fired (however seems to be only in the cache since the database does not change)
Without the #GeneratedValue on the entity, only a select request is fired (no insert attempt)
This is a big issue when my Entity (without generated value) is mapped to MyOtherEntity (with generated value) in a one or many relationship.
I thus have the following error :
ERROR: insert or update on table "t_other_entity" violates foreign key constraint "other_entity_entity"
Détail : Key (entity_id)=(110) is not present in table "t_entity"
Seems legit since the insert has not been sent for Entity, but why ? Again, if I change the ID of the Entity and use #GeneratedValue I don't get any error.
I'm using Spring Boot 1.5.12, Java 8 and PostgreSQL 9
You're basically switching from automatically assigned identifiers to manually defined ones which has a couple of consequences both on the JPA and Spring Data level.
Database operation timing
On the plain JPA level, the persistence provider doesn't necessarily need to immediately execute a single insert as it doesn't have to obtain an identifier value. That's why it usually delays the execution of the statement until it needs to flush, which is on either an explicit call to EntityManager.flush(), a query execution as that requires the data in the database to be up to date to deliver correct results or transaction commit.
Spring Data JPA repositories automatically use default transactions on the call to save(…). However, if you're calling repositories within a method annotated with #Transactional in turn, the databse interaction might not occur until that method is left.
EntityManager.persist(…) VS. ….merge(…)
JPA requires the EntityManager client code to differentiate between persisting a completely new entity or applying changes to an existing one. Spring Data repositories w ant to free the client code from having to deal with this distinction as business code shouldn't be overloaded with that implementation detail. That means, Spring Data will somehow have to differentiate new entities from existing ones itself. The various strategies are described in the reference documentation.
In case of manually identifiers the default of inspecting the identifier property for null values will not work as the property will never be null by definition. A standard pattern is to tweak the entities to implement Persistable and keep a transient is-new-flag around and use entity callback annotations to flip the flag.
#MappedSuperclass
public abstract class AbstractEntity<ID extends SalespointIdentifier> implements Persistable<ID> {
private #Transient boolean isNew = true;
#Override
public boolean isNew() {
return isNew;
}
#PrePersist
#PostLoad
void markNotNew() {
this.isNew = false;
}
// More code…
}
isNew is declared transient so that it doesn't get persisted. The type implements Persistable so that the Spring Data JPA implementation of the repository's save(…) method will use that. The code above results in entities created from user code using new having the flag set to true, but any kind of database interaction (saving or loading) turning the entity into a existing one, so that save(…) will trigger EntityManager.persist(…) initially but ….merge(…) for all subsequent operations.
I took the chance to create DATAJPA-1600 and added a summary of this description to the reference docs.

Modifications on parent not persisted without explicit flush from parents repository

With spring-data-jpa 2.0.8.RELEASE
I have a #OneToMany related entity pair. Let's say King & Peasant. I wanted to have a logic where when a peasant is updated, this would also update the kings #LastModifiedDate value manually. I do something like this;
#PreUpdate
#PreRemove
#PrePersist
void updateParents() {
Date now = new Date();
BaseEntity container = getParent();
while (Objects.nonNull(container)) {
container.setUpdateDateTime(now);
container = container.getParent();
}
}
and this works well, meaning it does update all the parents up to the king (the table structure is really messy with 5 depth from king to the lowest serf), the problem I am having is, the modifications on parents are not persisted at all. I have a service like following;
#Transactional
public void update(String kingId, String peasantSeqNo) {
Peasant peasant = peasantRepository.getPeasant(kingId, peasantSeqNo);
peasant.setNobility(false);
peasantRepository.save(peasant);
}
In above code, the #PreUpdate annotated updateParents() method is triggered, and king's update timestamp is updated, still after the transactions end, this change is not persisted. I can trigger this persistence with an explicit kingRepository.flush() but I want it to be done automatically, just with the modification of the parent.
The linkage between King-Peasant is as follows;
#JsonManagedReference
#OneToMany(mappedBy = "parent", cascade = REMOVE)
private List<Peasant> peasantry;
and
#JsonBackReference
#MapsId("kingId")
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "ID_KING", referencedColumnName = "ID_KING", nullable = false)
private King parent;
This is somehow an issue with my utilization of JPA, but cannot find the exact reason & a solution, can you give me any input on this?
You are hitting a limitation of JPA (implementations).
When the event gets triggered the JPA implementation already decided which entities it is going to persist, thus your changes don't get picked up.
This behavior is actually defined as not being defined in the JPA specification:
In general, the lifecycle method of a portable application should not invoke EntityMan- ager or query operations, access other entity instances, or modify relationships within the same persistence context[46].[47] A lifecycle callback method may modify the non-relationship state of the entity on which it is invoked.
[46] Note that this caution applies also to the actions of objects that might be injected into an entity listener.
[47] The semantics of such operations may be standardized in a future release of this specification.
So you need to move this behavior away from the JPA Listeners into your business logic.

Calculated Property wrt JPA. Does this work in my case?

I have a scenario where I have 2 labels that need to be configured. The names of the labels are 'Out Date' and 'In Date'. I only have one field in the database called 'Date'. Whether it is 'Out' or 'In' is decided at the runtime by the value of an Enum 'Scenario'. However, I need to actually show the user Out Date&In Date so that he can select 1 or both of them. I heard that calculated field concept it JPA will assist in this. Is this true or is there some other way that I can achieve this. Below is some sample code.
Date
#Override
#Convert("DateTimeConverter")
#Column(name = "DATE")
public DateTime getDate() {
return date;
}
Scenario
#Override
#Convert("EnumConverter")
#Column(name = "SCENARIO")
public Scenario getScenario() {
return scenario;
}
Scenario is any enum with the values OUT(1),IN(2)
There are no calculated properties in JPA.
You can use #Transient annotation to create properties that are not persisted but calculated based on other fields:
#Transient
public DateTime getInDate() {
if (scenario == Scenario.IN) {
return date;
}
return null;
}
#Transient
public DateTime getOutDate() {
if (scenario == Scenario.OUT) {
return date;
}
return null;
}
Alternatively, if you are using Hibernate you can use proprietary annotation #Formula:
#Formula("case when SCENARIO = 2 then DATE else NULL end")
#Convert("DateTimeConverter")
private DateTime inDate;
#Formula("case when SCENARIO = 1 then DATE else NULL end")
#Convert("DateTimeConverter")
private DateTime outDate;
I prefer the first option because:
it is easier to test with unit tests
it is easier to use the entity in unit tests
it does not require proprietary extensions
generally there might be some problems with portability of SQL, although in this problem case when is SQL 92 compatible so it does not apply here
The only problem I can is is that in simplest approach is that we abandon encapsulation by exposing to clients internals of the entity (scenario and date properties). But you can always hide these properties with accessor protected, JPA will still handle that.
To compute properties within JPA entities, you can use JPA callbacks.
See this Hibernate JPA Callbacks documentation. (Note: JPA callbacks are not specific to hibernate, it's part of latest JPA 2.1 specification).
And also this OpenJpa JPA Calbacks one.
Following entity life-cycle categories have a Pre and Post event which can be intercepted by the entity manager to invoke methods:
Persist -> #PrePersist, #PostPersist
Remove -> #PreRemove, #PostRemove
Update -> #PreUpdate, #PostUpdate
Load -> #PostLoad (No Pre for this ...)
So let's say you want to compute a complexLabel label from two persisted entity fields label1 and label2 in an entity titled MyEntity:
#Entity
public class MyEntity {
private String label1;
private String label2;
#Transient
private String complexLabel;
#PostLoad
#PostUpdate // See EDIT
// ...
public void computeComplexLabel(){
complexLabel = label1 + "::" + label2;
}
}
As #Dawid wrote, you have to annotate complexLabel with #Transient in order to make them ignored by persistence. If you don't do this, persistence fails because there is no such column in MyEntity corresponding table.
With #PostLoad annotation, computeComplexLabel() method is called by entity manager just after the loading of any instance of MyEntity from persistence.
Thus, #PostLoad annotated method is best suited to put your post loading entity properties enhancement code.
Bellow is an extract from JPA 2.1 specification about PostLoad:
The PostLoad method for an entity is invoked after the entity has been
loaded into the current persistence context from the database or
after the refresh operation has been applied to it. The PostLoad
method is invoked before a query result is returned or accessed or
before an association is traversed.
EDIT
As pointed out by #Dawid, you could also use #PostUpdate in case you want to compute this transient field just after the entity update, and use other callbacks when needed.

Protect entity from cascade delete in Hibernate

Simple question: does anyone have any ideas how to protect some entity from being deleted via CascadeType.ALL in hibernate in runtime (may be with throwing a runtime exception)?
Say, we have some entity:
#Entity
#Table(name = "FOO_ENTITY")
public class FooEntity {
...
}
And I want to protect it from accidental wrong mapping, like:
#Entity
#Table(name = "SOME_OTHER_FOO_ENTITY")
public class SomeOtherFooEntity {
...
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "FOO_ENTITY_ID")
private FooEntity fooEntity;
}
So, it should be possible to delete some entity of type FooEntity via session.delete(fooEntityObj), but it must be disabled ability to delete it via cascade removal (session.delete(someOtherFooEntityObj)).
ATTENTION: For those who read my question inattentive or think that I do not understand what am I asking:
1) I can not remove CascadeType.ALL. The question is: who programming avoid and protect from this?
2) Unit tests is not the way, I'm looking for runtime solution.
One of the ways this can be done is to programmatically inspect Hibernate mapping meta-data and to check whether any delete operation (ALL, REMOVE, orphanRemoval) cascades to the protected entity from any other entity; something like:
String protectedEntityName = FooEntity.class.getName();
SessionFactoryImpl sessionFactory = (SessionFactoryImpl) session.getSessionFactory();
for (EntityPersister entityPersister : sessionFactory.getEntityPersisters().values()) {
for (int i = 0; i < entityPersister.getPropertyTypes().length; i++) {
Type type = entityPersister.getPropertyTypes()[i];
EntityType entityType = null;
if (type.isCollectionType()) {
CollectionType collectionType = (CollectionType) type;
Type elementType = sessionFactory.getCollectionPersister(collectionType.getRole()).getElementType();
if (elementType.isEntityType()) {
entityType = (EntityType) elementType;
}
} else if (type.isEntityType()) {
entityType = (EntityType) type;
}
if (entityType != null && entityType.getName().equals(protectedEntityName)) {
if (entityPersister.getPropertyCascadeStyles()[i].doCascade(CascadingAction.DELETE)) {
// Exception can be thrown from here.
System.out.println("Found! Class: " + entityPersister.getEntityName() + "; property: " + entityPersister.getPropertyNames()[i]);
}
}
}
}
This validation can be performed on server startup or in an integration test.
The advantage of this approach is that you don't have to modify the defined behavior of Hibernate; it just acts as a reminder that you forgot not to cascade deletion to the FooEntity.
Regarding the tests, yes, I know that the OP explicitly said that tests are not an acceptable solution for this use case (and I personally agree with it in general). But these kinds of automatic tests may be useful because you write them and forget about them; you don't have to update the tests whenever you add a new mapping or modify an existing one (which defeats the purpose of the tests because you may forget or oversee to adopt the tests for each possible use case).
For starters I think you do understand what you're asking, you've just settled on a specific solution that many people, myself included, are questioning. It's not inattentiveness...it's trying to solve your actual problem.
If you really want to stop the CascadeType.ALL value on annotations from having its documented effect, instead of verifying that CascadeType.ALL is not used where it shouldn't be (and validating those expectations via unit tests), then extend the DefaultDeleteEventListener and override the deleteEntity method to always pass false to the super implementation for the isCascadeDeleteEnabled flag.
If you want a solution that has some semblance of standard, expected behavior, then define relationship that should do cascading deletes at the schema level, and establish best practices to only use the CascadeTypes that you care about in your code. Maybe that's PERSIST and MERGE, maybe you're using save and update functionality of session factory and so you need to use the Hibernate-specific #CascadeType annotation .
Can't you just remove cascade attribute from #ManyToOne annotation if you don't want to cascade changes to objects associated with the one that is really changed?
The most reliable way to catch any kind of programming error is to write unit tests.
If you practice Test Driven Development you will minimise the chances of "forgetting" to do it.

JPA equivalent of Hibernate's #Generated(GenerationTime.ALWAYS)

When certain non key fields of a entity are generated in the database (for instance, by triggers) a call to persist will not bring back values that the database has just generated. In practice this means that you may need to refresh an entity after persist or merge (and when level 2 cache is enabled you may even need to evict the entity).
Hibernate have a custom annotation #Generated which handles Generated Properties.
// Refresh property 1 on insert and update
#Generated(GenerationTime.ALWAYS)
#Column(insertable = false, updatable = false)
private String property1;
// Refresh property 2 on insert
#Generated(GenerationTime.INSERT)
#Column(insertable = false)
private String property2;
JPA #GeneratedValue only works with primary key properties.
So, my question is if there is a replacement for #Generated on JPA API (maybe on 2.1)? And if there isn't one, what is the best practice to handle non key database generated fields?
I read the specs from the beginning until the end and it is not such thing, nothing comparable with #Generated, sorry , and as you said.
The GeneratedValue annotation may be applied to a primary key property
or field of an entity or mapped superclass in conjunction with the Id
annotation.
What you could do is use Event Listener #PrePersist and #PreUpdate to set some properties by default or generated by utility classes before em persist the object , try that approach it comes to my mind to something similiar.

Categories