Java result set mapping

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

В этом руководстве мы рассмотрим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

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

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

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

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

3.1. ColumnResultс

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

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

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

@SqlResultSetMapping( name="FridayEmployeeResult", columns=)

Свойство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()

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

@Test public void whenNamedQuery_thenColumnResult() < ListemployeeIds = 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)

Кроме того, сопоставление определяет целевой класс и столбцы (оба обязательны):

@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() < ListscheduleDays = 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() < Listemployees = 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"); Listresults = 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.

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

Источник

Result Set Mapping: The Basics

The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, 2 monthly Q&A calls, monthly coding challenges, a community of like-minded developers, and regular expert sessions.

Quite often JPQL is not powerful enough to perform the queries we need in real world projects. In general, this is not an issue because JPA is designed as a leaky abstraction and we can use the full potential of SQL by using native queries or calling stored procedures.

The only downside is, that these queries return a List of Object[] instead of the mapped entities and value objects we are used to working with. Each Object[] contains one record returned by the database. We then need to iterate through the array, cast each Object to its specific type, and map them to our domain model. This creates lots of repetitive code and type casts as you can see in the following example.

List results = this.em.createNativeQuery("SELECT a.id, a.firstName, a.lastName, a.version FROM Author a").getResultList(); results.stream().forEach((record) -> < Long record[0]).longValue(); String firstName = (String) record[1]; String lastName = (String) record[2]; Integer version = (Integer) record[3]; >);

It would be more comfortable if we could tell the EntityManager to map the result of the query into entities or value objects as it is the case for JPQL statements. The good news is, JPA provides this functionality. It is called SQL result set mapping and we will have a detailed look at it during this series:

The example

We only need a simple Author entity with an id, a version, a first name and a last name for this post.

class diagram Author

How to use the default mapping

The easiest way to map a query result to an entity is to provide the entity class as a parameter to the createNativeQuery(String sqlString, Class resultClass) method of the EntityManager and use the default mapping. The following snippet shows how this is done with a very simple query. In a real project, you would use this with a stored procedure or a very complex SQL query.

List results = this.em.createNativeQuery("SELECT a.id, a.firstName, a.lastName, a.version FROM Author a", Author.class).getResultList();

The query needs to return all properties of the entity and the JPA implementation (e.g. Hibernate) will try to map the returned columns to the entity properties based on their name and type. If that is successful, the EntityManager will return a list of fully initialized Author entities that are managed by the current persistence context. So the result is the same as if we had used a JPQL query, but we are not limited to the small feature set of JPQL.

How to define a custom mapping

While this automatic mapping is useful and easy to define, it is often not sufficient. If we perform a more complex query or call a stored procedure, the names of the returned columns might not match the entity definition. In these cases we need to define a custom result mapping. This needs to define the mapping for all entity properties, even if the default mapping cannot be applied to only one property.

Let’s have a look at our example and change the query we used before and rename the id column to authorId:

SELECT a.id as authorId, a.firstName, a.lastName, a.version FROM Author a

The default mapping to the Author entity will not work with this query result because the names of the selected columns and the entity properties do not match. We need to define a custom mapping for it. This can be done with annotations or in a mapping file (e.g. orm.xml). The following code snippet shows how to define the result mapping with the @SqlResultSetMapping annotation. The mapping consists of a name and an @EntityResult definition. The name of the mapping, AuthorMapping in this example, will later be used to tell the EntityManager which mapping to use. The @EntityResult defines the entity class to which the result shall be mapped and an array of @FieldResult which defines the mapping between the column name and the entity property. Each @FieldResult gets the name of the property and the column name as a parameter.

@SqlResultSetMapping( name = "AuthorMapping", entities = @EntityResult( entityClass = Author.class, fields = < @FieldResult(name = "id", column = "authorId"), @FieldResult(name = "firstName", column = "firstName"), @FieldResult(name = "lastName", column = "lastName"), @FieldResult(name = "version", column = "version")>))

Since Hibernate 5 and JPA 2.2, the @SqlResultMapping annotation is repeatable. You, therefore, no longer need to place your @SqlResultSetMapping annotations within a @SqlResultMappings annotation if you want to define more than one mapping at an entity.

If you don’t like to add huge blocks of annotations to your entities, you can define the mapping in an XML mapping file. The default mapping file is called orm.xml and will be used automatically, if it is added to the META-INF directory of the jar file.

As you can see below, the mapping is very similar to the annotation-based mapping that we discussed before. I named it AuthorMappingXml to avoid name clashes with the annotation-based mapping. In a real project, you don’t need to worry about this, because you would normally use only one of the two described mappings.

OK, so now we have defined our own mapping between the query result and the Author entity. We can now provide the name of the mapping instead of the entity class as a parameter to the createNativeQuery(String sqlString, String resultSetMapping) method. In the code snippet below, I used the annotation defined mapping.

List results = this.em.createNativeQuery("SELECT a.id as authorId, a.firstName, a.lastName, a.version FROM Author a", "AuthorMapping").getResultList();

Conclusion

In this first post of the series, we had a look at two basic ways to map the query result to an entity:

  1. If the names and the types of the query result match to the entity properties, we only need to provide the entity class to the createNativeQuery(String sqlString, Class resultClass) method of the EntityManager to use the default mapping.
  2. If the default mapping cannot be applied to the query result, we can use XML or the @SqlResultSetMapping annotation to define a custom mapping between the columns of the query result and the properties of an entity. The name of the mapping can then be provided to the createNativeQuery(String sqlString, String resultSetMapping) method.

The mappings described in this post were quite simple. In the following posts of this series, we will have a look at more complex mappings that can handle more than one entity and additional columns or that can map to value objects instead of entities:

Источник

Читайте также:  Максимальный размер массива python
Оцените статью