1.0jpa 2.0
重要要点
- JPA 2.2于夏天发布,提供了一些经常要求的增强功能:
- 与Java 8功能(如日期和时间API)更好地结合
- 向AttributeConverter添加了CDI支持
- 多个注释变为@Repeatable
- 您现在可以将查询结果作为java.util.Stream检索
- 准备了Java 9规范。
去年夏天发布的JPA 2.2是Java EE 8中较为适度的规范之一,也许是因为JPA 2.1已经是非常成熟的规范,提供了现代应用程序所需的大多数功能。
但是,JPA 2.2确实带来了一些显着的变化。 首先,它引入了两个小规模的变化:
- 向AttributeConverter添加了CDI支持
- 准备了Java 9规范。
其次(更重要的是)它使规范与Java 8保持一致:
- JPA 2.now现在支持Date和Time API的某些(但并非全部,也会看到)类
- 几个注解成了[R epeatable ,和
- 您现在可以将查询结果检索为java.util。 流 。
稍后我们将详细讨论所有这些。
一些背景; 包含JPA 2.1的Java EE 7在Java 8之前发布,因此无法为Java 8中引入的数据类型和编程概念提供任何支持。因此,人们对JPA 2.2及其对Java 8的支持充满期待。
将JPA 2.2添加到您的项目
像所有Java EE规范一样,JPA仅定义API,允许开发人员在不同的实现之间进行选择。
以下Maven坐标将JPA 2.2 API jar添加到您的项目中。
<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的支持,或实现自定义枚举映射 。
转换器的实现非常简单。 只需实现AttributeConverter接口,使用@Converter批注对类进行批注,并实现两种接口方法:
- convertToDatabaseColumn-将Java类转换为其数据库表示形式
- convertToEntityAttribute-相反,将列转换回对象。
实现自定义枚举映射
您可以在以下代码段中看到一个简单的示例。
@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已经支持两种保留枚举的选项,为什么还要实现自己的转换:1)字符串表示形式或2)特定枚举值的序数值。 如果您需要更改枚举,则这两种选择都有其缺点。 如果将枚举保留为字符串,那么如果决定更改任何枚举值的名称,则需要更新数据库。 另一方面,如果您坚持序数值(表示该值在枚举定义中的位置),则如果您更改了枚举值的顺序,或者您在除最后一个条目之外的任何地方添加了新值,或者您删除了除最后一个枚举值以外的任何枚举值,您将再次需要更新持久数据。
因此,在这两种策略中,如果使用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);
}
}
日期和时间类作为实体属性
Java 8的日期和时间API早已引起人们的期待,许多开发人员希望将这些新类用作实体属性。 不幸的是,JPA 2.1是在Java 8之前发布的,因此不支持这些类。
在发布JPA 2.2之前,您有两个用于保留日期和时间的选项:
- 实现AttributeConverter,例如,它将LocalDate映射到java.sql.Date 。 Spring Data团队和许多其他开发人员就是这样做的。
- 使用持久性提供程序的专有功能,例如Hibernate对Date and Time API 的支持 。
使用JPA 2.2,该规范现在支持将Date and Time API的某些类作为基本类型,因此您不再需要提供任何其他映射注释,例如与java.util.Date一起使用的@Temporal注释 。 与旧的java.util.Date相比,Date and 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 and Time API,但是某些实现(例如Hibernate)为更多类(例如java.time.Duration)提供专有支持。 如果可以在下一个版本中将它们添加到规范中,那将是非常不错的,但是由于Oracle将所有规范移交给Eclipse基础,因此我们需要拭目以待。
Java类型 | JDBC类型 |
java.time.LocalDate | 日期 |
java.time.LocalTime | 时间 |
java.time.LocalDateTime | 时间戳 |
java.time.OffsetTime | TIME_WITH_TIMEZONE |
java.time.OffsetDateTime | TIMESTAMP_WITH_TIMEZONE |
一些JPA注释变为@Repeatable
可重复的批注允许您使用相同的批注多次注释类,方法或属性。 这意味着您不再需要使用@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
- 连接列
- MapKeyJoinColumn
- 命名实体图
- NamedNativeQuery
- 命名查询
- 命名为StoredProcedureQuery
- 持久性上下文
- 持久性单元
- PrimaryKeyJoinColumn
- 二级表
- SqlResultSetMapping
- 序列发生器
- 表格生成器
以流的形式获取查询结果
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引入了新方法。 有两个原因:
- 新的
getResultStream
方法比以前的方法更直接。 - 它使持久性提供程序能够以不同方式实现这两种方法。
第二个原因更重要。 当调用getResultStream
方法时,持久性提供程序必须从数据库中获取完整的结果集并将其存储在本地内存中。 如果您想将结果作为List
或者将其发送到客户端应用程序,那可能没问题。 但是,如果使用Stream
则可能不是最佳方法。 在处理Stream
,您将遍历其元素并逐一处理它们,因此,您无需在开始处理它们之前就获取所有元素。 在需要时分批加载它们,然后再释放它们,可以使您更有效地使用资源。 JDBC为这些用例提供了可滚动的结果集。 getResultStream
方法使持久性提供程序可以使用该方法,而不是一次获取所有记录。
但是在您的项目中使用此方法之前,应注意两件事:
首先,JPA规范提供的Query
接口包含一个默认方法,该方法仅调用getResultList
方法,然后将List
转换为Stream
。 因此,如果持久性提供程序没有覆盖此方法,则它只会提供一个小的可用性增强。 但是可以预料,所有常用的JPA实现都会覆盖它。 例如,Hibernate已经在其专有的Query
接口上提供了实现相同功能的result方法 。 如果他们不重用该实现来覆盖JPA的getResultStream方法,我将感到惊讶。
其次,您应始终牢记,在SQL查询中执行某些操作比使用Stream API更有效。 可能很想从Stream
过滤元素,以在某个时候取消处理或更改元素的顺序。 但是,最好在您SQL语句中执行此操作。 数据库针对此类操作进行了高度优化,并且比您在Java中实现的任何例程更高效。 最重要的是,您很有可能会减少从数据库传输到应用程序,映射到实体并存储在内存中所需的数据量。 因此,始终确保在查询中执行尽可能多的操作,并充分利用SQL的功能。
只要遵循这些简单规则,新的getResultStream
方法就可以将您的查询结果作为Stream
检索和处理。
摘要
JPA 2.2是一个小的维护版本,并没有进行太多更改。 但是它确实提供了一些经常要求的增强功能,特别是在与Java 8功能更好地保持一致方面,例如对Date and Time API的支持以及作为Stream
检索查询结果。
总体而言,该规范提供了一个稳定的功能集,可满足应用程序开发人员的常见需求。 对于当前将JPA和所有其他Java EE规范迁移到Eclipse Foundation而言,这是一个好情况。
![](https://i-blog.csdnimg.cn/blog_migrate/2ed64d42a4b8d7e043fa5762d7211bb1.png)
1.0jpa 2.0