祝大家节日快乐!
突然间,第二个小组
“ Java Enterprise Developer”的开始恰逢一年的第256天。
巧合吗? 我不这么认为。好吧,我们有着倒数第二个兴趣:JPA 2.2带来了哪些新功能-流式传输结果,改进的日期转换,新的注释-仅是一些有用的改进示例。
走吧
Java Persistence API(JPA)是基本的Java EE规范,已在业界广泛使用。 无论您是为Java EE平台还是为替代Java框架而开发,JPA都是保存数据的选择。 JPA 2.1改进了规范,使开发人员能够解决诸如自动生成数据库模式和有效地使用存储在数据库中的过程之类的问题。 最新版本的JPA 2.2根据这些更改改进了规范。
在本文中,我将讨论新功能并提供示例,以帮助您开始使用它。 作为示例,我使用
GitHub上的“ Java EE 8 Playground”项目。 该示例应用程序基于Java EE 8规范,并使用JavaServer Faces(JSF),Enterprise JavaBeans(EJB)和JPA框架来实现持久性。 您需要熟悉JPA才能了解其含义。
使用JPA 2.2JPA 2.2版是Java EE 8平台的一部分,值得注意的是,只有与Java EE 8兼容的应用服务器才提供可立即使用的规范。 在撰写本文时(2017年末),有很多这样的应用程序服务器。 但是,将JPA 2.2与Java EE7一起使用很容易。 首先,您需要使用
Maven Central下载适当的JAR文件并将其添加到项目中。 如果您在项目中使用Maven,请将坐标添加到Maven POM文件:
<dependency> <groupId>javax.persistence</groupId> <artifactId>javax.persistence-api</artifactId> <version>2.2</version> </dependency>
然后,选择要使用的JPA实现。 从JPA 2.2开始,EclipseLink和Hibernate都有兼容的实现。 作为本文中的示例,我通过添加以下依赖项来使用
EclipseLink :
<dependency> <groupId>org.eclipse.persistence</groupId> <artifactId>eclipselink</artifactId> <version>2.7.0 </version> </dependency>
如果使用的是与Java EE 8兼容的服务器,例如GlassFish 5或Payara 5,则应该能够在POM文件中为这些依赖项指定“提供”区域。 否则,请指定“编译”区域以将它们包括在项目程序集中。
Java 8日期和时间支持Java 8 Date and Time API支持可能是最积极的补充之一。 自2014年Java SE 8发布以来,开发人员已使用变通办法将Date and Time API与JPA结合使用。 尽管大多数解决方法都非常简单,但是早就应该为更新的Date and Time API添加基本支持。 JPA对日期和时间API的支持包括以下类型:
java.time.LocalDate
java.time.LocalTime
java.time.LocalDateTime
java.time.OffsetTime
java.time.OffsetDateTime
为了更好地理解,我将首先说明在没有JPA 2.2的情况下如何支持Date and Time API。 JPA 2.1只能与较早的日期构造一起使用,例如
java.util.Date
和
java.sql.Timestamp
。 因此,必须使用转换器将存储在数据库中的日期转换为JPA 2.1支持的旧设计,然后将其转换为更新的Date and Time API以在应用程序中使用。 可以进行转换的JPA 2.1中的日期转换器可能类似于清单1。其中的转换器用于在
LocalDate
和
java.util.Date
之间进行转换。
清单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(); } }
JPA 2.2不再需要编写这样的转换器,因为您使用的是受支持的日期时间类型。 对此类类型的支持是内置的,因此您可以简单地在实体类字段中指定受支持的类型,而无需其他代码。 下面的代码段演示了此概念。 请注意,由于类型映射是自动发生的,因此无需在
@Temporal
代码中添加注释。
public class Job implements Serializable { . . . @Column(name = "WORK_DATE") private LocalDate workDate; . . . }
由于受支持的日期时间类型是JPA中的一流对象,因此可以在没有附加仪式的情况下指定它们。 在JPA 2.1
@Temporal
必须在
java.util.Date
和
java.util.Calendar
所有常量字段和属性中描述注释。
值得注意的是,此版本仅支持部分数据时间类型,但是可以轻松生成属性转换器以与其他类型一起使用,例如,将
LocalDateTime
转换为
ZonedDateTime
。 编写此类转换器的最大问题是确定如何在不同类型之间进行最佳转换。 为了使事情变得更加容易,现在可以实现属性转换器。 我将在下面给出一个示例实现。
清单2中的代码显示了如何将时间从
LocalDateTime
转换为
ZonedDateTime
。
清单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()); } }
特别地,此示例非常简单,因为
ZonedDateTime
包含易于转换的方法。 通过调用
toLocalDateTime()
方法进行转换。 可以通过调用
ZonedDateTimeOf()
方法并将
LocalDateTime
值与
ZoneId
一起使用以使用时区来完成逆转换。
嵌入式属性转换器属性转换器是JPA 2.1的一个很好的补充,因为它们允许属性类型更加灵活。 JPA 2.2更新增加了使属性转换器可实现的有用功能。 这意味着您可以将上下文和依赖注入(CDI)资源直接嵌入到属性转换器中。 此修改与Java EE 8规范中的其他CDI增强功能(例如高级JSF转换器)一致,因为它们现在也可以使用CDI注入。
要利用此新功能,只需根据需要将CDI资源嵌入属性转换器中即可。 清单2提供了一个属性转换器的示例,现在我将对其进行分解,解释所有重要的细节。
转换器类必须实现
javax.persistence.AttributeConverter
接口,并传递X和Y值,X值对应于Java对象中的数据类型,而Y值必须对应于数据库列类型。 然后,转换器类应使用
@Converter
注释。 最后,该类必须重写
convertToDatabaseColumn()
和
convertToEntityAttribute()
方法。 这些方法中的每个方法的实现都应将值从特定类型转换回去。
要在每次使用指定的数据类型时自动应用转换器,请添加“自动”,如
@Converter(autoApply=true)
。 要将转换器应用于单个属性,请在属性级别使用@Converter批注,如下所示:
@Convert(converter=LocalDateConverter.java) private LocalDate workDate;
转换器也可以在类级别应用:
@Convert(attributeName="workDate", converter = LocalDateConverter.class) public class Job implements Serializable { . . .
假设我要在保存时加密
Customer
实体的
creditLimit
字段中包含的值。 要实现此过程,必须先对值进行加密,然后再保存,并在从数据库中检索后解密。 这可以由转换器完成,并且使用JPA 2.2,我可以将加密对象嵌入转换器中以获得所需的结果。 清单3提供了一个示例。
清单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)); } ... }
在此代码中,通过
CreditLimitEncryptor
类注入到转换器中,然后使用它来帮助该过程来执行该过程。
流查询结果现在,在使用查询结果时,您可以轻松地充分利用Java SE 8流功能。 线程不仅可以简化代码的读取,编写和维护,而且在某些情况下还可以帮助提高查询性能。 尽管在某些情况下使用
ResultSet
分页可能比流更好地工作,但某些线程实现也有助于避免同时发送过多的数据请求。
为了启用此功能,已将
getResultStream()
方法添加到
Query
和
TypedQuery
。 这个微小的变化使JPA可以简单地返回结果流而不是列表。 因此,如果您使用的是大型
ResultSet
,则有必要比较新线程实现与可滚动
ResultSets
或分页之间的性能。 原因是线程实现一次检索所有记录,将它们存储在列表中,然后返回它们。 可滚动的
ResultSet
和分页技术可以零碎地检索数据,这对于大型数据集可能更好。
持久性提供程序可能会决定
getResultStream()
改进的实现来覆盖新的
getResultStream()
方法。 Hibernate已经包含一个stream()方法,该方法使用可滚动的
ResultSet
来解析记录的结果,而不是完全返回它们。 这使Hibernate可以处理非常大的数据集,并且表现出色。 可以期望其他提供者重写此方法,以提供对JPA有益的类似功能。
除了性能之外,流结果的功能是对JPA的很好补充,它提供了一种方便的方式来处理数据。 我将演示一些可能派上用场的场景,但是可能性本身是无限的。 在这两种情况下,我都查询
Job
实体并返回流。 首先,看下面的代码,在这里我通过调用
Query
getResultStream()
接口方法来针对特定的
Customer
解析
Jobs
流。 然后,我使用该线程输出有关
customer
和
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)); }
可以稍加修改此方法,以便使用
Collectors .toList()
方法返回结果列表,如下所示。
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()); }
在下面显示的以下场景中,我找到了与特定形式的池相关
List
任务
List
。 在这种情况下,我返回所有与作为字符串提交的表单匹配的任务。 与第一个示例类似,首先我返回
Jobs
记录流。 然后,我根据客户群表单过滤记录。 如您所见,生成的代码非常紧凑并且易于阅读。
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()); }
如前所述,记住返回大量数据的情况下的性能非常重要。 在某些情况下,线程在查询数据库中更有用,但在某些情况下,它们可能导致性能下降。 一条好的经验法则是,如果可以在SQL查询中查询数据,则可以这样做。 有时,使用优雅的线程语法的好处不会超过使用标准SQL过滤所能达到的最佳性能。
重复注释支持发布Java SE 8时,可以使用重复的批注,从而允许您在声明中重用批注。 在某些情况下,需要多次在类或字段上使用相同的注释。 例如,给定实体类可能有多个
@SqlResultSetMapping
批注。 在需要支持重新注释的情况下,应使用容器注释。 重复注释不仅减少了将相同注释的集合包装在容器注释中的要求,而且还使代码更易于阅读。
它的工作方式如下:注释类的实现应使用
@Repeatable
元注释进行标记,以指示可以多次使用它。
@Repeatable
元注释采用容器注释类的类型。 例如,
NamedQuery
注释
NamedQuery
现在已标有
@Repeatable(NamedQueries.class)
注释。 在这种情况下,仍然使用容器注释,但是当在声明或类上使用相同的注释时,您不必考虑它,因为
@Repeatable
抽象了此细节。
我们举一个例子。 如果要向JPA 2.1中的实体类添加多个
@NamedQuery
批注,则需要将它们封装在
@NamedQueries
批注内,如清单4所示。
清单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 { . . . }
但是,在JPA 2.2中,一切都不同。 由于
@NamedQuery
是重复的注释,因此可以在实体类中多次指定它,如清单5所示。
清单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 { . . . }
重复注释列表:
@AssociationOverride
@AttributeOverride
@Convert
@JoinColumn
@MapKeyJoinColumn
@NamedEntityGraphy
@NamedNativeQuery
@NamedQuery
@NamedStoredProcedureQuery
@PersistenceContext
@PersistenceUnit
@PrimaryKeyJoinColumn
@SecondaryTable
@SqlResultSetMapping
结论JPA 2.2版本进行了一些更改,但是其中包含的改进非常重要。 最后,JPA与Java SE 8保持一致,从而使开发人员可以使用诸如Date and Time API,流查询结果和重复注释之类的功能。 此版本还通过添加将CDI资源嵌入属性转换器的功能来提高CDI一致性。 JPA 2.2现在可用,并且是Java EE 8的一部分,我想您会喜欢使用它。
结束
与往常一样,我们正在等待问题和评论。