Перейти к основному содержимому

Руководство по SqlResultSetMapping

· 5 мин. чтения

1. Введение

В этом руководстве мы рассмотрим SqlResultSetMapping из Java Persistence API (JPA).

Основная функциональность здесь включает отображение наборов результатов из операторов SQL базы данных в объекты Java.

2. Настройка

Прежде чем мы рассмотрим его использование, давайте сделаем некоторую настройку.

2.1. Зависимость от Maven

Наши необходимые зависимости Maven — это Hibernate и база данных H2. Hibernate дает нам реализацию спецификации JPA. Мы используем базу данных H2 для базы данных в памяти.

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));

Скрипт для создания данных можно найти в коде этого руководства .

2.3. Сущностные объекты

Наши объекты Entity должны выглядеть одинаково:

@Entity
public class Employee {
@Id
private Long id;
private String name;
}

Объекты сущностей могут называться не так, как таблицы базы данных. Мы можем аннотировать класс с помощью @ Table , чтобы явно сопоставить их:

@Entity
@Table(name = "SCHEDULE_DAYS")
public class ScheduledDay {

@Id
@GeneratedValue
private Long id;
private Long employeeId;
private String dayOfWeek;
}

3. Скалярное отображение

Теперь, когда у нас есть данные, мы можем начать сопоставление результатов запроса.

3.1. СтолбецРезультат

Хотя аннотации SqlResultSetMapping и Query также работают с классами Repository , в этом примере мы используем аннотации для класса Entity .

Для каждой аннотации SqlResultSetMapping требуется только одно свойство — имя. Однако без одного из типов элементов ничего не будет сопоставлено. Типы членов — ColumnResult , ConstructorResult и EntityResult .

В этом случае ColumnResult отображает любой столбец в скалярный тип результата:

@SqlResultSetMapping(
name="FridayEmployeeResult",
columns={@ColumnResult(name="employeeId")})

Имя свойства ColumnResult идентифицирует столбец в нашем запросе :

@NamedNativeQuery(
name = "FridayEmployees",
query = "SELECT employeeId FROM schedule_days WHERE dayOfWeek = 'FRIDAY'",
resultSetMapping = "FridayEmployeeResult")

Обратите внимание, что значение resultSetMapping в нашей аннотации NamedNativeQuery важно, поскольку оно соответствует свойству name из нашего объявления ResultSetMapping .

В результате набор результатов NamedNativeQuery отображается должным образом. Точно так же эта ассоциация требуется для StoredProcedure API.

3.2. Проверка результатов столбца

Нам понадобятся некоторые объекты, специфичные для Hibernate, для запуска нашего кода:

@BeforeAll
public static void setup() {
emFactory = Persistence.createEntityManagerFactory("java-jpa-scheduled-day");
em = emFactory.createEntityManager();
}

Наконец, мы вызываем именованный запрос для запуска нашего теста:

@Test
public void whenNamedQuery_thenColumnResult() {
List<Long> employeeIds = em.createNamedQuery("FridayEmployees").getResultList();
assertEquals(2, employeeIds.size());
}

4. Отображение конструктора

Давайте посмотрим, когда нам нужно сопоставить результирующий набор со всем объектом.

4.1. КонструкторРезультат

Как и в нашем примере 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.foreach.sqlresultsetmapping.ScheduledDay.class,
columns={
@ColumnResult(name="id", type=Long.class),
@ColumnResult(name="employeeId", type=Long.class),
@ColumnResult(name="dayOfWeek")})})

Порядок ColumnResults очень важен. Если столбцы расположены не по порядку, конструктор не будет идентифицирован. В нашем примере порядок соответствует столбцам таблицы, поэтому на самом деле это не требуется.

@NamedNativeQuery(name = "Schedules",
query = "SELECT * FROM schedule_days WHERE employeeId = 8",
resultSetMapping = "ScheduleResult")

Другое уникальное отличие ConstructorResult заключается в том, что результирующий экземпляр объекта создается как «новый» или «отсоединенный». Сопоставленная сущность будет находиться в отсоединенном состоянии, если в EntityManager существует соответствующий первичный ключ, в противном случае она будет новой.

Иногда мы можем столкнуться с ошибками времени выполнения из-за несоответствия типов данных SQL типам данных Java. Следовательно, мы можем явно объявить его с типом.

4.2. Тест ConstructorResult

Давайте проверим ConstructorResult в модульном тесте:

@Test
public void whenNamedQuery_thenConstructorResult() {
List<ScheduledDay> 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. Отображение сущностей

Наконец, для простого сопоставления сущностей с меньшим количеством кода давайте посмотрим на EntityResult .

5.1. Единое лицо

EntityResult требует, чтобы мы указали класс сущности, Employee . Мы используем свойство необязательных полей для большего контроля. В сочетании с FieldResult мы можем отображать псевдонимы и поля, которые не совпадают:

@SqlResultSetMapping(
name="EmployeeResult",
entities={
@EntityResult(
entityClass = com.foreach.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.foreach.sqlresultsetmapping.Employee.class),
@EntityResult(entityClass = com.foreach.sqlresultsetmapping.ScheduledDay.class)

5.3. Тесты EntityResult

Давайте посмотрим на EntityResult в действии:

@Test
public void whenNamedQuery_thenSingleEntityResult() {
List<Employee> 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<Object[]> results = query.getResultList();
assertEquals(4, results.size());
assertTrue(results.get(0).length == 2);

Employee emp = (Employee) results.get(1)[0];
ScheduledDay day = (ScheduledDay) results.get(1)[1];

assertTrue(day.getEmployeeId() == emp.getId());
}

6. Заключение

В этом руководстве мы рассмотрели разные варианты использования аннотации SqlResultSetMapping . SqlResultSetMapping является ключевой частью Java Persistence API.

Фрагменты кода можно найти на GitHub .