Um guia para SqlResultSetMapping
1. Introdução
Neste guia, daremos uma olhada emSqlResultSetMapping, fora da Java Persistence API (JPA).
A funcionalidade principal aqui envolve o mapeamento de conjuntos de resultados de instruções SQL do banco de dados em objetos Java.
2. Configuração
Antes de olharmos para seu uso, vamos fazer algumas configurações.
2.1. Dependência do Maven
Nossas dependências necessárias do Maven são o Hibernate e o H2 Database. Hibernate nos dá a implementação da especificação JPA. UsamosH2 Database para um banco de dados na memória.
2.2. Base de dados
A seguir, criaremos duas tabelas, conforme visto aqui:
CREATE TABLE EMPLOYEE
(id BIGINT,
name VARCHAR(10));
O estávelEMPLOYEE armazena um objetoEntity de resultado. SCHEDULE_DAYS não contém registros vinculados à tabelaEMPLOYEE pela colunaemployeeId:
CREATE TABLE SCHEDULE_DAYS
(id IDENTITY,
employeeId BIGINT,
dayOfWeek VARCHAR(10));
Um script para criação de dados pode ser encontrado emthe code for this guide.
2.3. Objetos de entidade
NossosEntity objects devem ser semelhantes:
@Entity
public class Employee {
@Id
private Long id;
private String name;
}
Entity objects podem ser nomeados de forma diferente das tabelas de banco de dados. Podemos anotar a classe com @Table to mapeá-los explicitamente:
@Entity
@Table(name = "SCHEDULE_DAYS")
public class ScheduledDay {
@Id
@GeneratedValue
private Long id;
private Long employeeId;
private String dayOfWeek;
}
3. Mapeamento escalar
Agora que temos dados, podemos começar a mapear os resultados da consulta.
3.1. ColumnResult
Embora as anotaçõesSqlResultSetMappingeQuery funcionem nas classesRepository também, usamos as anotações em uma classeEntity neste exemplo.
Cada anotaçãoSqlResultSetMapping requer apenas uma propriedade,name. No entanto, sem um dos tipos de membro, nada será mapeado. Os tipos de membro sãoColumnResult,ConstructorResult eEntityResult.
Nesse caso, ColumnResult mapeia qualquer coluna para um tipo de resultado escalar:
@SqlResultSetMapping(
name="FridayEmployeeResult",
columns={@ColumnResult(name="employeeId")})
OColumnResult propertyname identifica a coluna em nossa consulta:
@NamedNativeQuery(
name = "FridayEmployees",
query = "SELECT employeeId FROM schedule_days WHERE dayOfWeek = 'FRIDAY'",
resultSetMapping = "FridayEmployeeResult")
Observe quethe value of resultSetMapping em nossa anotaçãoNamedNativeQueryis important because it matches the name property from our ResultSetMapping declaration.
Como resultado, o conjunto de resultadosNamedNativeQuery é mapeado conforme o esperado. Da mesma forma,StoredProcedure API requer esta associação.
3.2. Teste deColumnResult
Precisaremos de alguns objetos específicos do Hibernate para executar nosso código:
@BeforeAll
public static void setup() {
emFactory = Persistence.createEntityManagerFactory("java-jpa-scheduled-day");
em = emFactory.createEntityManager();
}
Por fim, chamamos a consulta nomeada para executar nosso teste:
@Test
public void whenNamedQuery_thenColumnResult() {
List employeeIds = em.createNamedQuery("FridayEmployees").getResultList();
assertEquals(2, employeeIds.size());
}
4. Mapeamento do Construtor
Vamos dar uma olhada em quando precisamos mapear um conjunto de resultados para um objeto inteiro.
4.1. ConstructorResult
Similarmente ao nosso exemploColumnResult, adicionaremos a anotaçãoSqlResultMapping em nossa classeEntity,ScheduledDay. No entanto, para mapear usando um construtor, precisamos criar um:
public ScheduledDay (
Long id, Long employeeId,
Integer hourIn, Integer hourOut,
String dayofWeek) {
this.id = id;
this.employeeId = employeeId;
this.dayOfWeek = dayofWeek;
}
Além disso, o mapeamento especifica a classe de destino e as colunas (ambas necessárias):
@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. Se as colunas estiverem fora de ordem, o construtor não será identificado. No nosso exemplo, a ordem corresponde às colunas da tabela, portanto, na verdade, não seria necessário.
@NamedNativeQuery(name = "Schedules",
query = "SELECT * FROM schedule_days WHERE employeeId = 8",
resultSetMapping = "ScheduleResult")
Outra diferença única paraConstructorResult é que a instanciação do objeto resultante como “novo” ou “destacado”. OEntity mapeado estará no estado desconectado quando uma chave primária correspondente existir emEntityManager, caso contrário, ela será nova.
Às vezes, podemos encontrar erros de tempo de execução devido à incompatibilidade de tipos de dados SQL para tipos de dados Java. Portanto, podemos declará-lo explicitamente comtype.
4.2. Teste deConstructorResult
Vamos testar oConstructorResult em um teste de unidade:
@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. Mapeamento de Entidades
Finalmente, para um mapeamento de entidade simples com menos código, vamos dar uma olhada emEntityResult.
5.1. Entidade única
EntityResult exige que especifiquemos a classe de entidade,Employee. Usamos o sproperty opcionalfields para mais controle. Combinado comFieldResult,, podemos mapear aliases e campos que não correspondem:
@SqlResultSetMapping(
name="EmployeeResult",
entities={
@EntityResult(
entityClass = com.example.sqlresultsetmapping.Employee.class,
fields={
@FieldResult(name="id",column="employeeNumber"),
@FieldResult(name="name", column="name")})})
Agora nossa consulta deve incluir a coluna com alias:
@NamedNativeQuery(
name="Employees",
query="SELECT id as employeeNumber, name FROM EMPLOYEE",
resultSetMapping = "EmployeeResult")
Da mesma forma queConstructorResult,EntityResult requer um construtor. No entanto, um padrão funciona aqui.
5.2. Várias entidades
O mapeamento de várias entidades é bem simples, uma vez que mapeamos uma única entidade:
@SqlResultSetMapping(
name = "EmployeeScheduleResults",
entities = {
@EntityResult(entityClass = com.example.sqlresultsetmapping.Employee.class),
@EntityResult(entityClass = com.example.sqlresultsetmapping.ScheduledDay.class)
5.3. EntityResult testes
Vamos dar uma olhada emEntityResult em ação:
@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));
}
Como os resultados de várias entidades juntam duas entidades, a anotação de consulta em apenas uma das classes é confusa.
Por esse motivo, definimos a consulta no teste:
@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. Conclusão
Neste guia, vimos diferentes opções para usar a anotaçãoSqlResultSetMapping. SqlResultSetMapping é uma parte importante da API de persistência Java.
Trechos de código podem ser encontradosover on GitHub.