O que há de novo no JPA 2.2

Feliz feriado a todos!

De repente, aconteceu que o início do segundo grupo "Java Enterprise Developer" coincidiu com o 256º dia do ano. Coincidência? Eu acho que não.

Bem, compartilhamos o penúltimo interesse: que novidades o JPA 2.2 trouxe - resultados de streaming, conversão aprimorada de datas, novas anotações - apenas alguns exemplos de melhorias úteis.

Vamos lá!

A Java Persistence API (JPA) é uma especificação Java EE fundamental que é amplamente usada no setor. Independentemente de você estar desenvolvendo para a plataforma Java EE ou para a estrutura Java alternativa, o JPA é sua escolha para salvar dados. O JPA 2.1 melhorou a especificação, permitindo que os desenvolvedores resolvessem problemas como geração automática de esquemas de banco de dados e trabalho eficiente com procedimentos armazenados no banco de dados. A versão mais recente, JPA 2.2, aprimora a especificação com base nessas alterações.
Neste artigo, falarei sobre novas funcionalidades e darei exemplos que ajudarão você a começar. Como exemplo, eu uso o projeto "Java EE 8 Playground", disponível no GitHub . O aplicativo de amostra é baseado na especificação Java EE 8 e usa as estruturas JavaServer Faces (JSF), Enterprise JavaBeans (EJB) e JPA para persistência. Você precisa estar familiarizado com o JPA para entender do que se trata.



Usando o JPA 2.2

O JPA versão 2.2 faz parte da plataforma Java EE 8. É importante notar que apenas os servidores de aplicativos compatíveis com Java EE 8 fornecem uma especificação pronta para uso imediato. No momento da redação deste texto (final de 2017), havia muitos servidores de aplicativos. No entanto, é fácil usar o JPA 2.2 com Java EE7. Primeiro, você precisa baixar os arquivos JAR apropriados usando o Maven Central e adicioná-los ao projeto. Se você estiver usando o Maven em seu projeto, adicione as coordenadas ao arquivo POM do Maven:

<dependency> <groupId>javax.persistence</groupId> <artifactId>javax.persistence-api</artifactId> <version>2.2</version> </dependency> 

Em seguida, selecione a implementação JPA que você deseja usar. A partir do JPA 2.2, o EclipseLink e o Hibernate têm implementações compatíveis. Como exemplos neste artigo, eu uso o EclipseLink adicionando a seguinte dependência:

 <dependency> <groupId>org.eclipse.persistence</groupId> <artifactId>eclipselink</artifactId> <version>2.7.0 </version> </dependency> 

Se você estiver usando um servidor compatível com Java EE 8, como GlassFish 5 ou Payara 5, poderá especificar a área "fornecida" para essas dependências no arquivo POM. Caso contrário, especifique a área "compilar" para incluí-los na montagem do projeto.

Suporte a data e hora do Java 8

Talvez uma das adições mais positivas seja o suporte à API de data e hora do Java 8. Desde o lançamento do Java SE 8 em 2014, os desenvolvedores usaram soluções alternativas para usar a API Date and Time com JPA. Embora a maioria das soluções alternativas seja bastante simples, a necessidade de adicionar suporte básico para a API de data e hora atualizada está atrasada. O suporte JPA para a API Data e hora inclui os seguintes tipos:

  • java.time.LocalDate
  • java.time.LocalTime
  • java.time.LocalDateTime
  • java.time.OffsetTime
  • java.time.OffsetDateTime

Para um melhor entendimento, explicarei primeiro como o suporte à API de data e hora funciona sem o JPA 2.2. O JPA 2.1 só pode funcionar com construções de data mais antigas, como java.util.Date e java.sql.Timestamp . Portanto, você deve usar um conversor para converter a data armazenada no banco de dados em um design antigo suportado pelo JPA 2.1 e, em seguida, convertê-lo em uma API de Data e Hora atualizada para uso no aplicativo. Um conversor de data no JPA 2.1 capaz dessa conversão pode se parecer com a Listagem 1. O conversor nele é usado para converter entre LocalDate e java.util.Date .

Listagem 1

 @Converter(autoApply = true) public class LocalDateTimeConverter implements AttributeConverter<LocalDate, Date> { @Override public Date convertToDatabaseColumn(LocalDate entityValue) { LocalTime time = LocalTime.now(); Instant instant = time.atDate(entityValue) .atZone(ZoneId.systemDefault()) .toInstant(); return Date.from(instant); } @Override public LocalDate convertToEntityAttribute(Date databaseValue){ Instant instant = Instant.ofEpochMilli(databaseValue.getTime()); return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()).toLocalDate(); } } 

O JPA 2.2 não precisa mais gravar esse conversor, pois você está usando tipos de data e hora suportados. O suporte para esses tipos é incorporado, portanto, você pode simplesmente especificar o tipo suportado no campo de classe da entidade sem código adicional. O trecho de código abaixo demonstra esse conceito. Observe que não há necessidade de adicionar anotação ao código @Temporal , porque o mapeamento de tipo ocorre automaticamente.

 public class Job implements Serializable { . . . @Column(name = "WORK_DATE") private LocalDate workDate; . . . } 

Como os tipos de data e hora suportados são objetos de primeira classe na JPA, eles podem ser especificados sem cerimônias adicionais. No JPA 2.1 @Temporal anotação deve ser descrita em todos os campos e propriedades constantes dos java.util.Calendar e java.util.Calendar .

Vale ressaltar que apenas alguns dos tipos de data e hora são suportados nesta versão, mas o conversor de atributos pode ser facilmente gerado para funcionar com outros tipos, por exemplo, para converter LocalDateTime em ZonedDateTime . O maior problema ao escrever esse conversor é determinar a melhor forma de converter entre tipos diferentes. Para facilitar ainda mais, os conversores de atributos agora podem ser implementados. Vou dar um exemplo de implementação abaixo.

O código na Listagem 2 mostra como converter o tempo de LocalDateTime para ZonedDateTime .

Listagem 2

 @Converter public class LocalToZonedConverter implements AttributeConverter<ZonedDateTime, LocalDateTime> { @Override public LocalDateTime convertToDatabaseColumn(ZonedDateTime entityValue) { return entityValue.toLocalDateTime(); } @Override public ZonedDateTime convertToEntityAttribute(LocalDateTime databaseValue) { return ZonedDateTime.of(databaseValue, ZoneId.systemDefault()); } } 

Especificamente, este exemplo é muito direto porque ZonedDateTime contém métodos fáceis de converter. A conversão ocorre chamando o método toLocalDateTime() . A conversão inversa pode ser feita chamando o método ZonedDateTimeOf() e passando o valor LocalDateTime junto com ZoneId para usar o fuso horário.

Conversores de atributos incorporados

Os conversores de atributo foram uma adição muito interessante ao JPA 2.1, pois permitiram que os tipos de atributo fossem mais flexíveis. A atualização do JPA 2.2 adiciona uma capacidade útil para tornar os conversores de atributos implementáveis. Isso significa que você pode incorporar recursos CDI (Contexts and Injection Dependency Injection) diretamente no conversor de atributos. Essa modificação é consistente com outros aprimoramentos de CDI nas especificações do Java EE 8, como conversores JSF avançados, pois agora eles também podem usar a injeção de CDI.

Para aproveitar esse novo recurso, basta incorporar os recursos CDI no conversor de atributos, conforme necessário. A Listagem 2 fornece um exemplo de conversor de atributo e agora vou desmontá-lo, explicando todos os detalhes importantes.

A classe converter deve implementar a interface javax.persistence.AttributeConverter , transmitindo os valores X e Y. O valor X corresponde ao tipo de dados no objeto Java e o valor Y deve corresponder ao tipo da coluna do banco de dados. Em seguida, a classe do conversor deve ser anotada com @Converter . Por fim, a classe deve substituir os convertToDatabaseColumn() e convertToEntityAttribute() . A implementação em cada um desses métodos deve converter valores de tipos específicos e retornar a eles.

Para aplicar o conversor automaticamente toda vez que o tipo de dados especificado for usado, adicione "automático", como em @Converter(autoApply=true) . Para aplicar um conversor a um único atributo, use a anotação @Converter no nível do atributo, conforme mostrado aqui:

 @Convert(converter=LocalDateConverter.java) private LocalDate workDate; 

O conversor também pode ser aplicado no nível da classe:

 @Convert(attributeName="workDate", converter = LocalDateConverter.class) public class Job implements Serializable { . . . 

Suponha que eu queira criptografar os valores contidos no campo creditLimit da entidade Customer quando ele for salvo. Para implementar esse processo, os valores devem ser criptografados antes de serem salvos e descriptografados após serem recuperados do banco de dados. Isso pode ser feito pelo conversor e, usando o JPA 2.2, posso incorporar o objeto de criptografia no conversor para obter o resultado desejado. A Listagem 3 fornece um exemplo.

Listagem 3

 @Converter public class CreditLimitConverter implements AttributeConverter<BigDecimal, BigDecimal> { @Inject CreditLimitEncryptor encryptor; @Override public BigDecimal convertToDatabaseColumn (BigDecimal entityValue) { String encryptedFormat = encryptor.base64encode(entityValue.toString()); return BigDecimal.valueOf(Long.valueOf(encryptedFormat)); } ... } 

Nesse código, o processo é realizado CreditLimitEncryptor classe CreditLimitEncryptor no conversor e, em seguida, usando-a para ajudar no processo.

Resultados da consulta de streaming

Agora você pode tirar proveito fácil dos recursos de fluxos do Java SE 8 ao trabalhar com os resultados da consulta. Os threads não apenas simplificam a leitura, gravação e manutenção de código, mas também ajudam a melhorar o desempenho da consulta em algumas situações. Algumas implementações de encadeamentos também ajudam a evitar um número simultâneo excessivamente grande de solicitações de dados, embora em alguns casos o uso da paginação ResultSet possa funcionar melhor que os fluxos.

Para ativar essa função, o método getResultStream() foi adicionado às TypedQuery Query e TypedQuery . Essa pequena alteração permite que a JPA retorne simplesmente um fluxo de resultados em vez de uma lista. Portanto, se você estiver trabalhando com um ResultSet grande, faz sentido comparar o desempenho entre uma nova implementação de encadeamento e um ResultSets ou paginação rolável. O motivo é que as implementações de encadeamento recuperam todos os registros de uma só vez, armazenam-nos em uma lista e depois os devolvem. Uma ResultSet rolável e uma técnica de paginação recuperam dados aos poucos, o que pode ser melhor para grandes conjuntos de dados.

Os provedores de persistência podem decidir substituir o novo método getResultStream() uma implementação aprimorada. O Hibernate já inclui um método stream () que usa um ResultSet rolável para analisar os resultados dos registros em vez de retorná-los completamente. Isso permite que o Hibernate trabalhe com conjuntos de dados muito grandes e faça-o bem. Pode-se esperar que outros provedores substituam esse método para fornecer recursos semelhantes que são benéficos para a JPA.

Além do desempenho, a capacidade de transmitir resultados é uma boa adição ao JPA, que fornece uma maneira conveniente de trabalhar com dados. Vou demonstrar alguns cenários em que isso pode ser útil, mas as possibilidades são infinitas. Nos dois cenários, eu consulto a entidade Job e retorno o fluxo. Primeiro, observe o código a seguir, onde eu simplesmente analiso o fluxo de Jobs relação a um Customer específico, chamando o método de interface Query getResultStream() . Então, eu uso esse encadeamento para exibir detalhes sobre o customer e a work date Job'a.

 public void findByCustomer(PoolCustomer customer){ Stream<Job> jobList = em.createQuery("select object(o) from Job o " + "where o.customer = :customer") .setParameter("customer", customer) .getResultStream(); jobList.map(j -> j.getCustomerId() + " ordered job " + j.getId() + " - Starting " + j.getWorkDate()) .forEach(jm -> System.out.println(jm)); } 


Este método pode ser ligeiramente modificado para que ele retorne uma lista de resultados usando o método Collectors .toList() seguinte maneira.

 public List<Job> findByCustomer(PoolCustomer customer){ Stream<Job> jobList = em.createQuery( "select object(o) from Job o " + "where o.customerId = :customer") .setParameter("customer", customer) .getResultStream(); return jobList.collect(Collectors.toList()); } 

No cenário a seguir, mostrado abaixo, encontro uma List tarefas relacionadas aos conjuntos de um formulário específico. Nesse caso, retorno todas as tarefas que correspondem ao formulário enviado como uma sequência. Semelhante ao primeiro exemplo, primeiro retorno um fluxo de registros de Jobs . Em seguida, filtro os registros com base no formulário do pool de clientes. Como você pode ver, o código resultante é muito compacto e fácil de ler.

 public List<Job> findByCustPoolShape(String poolShape){ Stream<Job> jobstream = em.createQuery( "select object(o) from Job o") .getResultStream(); return jobstream.filter( c -> poolShape.equals(c.getCustomerId().getPoolId().getShape())) .collect(Collectors.toList()); } 

Como mencionei anteriormente, é importante lembrar o desempenho em cenários em que grandes quantidades de dados são retornadas. Existem condições em que os threads são mais úteis na consulta de bancos de dados, mas também existem aqueles em que eles podem causar degradação no desempenho. Uma boa regra geral é que, se os dados puderem ser consultados como parte de uma consulta SQL, faz sentido fazer exatamente isso. Às vezes, os benefícios do uso de sintaxe de encadeamento elegante não superam o melhor desempenho possível com a filtragem SQL padrão.

Suporte de anotação duplicada

Quando o Java SE 8 foi lançado, as anotações duplicadas se tornaram possíveis, permitindo que você reutilizasse as anotações na declaração. Algumas situações exigem o uso da mesma anotação em uma classe ou campo várias vezes. Por exemplo, pode haver mais de uma anotação @SqlResultSetMapping para uma determinada classe de entidade. Nas situações em que o suporte à re-anotação é necessário, a anotação do contêiner deve ser usada. As anotações duplicadas não apenas reduzem o requisito de agrupar coleções de anotações idênticas nas anotações de contêiner, mas também podem facilitar a leitura do código.

Isso funciona da seguinte maneira: a implementação da classe de anotação deve ser marcada com a meta-anotação @Repeatable para indicar que ela pode ser usada mais de uma vez. A meta-anotação @Repeatable usa o tipo da classe de anotação do contêiner. Por exemplo, a NamedQuery anotação NamedQuery agora NamedQuery marcada com a @Repeatable(NamedQueries.class) . Nesse caso, a anotação de contêiner ainda está em uso, mas você não precisa pensar nisso ao usar a mesma anotação na declaração ou classe, porque @Repeatable abstrai esse detalhe.

Nós damos um exemplo. Se você deseja adicionar mais de uma anotação @NamedQuery a uma classe de entidade no JPA 2.1, precisará encapsulá-las dentro da anotação @NamedQueries , conforme mostrado na Listagem 4.

Listagem 4

 @Entity @Table(name = "CUSTOMER") @XmlRootElement @NamedQueries({ @NamedQuery(name = "Customer.findAll", query = "SELECT c FROM Customer c") , @NamedQuery(name = "Customer.findByCustomerId", query = "SELECT c FROM Customer c " + "WHERE c.customerId = :customerId") , @NamedQuery(name = "Customer.findByName", query = "SELECT c FROM Customer c " + "WHERE c.name = :name") . . .)}) public class Customer implements Serializable { . . . } 

No entanto, na JPA 2.2, tudo é diferente. Como @NamedQuery é uma anotação duplicada, ela pode ser especificada na classe de entidade mais de uma vez, conforme mostrado na Listagem 5.

Listagem 5

 @Entity @Table(name = "CUSTOMER") @XmlRootElement @NamedQuery(name = "Customer.findAll", query = "SELECT c FROM Customer c") @NamedQuery(name = "Customer.findByCustomerId", query = "SELECT c FROM Customer c " + "WHERE c.customerId = :customerId") @NamedQuery(name = "Customer.findByName", query = "SELECT c FROM Customer c " + "WHERE c.name = :name") . . . public class Customer implements Serializable { . . . } 

Lista de anotações duplicadas:

  • @AssociationOverride
  • @AttributeOverride
  • @Convert
  • @JoinColumn
  • @MapKeyJoinColumn
  • @NamedEntityGraphy
  • @NamedNativeQuery
  • @NamedQuery
  • @NamedStoredProcedureQuery
  • @PersistenceContext
  • @PersistenceUnit
  • @PrimaryKeyJoinColumn
  • @SecondaryTable
  • @SqlResultSetMapping

Conclusão

A versão JPA 2.2 possui algumas alterações, mas as melhorias incluídas são significativas. Por fim, o JPA está alinhado com o Java SE 8, permitindo que os desenvolvedores usem recursos como a API de Data e Hora, transmitindo resultados de consultas e repetindo anotações. Esta versão também aprimora a consistência do CDI, adicionando a capacidade de incorporar recursos CDI em conversores de atributos. O JPA 2.2 já está disponível e faz parte do Java EE 8, acho que você gostaria de usá-lo.

O FIM

Como sempre, estamos aguardando perguntas e comentários.

Source: https://habr.com/ru/post/pt423195/


All Articles