Auditing and #Embedded in Spring Data JPA - spring

I am having a problem with JPA auditing and for #Embedded members. Consider the following example scenario:
I set up a test table in an Oracle DB:
CREATE TABLE AUDIT_TEST (
ID NUMBER(38) NOT NULL PRIMARY KEY,
CREATION_DATE TIMESTAMP(6) DEFAULT SYSTIMESTAMP NOT NULL
);
I define a JPA #Entity with auditing:
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "AUDIT_TEST")
public class AuditTest {
private Long id;
private LocalDateTime creationDate;
#Id
#Column(name = "ID")
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
#CreatedDate
#Column(name = "CREATION_DATE")
public LocalDateTime getCreationDate() { return creationDate; }
public void setCreationDate(LocalDateTime creationDate) {
this.creationDate = creationDate;
}
}
Finally, I enable JPA auditing in my #Configuration:
#SpringBootApplication()
#EnableJpaAuditing()
public class AuditTestApplication {
}
So far so good; when I construct an AuditTest instance, assign it an id and commit, the creationDate column gets populated with the current timestamp as expected.
However, things stop working when I encapsulate the audit column in an #Embeddable:
#Embeddable
public class AuditTestEmbeddable {
private LocalDateTime creationDate;
#CreatedDate
#Column(name = "CREATION_DATE")
public LocalDateTime getCreationDate() { return creationDate; }
public void setCreationDate(LocalDateTime creationDate) {
this.creationDate = creationDate;
}
}
Then I change my entity class to embed the creation date:
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "AUDIT_TEST")
public class AuditTest {
private Long id;
private AuditTestEmbeddable auditTestEmbeddable = new AuditTestEmbeddable();
#Id
#Column(name = "ID")
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
#Embedded
public AuditTestEmbeddable getAuditTestEmbeddable() {
return auditTestEmbeddable;
}
public void setAuditTestEmbeddable(AuditTestEmbeddable auditTestEmbeddable) {
this.auditTestEmbeddable = auditTestEmbeddable;
}
}
And unfortunately, the auditing is no longer working.
Is anyone here aware of a way to save the auditing functionality while still using #Embedded classes?

Update:
This functionality has been added to Spring Data 2.1 M2 (Lovelace).
https://jira.spring.io/browse/DATACMNS-1274
Spring Data audit annotations in nested (embeddable) classes isn't supported yet. Here's the jira ticket requesting this feature.
However, we could use custom audit listener to set audit information in embeddable classes.
Here's the sample implementation taken from a blog: How to audit entity modifications using the JPA #EntityListeners, #Embedded, and #Embeddable annotations.
Embeddable Audit
#Embeddable
public class Audit {
#Column(name = "created_on")
private LocalDateTime createdOn;
#Column(name = "created_by")
private String createdBy;
#Column(name = "updated_on")
private LocalDateTime updatedOn;
#Column(name = "updated_by")
private String updatedBy;
//Getters and setters omitted for brevity
}
Audit Listener
public class AuditListener {
#PrePersist
public void setCreatedOn(Auditable auditable) {
Audit audit = auditable.getAudit();
if(audit == null) {
audit = new Audit();
auditable.setAudit(audit);
}
audit.setCreatedOn(LocalDateTime.now());
audit.setCreatedBy(LoggedUser.get());
}
#PreUpdate
public void setUpdadtedOn(Auditable auditable) {
Audit audit = auditable.getAudit();
audit.setUpdatedOn(LocalDateTime.now());
audit.setUpdatedBy(LoggedUser.get());
}
}
Auditable
public interface Auditable {
Audit getAudit();
void setAudit(Audit audit);
}
Sample Entity
#Entity
#EntityListeners(AuditListener.class)
public class Post implements Auditable {
#Id
private Long id;
#Embedded
private Audit audit;
private String title;
}

With spring-data 2.4.4 the AuditListener works fine with embedded objects, see
documentation spring-data
The minimal version of spring-data is bundled in spring-boot version 2.4.3

Related

Save creationTimestamp and updatedTime in spring + hibernate

I need to update the postgres DB with createdDate and updatedDate
I tried using approach 1, But it is inserting null values.
When I read about, it seems the #prepersist annotations does not work for session.
So I decided to go with Approach 2 : Hibernate #CreationTimeStamp Annotation, I added hibernate-annotations maven dependency, But #CreationTimeStamp is not resolved and gives compilation error.
Can someone advise me on how I can resolve the issue ?
Approach 1
Entity class annotated with #Entity and #Table
public class Status{
#Id
#Column(name = "run_id")
private int run_id;
#Column(name = "status")
private String status;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_date" , updatable=false)
private Date created;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "updated_date" , insertable=false)
private Date updated;
#PrePersist
protected void onCreate() {
created = new Date();
}
#PreUpdate
protected void onUpdate() {
updated = new Date();
}
//Getters and setters here
}
implementation class is
sessionFactory.getCurrentSession().save(status);
Approach 2
using #CreationTimeStamp and #updatedTimeStamp. But the maven dependency
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-annotations -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-annotations</artifactId>
<version>3.5.0-Final</version>
</dependency>
does not add these annotations to classpath
Is there a reason you are using the session.save() method instead of an entitymanager? I'll post an example of my application using an entitymanager to persist and merge entities. Also I am using java.time.LocalDateTime instead of java.util.Date, that's why I don't need #Temporal.
This may also help: How to use #PrePersist and #PreUpdate on Embeddable with JPA and Hibernate
If you want to use an entitymanager this will help: Guide to the Hibernate EntityManager
Entity class:
public abstract class AbstractEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(updatable = false, nullable = false)
private Long id;
#Column
private LocalDateTime createdTimestamp;
#Column
private LocalDateTime modifiedTimestamp;
#Version
private Long version;
#PrePersist
public void setCreationDateTime() {
this.createdTimestamp = LocalDateTime.now();
}
#PreUpdate
public void setChangeDateTime() {
this.modifiedTimestamp = LocalDateTime.now();
}
//Getter and setter
}
Abstract database service class:
public abstract class AbstractDatabaseService {
#PersistenceContext(name = "examplePU")
protected EntityManager entityManager;
}
Example Entity Repository Interface:
public interface ExampleRepository {
ExampleEntity save(ExampleEntity exampleEntity);
}
Example Entity Repository Implementation:
public class ExampleRepositoryImpl extends AbstractDatabaseService implements ExampleRepository , Serializable {
#Transactional
#Override
public ExampleEntity save(ExampleEntity exampleEntity) {
ExampleEntity toPersist;
// Updating an already existing entity
if (exampleEntity.getId() != null) {
toPersist = entityManager.find(ExampleEntity .class, exampleEntity.getId());
// Omitted merging toPersist with the given exampleEntity through a mapper class here
} else {
toPersist = exampleEntity;
}
try {
toPersist = entityManager.merge(toPersist);
} catch (Exception e) {
// Logging e
}
return toPersist;
}
}
Hope this helps.

AuditingEntityListener is not working for the entity that extends another abstract entity in spring jpa

I have used the #CreatedBy, #CreatedDate, #LastModifiedBy, and #LastModifiedDate annotation on their respective fields. By using #MappedSuperclass,#EntityListeners i able to persist above columns.
But this is not working for the below case:
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
public abstract class Auditable<U> {
#CreatedBy
protected U createdBy;
#CreatedDate
#Temporal(TIMESTAMP)
protected Date creationDate;
#LastModifiedBy
protected U lastModifiedBy;
#LastModifiedDate
#Temporal(TIMESTAMP)
protected Date lastModifiedDate;
}
#Entity
#Table(name = "tabel1")
#PrimaryKeyJoinColumn(name = "ID")
class A extends B {
#Column(name = "NAME1", nullable = false)
private String name1;
#Column(name = "CONTENT1", nullable = false)
private String content1;
}
#Entity
#Table(name = "tabel2")
public abstract class B extends Auditable{
#Id
#GeneratedValue
#Column(name = "ID", nullable = false)
private int id;
#Column(name = "NAME", nullable = false)
private String name;
#Column(name = "CONTENT", nullable = false)
private String content;
}
AuditorAwareImpl.java
public class AuditorAwareImpl implements AuditorAware<String>
{
#Override
public Optional<String> getCurrentAuditor()
{
return Optional.ofNullable("Saravanan");
}
}
JpaAuditConfiguration.java
#Configuration
#EnableJpaAuditing(auditorAwareRef = "auditorProvider")
public class JpaAuditConfiguration
{
#Bean
public AuditorAware<String> auditorProvider()
{
return new AuditorAwareImpl();
}
}
In the case, Entity B is populated with audit columns. But Entity A is not. Is there a way to populate Entity A or did i missed anything here..??
I added #Entity annotation to your classes:
#Entity
public class A extends B {
#Id
#GeneratedValue
private Integer id;
private String name;
private String content;
}
#Entity
public class B extends Auditable<String> {
#Id
#GeneratedValue
private Integer id;
private String name;
private String content;
}
Persistence config class (for Spring Boot):
#Configuration
#EnableJpaAuditing
public class PersistenceConfig {
}
Everything works perfectly!

Hibernate Fetch #Formula annotated fields on demand

I have a entity (declared with 2 way)(some not influencing code part are ommited for readability)
Entity version 1.
#Entity
public class Article {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#Formula("(SELECT COUNT(w.id) FROM stock s LEFT JOIN warehouse w ON s.id=w.stock_id WHERE s.article_id = id)")
private int variants;
public int getVariants() {
return variants;
}
}
Entity version 2.
#Entity
public class Article {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#Transient
private int variants;
#Access(AccessType.PROPERTY)
#Formula("(SELECT COUNT(w.id) FROM stock s LEFT JOIN warehouse w ON s.id=w.stock_id WHERE s.article_id = id)")
public int getVariants() {
return variants;
}
}
respective DTO and ArticleMapper - MapStruct
#JsonIgnoreProperties(ignoreUnknown = true)
public class ArticleDTOCommon {
private Long id;
private String name;
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class ArticleDTO {
private Long id;
private String name;
private int variants;
}
#Mapper(componentModel = "spring", uses = {})
public interface ArticleMapper{
ArticleDTO toDto(Article article);
ArticleDTOCommon toDtoCommon(Article article);
}
I have a #Service layer on which how i know Hibernate creates it's proxy(for defining which field is fetch or not fetch) and transactions are occurs.
#Service
#Transactional
public class ArticleService {
#Transactional(readOnly = true)
public List<ArticleDTO> findAll() {
return articleRepository.findAll()
stream().map(articleMapper::toDto).collect(Collectors.toList());
}
#Transactional(readOnly = true)
public List<ArticleDTO> findAllCommon() {
return articleRepository.findAll()
stream().map(articleMapper::toDtoCommon).collect(Collectors.toList());
}
}
It works fine with fetching Related entity but
Problem is (fetching #Formula annotated field) when I am looking executed query on log it fetchs all time variants #Formula annotated query not depending on respective DTO.
But it must be ignored on toDtoCommon - i.e. It must not fetch variants field -> because when mapping Article to ArticleDtoCommon it not uses getVariant() field of Article. I have tried multiple ways as mentioned above.
I can solve it with writing native query(for findAllCommon() method) and map respectivelly with other way... But I want to know that how we can solve it with ORM way and where is problem.
Manupulating #Access type is not helping too.
Thanks is advance.

How to override #CreatedBY value from AbstractAuditingEntity in Spring JPA

We have a microweb service environment in which AbstractAuditingEntity coming from another common micro service. And I want to override #CreatedBy property of this abstract class with my own defined value.
My code is given below.
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
public abstract class AbstractAuditingEntity {
#Column(name = "created_by", insertable = true, updatable = false, nullable = false)
#CreatedBy
private String createdBy;
#Column(name = "created_date", insertable = true, updatable = false, nullable = false)
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime")
#CreatedDate
private DateTime createdDate;
#Column(name = "last_modified_by", nullable = false)
#LastModifiedBy
private String lastModifiedBy;
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime")
#LastModifiedDate
private DateTime lastModifiedDate;
// #Transient
public abstract Long getInternalId();
// #Transient
public abstract void setInternalId(Long internalId);
public DateTime getCreatedDate() {
return createdDate;
}
public void setCreatedDate(DateTime createdDate) {
this.createdDate = createdDate;
}
public DateTime getLastModifiedDate() {
return lastModifiedDate;
}
public void setLastModifiedDate(DateTime lastModifiedDate) {
this.lastModifiedDate = lastModifiedDate;
}
public String getCreatedBy() {
return createdBy;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
public String getLastModifiedBy() {
return lastModifiedBy;
}
public void setLastModifiedBy(String lastModifiedBy) {
this.lastModifiedBy = lastModifiedBy;
}
}
And my Domain class is like
#Entity
#Table(name = "programs")
#AttributeOverride(name = "createdBy", column = #Column(name = "created_by"))
public class Program extends AbstractAuditingEntity {
#Id
#SequenceGenerator(name = "programs_seq", sequenceName = "programs_seq")
#GeneratedValue(generator = "programs_seq")
#Column(name = "internal_id", nullable = false)
private Long internalId;
private String programName;
#Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
//Start getters & setters - auto generated by IDE
#Override
public Long getInternalId() {
return internalId;
}
#Override
public void setInternalId(Long internalId) {
this.internalId = internalId;
}
public String getProgramName() {
return programName;
}
public void setProgramName(String programName) {
this.programName = programName;
}
}
When we try to persist this domain object in the database, user defined value provided by me is not getting persist, instead Spring framework still adding its own value. I have tried #AttributeOverride but it doesn't work in my case.
Thanks in advance for help.
I got a solution as below.
#PrePersist
private void setCreateByNew() {
setCreatedBy("anonymousUser");
}
Using #PrePersist I am able to override value which I am getting from common framework.
An alternate solution is to set the securityContext
SecurityContextHolder.getContext().setAuthentication(___{AuthToken}___)
with the username/principal you are using to auto populate audit fields before entity save and reset it after that. (for message or event processing in asynchronous fashion). This populates both #Createdby and #LastUpdatedBy from the JPA AuditAware listener.
#PrePersist will only work if we are trying to hardcode a pre-defined String setCreatedBy("anonymousUser"); as indicated above. It does not allow us to pass in the username/name of the user as parameter to the method.
for : user defined value provided by me is not getting persist, instead Spring framework still adding its own value
you can do it with AuditorAware interface. 3.1.3 AuditorAware
In case you use either #CreatedBy or #LastModifiedBy, the auditing
infrastructure somehow needs to become aware of the current principal.
To do so, we provide an AuditorAware SPI interface that you have to
implement to tell the infrastructure who the current user or system
interacting with the application is. The generic type T defines of
what type the properties annotated with #CreatedBy or #LastModifiedBy
have to be.
Here’s an example implementation of the interface using Spring
Security’s Authentication object:
Example 3.2. Implementation of AuditorAware based on Spring Security
class SpringSecurityAuditorAware implements AuditorAware<User> {
public User getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
return null;
}
return ((MyUserDetails) authentication.getPrincipal()).getUser();
}
}

JPA doesn't fetch the updated data

I am facing a very weird issue in JPA entity manager. I have tow Entities
1) Incident
2) Country
Country is master and Incident is child with ManyToOne.
Incident.java
#Entity
#Table(name = "Incident")
public class Incident {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "incidentID")
private Integer incidentID;
#Column(name = "incidentTitle")
private String incidentTitle;
#ManyToOne
#JoinColumn(name = "countryID")
private Country country;
#Transient
#ManyToOne
#JoinColumn(name = "countryID")
public Country getCountry() {
return country;
}
public void setCountry(Country country) {
this.country = country;
}
// Getter and setters
}
Country.Java
#Entity
#Table(name="Country")
public class Country {
#Id
#Column(name="id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "country", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Incident> incident;
#OneToMany
#JoinColumn(
name="countryID",nullable=false)
public List<Incident> getIncident() {
return incident;
}
public void setIncident(List<Incident> incident) {
this.incident = incident;
}
//getter and setter
}
RepositoryImpl.java
#Repository
#Transactional
public class IncidentRepositoryImpl implements IncidentRepository{
#PersistenceContext
private EntityManager em;
#Autowired
public void setEntityManager(EntityManagerFactory sf) {
this.em = sf.createEntityManager();
}
#Override
public Incident addIncident(Incident incident) {
try {
em.getTransaction().begin();
em.persist(incident);
em.getTransaction().commit();
return incident;
} catch (HibernateException e) {
return null;
}
}
public Incident findById(int id) {
Incident incident = null;
incident = (Incident) em.find(Incident.class, id);
return incident;
}
}
When i add Incident, incident added successfully with countryID in Incident table, But when i try to fetch the same incident, country name comes null. But when i take restart of server or redeploy the application country name also comes. Hope there is cache issue with JAP entity manager. I try to use em.refresh(incident) in findById method, then country name comes successfully. But this refresh method is very expensive call.
Please suggest some alternate solution, how to update jpa cache automatically.
On your EntityManager em, add
#PersistenceContext(type = PersistenceContextType.TRANSACTION)
private EntityManager em;
With PersistenceContextType.TRANSACTION, Spring takes control of the life cycle of EntityManager

Resources