NonUniqueResultException и NoResultException

В этой статье рассмотрим, какие запросы вызывают исключения NonUniqueResultException и NoResultException, и как их избежать.

Модель

@Data
@NoArgsConstructor
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private long id;
    private String name;

   //setter/getter/constructor
}

Заполним таблицу данными с повторяющимся дважды значением John:

insert into user (name) values ('Ivan');
insert into user (name) values ('John');
insert into user (name) values ('Petr');
insert into user (name) values ('John');
insert into user (name) values ('Artem');

Задача

Мы ищем пользователей по имени.

Плохой запрос

Такой пользовательский запрос может спровоцировать оба исключения:

public class CustomUserRepositoryImpl implements CustomUserRepository {
    @PersistenceContext
    private EntityManager em;

    @Override
    public User findUseByItsName(String name) {
       return em.createQuery("select u from User u where u.name =:name", User.class)
                .setParameter("name", name)
                .getSingleResult();
    }
}

Если задать имя John, будет найдено два элемента и возникнет исключение:

org.springframework.dao.IncorrectResultSizeDataAccessException: query did not return a unique result: 2; nested exception is javax.persistence.NonUniqueResultException: query did not return a unique result: 2

Это логично, так как John встречается дважды, а мы берем в запросе getSingleResult().

А если искать по несуществующему  значению, например J, то снова проблема:

org.springframework.dao.EmptyResultDataAccessException: No entity found for query; nested exception is javax.persistence.NoResultException: No entity found for query

что в общем тоже логично.

Хороший запрос

  1. Лучше сменить возвращаемый тип на Optional.
  2. Но это одно не поможет избавиться от исключений, так что запрос тоже надо изменить.

Можно сделать так (проще):

@Override
public Optional<User> findUserByHisName(String name) {
   return em.createQuery("select u from User u where u.name =:name", User.class)
            .setParameter("name", name)
             .getResultStream().findAny();
}

Либо так (взять Session из EntityManager):

@Override
public Optional<User> findUserByHisName(String name) {
    return em.unwrap(Session.class).createQuery("select u from User u where u.name =:name", User.class)
            .setParameter("name", name)
            .setFirstResult(0)
            .setMaxResults(1)
            .uniqueResultOptional();
}

Оба варианта избавят нас от обоих исключений.

Запросы по имени метода

Если же говорить о генерируемых из имени метода запросах, то первые два вызывают проблему:

public interface UserRepository extends JpaRepository<User, Long>, CustomUserRepository {

    // Optional<User> findByName(String name);  //1
    // User findByName(String name); //2
    Optional<User> findFirstByName(String name); //3

}

Как (1), так и (2) если искать по John  спровоцирует

org.springframework.dao.IncorrectResultSizeDataAccessException: query did not return a unique result: 2; nested exception is javax.persistence.NonUniqueResultException: query did not return a unique result: 2

А (3) запрос с ключевым словом First работает идеально.

NoResultException не выбрасывается ни в каком случае, вместо этого возвращается пустой Optional в (1) и null в (2).

Исходный код

Исходный код есть на GitHub.

 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *