Автор оригинала: Vlad Mihalcea.
Вступление
Отвечая на вопросы на форуме Hibernate, я наткнулся на следующий вопрос, касающийся операции приведения PostgreSQL ::
, используемой в JPA и запросе сущности Hibernate|/.
Поскольку это очень интересный вариант использования, я решил превратить ответ в специальный пост в блоге.
Модель предметной области
Учитывая, что у нас есть следующая организация:
@Entity(name = "Post") @Table(name = "post") public class Post { @Id @GeneratedValue private Long id; private String title; @Column(name = "created_on") private LocalDateTime createdOn; //Getters and setters omitted for brevity }
Как я объяснил в этой статье , Hibernate поддерживает типы даты и времени Java 1.8 начиная с версии 5.1. Теперь, когда они были включены в JPA 2.2, каждый поставщик JPA должен начать их поддерживать.
Теперь давайте сохраним следующие три Сообщения
сущности:
Post part1 = new Post(); part1.setTitle("High-Performance Java Persistence, Part 1"); part1.setCreatedOn( LocalDateTime.now().with( TemporalAdjusters.previous(DayOfWeek.MONDAY) ) ); entityManager.persist(part1); Post part2 = new Post(); part2.setTitle("High-Performance Java Persistence, Part 2"); part2.setCreatedOn( LocalDateTime.now().with( TemporalAdjusters.previous(DayOfWeek.TUESDAY) ) ); entityManager.persist(part2); Post part3 = new Post(); part3.setTitle("High-Performance Java Persistence, Part 3"); part3.setCreatedOn( LocalDateTime.now().with( TemporalAdjusters.previous(DayOfWeek.THURSDAY) ) ); entityManager.persist(part3);
Первый Пост
создается в понедельник, второй-во вторник, а последний-в четверг.
Проблема
Теперь мы хотели бы выполнить запрос, чтобы получить все объекты Post
, соответствующие тому же дню недели, который мы указываем с помощью параметра запроса.
Для этого мы используем собственный SQL-запрос, потому что он подобен волшебной палочке .
Если вы работаете на PostgreSQL, вы можете использовать функцию date_part
и привести параметр с помощью :: оператора преобразования типов .
Listposts = entityManager.createNativeQuery( "SELECT * " + "FROM post " + "WHERE " + " date_part('dow', created_on) = " + " date_part('dow', :datetime::date)", Post.class) .setParameter("datetime", Timestamp.valueOf( LocalDateTime.now().with( TemporalAdjusters.next(DayOfWeek.MONDAY))) ) .getResultList();
Однако при выполнении приведенного выше запроса Hibernate выдает следующее исключение:
java.lang.IllegalArgumentException: Unknown parameter name : datetime at org.hibernate.query.internal.QueryParameterBindingsImpl.getBinding(QueryParameterBindingsImpl.java:208) at org.hibernate.query.internal.AbstractProducedQuery.setParameter(AbstractProducedQuery.java:486) at org.hibernate.query.internal.NativeQueryImpl.setParameter(NativeQueryImpl.java:586)
Это связано с тем, что оператор приведения типа :: конфликтует с синтаксисом именованных параметров JPA : .
Итак, как мы можем это исправить?
Есть два способа решить эту проблему. Мы можем либо избежать символа :
с помощью \
, либо использовать функцию CAST
.
Побег из: персонажа
Если мы экранируем каждый из символов :
с помощью \
, так что ::
станет \:\:
, то все будет работать просто отлично:
Listposts = entityManager.createNativeQuery( "SELECT * " + "FROM post " + "WHERE " + " date_part('dow', created_on) = " + " date_part('dow', :datetime\\:\\:date)", Post.class) .setParameter("datetime", Timestamp.valueOf( LocalDateTime.now().with( TemporalAdjusters.next(DayOfWeek.MONDAY))) ) .getResultList(); assertEquals(1, posts.size()); assertEquals( "High-Performance Java Persistence, Part 1", posts.get(0).getTitle() );
Использование функции ПРИВЕДЕНИЯ
Другой вариант устранения этой проблемы-использование функции PostgreSQL CAST .
Listposts = entityManager.createNativeQuery( "SELECT * " + "FROM post " + "WHERE " + " date_part('dow', created_on) = " + " date_part('dow', cast(:datetime AS date))", Post.class) .setParameter("datetime", Timestamp.valueOf( LocalDateTime.now().with( TemporalAdjusters.next(DayOfWeek.MONDAY))) ) .getResultList(); assertEquals(1, posts.size()); assertEquals( "High-Performance Java Persistence, Part 1", posts.get(0).getTitle() );
И это работает так, как и ожидалось.
Преобразование из строки
В качестве альтернативы мы также могли бы преобразовать значение из строки
. Учтите, что мы получаем параметр запроса в виде Строки
:
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern( "dd-MM-YYYY" ); String dateString = dateTimeFormatter.format( LocalDateTime.now().with(TemporalAdjusters.next(DayOfWeek.MONDAY)) );
На этот раз вместо простого вызова функции ПРИВЕДЕНИЯ нам нужно использовать функцию TO_TIMESTAMP:
Listposts = entityManager.createNativeQuery( "SELECT * " + "FROM post " + "WHERE " + " date_part('dow', created_on) = " + " date_part('dow', to_timestamp(:datetime, 'dd-MM-YYYY'))", Post.class) .setParameter("datetime", dateString) .getResultList(); assertEquals(1, posts.size()); assertEquals( "High-Performance Java Persistence, Part 1", posts.get(0).getTitle() );
И это работает как заклинание.
Вывод
Собственные SQL-запросы очень мощны, и именно поэтому вы должны их использовать. Однако несколько специфичных для базы данных операторов, таких как :: оператор преобразования типов, могут мешать синтаксису именованных параметров, специфичных для JPA.
Исправить эту проблему на самом деле очень просто, так как вы можете заменить рассматриваемый оператор эквивалентным вызовом функции SQL.