原标题:Spring认证|Spring Data JPA 参考文档四(内容来源:Spring中国教育管理中心)
4.8.3. 存储库填充器
如果您使用 Spring JDBC 模块,您可能熟悉DataSource使用 SQL 脚本填充 a 的支持。存储库级别上也有类似的抽象,尽管它不使用 SQL 作为数据定义语言,因为它必须与存储无关。因此,填充器支持 XML(通过 Spring 的 OXM 抽象)和 JSON(通过 Jackson)来定义用于填充存储库的数据。
假设您有一个包含data.json以下内容的文件:
示例 51. JSON 中定义的数据
[ { "_class" : "com.acme.Person",
"firstname" : "Dave",
"lastname" : "Matthews" },
{ "_class" : "com.acme.Person",
"firstname" : "Carter",
"lastname" : "Beauford" } ]
您可以使用 Spring Data Commons 中提供的存储库命名空间的 populator 元素来填充存储库。要将前面的数据填充到您的PersonRepository,请声明一个类似于以下内容的填充器:
示例 52. 声明一个 Jackson 存储库填充器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:repository="http://www.springframework.org/schema/data/repository"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/repository
https://www.springframework.org/schema/data/repository/spring-repository.xsd">
<repository:jackson2-populator locations="classpath:data.json" />
</beans>
前面的声明导致data.json文件被 Jackson 读取和反序列化ObjectMapper。
JSON 对象解组的类型是通过检查_classJSON 文档的属性来确定的。基础架构最终会选择合适的存储库来处理反序列化的对象。
要改为使用 XML 定义应填充存储库的数据,您可以使用该unmarshaller-populator元素。您将其配置为使用 Spring OXM 中可用的 XML marshaller 选项之一。有关详细信息,请参阅Spring 参考文档。以下示例显示了如何使用 JAXB 解组存储库填充器:
示例 53. 声明解组存储库填充器(使用 JAXB)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:repository="http://www.springframework.org/schema/data/repository"
xmlns:oxm="http://www.springframework.org/schema/oxm"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/repository
https://www.springframework.org/schema/data/repository/spring-repository.xsd
http://www.springframework.org/schema/oxm
https://www.springframework.org/schema/oxm/spring-oxm.xsd">
<repository:unmarshaller-populator locations="classpath:data.json"
unmarshaller-ref="unmarshaller" />
<oxm:jaxb2-marshaller contextPath="com.acme" />
</beans>
5. 参考文档
5.1. JPA 存储库
本章指出了 JPA 存储库支持的特点。这建立在“使用 Spring 数据存储库”中解释的核心存储库支持之上。确保您对那里解释的基本概念有充分的理解。
5.1.1. 介绍
本节描述了通过以下任一方式配置 Spring Data JPA 的基础知识:
“ Spring 命名空间”(XML 配置)
“基于注解的配置”(Java配置)
Spring 命名空间
Spring Data 的 JPA 模块包含一个允许定义存储库 bean 的自定义命名空间。它还包含 JPA 特有的某些功能和元素属性。通常,可以使用repositories元素设置 JPA 存储库,如下例所示:
示例 54. 使用命名空间设置 JPA 存储库
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa
https://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<jpa:repositories base-package="com.acme.repositories" />
</beans>
使用该repositories元素查找 Spring Data 存储库,如“创建存储库实例”中所述。除此之外,它还为所有用 注释的 bean 激活持久性异常转换@Repository,让 JPA 持久性提供程序抛出的异常转换为 Spring 的DataAccessException层次结构。
自定义命名空间属性
除了repositories元素的默认属性之外,JPA 命名空间还提供其他属性,让您可以更详细地控制存储库的设置:
如果未定义显式, Spring Data JPA 需要一个
PlatformTransactionManager名为 bean 的 bean 。 transactionManagertransaction-manager-ref
基于注解的配置
Spring Data JPA 存储库支持不仅可以通过 XML 命名空间激活,还可以通过 JavaConfig 使用注释激活,如以下示例所示:
示例 55. 使用 JavaConfig 的 Spring Data JPA 存储库
@Configuration
@EnableJpaRepositories
@EnableTransactionManagement
class ApplicationConfig {
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
return builder.setType(EmbeddedDatabaseType.HSQL).build();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.acme.domain");
factory.setDataSource(dataSource());
return factory;
}
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
}
您必须创建
LocalContainerEntityManagerFactoryBean而不是EntityManagerFactory直接创建,因为前者除了创建EntityManagerFactory.
前述配置类,通过使用设置了一个嵌入式HSQL数据库EmbeddedDatabaseBuilder的API spring-jdbc。然后 Spring Data 设置EntityManagerFactory并使用 Hibernate 作为示例持久性提供程序。此处声明的最后一个基础结构组件是JpaTransactionManager. 最后,该示例通过使用@EnableJpaRepositories注释激活 Spring Data JPA 存储库,注释本质上带有与 XML 命名空间相同的属性。如果没有配置基础包,它使用配置类所在的包。
引导模式
默认情况下,Spring Data JPA 存储库是默认的 Spring bean。它们是单例范围的并且急切地初始化。在启动期间,他们已经与 JPA 交互以EntityManager进行验证和元数据分析。Spring Framework 支持EntityManagerFactory在后台线程中初始化 JPA ,因为该进程通常会在 Spring 应用程序中占用大量启动时间。为了有效地利用后台初始化,我们需要确保 JPA 存储库尽可能晚地初始化。
从 Spring Data JPA 2.1 开始,您现在可以配置一个BootstrapMode(通过@EnableJpaRepositories注释或 XML 命名空间)采用以下值:
DEFAULT(默认)— 除非明确用@Lazy. 仅当没有客户端 bean 需要存储库的实例时,延迟化才有效,因为这将需要存储库 bean 的初始化。
LAZY — 隐式声明所有存储库 bean 为惰性,并导致创建惰性初始化代理以将其注入客户端 bean。这意味着,如果客户端 bean 只是将实例存储在一个字段中并且在初始化期间没有使用存储库,那么存储库将不会被实例化。存储库实例将在第一次与存储库交互时进行初始化和验证。
DEFERRED — 与 基本相同的操作模式LAZY,但触发存储库初始化以响应 ,ContextRefreshedEvent以便在应用程序完全启动之前验证存储库。
建议
如果您不使用具有默认引导模式的异步 JPA 引导棒。
如果您异步引导 JPA,这DEFERRED是一个合理的默认值,因为它将确保 Spring Data JPA 引导程序仅在EntityManagerFactory设置本身比初始化所有其他应用程序组件花费的时间更长的情况下等待设置。尽管如此,它仍可确保在应用程序发出信号之前正确初始化和验证存储库。
LAZY是测试场景和本地开发的不错选择。一旦您非常确定存储库可以正确引导,或者在您测试应用程序的其他部分的情况下,对所有存储库运行验证可能会不必要地增加启动时间。这同样适用于本地开发,其中您只能访问可能需要初始化单个存储库的应用程序部分。
5.1.2. 持久实体
本节介绍如何使用 Spring Data JPA 持久化(保存)实体。
保存实体
可以使用该CrudRepository.save(…)方法执行保存实体。它通过使用底层 JPA 来持久化或合并给定的实体EntityManager。如果实体尚未持久化,Spring Data JPA 会通过调用该entityManager.persist(…)方法来保存实体。否则,它调用该entityManager.merge(…)方法。
实体状态检测策略
Spring Data JPA 提供以下策略来检测实体是否为新实体:
Version-Property 和 Id-Property 检查(默认):默认情况下,Spring Data JPA 首先检查是否存在非原始类型的 Version-property。如果存在,并且该属性的值为 ,则该实体被视为新实体null。如果没有这样的 Version-property Spring Data JPA 检查给定实体的 identifier 属性。如果标识符属性是null,则假定实体是新的。否则,它被认为不是新的。
实现Persistable:如果实体实现了Persistable,Spring Data JPA 将新的检测委托给isNew(…)实体的方法。有关详细信息,请参阅JavaDoc。
实现EntityInformation:您可以通过创建子类并相应地覆盖方法来自定义实现中EntityInformation使用的抽象。然后,您必须将 的自定义实现注册为 Spring bean。请注意,这应该很少需要。有关详细信息,请参阅JavaDoc。
SimpleJpaRepositoryJpaRepositoryFactorygetEntityInformation(…)JpaRepositoryFactory
对于使用手动分配的标识符且没有版本属性的实体,选项 1 不是一个选项,因为标识符将始终为非null。在这种情况下,一个常见的模式是使用一个公共基类,它带有一个默认的瞬态标志来指示一个新实例,并使用 JPA 生命周期回调在持久化操作中翻转该标志:
示例 56. 具有手动分配标识符的实体的基类
@MappedSuperclass
public abstract class AbstractEntity<ID> implements Persistable<ID> {
@Transient
private boolean isNew = true;
@Override
public boolean isNew() {
return isNew;
}
@PrePersist
@PostLoad
void markNotNew() {
this.isNew = false;
}
// More code…
}
声明一个标志来保持新状态。瞬态,以便它不会持久化到数据库。
返回实现中的标志,Persistable.isNew()以便 Spring Data 存储库知道是否调用EntityManager.persist()或….merge()。
声明一个使用 JPA 实体回调的方法,以便save(…)在持久性提供程序调用存储库或创建实例后切换标志以指示现有实体。
5.1.3. 查询方法
本节介绍使用 Spring Data JPA 创建查询的各种方法。
查询查找策略
JPA 模块支持将查询手动定义为字符串或从方法名称派生。
与谓词派生查询IsStartingWith,StartingWith,StartsWith,IsEndingWith,EndingWith,EndsWith, IsNotContaining,NotContaining,NotContains,IsContaining,Containing,Contains这些查询的各个参数将得到消毒。这意味着如果参数实际上包含被识别LIKE为通配符的字符,这些字符将被转义,因此它们仅作为文字匹配。所使用的转义字符可以通过设置来配置escapeCharacter所述的@EnableJpaRepositories注释。与使用 SpEL 表达式进行比较。
声明的查询
虽然从方法名获取查询是很方便的,但人们可能会面临这样的情况,即方法名解析器不支持想要使用的关键字,或者方法名会变得不必要地丑陋。因此,您可以通过命名约定使用 JPA 命名查询(有关详细信息,请参阅使用 JPA 命名查询),或者使用注释您的查询方法@Query(有关详细信息,请参阅使用@Query)。
查询创建
通常,JPA 的查询创建机制按照“查询方法”中的描述工作。以下示例显示了 JPA 查询方法转换为的内容:
示例 57. 根据方法名称创建查询
公共接口 UserRepository extends Repository<User, Long> {
List<User> findByEmailAddressAndLastname(String emailAddress, String lastname);
}
我们使用 JPA 标准 API 从中创建一个查询,但本质上,这会转换为以下查询:select u from User u where u.emailAddress = ?1 and u.lastname = ?2. Spring Data JPA 执行属性检查并遍历嵌套属性,如“属性表达式”中所述。
下表描述了 JPA 支持的关键字以及包含该关键字的方法转换为什么:
表 3. 方法名称中支持的关键字
In并且NotIn还可以将任何子类Collection作为参数以及数组或可变参数。对于相同逻辑运算符的其他语法版本,请检查“存储库查询关键字”。
使用 JPA 命名查询
这些示例使用<named-query />元素和@NamedQuery注释。这些配置元素的查询必须在 JPA 查询语言中定义。当然,您也可以使用<named-native-query />or @NamedNativeQuery。通过失去数据库平台独立性,这些元素使您可以在本机 SQL 中定义查询。
XML 命名查询定义
要使用 XML 配置,请将必要的<named-query />元素添加到orm.xml位于META-INF类路径文件夹中的JPA 配置文件中。通过使用一些定义的命名约定来启用命名查询的自动调用。有关更多详细信息,请参见下文。
示例 58. XML 命名查询配置
<named-query name="User.findByLastname">
<query>select u from User u where u.lastname = ?1</query>
</named-query>
查询有一个特殊的名称,用于在运行时解析它。
基于注解的配置
基于注解的配置的优点是不需要编辑另一个配置文件,减少维护工作。您需要为每个新的查询声明重新编译域类,从而为获得这种好处付出代价。
示例 59.基于注解的命名查询配置
@Entity
@NamedQuery(name = "User.findByEmailAddress",
query = "select u from User u where u.emailAddress = ?1")
public class User {
}
声明接口
要允许这些命名查询,请指定UserRepository如下:
示例 60. UserRepository 中的查询方法声明
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByLastname(String lastname);
User findByEmailAddress(String emailAddress);
}
Spring Data 尝试将对这些方法的调用解析为命名查询,从配置的域类的简单名称开始,然后是用点分隔的方法名称。因此,前面的示例将使用之前定义的命名查询,而不是尝试从方法名称创建查询。
使用 @Query
使用命名查询来声明实体查询是一种有效的方法,并且适用于少量查询。由于查询本身与运行它们的 Java 方法相关联,因此您实际上可以通过使用 Spring Data JPA@Query注释直接绑定它们,而不是将它们注释到域类。这将域类从持久性特定信息中解放出来,并将查询共同定位到存储库接口。
注释到查询方法的查询优先于使用中定义的@NamedQuery查询或在 中声明的命名查询orm.xml。
以下示例显示了使用@Query注释创建的查询:
示例 61. 在查询方法中声明查询使用 @Query
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);
}
使用高级LIKE表达式
使用创建的手动定义查询的查询运行机制@Query允许LIKE在查询定义中定义高级表达式,如以下示例所示:
示例 62. like@Query 中的高级表达式
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.firstname like %?1")
List<User> findByFirstnameEndsWith(String firstname);
}
在前面的示例中,LIKE分隔符 ( %) 被识别,并将查询转换为有效的 JPQL 查询(删除%)。运行查询时,传递给方法调用的参数将使用先前识别的LIKE模式进行扩充。
本机查询
该@Query注释允许通过将nativeQuery标志设置为 true来运行本机查询,如以下示例所示:
示例 63. 使用 @Query 在查询方法中声明本机查询
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1", nativeQuery = true)
User findByEmailAddress(String emailAddress);
}
Spring Data JPA 目前不支持对原生查询进行动态排序,因为它必须操作声明的实际查询,而对于原生 SQL,它不能可靠地做到这一点。但是,您可以通过自己指定计数查询来使用本机查询进行分页,如下例所示:
示例 64.在查询方法中声明用于分页的原生计数查询,使用 @Query
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "SELECT * FROM USERS WHERE LASTNAME = ?1",
countQuery = "SELECT count(*) FROM USERS WHERE LASTNAME = ?1",
nativeQuery = true)
Page<User> findByLastname(String lastname, Pageable pageable);
}
通过将.count后缀添加到查询的副本,类似的方法也适用于命名的本机查询。不过,您可能需要为计数查询注册一个结果集映射。
使用排序
排序可以通过提供 aPageRequest或Sort直接使用来完成。Order实例中实际使用的属性Sort需要匹配您的域模型,这意味着它们需要解析为查询中使用的属性或别名。JPQL 将其定义为状态字段路径表达式。
使用任何不可引用的路径表达式会导致Exception.
但是,Sort与 with 一起使用@Query可以让您潜入Order包含ORDER BY子句中函数的非路径检查实例。这是可能的,因为Order附加到给定的查询字符串。默认情况下,Spring Data JPA 拒绝任何Order包含函数调用的实例,但您可以使用JpaSort.unsafe添加潜在的不安全排序。
以下示例使用Sortand JpaSort,包括一个不安全的选项 on JpaSort:
示例 65. 使用Sort和JpaSort
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.lastname like ?1%")
List<User> findByAndSort(String lastname, Sort sort);
@Query("select u.id, LENGTH(u.firstname) as fn_len from User u where u.lastname like ?1%")
List<Object[]> findByAsArrayAndSort(String lastname, Sort sort);
}
repo.findByAndSort("lannister", Sort.by("firstname"));
repo.findByAndSort("stark", Sort.by("LENGTH(firstname)"));
repo.findByAndSort("targaryen", JpaSort.unsafe("LENGTH(firstname)"));
repo.findByAsArrayAndSort("bolton", Sort.by("fn_len"));
Sort指向域模型中属性的有效表达式。
无效的Sort包含函数调用。抛出异常。
有效Sort包含显式不安全 Order。
Sort指向别名函数的有效表达式。
使用命名参数
默认情况下,Spring Data JPA 使用基于位置的参数绑定,如前面所有示例中所述。这使得在重构参数位置时查询方法有点容易出错。为了解决这个问题,您可以使用@Param注解给方法参数一个具体的名称并在查询中绑定名称,如下例所示:
示例 66.使用命名参数
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.firstname = :firstname or u.lastname = :lastname")
User findByLastnameOrFirstname(@Param("lastname") String lastname,
@Param("firstname") String firstname);
}
方法参数根据它们在定义的查询中的顺序进行切换。
从 version 4 开始,Spring 完全支持 Java 8 的基于-parameters编译器标志的参数名称发现。通过在构建中使用此标志作为调试信息的替代方法,您可以省略@Param命名参数的注释。