В этой статье рассмотрим, какие запросы вызывают исключения NonUniqueResultException и NoResultException, и как их избежать.
NonUniqueResultException выбрасывается, когда ожидается единственная запись, а приходит несколько.
NoResultException выбрасывается, когда ожидается хоть что-то (возвращаемый тип не Optional), а не приходит ничего.
Для устранения исключений обычно нужно сменить тип возвращаемого значения и/или поправить метод. Рассмотрим пример.
Модель
@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
что в общем тоже логично.
Хороший запрос
- Лучше сменить возвращаемый тип на Optional.
- Но это одно не поможет избавиться от исключений, так что запрос тоже надо изменить.
Можно сделать так (проще):
@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.