本文要点
JPA 2.2 在去年夏天发布, 交付了一些备受期待的功能改善:
对 Java 8 特性的更好支持, 例如 Date 和 Time API
新增对 AttributeConverter 的 CDI 支持
一些注解变得 @Repeatable
你现在可以获取 java.util.Stream 形式的查询结果
准备针对 Java 9 的规范
去年夏天发布, JPA 2.2 是 Java EE 8 中比较稳健的规范之一, 可能是因为 JPA 2.1 已经是一个非常成熟的规范, 提供了现代应用程序所需的大部分功能
尽管如此, JPA 2.2 带来了一些显著的变更首先, 它引入了 2 个小规模的变更:
增加对 AttributeConverter 的 CDI 支持
准备针对 Java 9 的规范
从 C# 看开放对编程语言发展的影响 Netflix 的工程文化: 是什么在激励着我们? 百度贴吧之父: 产品经理的发现和成长 Apache Kafka 的过去, 现在, 和未来
其次, 也更重要的是, JPA 2.2 规范支持 Java 8 的一些特性:
JPA 2.x 当前版本支持 Date 和 Time API 的一些类(并没有支持所有的类, 下面会详细介绍)
一些注解变得可重复
你现在可以获取 java.util.Stream 形式的的查询结果
我们稍后会介绍更多关于这些内容的细节
一些背景信息: Java EE 7(包含 JPA 2.1)在 Java 8 之前发布, 因此不能对 Java 8 引入的任何数据类型和编程概念提供支持因此, JPA 2.2 及其对 Java 8 的支持备受期待
在你的项目中加入 JPA 2.2
像所有 Java EE 规范一样, JPA 只定义 API, 允许开发者在不同的实现中选择适合自己的
下面的 Maven 坐标会增加 JPA 2.2 API 到你的项目中
- <dependency>
- <groupId>javax.persistence</groupId>
- <artifactId>javax.persistence-api</artifactId>
- <version>2.2</version>
- </dependency>
尽管 JPA 2.1 有一些不同的实现, 但是只有 EclipseLink 提供了完整的 JPA 2.2 实现你可以使用下面的 Maven 坐标将它增加到你的项目中
- <dependency>
- <groupId>org.eclipse.persistence</groupId>
- <artifactId>eclipselink</artifactId>
- <version>2.7.0</version>
- </dependency>
在 AttributeConverter 中的 CDI 注入
AttributeConverter 为读取和写入自定义数据类型提供了一种易用的标准化方法它通常用在 JPA 2.1 中, 来增加对 LocalDate 和 LocalDateTime 的支持, 或者来实现一个自定义的枚举映射
实现一个 converter 非常简单; 只需要实现 AttributeConverter 接口, 为这个类添加 @Converter 注解, 然后实现 2 个接口方法:
convertToDatabaseColumn 将 Java 类转变为它的数据库表现形式
convertToEntityAttribute 相反地, 将数据库列转变回 Java 对象
实现一个自定义枚举映射
下面的代码片段展示了一个简单的例子:
- @Converter(autoApply = true)
- public class ContactInfoTypeConverter implements
- AttributeConverter<ContactInfoType, String> {
- @Override
- public String convertToDatabaseColumn(ContactInfoType type) {
- switch (type) {
- case PRIVATE:
- return "P";
- case BUSINESS:
- return "B";
- default:
- throw new IllegalArgumentException("ContactInfoType ["
- + type.name() + "] not supported.");
- }
- }
- @Override
- public ContactInfoType convertToEntityAttribute(String dbType) {
- switch (dbType) {
- case "P":
- return ContactInfoType.PRIVATE;
- case "B":
- return ContactInfoType.BUSINESS;
- default:
- throw new IllegalArgumentException("ContactInfoType ["
- + dbType + "] not supported.");
- }
- }
- }
AttributeConverter 将我们的 ContactInfoType 枚举转变为一个 String 现在你可能会疑惑, 为什么你要实现自己的转换器, 因为 JPA 已经提供了 2 种可选方案来持久化一个枚举: 1)String 表现形式; 2)使用特定枚举值的序号值但是如果你想改变枚举的时候, 就会发现这两种方案都各有它们的缺点当将一个枚举持久化为一个 String 时, 你如果想要修改任何枚举值的名字, 就需要更改你的数据库另一方面, 当将枚举持久化为序号值时, 因为序号值代表了枚举值在枚举定义中的位置, 因此, 如果你更改了枚举值的顺序, 或者你增加了新的枚举值并且不是放在最后, 或者你删掉了除最后一个枚举值之外的其它任何枚举值, 你就又需要更新你的持久化数据了
因此, 在上述任何一种策略中, 如果你使用 JPA 的标准映射, 你的枚举的大部分变更都会需要数据库更新否则, 你的持久层框架会无法映射已存在的值, 或者会把它们映射成错误的枚举值
你可以通过使用 AttributeConverter 实现一个自定义的映射来避免上述缺点, 这样你就可以控制映射了, 并且在需要重构枚举时, 避免任何对已存在的映射的变更
使用 CDI 注入
正如你在先前的代码片段中看到的那样, 我用 AttributeConverter 实现了映射如果你不需要复用这个转换实现, 那么它是一个不错的方案但是, 如果你想要复用它, JPA 2.2 允许你选择使用 CDI 注入来将你的转换实现注入到你的 AttributeConverter 中
- @Converter(autoApply = true)
- public class ContactInfoTypeCdiConverter implements
- AttributeConverter<ContactInfoType, String> {
- // Conversion implementation class:
- @Inject
- ContactInfoTypeHelper conversionHelper;
- @Override
- public String convertToDatabaseColumn(ContactInfoType type) {
- return conversionHelper.convertToString(type);
- }
- @Override
- public ContactInfoType convertToEntityAttribute(String dbType) {
- return conversionHelper.convertToContactInfoType(dbType);
- }
- }
使用 Date 和 Time 类作为实体属性
Java 8 的 Date 和 Time API 备受期待, 许多开发者想要使用这些新类作为实体属性不幸的是, JPA 2.1 在 Java 8 之前发布, 因此不支持这些类
在 JPA 2.2 发布之前, 你有两种方法来持久化 dates 和 times:
例如, 实现一个 AttributeConverter, 将一个 LocalDate 映射为一个 java.sql.DateSpring Data 团队和许多其他开发者都是这样做的
使用持久层框架的一些特性, 例如 Hibernate 对 Date 和 Time API 的支持
JPA 2.2 规范现在支持 Date 和 Time API 的一些类作为基础类型, 因此你不再需要提供任何额外的映射注解, 例如用于 java.util.Date 的 @Temporal 注解相比于过去的 java.util.Date,Date 和 Time API 中的类将简单的日期和带时间的日期区别开来因此, 持久层框架拥有将这些类持久化成相应的 SQL 数据类型而需要的所有信息
- @Entity
- public class DateAndTimeEntity {
- @Id
- @GeneratedValue(strategy = GenerationType.SEQUENCE)
- private Long id;
- private LocalDate date;
- private LocalDateTime dateTime;
- public LocalDate getDate() {
- return date;
- }
- public void setDate(LocalDate date) {
- this.date = date;
- }
- public LocalDateTime getDateTime() {
- return dateTime;
- }
- public void setDateTime(LocalDateTime dateTime) {
- this.dateTime = dateTime;
- }
- public Long getId() {
- return id;
- }
- }
尽管 JPA 2.2 未支持全部 Date 和 Time API, 但是一些实现, 例如 Hibernate, 会提供更多类的专门支持, 例如 java.time.Duration 如果可以在下一个发布版本中加入这些功能会非常棒, 但是因为 Oracle 正将所有的规范移交给 Eclipse 基金会, 因此我们需要拭目以待
Java Type | JDBC Type |
java.time.LocalDate | DATE |
java.time.LocalTime | TIME |
java.time.LocalDateTime | TIMESTAMP |
java.time.OffsetTime | TIME_WITH_TIMEZONE |
java.time.OffsetDateTime | TIMESTAMP_WITH_TIMEZONE |
一些 JPA 注解变得可重复
可重复的注解允许你用相同的注解多次注解类方法或属性那意味着你不再需要用像 @NamedQueries 来包裹多个 @NamedQuery 那样来使用注解对比过去的方式:
- @NamedQueries({
- @NamedQuery(name = EntityWithNamedQueries.findByName,
- query = "SELECT e FROM EntityWithNamedQueries e WHERE e.name = :name"),
- @NamedQuery(name = EntityWithNamedQueries.findByContent,
- query="SELECT e FROM EntityWithNamedQueries e WHERE e.content = :content")
- })
- public class EntityWithNamedQueries { }
对于使用多个注解的实体, 使用新的注解方式提升了代码的可读性
- @NamedQuery(name = EntityWithMultipleNamedQuery.findByName,
- query = "SELECT e FROM EntityWithNamedQueries e WHERE e.name = :name")
- @NamedQuery(name = EntityWithMultipleNamedQuery.findByContent,
- query = "SELECT e FROM EntityWithNamedQueries e WHERE e.content = :content")
- public class EntityWithMultipleNamedQuery { }
在 JPA 2.2 中, 可以被包裹进一个容器注解中的所有注解都变得可重复这些注解有:
- AssociationOverride
- AttributeOverride
- JoinColumn
- MapKeyJoinColumn
- NamedEntityGraph
- NamedNativeQuery
- NamedQuery
- NamedStoredProcedureQuery
- PersistenceContext
- PersistenceUnit
- PrimaryKeyJoinColumn
- SecondaryTable
- SqlResultSetMapping
- SequenceGenerator
- TableGenerator
获得 Stream 形式的查询结果
Stream API 是 Java 8 引入的另外一个流行的特性, 而且许多开发者想要使用它来处理他们的查询结果
JPA 2.1 支持的唯一方式是调用 Query 接口的 getResultList 方法, 来获取 List 形式的查询结果然后, 你可以调用 List 接口的 stream 方法来获取它的 Stream 形式:
- TypedQuery<DateAndTimeEntity> q = em.createQuery(
- "SELECT e FROM DateAndTimeEntity e", DateAndTimeEntity.class);
- Stream<DateAndTimeEntity> s = q.getResultList().stream();
JPA 2.2 引入了一种更直接的方式来获取 Stream 形式的查询结果你现在可以简单地调用 Query 接口的 getResultStream 方法来获取 Stream 查询结果
- TypedQuery<DateAndTimeEntity> q = em.createQuery(
- "SELECT e FROM DateAndTimeEntity e", DateAndTimeEntity.class);
- Stream<DateAndTimeEntity> s = q.getResultStream();
过去的方案就很有效, 你可能会疑问为什么 JPA 2.2 为此引入一种新的方法之所以这么做, 是有 2 个原因:
新的 getResultStream 方法比之前的方案更直接
它使得持久层框架可以不同地实现这两种方法
第 2 个原因尤为重要; 当你调用 getResultStream 方法, 你的持久层框架必须从数据中获取完整的结果集, 然后将它存储在本地内存中无论是你想要将结果作为 List 处理, 或者是将它发送给客户端应用程序, 都没有问题但是如果你在使用 Stream, 这可能不是一个可选的方案当你处理 Stream 的时候, 你遍历它的元素并且一个接一个处理这些元素, 因此, 你不需要在开始处理它们之前获取所有的元素当你需要的时候再小批次的加载它们, 稍后再释放它们, 会让你更高效地使用资源 JDBC 为这些用例提供了动态化的结果集 getResultStream 方法使得持久层框架可以使用这种方案, 而不是一次获取所有记录
但是, 在你在项目中使用这个方法之前, 你应该了解 2 件事:
首先, JPA 规范提供的 Query 接口包含了一个默认方法这个方法直接调用 getResultList 方法, 然后将 List 结果转变为 Stream 因此, 如果持久层框架没有重写这个方法, 它只是提供了一点易用性的改善但是, 所有常用的 JPA 实现应该都会重写这个方法例如, Hibernate 已经在它的 Query 接口上提供了一个实现了相同功能的结果方法如果他们没有复用这个实现来重写 JPA 的 getResultStream 方法, 我才会感到惊讶
其次, 你应该始终牢记, 有些事情用 SQL 查询做比使用 Stream API 更高效从 Stream 中过滤一些元素, 在某个节点取消处理过程, 或者改变元素的顺序, 可能比较有吸引力但是, 用 SQL 语句来做这些事情是一种更好的实践数据库对这些类型的操作做了高度优化, 而且比你用 Java 实现的任何方法都更高效最重要的是, 你通常很可能会减少需要从数据库传送到应用程序的数据量, 将它们映射成实体, 然后存储在内存中因此, 始终确保在查询中执行尽可能多的操作, 并尽可能充分地利用 SQL 的功能
只要你遵循这些简单的规则, 新的 getResultStream 方法提供了一种获取和处理 Stream 形式的查询结果的不错方式
总结
JPA 2.2 是一次维护性的小发布, 并没有引入太多变更但是, 它确实交付了一些经常被请求的改善, 特别是对 Java 8 特性的支持, 例如对 Date 和 Time API 的支持, 以及获取 Stream 形式的查询结果
总之, JPA 2.2 规范提供了一组重点关注开发者常用需求的稳定功能集这对于当前 JPA 和所有其它 Java EE 规范向 Eclipse 基金会的迁移来说, 是一个不错的情况
来源: http://www.infoq.com/cn/articles/JPA-2.2-Brings-Highly-Anticipated-Changes