Skip to content

JPA Entity Lifecycle Events#

What Are Entity Lifecycle Events?#

  • In the context of Java Persistence API (JPA), entity lifecycle events refer to the various points during the existence of an entity when specific operations or events are triggered. JPA provides a set of standard lifecycle events that correspond to different phases in the life of an entity. These events allow developers to execute custom logic or actions in response to certain state transitions of the entity.
  • Below are all supported events of JPA.
Event Annotation Description
PrePersist @PrePersist Executed before a new entity is persisted.
PostPersist @PostPersist Executed after a new entity has been persisted.
PreRemove @PreRemove Executed before an entity is removed from the database.
PostRemove @PostRemove Executed after an entity has been removed from the database.
PreUpdate @PreUpdate Executed before an entity is updated in the database.
PostUpdate @PostUpdate Executed after an entity has been updated in the database.
PostLoad @PostLoad Executed after an entity has been loaded into the persistence context.
  • Example
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
64
65
66
67
68
package com.springboot.project.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.util.Date;
import java.util.UUID;

@Getter
@Setter
@Entity
@Slf4j
@Table(name = "customers", indexes = {
        @Index(name = "uniqueEmailIndex", columnList = "email", unique = true),
        @Index(name = "uniquePhoneIndex", columnList = "phone", unique = true),
        @Index(name = "uniqueMultiIndex", columnList = "email, phone", unique = true)
})
public class CustomerEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;
    private String fullName;
    private String email;
    private String address;
    private String phone;
    @Enumerated(EnumType.STRING)
    private Gender gender;
    private Date dob;

    @PrePersist
    public void logNewCustomerAttempt() {
        log.info("@PrePersist Attempting to add new Customer with username: " + this.fullName);
    }

    @PostPersist
    public void logNewCustomerAdded() {
        log.info("@PostPersist Added Customer '" + this.fullName + "' with ID: " + this.id);
    }

    @PreRemove
    public void logCustomerRemovalAttempt() {
        log.info("@PreRemove Attempting to delete Customer: " + this.fullName);
    }

    @PostRemove
    public void logCustomerRemoval() {
        log.info("@PostRemove Deleted Customer: " + this.fullName);
    }

    @PreUpdate
    public void logCustomerUpdateAttempt() {
        log.info("@PreUpdate Attempting to update Customer: " + this.fullName);
    }

    @PostUpdate
    public void logCustomerUpdate() {
        log.info("@PostUpdate Updated Customer: " + this.fullName);
    }

    @PostLoad
    public void logCustomerLoad() {
        log.info("@PostLoad load customer: " + this.fullName);
    }

}
  • With the example above we are using the lifecycle event annotations directly in the entity through callback methods. Remember that these methods have to have a void return type.

  • There is another way to use these annotations is creating an EntityListener with annotated callback methods.

CustomerListener.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
package com.springboot.project.entity;


import jakarta.persistence.PostLoad;
import jakarta.persistence.PostPersist;
import jakarta.persistence.PostRemove;
import jakarta.persistence.PostUpdate;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreRemove;
import jakarta.persistence.PreUpdate;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class CustomerListener {

    @PrePersist
    public void logNewCustomerAttempt(CustomerEntity customerEntity) {
        log.info("CustomerListener Attempting to add new Customer with username: " + customerEntity.getFullName());
    }

    @PostPersist
    public void logNewCustomerAdded(CustomerEntity customerEntity) {
        log.info("CustomerListener Added Customer '" + customerEntity.getFullName() + "' with ID: " + customerEntity.getId());
    }

    @PreRemove
    public void logCustomerRemovalAttempt(CustomerEntity customerEntity) {
        log.info("CustomerListener Attempting to delete Customer: " + customerEntity.getFullName());
    }

    @PostRemove
    public void logCustomerRemoval(CustomerEntity customerEntity) {
        log.info("CustomerListener Deleted Customer: " + customerEntity.getFullName());
    }

    @PreUpdate
    public void logCustomerUpdateAttempt(CustomerEntity customerEntity) {
        log.info("CustomerListener Attempting to update Customer: " + customerEntity.getFullName());
    }

    @PostUpdate
    public void logCustomerUpdate(CustomerEntity customerEntity) {
        log.info("CustomerListener Updated Customer: " + customerEntity.getFullName());
    }

    @PostLoad
    public void logCustomerLoad(CustomerEntity customerEntity) {
        log.info("CustomerListener load customer: " + customerEntity.getFullName());
    }

}
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
package com.springboot.project.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.util.Date;
import java.util.UUID;

@Getter
@Setter
@EntityListeners(CustomerListener.class)
@Entity
@Slf4j
@Table(name = "customers", indexes = {
        @Index(name = "uniqueEmailIndex", columnList = "email", unique = true),
        @Index(name = "uniquePhoneIndex", columnList = "phone", unique = true),
        @Index(name = "uniqueMultiIndex", columnList = "email, phone", unique = true)
})
public class CustomerEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;
    private String fullName;
    private String email;
    private String address;
    private String phone;
    @Enumerated(EnumType.STRING)
    private Gender gender;
    private Date dob;

}
  • As you can see we will create a CustomerListener and put callback methods with lifecycle annotations above. These callback methods have to have a void return type also.

  • We can also use two approaches above at the same time in the same entity.

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
64
65
66
67
68
69
package com.springboot.project.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.util.Date;
import java.util.UUID;

@Getter
@Setter
@EntityListeners(CustomerListener.class)
@Entity
@Slf4j
@Table(name = "customers", indexes = {
        @Index(name = "uniqueEmailIndex", columnList = "email", unique = true),
        @Index(name = "uniquePhoneIndex", columnList = "phone", unique = true),
        @Index(name = "uniqueMultiIndex", columnList = "email, phone", unique = true)
})
public class CustomerEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;
    private String fullName;
    private String email;
    private String address;
    private String phone;
    @Enumerated(EnumType.STRING)
    private Gender gender;
    private Date dob;

    @PrePersist
    public void logNewCustomerAttempt() {
        log.info("@PrePersist Attempting to add new Customer with username: " + this.fullName);
    }

    @PostPersist
    public void logNewCustomerAdded() {
        log.info("@PostPersist Added Customer '" + this.fullName + "' with ID: " + this.id);
    }

    @PreRemove
    public void logCustomerRemovalAttempt() {
        log.info("@PreRemove Attempting to delete Customer: " + this.fullName);
    }

    @PostRemove
    public void logCustomerRemoval() {
        log.info("@PostRemove Deleted Customer: " + this.fullName);
    }

    @PreUpdate
    public void logCustomerUpdateAttempt() {
        log.info("@PreUpdate Attempting to update Customer: " + this.fullName);
    }

    @PostUpdate
    public void logCustomerUpdate() {
        log.info("@PostUpdate Updated Customer: " + this.fullName);
    }

    @PostLoad
    public void logCustomerLoad() {
        log.info("@PostLoad load customer: " + this.fullName);
    }

}
  • Usually we will use EntityListener approach in cases that we want to apply some operations for multiple entities.

Example#

  • Now, let's take an example for applying JPA lifecycle event annotations.

Dependency#

  • Let's create an sample project and add these dependencies below.
pom.xml
 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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>3.1.4</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
            <version>3.1.4</version>
        </dependency>

        <!-- postgresql driver -->
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>42.6.0</version>
        </dependency>


        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.28</version>
            <scope>provided</scope>
        </dependency>

        <!-- Apache commons-lang3 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.13.0</version>
        </dependency>

        <!-- slf4j -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>2.0.9</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>2.0.9</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.openapitools</groupId>
            <artifactId>jackson-databind-nullable</artifactId>
            <version>0.2.6</version>
        </dependency>

        <dependency>
            <groupId>jakarta.validation</groupId>
            <artifactId>jakarta.validation-api</artifactId>
            <version>3.0.2</version>
        </dependency>

        <dependency>
            <groupId>io.swagger.core.v3</groupId>
            <artifactId>swagger-annotations</artifactId>
            <version>2.2.16</version>
        </dependency>

        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>8.0.1.Final</version>
        </dependency>

        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>1.5.5.Final</version>
        </dependency>
    </dependencies>
  • Then let's apply the plugin below for OpenApi generator and Mapstruct
 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
64
65
66
 <plugins>
    <plugin>
        <groupId>org.openapitools</groupId>
        <artifactId>openapi-generator-maven-plugin</artifactId>
        <version>7.0.1</version>
        <executions>
            <execution>
                <goals>
                    <goal>generate</goal>
                </goals>
                <configuration>
                    <!-- path to the openapi file spec `.yml` -->
                    <inputSpec>
                        ${project.basedir}/src/main/resources/openapi/openapi-server.yml
                    </inputSpec>
                    <generatorName>spring</generatorName>
                    <!-- generated package for api interface -->
                    <apiPackage>com.springboot.project.generated.api</apiPackage>
                    <!-- generated package for models -->
                    <modelPackage>com.springboot.project.generated.model</modelPackage>
                    <!-- using supportingFilesToGenerate -->
                    <supportingFilesToGenerate>
                        ApiUtil.java
                    </supportingFilesToGenerate>
                    <configOptions>
                        <useTags>true</useTags>
                        <delegatePattern>true</delegatePattern>
                        <dateLibrary>java8</dateLibrary>
                        <java8>false</java8>
                        <interfaceOnly>true</interfaceOnly>
                        <useBeanValidation>true</useBeanValidation>
                        <performBeanValidation>true</performBeanValidation>
                        <useOptional>false</useOptional>
                        <useSpringBoot3>true</useSpringBoot3>
                    </configOptions>
                </configuration>
            </execution>
        </executions>
    </plugin>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.11.0</version>
        <configuration>
            <source>17</source>
            <target>17</target>
            <annotationProcessorPaths>
                <path>
                    <groupId>org.mapstruct</groupId>
                    <artifactId>mapstruct-processor</artifactId>
                    <version>1.5.5.Final</version>
                </path>
                <path>
                    <groupId>org.projectlombok</groupId>
                    <artifactId>lombok</artifactId>
                    <version>1.18.28</version>
                </path>
                <path>
                    <groupId>org.projectlombok</groupId>
                    <artifactId>lombok-mapstruct-binding</artifactId>
                    <version>0.2.0</version>
                </path>
            </annotationProcessorPaths>
        </configuration>
        </plugin>
</plugins>

OpenApi#

  • Now, in the folder resource, we will create a folder openapi and a openapi-server.yaml inside it. The content of this file will look like below with some apis.
openapi-server.yml
  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
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
openapi: 3.0.3
info:
  title: Swagger Customer - OpenAPI 3.0
  description: Everything about sample hibernate second level cache
  termsOfService: http://swagger.io/terms/
  contact:
    email: apiteam@swagger.io
  license:
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
  version: 1.0.11
externalDocs:
  description: Find out more about Swagger
  url: http://swagger.io
servers:
  - url: https://petstore3.swagger.io/api/v1
tags:
  - name: customer
    description: Everything about your Customer
    externalDocs:
      description: Find out more
      url: http://swagger.io
  - name: order
    description: Everything about your Order
    externalDocs:
      description: Find out more
      url: http://swagger.io
paths:
  /v1/customers:
    get:
      tags:
        - customer
      summary: get an existing customer info
      description: get an existing customer info by Email
      operationId: getCustomerInfoByEmail
      parameters:
        - name: email
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerDetail'
        '400':
          description: Invalid ID supplied
        '404':
          description: Pet not found
        '405':
          description: Validation exception
    post:
      tags:
        - customer
      summary: Add a Customer to database
      description: Add a Customer to database
      operationId: addCustomer
      requestBody:
        description: Create a Customer to database
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerRequest'
        required: true
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerDetail'
        '405':
          description: Invalid input
  /v1/customers/{customerId}:
    get:
      tags:
        - customer
      summary: get an existing customer info
      description: get an existing customer info by Id
      operationId: getCustomer
      parameters:
        - name: customerId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerDetail'
        '400':
          description: Invalid ID supplied
        '404':
          description: Pet not found
        '405':
          description: Validation exception
    put:
      tags:
        - customer
      summary: Update an existing customer
      description: Update an existing customer by Id
      operationId: updateCustomer
      parameters:
        - name: customerId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        description: Update an existent customer in the database
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerDetail'
        required: true
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerDetail'
        '400':
          description: Invalid ID supplied
        '404':
          description: Pet not found
        '405':
          description: Validation exception
    delete:
      tags:
        - customer
      summary: delete an existing customer
      description: delete an existing customer by Id
      operationId: deleteCustomer
      parameters:
        - name: customerId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Successful operation
        '400':
          description: Invalid ID supplied
        '404':
          description: Pet not found
        '405':
          description: Validation exception
  /v1/customers/action-filter:
    post:
      tags:
        - customer
      summary: filter customers
      description: filter customers
      operationId: filterCustomers
      requestBody:
        description: Update an existent customer in the database
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerFilterRequest'
        required: true
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/CustomerDetail'
        '400':
          description: Invalid ID supplied
        '404':
          description: Pet not found
        '405':
          description: Validation exception

components:
  schemas:
    CustomerDetail:
      allOf:
        - $ref: '#/components/schemas/CustomerRequest'
        - type: object
          properties:
            id:
              type: string
              format: uuid

    CustomerRequest:
      type: object
      required:
        - email
        - phone
      properties:
        fullName:
          type: string
        email:
          type: string
        address:
          type: string
        phone:
          type: string
        gender:
          type: string
          enum:
            - M
            - F
        dob:
          type: string
          format: date

    CustomerFilterRequest:
      type: object
      properties:
        fullName:
          type: string
        email:
          type: string
        address:
          type: string
        phone:
          type: string
  • Now, we can use command mvn clean install to build and generate apis and model from openapi-server.yaml.

 #zoom

Entity#

  • Then let's define a CustomerEnitty as below.
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
64
65
66
67
68
69
package com.springboot.project.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.util.Date;
import java.util.UUID;

@Getter
@Setter
@EntityListeners(CustomerListener.class)
@Entity
@Slf4j
@Table(name = "customers", indexes = {
        @Index(name = "uniqueEmailIndex", columnList = "email", unique = true),
        @Index(name = "uniquePhoneIndex", columnList = "phone", unique = true),
        @Index(name = "uniqueMultiIndex", columnList = "email, phone", unique = true)
})
public class CustomerEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;
    private String fullName;
    private String email;
    private String address;
    private String phone;
    @Enumerated(EnumType.STRING)
    private Gender gender;
    private Date dob;

    @PrePersist
    public void logNewCustomerAttempt() {
        log.info("@PrePersist Attempting to add new Customer with username: " + this.fullName);
    }

    @PostPersist
    public void logNewCustomerAdded() {
        log.info("@PostPersist Added Customer '" + this.fullName + "' with ID: " + this.id);
    }

    @PreRemove
    public void logCustomerRemovalAttempt() {
        log.info("@PreRemove Attempting to delete Customer: " + this.fullName);
    }

    @PostRemove
    public void logCustomerRemoval() {
        log.info("@PostRemove Deleted Customer: " + this.fullName);
    }

    @PreUpdate
    public void logCustomerUpdateAttempt() {
        log.info("@PreUpdate Attempting to update Customer: " + this.fullName);
    }

    @PostUpdate
    public void logCustomerUpdate() {
        log.info("@PostUpdate Updated Customer: " + this.fullName);
    }

    @PostLoad
    public void logCustomerLoad() {
        log.info("@PostLoad load customer: " + this.fullName);
    }

}
Gender.java
1
2
3
4
5
6
7
package com.springboot.project.entity;  

public enum Gender {  

M, F  

}
CustomerListener.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
package com.springboot.project.entity;


import jakarta.persistence.PostLoad;
import jakarta.persistence.PostPersist;
import jakarta.persistence.PostRemove;
import jakarta.persistence.PostUpdate;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreRemove;
import jakarta.persistence.PreUpdate;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class CustomerListener {

    @PrePersist
    public void logNewCustomerAttempt(CustomerEntity customerEntity) {
        log.info("CustomerListener Attempting to add new Customer with username: " + customerEntity.getFullName());
    }

    @PostPersist
    public void logNewCustomerAdded(CustomerEntity customerEntity) {
        log.info("CustomerListener Added Customer '" + customerEntity.getFullName() + "' with ID: " + customerEntity.getId());
    }

    @PreRemove
    public void logCustomerRemovalAttempt(CustomerEntity customerEntity) {
        log.info("CustomerListener Attempting to delete Customer: " + customerEntity.getFullName());
    }

    @PostRemove
    public void logCustomerRemoval(CustomerEntity customerEntity) {
        log.info("CustomerListener Deleted Customer: " + customerEntity.getFullName());
    }

    @PreUpdate
    public void logCustomerUpdateAttempt(CustomerEntity customerEntity) {
        log.info("CustomerListener Attempting to update Customer: " + customerEntity.getFullName());
    }

    @PostUpdate
    public void logCustomerUpdate(CustomerEntity customerEntity) {
        log.info("CustomerListener Updated Customer: " + customerEntity.getFullName());
    }

    @PostLoad
    public void logCustomerLoad(CustomerEntity customerEntity) {
        log.info("CustomerListener load customer: " + customerEntity.getFullName());
    }

}
  • As you can see, we will use two approaches for using lifecycle event annotations at the same time in CustomerEntity.

Repository#

  • Now, lets create the Repository for the CustomerEntity above.
 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
package com.springboot.project.repository;


import com.springboot.project.entity.CustomerEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

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

@Repository
public interface CustomerRepository extends JpaRepository<CustomerEntity, UUID> {

    Optional<CustomerEntity> findCustomerByEmail(String email);

    @Query(
            value = "  SELECT * FROM customers c                                                      "  +  /*1*/
                    "  WHERE (:full_name is null or c.full_name = :full_name)                         "  +  /*2*/
                    "  AND   (:email is null or c.email = :email)                                     "  +  /*3*/
                    "  AND   (:address is null or c.address = :address)                               "  +  /*4*/
                    "  AND   (:phone is null or c.phone = :phone);                                    ",    /*5*/
            nativeQuery = true
    )
    List<CustomerEntity> filterCustomers(@Param("full_name") String fullName,
                                         @Param("email") String email,
                                         @Param("address") String address,
                                         @Param("phone") String phone);

}
  • As you can see in this Repository we will define a method to filter customers using native query with optional params.

Dto#

  • Next, let's create DTOs for CustomerEntity and CustomerFilterRequest as below.
Customer.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
package com.springboot.project.model;

import com.springboot.project.entity.Gender;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.Date;
import java.util.UUID;

@Getter
@Setter
@NoArgsConstructor
public class Customer {

    private UUID id;
    private String fullName;
    private String email;
    private String address;
    private String phone;
    private Gender gender;
    private Date dob;

}
CustomerFilter.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package com.springboot.project.model;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class CustomerFilter {

    private String fullName;
    private String address;
    private String email;
    private String phone;

}

Mapper#

  • Next, let's create a mapper interface for mapping Customer DTO, Entity and CustomerFilterRequest models using MapStruct.
AutoCustomerMapper.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
package com.springboot.project.mapper;

import com.springboot.project.entity.CustomerEntity;
import com.springboot.project.generated.model.CustomerDetail;
import com.springboot.project.generated.model.CustomerFilterRequest;
import com.springboot.project.generated.model.CustomerRequest;
import com.springboot.project.model.Customer;
import com.springboot.project.model.CustomerFilter;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;

import java.util.List;

@Mapper
public interface AutoCustomerMapper {

    AutoCustomerMapper MAPPER = Mappers.getMapper(AutoCustomerMapper.class);

    Customer mapToCustomerFromRequest(CustomerRequest customerRequest);

    Customer mapToCustomerFromDetail(CustomerDetail customerDetail);

    Customer mapToCustomer(CustomerEntity customerEntity);

    CustomerEntity mapToCustomerEntity(Customer customer);

    CustomerDetail mapToCustomerDetail(Customer customer);

    void updateCustomerEntity(@MappingTarget CustomerEntity customerEntityTarget, CustomerEntity updateEntity);

    List<CustomerEntity> mapToCustomerEntities(List<Customer> customers);

    List<Customer> mapToCustomers(List<CustomerEntity> customerEntities);

    List<CustomerDetail> mapToCustomerDetails(List<Customer> customers);

    CustomerFilter mapToCustomerFilter(CustomerFilterRequest customerFilterRequest);

}

Service#

  • Now, with these classes that we defined above, we can create service class for handling logics for CRUD entities.
CustomerService.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
64
65
66
67
68
69
70
package com.springboot.project.service;

import com.springboot.project.entity.CustomerEntity;
import com.springboot.project.mapper.AutoCustomerMapper;
import com.springboot.project.model.Customer;
import com.springboot.project.model.CustomerFilter;
import com.springboot.project.repository.CustomerRepository;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

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

@Service
@AllArgsConstructor(onConstructor = @__(@Autowired))
public class CustomerService {

    private final CustomerRepository customerRepository;

    public Customer createCustomer(Customer customer) {
        CustomerEntity customerEntity = AutoCustomerMapper.MAPPER.mapToCustomerEntity(customer);
        customerEntity = this.customerRepository.save(customerEntity);
        return AutoCustomerMapper.MAPPER.mapToCustomer(customerEntity);
    }

    public Customer getCustomer(UUID customerId) {
        Optional<CustomerEntity> customerEntity = this.customerRepository.findById(customerId);
        if (customerEntity.isPresent()) {
            return AutoCustomerMapper.MAPPER.mapToCustomer(customerEntity.get());
        }
        throw new RuntimeException("Customer Not Found!");
    }

    public List<Customer> filterCustomers(CustomerFilter customerFilter) {
        List<CustomerEntity> foundCustomers = this.customerRepository
                .filterCustomers(
                        customerFilter.getFullName(),
                        customerFilter.getEmail(),
                        customerFilter.getAddress(),
                        customerFilter.getPhone());
        return AutoCustomerMapper.MAPPER.mapToCustomers(foundCustomers);
    }

    public Customer updateCustomer(UUID customerId, Customer customer) {
        Optional<CustomerEntity> customerEntity = this.customerRepository.findById(customerId);
        if (customerEntity.isPresent()) {
            CustomerEntity existedCustomerEntity = customerEntity.get();
            CustomerEntity updateCustomerEntity = AutoCustomerMapper.MAPPER.mapToCustomerEntity(customer);
            AutoCustomerMapper.MAPPER.updateCustomerEntity(existedCustomerEntity, updateCustomerEntity);
            existedCustomerEntity = this.customerRepository.save(existedCustomerEntity);
            return AutoCustomerMapper.MAPPER.mapToCustomer(existedCustomerEntity);
        }
        throw new RuntimeException("Customer Not Found!");
    }

    public void deleteCustomer(UUID customerId) {
        this.customerRepository.deleteById(customerId);
    }

    public Customer findCustomerByEmail(String email) {
        Optional<CustomerEntity> customerEntity = this.customerRepository.findCustomerByEmail(email);
        if (customerEntity.isPresent()) {
            return AutoCustomerMapper.MAPPER.mapToCustomer(customerEntity.get());
        }
        throw new RuntimeException("Customer Not Found! with email: " + email);
    }

}

Controller#

  • Now, we can create some basic controllers and implement the generated apis.
CustomerController.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
64
65
66
67
68
69
70
package com.springboot.project.controller;

import com.springboot.project.generated.api.CustomerApi;
import com.springboot.project.generated.model.CustomerDetail;
import com.springboot.project.generated.model.CustomerFilterRequest;
import com.springboot.project.generated.model.CustomerRequest;
import com.springboot.project.mapper.AutoCustomerMapper;
import com.springboot.project.model.Customer;
import com.springboot.project.model.CustomerFilter;
import com.springboot.project.service.CustomerService;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;

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

@RestController
@AllArgsConstructor(onConstructor = @__(@Autowired))
public class CustomerController implements CustomerApi {

    private final CustomerService customerService;

    @Override
    public ResponseEntity<CustomerDetail> addCustomer(CustomerRequest customerRequest) {
        Customer customer = AutoCustomerMapper.MAPPER.mapToCustomerFromRequest(customerRequest);
        customer = this.customerService.createCustomer(customer);
        CustomerDetail customerResponse = AutoCustomerMapper.MAPPER.mapToCustomerDetail(customer);
        return new ResponseEntity<>(customerResponse, HttpStatus.CREATED);
    }

    @Override
    public ResponseEntity<CustomerDetail> getCustomer(UUID customerId) {
        Customer customer = this.customerService.getCustomer(customerId);
        CustomerDetail customerResponse = AutoCustomerMapper.MAPPER.mapToCustomerDetail(customer);
        return new ResponseEntity<>(customerResponse, HttpStatus.OK);

    }

    @Override
    public ResponseEntity<CustomerDetail> getCustomerInfoByEmail(String email) {
        Customer customer = this.customerService.findCustomerByEmail(email);
        CustomerDetail customerResponse = AutoCustomerMapper.MAPPER.mapToCustomerDetail(customer);
        return new ResponseEntity<>(customerResponse, HttpStatus.OK);
    }

    @Override
    public ResponseEntity<List<CustomerDetail>> filterCustomers(CustomerFilterRequest customerFilterRequest) {
        CustomerFilter customerFilter = AutoCustomerMapper.MAPPER.mapToCustomerFilter(customerFilterRequest);
        List<Customer> customers = this.customerService.filterCustomers(customerFilter);
        List<CustomerDetail> customerResponses = AutoCustomerMapper.MAPPER.mapToCustomerDetails(customers);
        return new ResponseEntity<>(customerResponses, HttpStatus.OK);
    }

    @Override
    public ResponseEntity<Void> deleteCustomer(UUID customerId) {
        this.customerService.deleteCustomer(customerId);
        return new ResponseEntity<>(HttpStatus.OK);
    }

    @Override
    public ResponseEntity<CustomerDetail> updateCustomer(UUID customerId, CustomerDetail customerDetail) {
        Customer customer = AutoCustomerMapper.MAPPER.mapToCustomerFromDetail(customerDetail);
        customer = this.customerService.updateCustomer(customerId, customer);
        return new ResponseEntity<>(AutoCustomerMapper.MAPPER.mapToCustomerDetail(customer), HttpStatus.OK);
    }

}

Testing#

  • Now, let's start the application then use the postman and with the request body as below.
1
2
3
4
5
6
7
8
{
    "fullName": "{{$randomFullName}}",
    "email": "{{$randomExampleEmail}}",
    "address": "{{$randomStreetAddress}}",
    "phone": "{{$randomPhoneNumber}}",
    "gender": "M",
    "dob": "1995-10-10"
}
  • Postman supports us to random some common fields every time we send the request body.
  • Now, let's run the api CreateCustomer. Then we will have the log below.

 #zoom

  • We can see PrePersist and PostPersist events are caught for both CustomerListener and callback methods.
  • Then if we update the entity then we will see the log below.

 #zoom

  • The PostLoad, PreUpdate and PostUpdate are caught for both CustomerListener and callback methods.
  • Then if we get the entity then we will see the log below.

 #zoom - The PostLoad is caught for both CustomerListener and callback methods. - Finally if we delete the entity then we will see the log below.

 #zoom

  • The PostLoad, PreRemove and PostRemove are caught for both CustomerListener and callback methods.

See Also#

References#