Hibernate - Using @Find Annotation Examples

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: