Руководство по SqlResultSetMapping
1. Вступление
В этом руководстве мы рассмотримSqlResultSetMapping из Java Persistence API (JPA).
Основная функциональность здесь включает отображение наборов результатов из операторов SQL базы данных в объекты Java.
2. Настроить
Прежде чем мы рассмотрим его использование, давайте сделаем некоторые настройки.
2.1. Maven Dependency
Наши требуемые зависимости Maven - Hibernate и H2 Database. Hibernate дает нам реализацию спецификации JPA. Мы используемH2 Database для базы данных в памяти.
2.2. База данных
Затем мы создадим две таблицы, как показано здесь:
CREATE TABLE EMPLOYEE
(id BIGINT,
name VARCHAR(10));
В контуреEMPLOYEE хранится один объект результатаEntity. SCHEDULE_DAYS содержит записи, связанные с таблицейEMPLOYEE столбцомemployeeId:
CREATE TABLE SCHEDULE_DAYS
(id IDENTITY,
employeeId BIGINT,
dayOfWeek VARCHAR(10));
Скрипт для создания данных можно найти вthe code for this guide.
2.3. Объекты Entity
Наши объектыEntity должны выглядеть примерно так:
@Entity
public class Employee {
@Id
private Long id;
private String name;
}
Entity objects могут называться иначе, чем таблицы базы данных. Мы можем аннотировать класс с помощью @Table to, явно отображая их:
@Entity
@Table(name = "SCHEDULE_DAYS")
public class ScheduledDay {
@Id
@GeneratedValue
private Long id;
private Long employeeId;
private String dayOfWeek;
}
3. Скалярное картирование
Теперь, когда у нас есть данные, мы можем начать отображать результаты запроса.
3.1. ColumnResultс
Хотя аннотацииSqlResultSetMapping иQuery также работают с классамиRepository, в этом примере мы используем аннотации классаEntity .
Для каждой аннотацииSqlResultSetMapping требуется только одно свойство,name. , однако без одного из типов элементов ничего не будет отображаться. Типы элементов:ColumnResult,ConstructorResult иEntityResult.
В этом случае ColumnResult заменяет любой столбец скалярным типом результата:
@SqlResultSetMapping(
name="FridayEmployeeResult",
columns={@ColumnResult(name="employeeId")})
СвойствоColumnResult name идентифицирует столбец в нашем запросе:
@NamedNativeQuery(
name = "FridayEmployees",
query = "SELECT employeeId FROM schedule_days WHERE dayOfWeek = 'FRIDAY'",
resultSetMapping = "FridayEmployeeResult")
Обратите внимание, чтоthe value of resultSetMapping в нашей аннотацииNamedNativeQueryis important because it matches the name property from our ResultSetMapping declaration.
В результате набор результатовNamedNativeQuery отображается должным образом. Точно так же APIStoredProcedure требует этой связи.
3.2. ColumnResult Тест
Для запуска нашего кода нам понадобятся некоторые специфические объекты Hibernate:
@BeforeAll
public static void setup() {
emFactory = Persistence.createEntityManagerFactory("java-jpa-scheduled-day");
em = emFactory.createEntityManager();
}
Наконец, мы вызываем именованный запрос для запуска нашего теста:
@Test
public void whenNamedQuery_thenColumnResult() {
List employeeIds = em.createNamedQuery("FridayEmployees").getResultList();
assertEquals(2, employeeIds.size());
}
4. Отображение конструктора
Давайте посмотрим, когда нам нужно сопоставить набор результатов со всем объектом.
4.1. ConstructorResultс
Подобно нашему примеруColumnResult, мы добавим аннотациюSqlResultMapping в наш классEntity,ScheduledDay. Однако, чтобы отобразить с помощью конструктора, нам нужно создать его:
public ScheduledDay (
Long id, Long employeeId,
Integer hourIn, Integer hourOut,
String dayofWeek) {
this.id = id;
this.employeeId = employeeId;
this.dayOfWeek = dayofWeek;
}
Кроме того, сопоставление определяет целевой класс и столбцы (оба обязательны):
@SqlResultSetMapping(
name="ScheduleResult",
classes={
@ConstructorResult(
targetClass=com.example.sqlresultsetmapping.ScheduledDay.class,
columns={
@ColumnResult(name="id", type=Long.class),
@ColumnResult(name="employeeId", type=Long.class),
@ColumnResult(name="dayOfWeek")})})
The order of the ColumnResults is very important. Если столбцы не в порядке, конструктор не может быть идентифицирован. В нашем примере порядок соответствует столбцам таблицы, поэтому он фактически не требуется.
@NamedNativeQuery(name = "Schedules",
query = "SELECT * FROM schedule_days WHERE employeeId = 8",
resultSetMapping = "ScheduleResult")
Еще одно уникальное отличиеConstructorResult заключается в том, что результирующий объект создается как «новый» или «отсоединенный». ОтображенныйEntity будет в отсоединенном состоянии, если соответствующий первичный ключ существует вEntityManager, в противном случае он будет новым.
Иногда мы можем столкнуться с ошибками во время выполнения из-за несоответствия типов данных SQL и типов данных Java. Следовательно, мы можем явно объявить его с помощьюtype.
4.2. ConstructorResult Тест
Давайте проверимConstructorResult в модульном тесте:
@Test
public void whenNamedQuery_thenConstructorResult() {
List scheduleDays
= Collections.checkedList(
em.createNamedQuery("Schedules", ScheduledDay.class).getResultList(), ScheduledDay.class);
assertEquals(3, scheduleDays.size());
assertTrue(scheduleDays.stream().allMatch(c -> c.getEmployeeId().longValue() == 3));
}
5. Entity Mapping
Наконец, для простого сопоставления сущностей с меньшим количеством кода давайте взглянем наEntityResult.
5.1. Единый субъект
EntityResult требует, чтобы мы указали класс сущностиEmployee. Мы используем необязательное свойствоfields для большего контроля. В сочетании сFieldResult, мы можем сопоставить псевдонимы и поля, которые не совпадают:
@SqlResultSetMapping(
name="EmployeeResult",
entities={
@EntityResult(
entityClass = com.example.sqlresultsetmapping.Employee.class,
fields={
@FieldResult(name="id",column="employeeNumber"),
@FieldResult(name="name", column="name")})})
Теперь наш запрос должен включать столбец с псевдонимами:
@NamedNativeQuery(
name="Employees",
query="SELECT id as employeeNumber, name FROM EMPLOYEE",
resultSetMapping = "EmployeeResult")
АналогичноConstructorResult,EntityResult требует конструктора. Тем не менее, по умолчанию работает здесь.
5.2. Несколько сущностей
Сопоставление нескольких сущностей довольно просто, если мы сопоставили одну сущность:
@SqlResultSetMapping(
name = "EmployeeScheduleResults",
entities = {
@EntityResult(entityClass = com.example.sqlresultsetmapping.Employee.class),
@EntityResult(entityClass = com.example.sqlresultsetmapping.ScheduledDay.class)
5.3. EntityResult Тесты
Давайте посмотрим наEntityResult в действии:
@Test
public void whenNamedQuery_thenSingleEntityResult() {
List employees = Collections.checkedList(
em.createNamedQuery("Employees").getResultList(), Employee.class);
assertEquals(3, employees.size());
assertTrue(employees.stream().allMatch(c -> c.getClass() == Employee.class));
}
Поскольку результаты нескольких сущностей объединяют две сущности, аннотация запроса только для одного из классов сбивает с толку.
По этой причине мы определяем запрос в тесте:
@Test
public void whenNamedQuery_thenMultipleEntityResult() {
Query query = em.createNativeQuery(
"SELECT e.id, e.name, d.id, d.employeeId, d.dayOfWeek "
+ " FROM employee e, schedule_days d "
+ " WHERE e.id = d.employeeId", "EmployeeScheduleResults");
List
6. Заключение
В этом руководстве мы рассмотрели различные варианты использования аннотацииSqlResultSetMapping. SqlResultSetMapping - ключевой части Java Persistence API.
Фрагменты кода можно найтиover on GitHub.