Hibernate / Spring JPA, combining #Version and #DynamicUpdate - java

I have a question for JPA / Hibernate experts.
In my application all my entities inherit from a "Base intenty" that hold Id, cretedDate, Version and all fields common to all the the entities of the application.
#MappedSuperclass
public class BaseEntity implements Serializable {
/**
* The constant serialVersionUID.
*/
private static final long serialVersionUID = 1L;
/**
* The Id.
*/
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* The Created date.
*/
#Basic(optional = false)
#Column(columnDefinition = "TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP", name = "createddate")
#CreatedDate
#CreationTimestamp
#JsonIgnore
private ZonedDateTime createdDate;
/**
* The Updated date.
*/
#Basic(optional = false)
#Column(columnDefinition = "TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP", name = "updateddate")
#UpdateTimestamp
#LastModifiedDate
#JsonIgnore
private ZonedDateTime updatedDate;
/**
* The Version.
*/
#Version
#Column(columnDefinition = "INT DEFAULT 1")
#JsonIgnore
private Integer version;
/**
* The constant logger.
*/
static Logger logger = LogManager.getLogger(BaseEntity.class.getName());
/**
* Instantiates a new Base entity.
*/
public BaseEntity() {
}
[....]
}
But it happens that one of the entities ( one of the 120 tables of the database) is generating un-necessary Optimistic lock exception because it is a table that should not have required the #version, so I was thinking of adding :
#OptimisticLocking(type = OptimisticLockType.DIRTY)
#DynamicUpdate
for this particular entity.
I cannot remove the inheritance of BaseIntity for this class because, the abstract JPA repository is expecting a class extending BaseEntity
#NoRepositoryBean interface CustomRepository<T extends BaseEntity, ID extends Serializable>
extends Repository<T, ID>, Serializable {
So my question is the following :
What will be the behaviour of Hibernate / JPA, if an entity both has a #Version column defined through its parent but has the #DynamicUpdate also activated locally.
What will happen ? I checked it compiles and runs fine, but before putting this version in production i was wondering if it could have side effects...

You can use that approach, but I would recommend you try to get away from such a limiting model and move the versioning parts into a subclass VersionedBaseEntity.

Related

Javers with Spring Boot returning ENTITY_INSTANCE_WITH_NULL_ID for a #Transient field

I've just started using Javers on my Application but I have entities annoted with #Transient that I thought Javers would ignore than, but no :(, instead it's throwing me an exception:
JaversException ENTITY_INSTANCE_WITH_NULL_ID: Found Entity instance 'ProductData' with null Id-property 'id'
Do you guy know if there is a way to Ignore those transient fields?
The Documentation says that the #Transient annotation is a synonym for #DiffIgnore. But i dont know if that is related to only comparacion, or during the audit flow as well.
Here is my code:
#Entity
public class ProductExternal extends AbstractEntity implements ExternalEntity {
#Transient
private ProductData productData;
#NotNull
#Column(unique=true)
private Long externalId;
public ProductExternal() { }
//get set
}
--
#Entity
public class ProductData extends AbstractEntity {
private static final long serialVersionUID = 1L;
#Column
#NotNull
private String name;
public ProductData() { }
//get set
}
Parent class
#MappedSuperclass
public abstract class AbstractEntity implements Serializable {
public AbstractEntity() {}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
#Version
#Column(columnDefinition = "bigint default '0'")
protected Long version;
//get set
}
Your class and mapping (annotations) seems fine. The exception is saying:
Found Entity instance 'ProductData' with null Id-property 'id'
So you are trying to commit to Javers an object of class ProductData which has null id field. Obviously that's not possible. That's a common issue with Hibernate's #GeneratedValue magic. Your field is null at the first place, and then it's being updated later by Hibernate after calling DB sequence next val.
Generally, you should call Javers commit() after Hibernate is done with persisting your object. It can be easily achieved when using one of Javers' auto-audit aspects: #JaversAuditable or #JaversSpringDataAuditable. They are applied in the right phase and call Javers commit() for you. See https://javers.org/documentation/spring-integration/#auto-audit-aspect.

Spring Data REST - How to sort an entity by JSON ignored field?

I am using Spring Data REST and I have the following entity in my project.
#Data
#Entity
public class Loan{
#Id
#GeneratedValue
private Long id;
#JsonIgnore
private Long createdDate;
private Long amount;
private Long repaymentStartDate;
}
Now I want to sort the loans by the createdDate which will be automatically filled and JSONIgnored to prevent it from being updated. But I am unable to sort the loans by the createdDate when I call the endpoint loans?sort=createdDate.
How do I fix this?
Here is my repository:
public interface LoanRepository extends PagingAndSortingRepository<Loan, Long>{
}
To workaround try to replace #JsonIgnore to #JsonProperty(access = READ_ONLY). It prevents createdDate from changing but remains it in the json body.
UPDATED
For Spring Boot 1.5.10+ instead of #JsonProperty(access = READ_ONLY) you can use #JsonIgnoreProperties("createdDate") on top of the entity.

Hibernate ignoring property values from MappedSuperClasses?

I have a couple of base classes for my Hibernate entities:
#MappedSuperclass
public abstract class Entity<T> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private T id;
public T getId() { return this.id; }
}
#MappedSuperclass
public abstract class TimestampedEntity<T> extends Entity<T> {
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_at")
private Date createdAt;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "updated_at")
private Date updatedAt;
// Getters and setters for createdAt and updatedAt....
}
Each entity class obviously extends these base classes and gets the id property and the createdAt and updatedAt properties...
#Entity
public class SomeEntity extends TimestampedEntity<Long> {
// Insert SomeEntity's fields/getters/setters here. Nothing out of the ordinary.
}
My problem is that when saving a new instance of SomeEntity, Hibernate is ignoring any property values from the superclasses. It will attempt to insert its own value for the id column (which is obviously not in sync with the table's identity column) and attempts to insert nulls for both createdAt and updatedAt even though they've definitely been set.
SomeEntity e = new SomeEntity(/* ... */);
e.setCreatedAt(new Date());
e.setUpdatedAt(new Date());
// Here Hibernate runs an INSERT statement with nulls for both createdAt and updatedAt
// as well as a wildly out of sequence value for id
// I don't think it should be trying to insert id at all, since the GenerationType is IDENTITY
Long id = (Long)session.save(e);
What am I doing wrong here? How can I make Hibernate pick up the property values from the MappedSuperclasses?
You have those fields declared as private. If that's really what you intend (as opposed to protected or package-protected), move those annotations to the getters.
Having annotations on getters is defined as a hibernate best practice.
See: Hibernate Annotation Placement Question
An instance of SomeEntity doesn't actually have the field 'updatedAt' because that field is a private member of its super class. The persistence provider sees that the super class needs columns for those fields, but it also sees that the instantiated concrete class doesn't make use of those columns since the concrete class can't 'see' the field 'updateAt'.

#GeneratedValue in Oracle with no Sequence

I have created a trigger so that my entities ids are autogenerated with a sequence each time they're inserted into my Oracle database.
The problem comes with annotating these entities for Hibernate/JPA: I need to define a #GeneratedValue annotation but I don't want to specify the sequence name -- doing that will make Hibernate query the sequence first, then insert, which is a work that is already done by the trigger.
Is there any way to skip this sequence in the #GeneratedValue with the scenario I've proposed?
Exception I get if id is not provided:
org.hibernate.id.IdentifierGenerationException: ids for this class must be manually assigned before calling save(): Pattern
Pattern class:
#Entity
#Table(name = "PATTERN")
public class Patron extends HistoricoAbstractEntity {
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#Column(name = "ID_PATTERN")
private Integer idPattern;
#Column
private String description;
#Column(name = "NEGATIVE")
private Boolean isNegative;
#Column(name = "type")
private Integer type;
#Column(name = "N_DAYS")
private Integer numDays;
... (getters & setters)
}
From what your code,
What I can tell you is that its not related to #GeneratedValue, it specifies that the hibernate takes responsibility to generate and idetifier for your entity. In your case your are generating id your self, so you have to manually set the id for that particular entity. Then you won't get this error any more, the other thing that you can try is use of #PrePersist annotate a method with this and try assigning a value to id in it. I haven't tried this but this should work according to this answer on SO.
Assign Custom Identifier
If your id is being generated by the database then you should use #GeneratedValue(strategy=GenerationType.AUTO) on your id field along with your #Id annotation.

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 ...

Categories

Resources