Skip to content

Get Hibernate Envers Audit Data#

Get Hibernate Envers Audit Data#

  • In the section Hibnerate Envers With JPA, we know how to integrate hibernate envers with jpa for storing audit data. Now, we will continue investigate the way to get the audit data from hibernate envers for using.
  • So, let's assume that after we implemented hibernate envers for auditing data then the business team in company want us to export a history api that returns all changes that happened on an object which is stored in database. In this example it will be all the changes that happened with CustomerEntity
  • To do so we will extract audit data of hibernate envers from database and return.

Entities#

  • We will extend our entities a little bit with new two column createdDate and updatedDate because with audit data we would like to know the time that the record is created and updated.
  • We will update for CustomerEntity, and ItemEntity because they have not those fields createdDate and updatedDate before.
CustomerEntity.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package com.springboot.data.hibernate.envers.app.entity;

import org.hibernate.annotations.Type;
import org.hibernate.envers.Audited;
import org.hibernate.envers.NotAudited;

import javax.persistence.*;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;

@Audited
@Entity
@Table(name = "customers")
public class CustomerEntity {

    @Id
    @GeneratedValue
    @Type(type="uuid-char")
    private UUID id;
    private String fullName;
    @Column(unique = true)
    private String email;
    private String address;
    private String phone;
    @Enumerated(EnumType.STRING)
    private Gender gender;
    private Date dob;

    private OffsetDateTime createdDate;

    private OffsetDateTime updatedDate;

    @NotAudited
    @OneToMany(mappedBy = "customer", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private List<OrderEntity> orders = new ArrayList<>();

        ...
        //getter, setter

    public OffsetDateTime getCreatedDate() {
        return createdDate;
    }

    public OffsetDateTime getUpdatedDate() {
        return updatedDate;
    }

    @PrePersist
    private void onCreate() {
        OffsetDateTime now = OffsetDateTime.now();
        this.createdDate = now;
        this.updatedDate = now;
    }

    @PreUpdate
    private void onUpdate() {
        this.updatedDate = OffsetDateTime.now();
    }

}
ItemEntity.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.springboot.data.hibernate.envers.app.entity;

import org.hibernate.annotations.Type;
import org.hibernate.envers.Audited;
import org.hibernate.envers.NotAudited;

import javax.persistence.*;
import java.time.OffsetDateTime;
import java.util.UUID;

@Audited
@Entity
@Table(name = "items")
public class ItemEntity {

    @Id
    @GeneratedValue
    @Type(type="uuid-char")
    private UUID id;

    private String itemName;

    private Long quantity;

    private Float price;

    private OffsetDateTime createdDate;

    private OffsetDateTime updatedDate;

    @NotAudited
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "order_id")
    private OrderEntity order;

        ...
        //getter, setter

    public OffsetDateTime getCreatedDate() {
        return createdDate;
    }

    public OffsetDateTime getUpdatedDate() {
        return updatedDate;
    }

    @PrePersist
    private void onCreate() {
        OffsetDateTime now = OffsetDateTime.now();
        this.createdDate = now;
        this.updatedDate = now;
    }

    @PreUpdate
    private void onUpdate() {
        this.updatedDate = OffsetDateTime.now();
    }

}

Service#

  • Now, let's create an AuditService and put the implementation code as below
AuditService.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.springboot.data.hibernate.envers.app.service;

import com.springboot.data.hibernate.envers.app.entity.CustomerEntity;
import com.springboot.data.hibernate.envers.app.model.response.CustomerResponse;
import org.hibernate.envers.AuditReaderFactory;
import org.hibernate.envers.query.AuditEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.persistence.EntityManager;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

@Service
public class AuditService {

    private static final boolean SELECT_ENTITIES_ONLY_VALUE_TRUE = true;
    private static final boolean SELECT_DELETED_ENTITIES_VALUE_TRUE = false;

    @Autowired
    private EntityManager entityManager;

    public List<CustomerResponse> getAuditCustomer(UUID customerId) {
        List<CustomerEntity> auditEntities = AuditReaderFactory.get(entityManager)
                .createQuery()
                .forRevisionsOfEntity(CustomerEntity.class, SELECT_ENTITIES_ONLY_VALUE_TRUE,SELECT_DELETED_ENTITIES_VALUE_TRUE)
                .add(AuditEntity.property("id").eq(customerId))
                .addOrder(AuditEntity.property("createdDate").desc())
                .addOrder(AuditEntity.property("updatedDate").desc())
                .getResultList();
        return auditEntities.stream().map(this::toCustomerResponse).collect(Collectors.toList());
    }

    private CustomerResponse toCustomerResponse(CustomerEntity customerEntity) {
        CustomerResponse customerResponse = new CustomerResponse();
        customerResponse.setId(customerEntity.getId());
        customerResponse.setAddress(customerEntity.getAddress());
        customerResponse.setDob(customerEntity.getDob());
        customerResponse.setEmail(customerEntity.getEmail());
        customerResponse.setPhone(customerEntity.getPhone());
        customerResponse.setFullName(customerEntity.getFullName());
        customerResponse.setGender(customerEntity.getGender());
        customerResponse.setCreatedDate(customerEntity.getCreatedDate());
        customerResponse.setUpdatedDate(customerEntity.getUpdatedDate());
        return customerResponse;
    }

}
  • The implementation above is used for query all changed that happened on a customer except deleted record case by the customerId and sorted by createdDate and updatedDate with desc.
  • So, to query audit data we will use the AuditReaderFactory and an entityManager to create an AuditQueryCreator. Then with this AuditQueryCreator, we will use it to create AuditQuery through method .forRevisionsOfEntity(CustomerEntity.class, true, false). In which, the first parameter is the entity that we want to get, the second parameter is selectEntitiesOnly, we should put it as true to get only CustomerEntity and the finial one is the selectDeletedEntities for getting deleted record of CustomerEntity.
  • Then with AuditQuery, we can use it to create query CustomerEntity by column id and sort the result list with createdDate and updatedDate. Finally, we use the method getResultList() to end the query and return the audit list.

Controller#

  • Next, we just create a simple controller with a simple api for getting audit data as below
AuditController.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.springboot.data.hibernate.envers.app.controller;

import com.springboot.data.hibernate.envers.app.model.response.CustomerResponse;
import com.springboot.data.hibernate.envers.app.service.AuditService;
import com.springboot.data.hibernate.envers.app.service.CustomerJpaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.UUID;

@RestController
public class AuditController {

    @Autowired
    private AuditService auditService;

    @RequestMapping(method = RequestMethod.GET, path = "/v1/jpa/customers/{customerId}/audit", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<List<CustomerResponse>> getCustomerAudit(@PathVariable(name = "customerId") UUID customerId) {
        return new ResponseEntity<>(this.auditService.getAuditCustomer(customerId), HttpStatus.OK);
    }

}

Testing#

  • Now, let's start our spring boot application, then create and update Customer to have some records in the customers_aud table as below.
id rev revtype address created_date dob email full_name gender phone updated_date
8f0e57e4-5535-494e-b4b3-c84d9b68536e 1 0 Binh Duong Province 2022-11-23 20:38:15 1995-10-10 07:00:00 abc3@gmail.com Nguyen Minh Duc M 0123456789 2022-11-23 20:38:15
8f0e57e4-5535-494e-b4b3-c84d9b68536e 2 1 Ho Chi Minh City 2022-11-23 20:38:15 1995-10-10 07:00:00 abc5@gmail.com Nguyen Minh Duc M 0123456789 2022-11-23 20:41:02
  • Then let's execute the audit api from postman and you should see the result with 2 records and sorted as below.

 #zoom

See Also#

References#