Ръководство за SqlResultSetMapping

1. Въведение

В това ръководство ще разгледаме SqlResultSetMapping от API за устойчивост на Java (JPA).

Основната функционалност тук включва картографиране на набори от резултати от SQL изрази на база данни в Java обекти.

2. Настройка

Преди да разгледаме използването му, нека направим някои настройки.

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

Нашите задължителни зависимости от Maven са Hibernate и H2 Database. Hibernate ни дава изпълнението на спецификацията JPA. Използваме база данни H2 за база данни в паметта.

2.2. База данни

След това ще създадем две таблици, както се вижда тук:

CREATE TABLE EMPLOYEE (id BIGINT, name VARCHAR(10));

Таблицата EMPLOYEE съхранява един обект на обект на резултат . SCHEDULE_DAYS съдържа записи, свързани с таблицата EMPLOYEE от колоната workerId:

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 работят и върху класовете на хранилището , ние използваме поясненията на клас 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 се картографира, както се очаква. По същия начин API на StoredProcedure изисква тази асоциация.

3.2. Тест ColumnResult

Ще ни трябват някои специфични обекти за хибернация, за да стартираме нашия код:

@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.baeldung.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. Следователно можем изрично да го декларираме с type.

4.2. ConstructorResult Test

Нека тестваме 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. Съставяне на обекти

И накрая, за просто картографиране на обекти с по-малко код, нека да разгледаме EntityResult .

5.1. Единична единица

EntityResult requires us to specify the entity class, Employee. We use the optional fields property for more control. Combined with FieldResult, we can map aliases and fields that do not match:

@SqlResultSetMapping( name="EmployeeResult", entities={ @EntityResult( entityClass = com.baeldung.sqlresultsetmapping.Employee.class, fields={ @FieldResult(name="id",column="employeeNumber"), @FieldResult(name="name", column="name")})})

Now our query should include the aliased column:

@NamedNativeQuery( name="Employees", query="SELECT id as employeeNumber, name FROM EMPLOYEE", resultSetMapping = "EmployeeResult")

Similarly to ConstructorResult, EntityResult requires a constructor. However, a default one works here.

5.2. Multiple Entities

Mapping multiple entities is pretty straightforward once we have mapped a single Entity:

@SqlResultSetMapping( name = "EmployeeScheduleResults", entities = { @EntityResult(entityClass = com.baeldung.sqlresultsetmapping.Employee.class), @EntityResult(entityClass = com.baeldung.sqlresultsetmapping.ScheduledDay.class)

5.3. EntityResult Tests

Let's have a look at EntityResult in action:

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

Since the multiple entity results join two entities, the query annotation on only one of the classes is confusing.

Поради тази причина дефинираме заявката в теста:

@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 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 е ключова част от API за устойчивост на Java.

Кодови фрагменти могат да бъдат намерени в GitHub.