This tutorial shows you how to use Hibernate's @Find
annotation.
Hibernated added an annotation named @Find
in version 6.3. It can be used to automatically generate the implementation of queries that fetch data from the database. You just need to declare the methods and add the annotation without writing the implementation. Basically, it has similar functionality to Spring Data JPA's derived query. However, those two have different conventions on how to declare the methods.
Using @Find
Annotation
For example, we have an entity named User
as shown below.
@Entity
@Table(name = "users")
public class User {
@Id
@UuidGenerator
private UUID id;
private String name;
private String idCardNumber;
}
We want to create a query that fetches a user by the value of idCardNumber
. To do it, create an interface and declare a method inside. The method must be annotated with @Find
annotation. In addition, the types and names of the method parameters must exactly match the types and names of the corresponding fields of the entity. There is no convention for the method naming, which means you can use any name you want.
public interface UserRepository {
@Find
User fetchUserByIdCardNumber(String idCardNumber);
// other methods
}
It also supports queries that return a list of records.
public interface UserRepository {
@Find
List<User> fetchUsersByName(String name);
// other methods
}
Hibernate Metamodel Generator will create the implementation for each method annotated with @Find
in a class whose name is the interface name added with _
suffix. For the case above, the generated class name will be UserRepository_
. It has some static methods that contain the implementation of the methods annotated with @Find
. Besides the parameter that you declare, Hibernate will add EntityManager
as the first parameter.
@StaticMetamodel(UserRepository.class)
@Generated("org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
public abstract class UserRepository_ {
/**
* Find {@link User} by {@link User#idCardNumber idCardNumber}.
*
* @see com.woolha.hibernate6.example.repository.UserRepository#fetchUserByIdCardNumber(String)
**/
public static User fetchUserByIdCardNumber(@Nonnull EntityManager entityManager, String idCardNumber) {
var builder = entityManager.getEntityManagerFactory().getCriteriaBuilder();
var query = builder.createQuery(User.class);
var entity = query.from(User.class);
query.where(
idCardNumber==null
? entity.get(User_.idCardNumber).isNull()
: builder.equal(entity.get(User_.idCardNumber), idCardNumber)
);
return entityManager.createQuery(query).getSingleResult();
}
/**
* Find {@link User} by {@link User#name name}.
*
* @see com.woolha.hibernate6.example.repository.UserRepository#fetchUsersByName(String)
**/
public static List<User> fetchUsersByName(@Nonnull EntityManager entityManager, String name) {
var builder = entityManager.getEntityManagerFactory().getCriteriaBuilder();
var query = builder.createQuery(User.class);
var entity = query.from(User.class);
query.where(
name==null
? entity.get(User_.name).isNull()
: builder.equal(entity.get(User_.name), name)
);
return entityManager.createQuery(query).getResultList();
}
}
In the generated methods above, Hibernate adds EntityManager
as the first parameter. Therefore, you need to pass an EntityManager
as the first parameter. Below are the examples of how to call the generated methods.
@RequiredArgsConstructor
public class UserService {
private final EntityManager entityManager;
public UserDto fetchUserByIdCardNumber(String idCardNumber) {
User user = UserRepository_.fetchUserByIdCardNumber(entityManager, idCardNumber);
return this.mapToUserDto(user);
}
public List<UserDto> fetchUsersByName(String name) {
List<User> users = UserRepository_.fetchUsersByName(entityManager, name);
return users.stream()
.map(this::mapToUserDto)
.collect(Collectors.toList());
}
private UserDto mapToUserDto(User user) {
return UserDto.builder()
.id(user.getId())
.name(user.getName())
.idCardNumber(user.getIdCardNumber())
.build();
}
}
Below is an invalid example where the name of the parameter doesn't match a field of the entity.
public interface UserRepository {
@Find
User fetchUserByUnknownField(String unknownField);
}
When trying to build the code, Hibernate will give the following error.
/home/ivan/IdeaProjects/tutorial/woolha-demo-hibernate6-find/src/main/java/com/woolha/hibernate6/example/repository/UserRepository.java:12: error: no matching field named 'unknownField' in entity class 'com.woolha.hibernate6.example.model.User'
ser fetchUserByUnknownField(String unknownField);
Instead of using an interface, it's also possible to declare the methods inside an abstract class.
public abstract class UserRepositoryAlt {
@Find
abstract User fetchUserByIdCardNumber(String idCardNumber);
@Find
abstract List<User> fetchUsersByName(String name);
}
If there are multiple methods and you want to use the same EntityManager
to be passed to all methods, the alternative is to declare the EntityManger
in the interface or abstract class.
public interface UserRepositoryWithEntityManager {
@Find
User fetchUserByIdCardNumber(String idCardNumber);
@Find
List<User> fetchUsersByName(String name);
EntityManager entityManager();
}
As a result, the generated class has a constructor with one parameter whose type is EntityManager
. The generated methods are not static since they have to use the EntityManager
passed to the constructor.
@StaticMetamodel(UserRepositoryWithEntityManager.class)
@Generated("org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
public class UserRepositoryWithEntityManager_ implements UserRepositoryWithEntityManager {
/**
* Find {@link User} by {@link User#idCardNumber idCardNumber}.
*
* @see com.woolha.hibernate6.example.repository.UserRepositoryWithEntityManager#fetchUserByIdCardNumber( String)
**/
@Override
public User fetchUserByIdCardNumber(String idCardNumber) {
var builder = entityManager.getEntityManagerFactory().getCriteriaBuilder();
var query = builder.createQuery(User.class);
var entity = query.from(User.class);
query.where(
idCardNumber==null
? entity.get(User_.idCardNumber).isNull()
: builder.equal(entity.get(User_.idCardNumber), idCardNumber)
);
return entityManager.createQuery(query).getSingleResult();
}
private final @Nonnull EntityManager entityManager;
public UserRepositoryWithEntityManager_(@Nonnull EntityManager entityManager) {
this.entityManager = entityManager;
}
public @Nonnull EntityManager entityManager() {
return entityManager;
}
/**
* Find {@link User} by {@link User#name name}.
*
* @see com.woolha.hibernate6.example.repository.UserRepositoryWithEntityManager#fetchUsersByName(String)
**/
@Override
public List<User> fetchUsersByName(String name) {
var builder = entityManager.getEntityManagerFactory().getCriteriaBuilder();
var query = builder.createQuery(User.class);
var entity = query.from(User.class);
query.where(
name==null
? entity.get(User_.name).isNull()
: builder.equal(entity.get(User_.name), name)
);
return entityManager.createQuery(query).getResultList();
}
}
Below is the example of how to create the repository instance by passing the EntityManager
and how to call the methods.
public class UserService2 {
private final UserRepositoryWithEntityManager_ userRepository;
public UserService2(EntityManager entityManager) {
this.userRepository = new UserRepositoryWithEntityManager_(entityManager);
}
public UserDto fetchUserByIdCardNumber(String idCardNumber) {
User user = this.userRepository.fetchUserByIdCardNumber(idCardNumber);
return this.mapToUserDto(user);
}
public List<UserDto> fetchUsersByName(String name) {
List<User> users = this.userRepository.fetchUsersByName(name);
return users.stream()
.map(this::mapToUserDto)
.collect(Collectors.toList());
}
private UserDto mapToUserDto(User user) {
return UserDto.builder()
.id(user.getId())
.name(user.getName())
.idCardNumber(user.getIdCardNumber())
.build();
}
}
Summary
If you use Hibernate 6.3 or above, you can use the @Find
annotation to generate queries. You just need to declare methods without the implementation inside an interface or abstract class. The methods have to be annotated with @Find
and each parameter must have a matching field in the entity class. Then, you can call the implementation of the methods generated by Hibernate.
You can also read about: