Capture long running method execution in Spring Boot using annotation
It is crucial to capture long running method execution in your application (whether it is an API or traditional mvc application). You should also …
Sometimes you may need to create your sql queries on runtime with some specification given by your client. In these cases, you may create many JPQLs (or native queries) for each specification. Handling with this way could be hard to maintain. In these scenarios, you may use CRITERIA API to create your sql queries in your java code. Let’s quickly learn criteria api and its usages in Spring Boot to create sql query in our Java code
If you only need to see the code, here is the github link
First create a new spring boot project with the following dependencies:
<dependencies>
<!-- To generate metamodel -->
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Note: I am going to use testcontainer to run sql queries on the postgresql docker. Make sure that you have installed docker and it is running while running the test cases
Assume that we have User
and UserRole
entities:
@Entity
@Table(name = "user")
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Getter
@Setter
public class User {
@Id
@GeneratedValue(strategy= GenerationType.AUTO)
@Column(name = "id")
private Long id;
@Column(name = "email")
private String email;
@Column(name = "name")
private String name;
@Column(name = "version_id")
@Version
private Long versionId;
// if I update/delete user, hibernate should update/delete related UserRole
@ManyToMany(fetch = FetchType.LAZY, cascade ={
CascadeType.PERSIST,
CascadeType.MERGE
})
@JoinTable(name = "user_role_join", // table name
joinColumns = @JoinColumn(name = "id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<UserRole> userRoles = new HashSet<>();
}
@Entity
@Table(name = "user_role")
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Getter
@Setter
public class UserRole {
@Id
@GeneratedValue(strategy= GenerationType.AUTO)
@Column(name = "id")
private Long id;
@Column(name = "role")
private String role;
@Column(name = "version_id")
@Version
private Long versionId;
}
with the following repository classes:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
@Repository
public interface UserRoleRepository extends JpaRepository<UserRole, Long> {
}
with these setup is set. Let’s start what do we mean by Criteria and Metamodel?
The Criteria API is used to define queries for entities by creating queriesh objects. These queriesh objects are written in Java and typesafe.
While we are writing criteria queries we need to reference the entity class and its attributes, we often reference those attributes names as String which is not type-safe and we have to remember all the attributes of the entity. Metamodel generates statilc model(another class) of the entity and we can use generated metamodel (and its attributes) to write type-safe queries.
import jakarta.persistence.EntityManager;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
// ...
@RequiredArgsConstructor
@Service
public class UserService {
private final EntityManager entityManager;
public List<User> getAllUsers() {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = criteriaBuilder.createQuery(User.class);
}
}
public class UserService {
private final EntityManager entityManager;
public List<User> getAllUsers() {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = criteriaBuilder.createQuery(User.class);
}
}
FROM {Entity}
part of the query)public class UserService {
private final EntityManager entityManager;
public List<User> getAllUsers() {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = criteriaBuilder.createQuery(User.class);
Root<User> user = query.from(User.class); // FROM {Entity} part of the query
}
}
select
method of CriteriaQuery (think of that you are creating SELECT
part of the query):public class UserService {
private final EntityManager entityManager;
public List<User> getAllUsers() {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = criteriaBuilder.createQuery(User.class);
Root<User> user = query.from(User.class); // Set the 'FROM {Entity}' part of the query
query.select(user); // Set the `SELECT` part of the query:
}
}
TypedQuery<T>
, specify the type of the query result:public class UserService {
private final EntityManager entityManager;
public List<User> getAllUsers() {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = criteriaBuilder.createQuery(User.class);
Root<User> user = query.from(User.class); // Set the 'FROM {Entity}' part of the query
query.select(user); // Set the `SELECT` part of the query:
TypedQuery<User> typedQuery = entityManager.createQuery(query); // prepare for execution and specify the result type
}
}
getResultList
. We call that method because we know that there are many users in our database. (there is also method called getSingleResult
):public class UserService {
private final EntityManager entityManager;
public List<User> getAllUsers() {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = criteriaBuilder.createQuery(User.class);
Root<User> user = query.from(User.class); // Set the 'FROM {Entity}' part of the query
query.select(user); // Set the `SELECT` part of the query:
TypedQuery<User> typedQuery = entityManager.createQuery(query); // prepare for execution and specify the result type
List<User> allUsers = typedQuery.getResultList(); // Execute the query
return allUsers;
}
}
select
u1_0.id,
u1_0.email,
u1_0.name,
u1_0.version_id
from
users u1_0
public class UserService {
public void createUserWithRepositoryCall(User user) {
userRepository.save(user);
}
}
// ...
class CriteriaApiWithSpringBootApplicationTests {
@Autowired
private UserService userService;
@Test
void contextLoads() {
// create users
createUserWithRepositoryCall();
// then getAllUsers
List<User> allUsers = userService.getAllUsers();
Assertions.assertEquals(newUsers().count(), allUsers.size());
}
static Stream<User> newUsers() {
// new users
}
}
Let’s look at the generated metamodels for each entities
If you are using the following dependency:
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
</dependency>
While project is compiled, Hibernate will automatically generate metamodel for your @Entity classes. The class name will be {EntityClassName}_. For instance, User.java => User_.java
In general, query consists of a SELECT clause, a FROM clause and a WHERE clause. Criteria API set these clauses by using Java objects, so the query can be created in a typesafe manner.
CriteriaBuilder is used to construct:
CriteriaQuery (which is another java objects) defines a particular query that will use one or more entities in our project. To create CriteriaQuery instances call CriteriaBuilder.createQuery
method.
The CriteriaQuery’s type should be set to the expected result type of the query:
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
// query return the User entity
CriteriaQuery<User> query = criteriaBuilder.createQuery(User.class);
// query return the String
CriteriaQuery<String> anotherQuery = criteriaBuilder.createQuery(String.class);
This is similar to FROM
part of the query. In other words it specifies the search area(table) of the given query.
FROM
part can be created by calling criteriaQuery.from(Entity.class)
method:
CriteriaQuery<User> query = criteriaBuilder.createQuery(User.class);
Root<User> user = query.from(User.class);
Query root can also be set using EntityType (with generated metamodel):
CriteriaQuery<User> query = criteriaBuilder.createQuery(User.class);
Metamodel metamodel = entityManager.getMetamodel();
EntityType<User> User_ = metamodel.entity(User.class);
Root<User> pet = query.from(User_);
Note: Criteria queries may have more than one query root.
Root<User> userFrom = query.from(User.class); Root<User> anotherUserFrom = query.from(User.class);
If you are going to navigate related entity classes, your query must define a join and join clause can be defined from FROM.join
method. This is similar JOIN
keyword in sql.
The join method returns object of type Join<X, Y>
Join<User, UserRole> userWithUserRoleJoin = user.join(User_.userRoles);
Very often, we need to select some attributes (of the entity or entities) to show in “SELECT clause” or to use in “WHERE cluase”. To to that we can use Path
object in Criteria API.
Metamodel class(es) provides which attributes can be used in SELECT or WHERE clauses (or other clauses in any related sql part).
The attribute can be SingularAttribute(single value attribute selection), CollectionAttribute, SetAttribute, ListAttribute, or MapAttribute. Here is the example for User_.java
@StaticMetamodel(User.class)
@Generated("org.hibernate.processor.HibernateProcessor")
public abstract class User_ {
public static final String USER_ROLES = "userRoles";
public static final String VERSION_ID = "versionId";
public static final String NAME = "name";
public static final String ID = "id";
public static final String EMAIL = "email";
// Because we define private Set<UserRole> userRoles = new HashSet<>(); in the User entity, attribute's selection must be SetAttribute etc...
public static volatile SetAttribute<User, UserRole> userRoles;
public static volatile SingularAttribute<User, Long> versionId;
public static volatile SingularAttribute<User, String> name;
public static volatile SingularAttribute<User, Long> id;
}
Here is the query to select user’s email:
public class UserService {
// ...
/**
* Sql Query:["
* select
* u1_0.name
* from
* users u1_0"]
* @return
*/
public List<String> getAllUsersEmails() {
// get builder to build query
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
// create query with return type String
CriteriaQuery<String> query = criteriaBuilder.createQuery(String.class);
// using metamodel,
// set query's "FROM" clause means that '... FROM User'
Metamodel metamodel = entityManager.getMetamodel();
EntityType<User> user_ = metamodel.entity(User.class);
Root<User> user = query.from(user_);
// using metamodel,
// set query's select value(s), this should be matched with return type of the CriteriaQuery
query.select(user.get(User_.name));
// Prepare the query for execution, specify the query result type
TypedQuery<String> typedQuery = entityManager.createQuery(query);
// execute the query
List<String> allUsers = typedQuery.getResultList();
return allUsers;
}
}
CriteriaQuery.where
method we can restrict the results of a query on the CriteriaQuery object. This is simlar to the WHERE
clause in the sqlCriteriaQuery.where
evaluates instances of the Expression interface to restrict the results according to the conditions of the expressions.package jakarta.persistence.criteria;
/**
* Type for query expressions.
*
* @param <T> the type of the expression
*
* @since 2.0
*/
public interface Expression<T> extends Selection<T> {
/**
* Create a predicate to test whether the expression is null.
* @return predicate testing whether the expression is null
*/
Predicate isNull();
// ...
}
Let’s find all users where name is not null:
public class UserService {
/**
* Sql Query:["
* select
* u1_0.id,
* u1_0.email,
* u1_0.name,
* u1_0.version_id
* from
* users u1_0
* where
* u1_0.name is not null"]
* @return
*/
public List<User> findAllUsersWhereNameNotNull() {
// get builder to build query
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = criteriaBuilder.createQuery(User.class);
// using metamodel
// set query's "FROM" clause e.g ... FROM User
Metamodel metamodel = entityManager.getMetamodel();
EntityType<User> user_ = metamodel.entity(User.class);
Root<User> user = query.from(user_);
// set query's where clause
query.where(user.get(User_.name).isNotNull());
// Prepare the query for execution, specify the query result type
TypedQuery<User> typedQuery = entityManager.createQuery(query);
// execute the query
return typedQuery.getResultList();
}
}
There also other methods such as: isNull, in.
To use additional methods such as equal, gt (greater than), between, like … you can use CriteriaBuilder interface.
Here is the example to search user’s email with like query:
public class UserService {
/**
* Sql Query:["
* select
* u1_0.id,
* u1_0.email,
* u1_0.name,
* u1_0.version_id
* from
* users u1_0
* where
* u1_0.email like ? escape ''"]
* Params:[(%customer%)]
* @return
*/
public List<User> getUsersWithEmailLike(String email) {
// get builder to build query
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = criteriaBuilder.createQuery(User.class);
// using metamodel
// set query's "FROM" clause e.g ... FROM User
Metamodel metamodel = entityManager.getMetamodel();
EntityType<User> user_ = metamodel.entity(User.class);
Root<User> user = query.from(user_);
// set query's where clause (use additional expression from CriteriaBuilder
query.where(criteriaBuilder.like(user.get(User_.email), "%" + email + "%"));
// Prepare the query for execution, specify the query result type
TypedQuery<User> typedQuery = entityManager.createQuery(query);
// execute the query
return typedQuery.getResultList();
}
}
Enough for the theory part, let’s do some real world examples:
Here is the test scenario:
public class ComplexQueryExamples {
@Autowired
private UserService userService;
@Autowired
private Flyway flyway;
private void createUsers() {
newUsers().forEach(user -> userService.createUserWithRepositoryCall(user));
}
// Clear and migrate database for each test
@BeforeEach
public void restoreDatabase() {
flyway.clean();
flyway.migrate();
}
@Test
void testComplexQueries() {
// ....
}
static Stream<User> newUsers() {
// Four users:
// one is admin, one has two roles, one has two addresses, one has no address, one has no address and role
UserRole customer = UserRole.builder()
.role("customer")
.build();
UserRole admin = UserRole.builder()
.role("admin")
.build();
Address address = Address.builder()
.address("Dummy Address İstanbul")
.city("İstanbul")
.build();
Address anotherAddress = Address.builder()
.address("Dummy Address New York")
.city("New York")
.build();
return Stream.of(
User.builder()
.email("[email protected]")
.name("test2")
.userRoles(Set.of(
customer,
admin
))
.build(),
User.builder()
.email("[email protected]")
.name("test1")
.userRoles(Set.of(
customer
))
.userAddresses(Set.of(address))
.build(),
User.builder()
.email("[email protected]")
.name("test3")
.userRoles(Set.of(
customer
))
.userAddresses(Set.of(address, anotherAddress))
.build(),
User.builder()
.email("[email protected]")
.name("test4")
.build()
);
}
}
public Page < User > getAllUsersWithJoin(PageRequest pageRequest) {
// create builder
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
// set up query result's type
CriteriaQuery < User > query = criteriaBuilder.createQuery(User.class);
Metamodel metamodel = entityManager.getMetamodel();
EntityType < User > userEntityType = metamodel.entity(User.class);
// set up query's from clause
Root < User > fromUserEntity = query.from(userEntityType);
// create Select clause
query.select(fromUserEntity);
// convert pageRequest into jpa order by
query.orderBy(QueryUtils.toOrders(pageRequest.getSort(), fromUserEntity, criteriaBuilder));
// Prepare the query for execution, specify the query result type
TypedQuery < User > typedQuery = entityManager.createQuery(query);
// set related fields for pagination
typedQuery.setFirstResult((int) pageRequest.getOffset());
typedQuery.setMaxResults(pageRequest.getPageSize());
// execute query for content (list of users)
List < User > content = typedQuery.getResultList();
// Create "Count query" for the count part of pagination
CriteriaQuery < Long > countQuery = criteriaBuilder.createQuery(Long.class);
Root < User > countRoot = countQuery.from(userEntityType);
countQuery.select(criteriaBuilder.count(countRoot));
TypedQuery < Long > countQueryType = entityManager.createQuery(countQuery);
Long totalElementsInPagination = countQueryType.getSingleResult();
// create pagination
return new PageImpl < > (content, pageRequest, totalElementsInPagination);
}
public Page < User > getAllUsersWithJoin(PageRequest pageRequest) {
// create builder
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
// set up query result's type
CriteriaQuery < User > query = criteriaBuilder.createQuery(User.class);
Metamodel metamodel = entityManager.getMetamodel();
EntityType < User > userEntityType = metamodel.entity(User.class);
// set up query's from clause
Root < User > fromUserEntity = query.from(userEntityType);
// JOIN FETCH all related entities for User to include entities in User-entity
fromUserEntity.fetch(User_.userRoles, JoinType.LEFT);
fromUserEntity.fetch(User_.userAddresses, JoinType.LEFT);
// create Select clause
query.select(fromUserEntity);
// convert pageRequest into jpa order by
query.orderBy(QueryUtils.toOrders(pageRequest.getSort(), fromUserEntity, criteriaBuilder));
TypedQuery < User > typedQuery = entityManager.createQuery(query);
typedQuery.setFirstResult((int) pageRequest.getOffset());
typedQuery.setMaxResults(pageRequest.getPageSize());
List < User > content = typedQuery.getResultList();
// Create "Count query" for the count part of pagination
CriteriaQuery < Long > countQuery = criteriaBuilder.createQuery(Long.class);
Root < User > countRoot = countQuery.from(userEntityType);
countQuery.select(criteriaBuilder.count(countRoot));
TypedQuery < Long > countQueryType = entityManager.createQuery(countQuery);
Long totalElementsInPagination = countQueryType.getSingleResult();
return new PageImpl < >(content, pageRequest, totalElementsInPagination);
}
Here is projection class (I am going to use Record):
public record UserDTO(
Long id,
String email,
String name,
OffsetDateTime createDate
) {
}
public Page < UserDTO > getAllUserWithDTOProjection(PageRequest pageRequest) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery < UserDTO > query = criteriaBuilder.createQuery(UserDTO.class);
Metamodel metamodel = entityManager.getMetamodel();
EntityType < User > userEntityType = metamodel.entity(User.class);
Root < User > fromUserEntity = query.from(userEntityType);
query.select(
criteriaBuilder.construct(UserDTO.class,
fromUserEntity.get(User_.id).alias("id"),
fromUserEntity.get(User_.email).alias("email"),
fromUserEntity.get(User_.name).alias("name"),
fromUserEntity.get(BaseEntity_.createTime).alias("create_time")
));
query.orderBy(QueryUtils.toOrders(pageRequest.getSort(), fromUserEntity, criteriaBuilder));
TypedQuery < UserDTO > typedQuery = entityManager.createQuery(query);
typedQuery.setFirstResult((int) pageRequest.getOffset());
typedQuery.setMaxResults(pageRequest.getPageSize());
List < UserDTO > content = typedQuery.getResultList();
CriteriaQuery < Long > countQuery = criteriaBuilder.createQuery(Long.class);
Root < User > countRoot = countQuery.from(userEntityType);
countQuery.select(criteriaBuilder.count(countRoot));
TypedQuery < Long > countQueryType = entityManager.createQuery(countQuery);
Long totalElementsInPagination = countQueryType.getSingleResult();
return new PageImpl < > (content, pageRequest, totalElementsInPagination);
}
public record UserCountPerRole(
String role,
Long count
) {
}
public Page < UserCountPerRole > countDistinctUsersPerRole(PageRequest pageRequest) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery < UserCountPerRole > query = criteriaBuilder.createQuery(UserCountPerRole.class);
Metamodel metamodel = entityManager.getMetamodel();
EntityType < User > userEntityType = metamodel.entity(User.class);
Root < User > fromUserEntity = query.from(userEntityType);
// join with userRole to create groupby statement
Join < User, UserRole > userRoleJoin = fromUserEntity.join(User_.userRoles, JoinType.LEFT);
// query by query with roles
query.groupBy(userRoleJoin.get(UserRole_.role));
// construct select statement with DTO projection
query.select(
criteriaBuilder.construct(UserCountPerRole.class,
userRoleJoin.get(UserRole_.role),
criteriaBuilder.countDistinct(fromUserEntity.get(User_.id)))
);
// only grouping by not null roles
query.where(userRoleJoin.get(UserRole_.role).isNotNull());
// add order by statement
query.orderBy(criteriaBuilder.asc(userRoleJoin.get(UserRole_.role)));
TypedQuery < UserCountPerRole > typedQuery = entityManager.createQuery(query);
typedQuery.setFirstResult((int) pageRequest.getOffset());
typedQuery.setMaxResults(pageRequest.getPageSize());
List < UserCountPerRole > content = typedQuery.getResultList();
// Count query for pagination
CriteriaQuery < Long > countQuery = criteriaBuilder.createQuery(Long.class);
Root < User > countRoot = countQuery.from(User.class);
Join < User, UserRole > countRoleJoin = countRoot.join(User_.userRoles);
countQuery.select(criteriaBuilder.countDistinct(countRoleJoin.get(UserRole_.role)));
countQuery.where(countRoleJoin.get(UserRole_.role).isNotNull());
TypedQuery < Long > countQueryType = entityManager.createQuery(countQuery);
Long totalElementsInPagination = countQueryType.getSingleResult();
return new PageImpl < > (content, pageRequest, totalElementsInPagination);
}
Here is the generated query
select
ur1_1.role,
count(distinct u1_0.id)
from
users u1_0
left join
users_roles_join ur1_0
on u1_0.id=ur1_0.id
left join
users_roles ur1_1
on ur1_1.id=ur1_0.role_id
where
ur1_1.role is not null
group by
1
order by
1
offset
? rows
fetch
first ? rows only
JpaSpecificationExecutor
.JpaSpecificationExecutor
:@Repository
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
}
@Builder
@Getter
public class UserDynamicSearchSpecification {
@Singular
private Set<String> searchEmailLikes;
@Singular
private Set<String> searchRoles;
@Singular
private Set<String> searchCities;
}
// class to combine search specification in UserDynamicSearchSpecification object
public class UserSpecification {
private UserSpecification() {}
// combine specifications with and filter (you may also use "or" statement)
public static Specification<User> createSpecification(UserDynamicSearchSpecification dynamicSearchSpecification) {
return Specification.where(searchByEmail(dynamicSearchSpecification.getEmailLikes()))
.and(searchByRoles(dynamicSearchSpecification.getRoles()))
.and(searchByCities(dynamicSearchSpecification.getCities()));
}
public static Specification<User> searchByEmail(Set<String> emails) {
if (CollectionUtils.isEmpty(emails)) {
return null;
}
return (root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
emails.forEach(email -> {
Predicate predicate = criteriaBuilder.like(root.get(User_.email), "%" + email + "%");
predicates.add(predicate);
});
return criteriaBuilder.or(predicates.toArray(new Predicate[0]));
};
}
public static Specification<User> searchByRoles(Set<String> roles) {
// ... implementation for searchByRoles
}
public static Specification<User> searchByCities(Set<String> cities) {
// ... implementation for searchByCities
}
}
@Service
public class UserService {
private final UserRepository userRepository;
public Page<User> findUserDynamically(UserDynamicSearchSpecification dynamicSearchSpecification, PageRequest pageRequest) {
Specification<User> specification = UserSpecification.createSpecification(dynamicSearchSpecification);
return userRepository.findAll(specification, pageRequest);
}
}
@EnableTestcontainers
@SpringBootTest
public class ComplexQueryTest {
@Autowired
private UserService userService;
private UserDynamicSearchSpecification buildSpecification(Consumer<UserDynamicSearchSpecification.UserDynamicSearchSpecificationBuilder> customizer) {
var builder = UserDynamicSearchSpecification.builder();
customizer.accept(builder);
return builder.build();
}
@Test
void dynamicUserSearch() {
// search by email like and role
var searchSpecification = buildSpecification((builder) -> builder
.emailLike("[email protected]")
.emailLike("[email protected]")
.role("customer")
);
Page<User> users = userService.findUserDynamically(searchSpecification, PageRequest.of(0, 1));
Assertions.assertEquals(2, users.getTotalElements());
User user = users.toList().getFirst();
User finalUser = user;
Assertions.assertAll(
() -> Assertions.assertNotNull(finalUser.getEmail()),
() -> Assertions.assertNotNull(finalUser.getUserRoles()),
() -> Assertions.assertNotNull(finalUser.getUserAddresses())
);
// search only with roles
searchSpecification = buildSpecification((builder) -> builder
.role("customer")
);
users = userService.findUserDynamically(searchSpecification, PageRequest.of(0, 1));
Assertions.assertEquals(3, users.getTotalElements());
user = users.toList().getFirst();
User finalUser1 = user;
Assertions.assertAll(
() -> Assertions.assertNotNull(finalUser1.getEmail()),
() -> Assertions.assertNotNull(finalUser1.getUserRoles()),
() -> Assertions.assertNotNull(finalUser1.getUserAddresses())
);
}
}
It is crucial to capture long running method execution in your application (whether it is an API or traditional mvc application). You should also …
Spring provides reliable shutdown of the application using feature called Graceful Shutdown. With Graceful shutdown feature we can close the …