Spring Data JPA - 参考文档-3

参考文档

4. JPA存储库

本章将指出JPA对知识库的支持。这建立在使用Spring Data Repositories中解释的核心存储库支持上所以要确保你对这里解释的基本概念有一个很好的理解。

4.1。介绍

4.1.1。Spring命名空间

Spring Data的JPA模块包含一个允许定义存储库bean的自定义名称空间。它还包含JPA特有的某些功能和元素属性。通常JPA存储库可以使用repositories元素来设置

示例49.使用名称空间设置JPA存储库
xml version="1.0" encoding="UTF-8"?>
 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
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/data/jpa
    http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

   base-package="com.acme.repositories" />

使用这个元素按照创建存储库实例中的描述查找Spring Data存储除此之外,它还为所有注释的bean激活持久性异常转换,@Repository让JPA持久性提供者抛出的异常转换为Spring的DataAccessException层次结构。

自定义名称空间属性

除了repositories元素的默认属性之外,JPA命名空间还提供了额外的属性,以便更加详细地控制存储库的设置:

表2.存储库元素的自定义JPA特定属性

entity-manager-factory-ref

显式EntityManagerFactory连接要与repositories元素检测到的存储库一起使用通常EntityManagerFactory在应用程序中使用多个bean时使用。如果没有配置,我们将自动查找EntityManagerFactory名称entityManagerFactory中的bean ApplicationContext

transaction-manager-ref

显式PlatformTransactionManager连接要与repositories元素检测到的存储库一起使用通常只有EntityManagerFactory在配置了多个事务管理器和/或bean的情况下才需要默认为PlatformTransactionManager在当前内部定义的单个ApplicationContext

请注意,如果没有明确的定义,我们需要一个PlatformTransactionManager名为bean的bean transactionManagertransaction-manager-ref

4.1.2。基于注释的配置

Spring Data JPA存储库支持不仅可以通过XML命名空间激活,还可以通过JavaConfig使用注释。

例子50.使用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() {

    JpaTransactionManager txManager = new JpaTransactionManager();
    txManager.setEntityManagerFactory(entityManagerFactory());
    return txManager;
  }
}
重要的是要创造LocalContainerEntityManagerFactoryBean不和EntityManagerFactory直接,因为前者还参加了异常转换机制,除了简单地创建EntityManagerFactory

刚刚显示的配置类使用EmbeddedDatabaseBuilderspring-jdbc API 设置嵌入式HSQL数据库然后我们设置一个EntityManagerFactory并使用Hibernate作为示例持久化提供者。这里声明的最后一个基础设施组件是JpaTransactionManager我们最终使用@EnableJpaRepositories基本上带有与XML名称空间相同的属性注释来激活Spring Data JPA存储库如果没有配置基础软件包,它将使用配置类所在的软件包。

4.2。坚持实体

4.2.1。保存实体

保存一个实体可以通过CrudRepository.save(…)-Method 来执行它将坚持或合并给定的实体使用基础的JPA EntityManager如果实体没有被保存,Spring Data JPA将通过调用entityManager.persist(…)方法保存实体,否则该entityManager.merge(…)方法将被调用。

实体状态检测策略

Spring Data JPA提供了以下策略来检测一个实体是否是新的:

表3.用于检测实体在Spring Data JPA中是否新增的选项

Id-财产检查(默认

默认情况下,Spring Data JPA检查给定实体的标识符属性。如果标识符属性是null,则该实体将被认为是新的,否则不是新的。

实施 Persistable

如果一个实体实现Persistable,Spring Data JPA将把新的检测委托给isNew(…)实体方法。有关详细信息,请参阅JavaDoc

实施 EntityInformation

您可以通过创建子类并相应地重写该方法来自定义实现中EntityInformation使用抽象然后您必须将自定义的实现注册为一个Spring bean。请注意,这应该是很少有必要的。有关详细信息,请参阅JavaDocSimpleJpaRepositoryJpaRepositoryFactorygetEntityInformation(…)JpaRepositoryFactory

4.3。查询方法

4.3.1。查询查询策略

JPA模块支持以String形式手动定义查询,或者从方法名称派生。

声明的查询

尽管从方法名获得查询是非常方便的,但是可能会遇到这样的情况:方法名解析器不支持要使用的关键字,或者方法名称会变得不必要的难看。所以,你可以通过命名约定使用JPA命名查询(请参阅使用JPA NamedQueries获取更多信息)或相当具有注解你的查询方法@Query(请参阅使用@Query了解详细信息)。

4.3.2。查询创建

通常,JPA的查询创建机制按查询方法中的描述工作以下是JPA查询方法转化为的一个简短示例:

示例51.从方法名称创建查询
公共接口UserRepository扩展了Repository  {

  List  findByEmailAddressAndLastname(String emailAddress,String lastname);
}

我们将使用从这个JPA标准API查询但本质上这转换为以下查询:select u from User u where u.emailAddress = ?1 and u.lastname = ?2Spring Data JPA将执行属性检查并遍历属性表达式中所述的嵌套属性下面是对JPA支持的关键字的概述,以及包含该关键字本质的翻译方法。

表4.方法名称中支持的关键字
关键词 样品 JPQL片段

And

findByLastnameAndFirstname

… where x.lastname = ?1 and x.firstname = ?2

Or

findByLastnameOrFirstname

… where x.lastname = ?1 or x.firstname = ?2

Is,Equals

findByFirstnamefindByFirstnameIsfindByFirstnameEquals

… where x.firstname = ?1

Between

findByStartDateBetween

… where x.startDate between ?1 and ?2

LessThan

findByAgeLessThan

… where x.age < ?1

LessThanEqual

findByAgeLessThanEqual

… where x.age <= ?1

GreaterThan

findByAgeGreaterThan

… where x.age > ?1

GreaterThanEqual

findByAgeGreaterThanEqual

… where x.age >= ?1

After

findByStartDateAfter

… where x.startDate > ?1

Before

findByStartDateBefore

… where x.startDate < ?1

IsNull

findByAgeIsNull

… where x.age is null

IsNotNull,NotNull

findByAge(Is)NotNull

… where x.age not null

Like

findByFirstnameLike

… where x.firstname like ?1

NotLike

findByFirstnameNotLike

… where x.firstname not like ?1

StartingWith

findByFirstnameStartingWith

… where x.firstname like ?1(参数绑定附加%

EndingWith

findByFirstnameEndingWith

… where x.firstname like ?1(参数与预先绑定%

Containing

findByFirstnameContaining

… where x.firstname like ?1(参数绑定%

OrderBy

findByAgeOrderByLastnameDesc

… where x.age = ?1 order by x.lastname desc

Not

findByLastnameNot

… where x.lastname <> ?1

In

findByAgeIn(Collection ages)

… where x.age in ?1

NotIn

findByAgeNotIn(Collection ages)

… where x.age not in ?1

True

findByActiveTrue()

… where x.active = true

False

findByActiveFalse()

… where x.active = false

IgnoreCase

findByFirstnameIgnoreCase

… where UPPER(x.firstame) = UPPER(?1)

In并且NotIn还可以接受任何Collection参数的子类以及数组或可变参数。对于逻辑运算符相同的其他语法版本,请检查存储库查询关键字

4.3.3。使用JPA NamedQueries

这些示例使用简单的元素和@NamedQuery注释。这些配置元素的查询必须在JPA查询语言中定义。当然,你可以使用@NamedNativeQuery过。这些元素允许您通过失去数据库平台独立性来在本地SQL中定义查询。

XML命名查询定义

要使用XML配置,只需将必要的元素添加orm.xml位于META-INFclasspath文件夹中JPA配置文件中命名查询的自动调用是通过使用一些定义的命名约定来启用的。有关更多详情,请参阅下文

示例52. XML命名查询配置
 name="User.findByLastname">
  select u from User u where u.lastname = ?1

正如你所看到的,查询有一个特殊的名字,将在运行时用来解析它。

注释配置

注释配置的好处是不需要另外的配置文件进行编辑,可能会降低维护成本。您需要为每个新的查询声明重新编译您的域类,从而为此付出代价。

例53.基于注释的命名查询配置
@Entity
@NamedQuery(name = "User.findByEmailAddress",
  query = "select u from User u where u.emailAddress = ?1")
public class User {

}
声明接口

要允许执行这些命名的查询,您只需指定UserRepository如下所示:

示例54. UserRepository中的查询方法声明
public interface UserRepository extends JpaRepository<User, Long> {

  List<User> findByLastname(String lastname);

  User findByEmailAddress(String emailAddress);
}

Spring Data将尝试将对这些方法的调用解析为一个命名查询,从配置的域类的简单名称开始,接着用点分隔的方法名称开始。所以这里的示例将使用上面定义的命名查询,而不是试图从方法名称创建查询。

4.3.4。使用@Query

使用命名查询来声明对实体的查询是一个有效的方法,对于少量的查询来说工作正常。由于查询本身与执行它们的Java方法绑定,实际上可以使用Spring Data JPA @Query注释直接绑定它们,而不是将它们注释到域类。这将从持久化特定信息中释放域类,并将查询共同定位到存储库接口。

对查询方法进行注释的查询将优先于使用@NamedQuery或在其中声明的命名查询定义的查询orm.xml

示例55.使用@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在查询定义中定义高级表达式。

例子56. @Query中的高级like-expressions
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。

示例57.使用@Query在查询方法中声明本机查询
public interface UserRepository extends JpaRepository<User, Long> {

  @Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1", nativeQuery = true)
  User findByEmailAddress(String emailAddress);
}

请注意,我们当前不支持对本机查询执行动态排序,因为我们必须操作已声明的实际查询,并且我们无法可靠地为原生SQL执行此操作。但是,您可以通过自己指定计数查询来使用本机查询进行分页:

示例58.使用@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到您的查询的副本命名本地查询。请注意,尽管您可能必须为您的计数查询注册一个结果集映射。

4.3.5。使用排序

排序可以通过提供PageRequestSort直接使用来完成Order实例中实际使用的属性Sort需要与您的域模型匹配,这意味着它们需要解析为查询中使用的属性或别名。JPQL将其定义为一个state_field_path_expression

使用任何不可引用的路径表达式都会导致异常。

使用Sort连同@Query但是可以让你偷偷在非路检查Order包含实例功能的内ORDER BY子句。这是可能的,因为Order只是附加到给定的查询字符串。默认情况下,我们将拒绝任何Order包含函数调用的实例,但是您可以使用JpaSort.unsafe添加可能不安全的排序。

例59.使用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", new Sort("firstname"));               
repo.findByAndSort("stark", new Sort("LENGTH(firstname)"));           
repo.findByAndSort("targaryen", JpaSort.unsafe("LENGTH(firstname)")); 
repo.findByAsArrayAndSort("bolton", new Sort("fn_len"));              
Sort指向域模型中的属性的有效表达式。
无效的Sort包含函数调用。例外。
Sort包含明确不安全的 有效Order
Sort指向别名函数的有效表达式

4.3.6。使用命名参数

默认情况下,Spring Data JPA将使用上面所有示例中描述的基于位置的参数绑定。这使查询方法有一点点的错误容易重构有关参数的位置。为了解决这个问题,你可以使用@Param注释给一个方法参数一个具体的名字,并在查询中绑定这个名字。

例子60.使用命名参数
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);
}

请注意,根据定义的查询中的出现来切换方法参数。

Spring 4完全支持基于-parameters编译器标志的Java 8的参数名称发现在构建中使用此标志作为调试信息的替代方法,可以省略@Param命名参数注释。

4.3.7。使用SpEL表达式

从Spring Data JPA版本1.4开始,我们支持通过手动定义的查询来使用受限制的SpEL模板表达式@Query在查询执行后,这些表达式将针对一组预定义的变量进行评估。我们支持以下手动查询中使用的变量列表。

表5.基于SpEL的查询模板中支持的变量
变量 用法 描述

entityName

select x from #{#entityName} x

插入entityName与给定存储库关联的域类型。entityName解决如下:如果域类型已设置的name属性@Entity注解那么它将被使用。否则,将使用域类型的简单类名称。

以下示例演示#{#entityName}了查询字符串中表达式的一种用例,您希望使用手动定义的查询来定义具有查询方法的存储库接口。为了不必在@Query注释的查询字符串中声明实际的实体名称,可以使用#{#entityName}变量。

entityName可以通过自定义@Entity的注释。orm.xmlSpEL表达式不支持自定义

例子61.在存储库查询方法中使用SpEL表达式 - entityName
@Entity
public class User {

  @Id
  @GeneratedValue
  Long id;

  String lastname;
}

public interface UserRepository extends JpaRepository<User,Long> {

  @Query("select u from #{#entityName} u where u.lastname = ?1")
  List<User> findByLastname(String lastname);
}

当然你可以直接在查询声明中使用User,但是这也需要你改变查询。这个引用#entityName将会把用户类的潜在的未来重映射转换成一个不同的实体名称(例如通过使用@Entity(name = "MyUser")

#{#entityName}查询字符串中表达式的另一个用例是,如果要为具体的域类型定义具有专用存储库接口的通用存储库接口。为了不必在具体接口上重复自定义查询方法的定义,可以@Query在通用资源库接口注释的查询字符串中使用实体名称表达式

例62.在存储库查询方法中使用SpEL表达式 - 具有继承性的entityName
@MappedSuperclass
public abstract class AbstractMappedType {
  
  String attribute
}

@Entity
public class ConcreteType extends AbstractMappedType {  }

@NoRepositoryBean
public interface MappedTypeRepository<T extends AbstractMappedType>
  extends Repository<T, Long> {

  @Query("select t from #{#entityName} t where t.attribute = ?1")
  List<T> findAllByAttribute(String attribute);
}

public interface ConcreteRepository
  extends MappedTypeRepository<ConcreteType> {  }

在这个例子中,接口MappedTypeRepository是扩展了一些域类型的公共父接口AbstractMappedType它还定义了findAllByAttribute(…)可用于专用存储库接口实例的通用方法如果您现在调用findByAllAttribute(…)ConcreteRepository正在执行的查询会select t from ConcreteType t where t.attribute = ?1

4.3.8。修改查询

以上所有部分都描述了如何声明查询来访问给定实体或实体集合。当然,您可以使用Spring Data存储库自定义实现中描述的工具来添加自定义修改行为由于这种方法对于全面的自定义功能是可行的,所以可以通过使用以下注解查询方法来实现修改实际上只需要参数绑定的查询的执行@Modifying

例63.声明操作查询
@Modifying
@Query("update User u set u.firstname = ?1 where u.lastname = ?2")
int setFixedFirstnameFor(String firstname, String lastname);

这将触发注释到该方法的查询作为更新查询而不是选择一个。由于EntityManager执行修改查询之后可能包含过时的实体,因此我们不会自动将其清除(请参阅JavaDoc的EntityManager.clear()详细信息),因为这将有效地删除所有未刷新的更改,这些更改仍在等待处理EntityManager如果您希望EntityManager自动清除,可以将@Modifying注释的clearAutomatically属性设置true

派生删除查询

Spring Data JPA还支持导出的删除查询,可以避免必须显式声明JPQL查询。

例子64.使用派生的删除查询
interface UserRepository extends Repository<User, Long> {

  void deleteByRoleId(long roleId);

  @Modifying
  @Query("delete from User u where user.role.id = ?1")
  void deleteInBulkByRoleId(long roleId);
}

尽管这个deleteByRoleId(…)方法看起来像是基本上产生了与之相同的结果deleteInBulkByRoleId(…),但是这两个方法声明在执行方式上有一个重要的区别。顾名思义,后一种方法将针对数据库发出单个JPQL查询(即注释中定义的一个)。这意味着,即使是当前加载的实例User也不会看到调用的生命周期回调。

为了确保生命周期查询被实际调用,调用deleteByRoleId(…)将实际执行查询,然后逐个删除返回的实例,以便持久性提供者可以实际调用@PreRemove这些实体的回调。

实际上,派生删除查询是执行查询然后调用CrudRepository.delete(Iterable users)结果并保持行为与其他delete(…)方法的实现同步的快捷方式CrudRepository

4.3.9。应用查询提示

要将JPA查询提示应用于存储库接口中声明的查询,可以使用@QueryHints注释。它需要一个JPA @QueryHint批注数组和一个布尔型标志,以便可能禁用应用于应用分页时触发的附加计数查询的提示。

示例65.使用QueryHints和存储库方法
public interface UserRepository extends Repository<User, Long> {

  @QueryHints(value = { @QueryHint(name = "name", value = "value")},
              forCounting = false)
  Page<User> findByLastname(String lastname, Pageable pageable);
}

刚刚显示的声明将应用@QueryHint为实际查询配置,但省略将其应用于计数查询触发以计算总页数。

4.3.10。配置提取和LoadGraphs

JPA 2.1规范引入了对指定Fetch和LoadGraph的支持,我们也通过@EntityGraph注释来支持Fetch和LoadGraphs,它允许引用@NamedEntityGraph可以在实体上注释定义,以用于配置所得查询的提取计划。抓取的类型(取/载)可以通过注解上的type属性进行配置@EntityGraph请参阅JPA 2.1规范3.7.4以供进一步参考。

例66.在一个实体上定义一个命名实体图。
@Entity
@NamedEntityGraph(name = "GroupInfo.detail",
  attributeNodes = @NamedAttributeNode("members"))
public class GroupInfo {

  // default fetch mode is lazy.
  @ManyToMany
  List<GroupMember> members = new ArrayList<GroupMember>();

  
}
示例67.引用存储库查询方法上的命名实体图定义。
@Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {

  @EntityGraph(value = "GroupInfo.detail", type = EntityGraphType.LOAD)
  GroupInfo getByGroupName(String name);

}

也可以通过定义ad-hoc实体图@EntityGraph提供的attributePaths将被翻译成相应的,EntityGraph而不需要明确地添加@NamedEntityGraph到您的域类型。

例68.在存储库查询方法上使用AD-HOC实体图定义。
@Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {

  @EntityGraph(attributePaths = { "members" })
  GroupInfo getByGroupName(String name);

}

4.3.11。预测

Spring Data查询方法通常会返回一个或多个由存储库管理的聚合根实例。但是,有时可能需要对这些类型的某些属性进行投影。Spring Data允许为专门的返回类型建模,以更有选择地将部分视图检索到托管集合上。

设想一个样本库和聚合根类型是这样的:

示例69.示例聚合和存储库
class Person {

  @Id UUID id;
  String firstname, lastname;
  Address address;

  static class Address {
    String zipCode, city, street;
  }
}

interface PersonRepository extends Repository<Person, UUID> {

  Collection<Person> findByLastname(String lastname);
}

现在想象一下,我们只想检索人物的名字属性。Spring Data提供了什么手段来实现这一点?

基于界面的预测

限制查询结果只公开名称属性的最简单的方法是声明一个接口,该接口将公开读取属性的访问器方法:

示例70.用于检索属性的子集的投影接口
interface NamesOnly {

  String getFirstname();
  String getLastname();
}

这里重要的一点是,这里定义的属性完全匹配聚合根中的属性。这允许像这样添加查询方法:

例71.使用基于接口的投影和查询方法的仓库
interface PersonRepository extends Repository<Person, UUID> {

  Collection<NamesOnly> findByLastname(String lastname);
}

查询执行引擎将在运行时为每个返回的元素创建该接口的代理实例,并将调用的公开方法转发给目标对象。

可以递归地使用投影。如果你也想包含一些Address信息,为它创建一个投影接口,并从声明中返回这个接口getAddress()

示例72.用于检索属性的子集的投影接口
interface PersonSummary {

  String getFirstname();
  String getLastname();
  AddressSummary getAddress();

  interface AddressSummary {
    String getCity();
  }
}

在方法调用时,address将获取目标实例属性,并依次包装成投影代理。

封闭投影

其访问方法都匹配目标集合属性的投影接口被视为封闭投影。

例73.一个封闭的投影
interface NamesOnly {

  String getFirstname();
  String getLastname();
}

如果使用闭合投影,Spring Data模块甚至可以优化查询执行,因为我们完全知道支持投影代理所需的所有属性。有关更多详细信息,请参阅参考文档的模块特定部分。

打开预测

投影接口中的访问器方法也可以用来通过使用@Value注解来计算新值

例74.一个公开的投影
interface NamesOnly {

  @Value("#{target.firstname + ' ' + target.lastname}")
  String getFullName();
  
}

支持投影的聚合根可通过target变量获得。投影界面使用@Value开放投影。在这种情况下,Spring Data将无法应用查询执行优化,因为SpEL表达式可以使用聚合根的任何属性。

使用的表达式@Value不应该变得太复杂,因为你想避免在Strings中编程对于非常简单的表达式,一个选项可能是使用默认的方法:

例75.一个使用默认方法的投影接口用于定制逻辑
interface NamesOnly {

  String getFirstname();
  String getLastname();

  default String getFullName() {
    return getFirstname.concat(" ").concat(getLastname());
  }
}

这种方法要求您能够纯粹基于投影界面上公开的其他访问器方法来实现逻辑。第二个更灵活的选择是在Spring bean中实现自定义逻辑,然后简单地从SpEL表达式中调用它:

示例76.示例Person对象
@Component
class MyBean {

  String getFullName(Person person) {
    
  }
}

interface NamesOnly {

  @Value("#{@myBean.getFullName(target)}")
  String getFullName();
  
}

请注意,SpEL表达式如何引用myBean并调用getFullName(…)转发投影目标的方法作为方法参数。SpEL表达评估支持的方法也可以使用可以从表达式中引用的方法参数。方法参数可以通过Object名为数组获得args

示例77.示例Person对象
interface NamesOnly {

  @Value("#{args[0] + ' ' + target.firstname + '!'}")
  String getSalutation(String prefix);
}

同样,对于更复杂的表达式,而是使用Spring bean,让表达式只是调用上面描述的方法

基于类别的预测(DTO)

定义投影的另一种方法是使用值类型DTO,它们为应该检索的字段保存属性。这些DTO类型的使用方式与投影接口的使用方式完全相同,只是在这里没有代理进行,没有嵌套投影可以应用。

如果商店通过限制要加载的字段来优化查询执行,那么要加载的字段将根据所公开的构造函数的参数名称来确定。

例78.一个投影DTO
class NamesOnly {

  private final String firstname, lastname;

  NamesOnly(String firstname, String lastname) {

    this.firstname = firstname;
    this.lastname = lastname;
  }

  String getFirstname() {
    return this.firstname;
  }

  String getLastname() {
    return this.lastname;
  }

  // equals(…) and hashCode() implementations
}
避免投影DTO的样板代码

需要为DTO编写的代码可以使用Project Lombok进行简化,该项目提供了一个@Value注释(不要与@Value上面的接口示例中显示的Spring 注释混淆)。上面的示例DTO会变成这样:

@Value
class NamesOnly {
	String firstname, lastname;
}

字段默认是私有的,这个类暴露了一个构造函数接受所有的字段并自动获取equals(…)hashCode()实现方法。

动态投影

到目前为止,我们已经使用投影类型作为集合的返回类型或元素类型。但是,可能需要选择在调用时使用的类型。要应用动态投影,请使用如下所示的查询方法:

例子79.一个使用动态投影参数的仓库
interface PersonRepository extends Repository<Person, UUID> {

  Collection<T> findByLastname(String lastname, Class<T> type);
}

这样,该方法可以用来直接获得聚合物,或者应用投影:

例80.使用动态投影的存储库
void someMethod(PersonRepository people) {

  Collection<Person> aggregates =
    people.findByLastname("Matthews", Person.class);

  Collection<NamesOnly> aggregates =
    people.findByLastname("Matthews", NamesOnly.class);
}

4.4。存储过程

JPA 2.1规范引入了通过JPA标准查询API调用存储过程的支持。我们引入了@Procedure在存储库方法中声明存储过程元数据注释。

例81. HSQL DB中的pus1inout过程的定义。
/;
DROP procedure IF EXISTS plus1inout
/;
CREATE procedure plus1inout (IN arg int, OUT res int)
BEGIN ATOMIC
 set res = arg + 1;
END
/;

存储过程的元数据可以通过NamedStoredProcedureQuery实体类型注释进行配置

示例82.在实体上存储过程元数据定义。
@Entity
@NamedStoredProcedureQuery(name = "User.plus1", procedureName = "plus1inout", parameters = {
  @StoredProcedureParameter(mode = ParameterMode.IN, name = "arg", type = Integer.class),
  @StoredProcedureParameter(mode = ParameterMode.OUT, name = "res", type = Integer.class) })
public class User {}

存储过程可以通过多种方式从存储库方法中引用。该存储的过程被称为既可以通过直接定义valueprocedureName所述的属性@Procedure注释或经由间接name属性。如果没有配置名称,则使用存储库方法的名称作为回退。

示例83.在数据库中引用具有名称“plus1inout”的显式映射过程。
@Procedure("plus1inout")
Integer explicitlyNamedPlus1inout(Integer arg);
示例84:在数据库中通过 procedureName 别名引用名为“plus1inout”的隐式映射过程
@Procedure(procedureName = "plus1inout")
Integer plus1inout(Integer arg);
示例85.在EntityManager中引用显式映射的命名存储过程“User.plus1IO”。
@Procedure(name = "User.plus1IO")
Integer entityAnnotatedCustomNamedProcedurePlus1IO(@Param("arg") Integer arg);
示例86.通过method-name在EntityManager中引用隐式映射的命名存储过程“User.plus1”。
@Procedure
Integer plus1(@Param("arg") Integer arg);

4.5。产品规格

JPA 2引入了一个可用于以编程方式构建查询的标准API。写一个criteria你实际上定义了一个域类的查询的where子句。再往后退一步,这些标准可以被看作是由JPA标准API约束描述的实体的谓词。

Spring Data JPA采用Eric Evans的书籍“Domain Driven Design”中的规范概念,遵循相同的语义,并提供一个API来使用JPA标准API定义这样的规范。为了支持规范,您可以使用JpaSpecificationExecutor接口来扩展存储库接口:

public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor {
 
}

额外的界面带有方法,允许您以各种方式执行规格。例如,该findAll方法将返回符合规范的所有实体:

List<T> findAll(Specification<T> spec);

Specification接口被定义为如下:

public interface Specification<T> {
  Predicate toPredicate(Root<T> root, CriteriaQuery query,
            CriteriaBuilder builder);
}

好的,典型的用例是什么?可以很容易地使用规范在实体之上构建一个可扩展的谓词集,然后可以组合和使用这些谓词,JpaRepository而无需为每个需要的组合声明一个查询(方法)。这是一个例子:

例87.客户的规格
public class CustomerSpecs {

  public static Specification<Customer> isLongTermCustomer() {
    return new Specification<Customer>() {
      public Predicate toPredicate(Root<Customer> root, CriteriaQuery query,
            CriteriaBuilder builder) {

         LocalDate date = new LocalDate().minusYears(2);
         return builder.lessThan(root.get(_Customer.createdAt), date);
      }
    };
  }

  public static Specification<Customer> hasSalesOfMoreThan(MontaryAmount value) {
    return new Specification<Customer>() {
      public Predicate toPredicate(Root<T> root, CriteriaQuery query,
            CriteriaBuilder builder) {

         // build query here
      }
    };
  }
}

不得不承认,大量的样板文件有待改进(Java 8关闭会有所减少),但客户端会变得更好,如下所示。_Customer类型是使用JPA Metamodel生成器生成的元模型类型(例如,请参阅Hibernate实现的文档)。所以表达式_Customer.createdAt就是Customer具有createdAt类型属性Date除此之外,我们已经在业务需求抽象层次上表达了一些标准,并创建了可执行文件Specifications所以客户可能会使用一个Specification如下:

例88.使用一个简单的规范
List<Customer> customers = customerRepository.findAll(isLongTermCustomer());

好的,为什么不简单地为这种数据访问创建一个查询?你是对的。使用单个Specification函数不会比普通的查询声明获得很多好处。规格的力量真正发光,当你结合他们创造新的Specification对象。你可以通过Specifications我们提供帮助器类来实现这一点,来构建如下的表达式:

例89.组合的规格
MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR);
List<Customer> customers = customerRepository.findAll(
  where(isLongTermCustomer()).or(hasSalesOfMoreThan(amount)));

正如您所看到的,Specifications提供了一些粘合代码方法来链接和组合Specification实例。因此扩展你的数据访问层只是创建新的Specification实现并将它们与现有的实现相结合。

4.6。按实例查询

4.6.1。介绍

本章将向您介绍Query by Example并解释如何使用示例。

按实例查询(QBE)是一种用户界面友好的查询技术。它允许动态查询创建,不需要编写包含字段名称的查询。实际上,按示例查询不需要使用特定于商店的查询语言编写查询。

4.6.2。用法

查询示例API由三部分组成:

  • 探针:这是具有填充字段的域对象的实际示例。

  • ExampleMatcherExampleMatcher携带如何匹配特定领域的细节。它可以在多个示例中重用。

  • Example:一个Example由探针和ExampleMatcher它用于创建查询。

按实例查询适合多种使用情况,但也具有局限性:

何时使用

  • 用一组静态或动态约束查询数据存储

  • 经常重构域对象,而不用担心打破现有的查询

  • 独立于底层数据存储API工作

限制

  • 不支持嵌套/分组属性约束 firstname = ?0 or (firstname = ?1 and lastname = ?2)

  • 仅支持字符串的开始/包含/结束/正则表达式匹配以及其他属性类型的精确匹配

在按例查询开始之前,您需要有一个域对象。要开始,只需为您的存储库创建一个接口:

示例90.示例Person对象
public class Person {

  @Id
  private String id;
  private String firstname;
  private String lastname;
  private Address address;

  // … getters and setters omitted
}

这是一个简单的域对象。你可以用它来创建一个Example默认情况下,具有null值的字段将被忽略,字符串将使用商店特定的默认值进行匹配。示例可以使用of工厂方法或通过使用ExampleMatcherExample是不可改变的。

例91.简单的例子
Person person = new Person();                         
person.setFirstname("Dave");                          

Example<Person> example = Example.of(person);         
创建一个新的域对象实例
设置要查询的属性
创建 Example

理想情况下使用存储库执行示例。为此,让您的存储库接口扩展QueryByExampleExecutor以下是QueryByExampleExecutor界面摘录

例92.  QueryByExampleExecutor
public interface QueryByExampleExecutor<T> {

  <S extends T> S findOne(Example<S> example);

  <S extends T> Iterable<S> findAll(Example<S> example);

  // … more functionality omitted.
}

4.6.3。示例匹配器

示例不限于默认设置。您可以指定自己的默认字符串匹配,空处理和使用特定于属性的设置ExampleMatcher

示例93.具有定制匹配的示例匹配器
Person person = new Person();                          
person.setFirstname("Dave");                           

ExampleMatcher matcher = ExampleMatcher.matching()     
  .withIgnorePaths("lastname")                         
  .withIncludeNullValues()                             
  .withStringMatcherEnding();                          

Example<Person> example = Example.of(person, matcher); 
创建一个新的域对象实例。
设置属性。
创建一个ExampleMatcher期望所有值匹配。即使没有进一步的配置,在这个阶段也是可用的。
构建一个新ExampleMatcher的忽略属性路径lastname
构造一个新ExampleMatcher的忽略属性路径lastname并包含空值。
构造一个新ExampleMatcher的忽略属性路径lastname,包含空值,并使用执行后缀字符串匹配。
Example根据域对象和配置创建一个新ExampleMatcher

默认情况下,ExampleMatcher期望在探针上设置的所有值匹配。如果你想得到匹配任何隐式定义的谓词的结果,可以使用ExampleMatcher.matchingAny()

您可以为单个属性指定行为(例如嵌套属性的“firstname”和“lastname”,“address.city”)。您可以使用匹配选项和区分大小写来调整它。

例子94.配置匹配器选项
ExampleMatcher matcher = ExampleMatcher.matching()
  .withMatcher("firstname", endsWith())
  .withMatcher("lastname", startsWith().ignoreCase());
}

配置匹配器选项的另一种方式是使用Java 8 lambda表达式。这种方法是一个回调,要求实现者修改匹配器。无需返回匹配器,因为配置选项在匹配器实例中保存。

示例95.使用lambdas配置匹配器选项
ExampleMatcher matcher = ExampleMatcher.matching()
  .withMatcher("firstname", match -> match.endsWith())
  .withMatcher("firstname", match -> match.startsWith());
}

通过Example使用配置的合并视图创建的查询默认的匹配设置可以设置为ExampleMatcher级别,而单独的设置可以应用于特定的属性路径。设置的设置ExampleMatcher将由属性路径设置继承,除非它们是明确定义的。属性修补程序上的设置比默认设置具有更高的优先级。

表6.  ExampleMatcher 设置的范围
设置 范围

空操作

ExampleMatcher

字符串匹配

ExampleMatcher 和财产路径

忽略属性

属性路径

区分大小写

ExampleMatcher 和财产路径

价值转化

属性路径

4.6.4。执行一个例子

在Spring Data JPA中,您可以使用带有存储库的示例查询。

示例96.使用存储库的示例查询
public interface PersonRepository extends JpaRepository<Person, String> {  }

public class PersonService {

  @Autowired PersonRepository personRepository;

  public List<Person> findPeople(Person probe) {
    return personRepository.findAll(Example.of(probe));
  }
}
SingularAttribute目前 只有属性可以用于属性匹配。

属性说明符接受属性名称(例如“firstname”和“lastname”)。您可以通过链接属性(“address.city”)来导航。您可以使用匹配选项和区分大小写来调整它。

表7.  StringMatcher 选项
匹配 逻辑结果

DEFAULT (区分大小写)

firstname = ?0

DEFAULT (不区分大小写)

LOWER(firstname) = LOWER(?0)

EXACT (区分大小写)

firstname = ?0

EXACT (不区分大小写)

LOWER(firstname) = LOWER(?0)

STARTING (区分大小写)

firstname like ?0 + '%'

STARTING (不区分大小写)

LOWER(firstname) like LOWER(?0) + '%'

ENDING (区分大小写)

firstname like '%' + ?0

ENDING (不区分大小写)

LOWER(firstname) like '%' + LOWER(?0)

CONTAINING (区分大小写)

firstname like '%' + ?0 + '%'

CONTAINING (不区分大小写)

LOWER(firstname) like '%' + LOWER(?0) + '%'

4.7。事务性

资源库实例上的CRUD方法默认是事务性的。对于读取操作,将事务配置readOnly标志设置为true,其他所有其他配置均为普通,@Transactional以便应用默认事务配置。有关详细信息,请参阅JavaDoc SimpleJpaRepository如果您需要调整存储库中声明的某个方法的事务配置,只需在存储库接口中重新声明该方法,如下所示:

示例97.CRUD的自定义事务配置
public interface UserRepository extends CrudRepository<User, Long> {

  @Override
  @Transactional(timeout = 10)
  public List<User> findAll();

  // Further query method declarations
}

这将导致findAll()方法执行超时10秒,没有readOnly标志。

改变交易行为的另一种可能性是使用通常覆盖多个存储库的外观或服务实现。其目的是为非CRUD操作定义事务边界:

例子98.使用Facade为多个存储库调用定义事务
@Service
class UserManagementImpl implements UserManagement {

  private final UserRepository userRepository;
  private final RoleRepository roleRepository;

  @Autowired
  public UserManagementImpl(UserRepository userRepository,
    RoleRepository roleRepository) {
    this.userRepository = userRepository;
    this.roleRepository = roleRepository;
  }

  @Transactional
  public void addRoleToAllUsers(String roleName) {

    Role role = roleRepository.findByName(roleName);

    for (User user : userRepository.findAll()) {
      user.addRole(role);
      userRepository.save(user);
    }
}

这将导致调用addRoleToAllUsers(…)在事务内部运行(参与现有的或创建一个新的,如果没有运行)。存储库中的事务配置将被忽略,然后由外部事务配置确定实际使用的配置。请注意,您将必须激活@EnableTransactionManagement显式使用才能在正常工作中获取基于注释的配置。上面的示例假定您正在使用组件扫描。

4.7.1。事务查询方法

为了让您的查询方法成为事务性的,只需@Transactional在您定义的存储库接口上使用即可

例子99.在查询方法中使用@Transactional
@Transactional(readOnly = true)
public interface UserRepository extends JpaRepository<User, Long> {

  List<User> findByLastname(String lastname);

  @Modifying
  @Transactional
  @Query("delete from User u where u.active = false")
  void deleteInactiveUsers();
}

通常你会希望readOnly标志设置为true,因为大多数查询方法只能读取数据。相反,deleteInactiveUsers()使用@Modifying注释并重写事务配置。因此,该方法将被执行readOnly标志设置为false

将事务用于只读查询是绝对合理的,我们可以通过设置readOnly标志来标记它们但是,这不会作为检查来触发操作查询(尽管某些数据库在只读事务中拒绝INSERTUPDATE声明)。readOnly标志将作为提示传播到基础的JDBC驱动程序以进行性能优化。此外,Spring将对底层的JPA提供者进行一些优化。例如,当与Hibernate一起使用时,flush模式被设置为NEVER当您将事务配置为readOnly导致Hibernate跳过脏检查(大型对象树显着改进)时。

4.8。锁定

要指定要使用的锁定模式,可以@Lock在查询方法上使用注释:

示例100.在查询方法上定义锁定元数据
interface UserRepository extends Repository<User, Long> {

  // Plain query method
  @Lock(LockModeType.READ)
  List<User> findByLastname(String lastname);
}

这个方法的声明会导致被触发的查询被装备了LockModeType READ您还可以通过在存储库接口中重新声明CRUD方法来定义锁定,并添加@Lock注释:

例101.在CRUD方法上定义锁定元数据
interface UserRepository extends Repository<User, Long> {

  // Redeclaration of a CRUD method
  @Lock(LockModeType.READ);
  List<User> findAll();
}

4.9。审计

4.9.1。基本

Spring Data提供复杂的支持,以透明地跟踪谁创建或更改实体以及发生此事件的时间点。为了从这个功能中受益,你必须为你的实体类配备审计元数据,这些元数据可以使用注释或者通过实现一个接口来定义。

基于注释的审计元数据

我们提供@CreatedBy@LastModifiedBy捕捉谁创建或修改的实体以及用户@CreatedDate@LastModifiedDate捕捉一次发生这种情况的地步。

例子102一个被审计的实体
class Customer {

  @CreatedBy
  private User user;

  @CreatedDate
  private DateTime createdDate;

  // … further properties omitted
}

正如您所看到的,注释可以选择性地应用,具体取决于您想要捕获的信息。捕获时间点的注释可用于类型为JodaTimes DateTime,遗留Java DateCalendarJDK8日期/时间类型以及long/的属性Long

基于接口的审计元数据

如果您不想使用注释来定义审计元数据,则可以让您的域类实现该Auditable界面。它公开了所有审计属性的setter方法。

还有一个便利的基类AbstractAuditable,您可以扩展以避免手动实现接口方法。请注意,这会增加您的域类到Spring数据的耦合,这可能是您想要避免的。通常,定义审计元数据的基于注释的方式是优选的,因为它是较少侵入性和较灵活的。

AuditorAware

如果您使用@CreatedBy或者@LastModifiedBy,审计基础设施需要知道当前的委托人。为此,我们提供了一个AuditorAwareSPI接口,您必须执行接口,以告知基础架构当前用户或系统与应用程序进行交互的人员。泛型T定义了哪些类型的属性注释@CreatedBy@LastModifiedBy必须是。

下面是使用Spring Security Authentication对象的接口的示例实现

例103.基于Spring Security的AuditorAware的实现
class SpringSecurityAuditorAware implements AuditorAware<User> {

  public User getCurrentAuditor() {

    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

    if (authentication == null || !authentication.isAuthenticated()) {
      return null;
    }

    return ((MyUserDetails) authentication.getPrincipal()).getUser();
  }
}

这个实现是访问AuthenticationSpring Security提供对象,并UserDetails在你的UserDetailsService实现中查找它创建的自定义实例我们在这里假设你正在通过该UserDetails实现公开域用户,但你也可以从任何地方查找基于Authentication找到的。

4.10。JPA审计

4.10.1。一般审计配置

Spring Data JPA附带一个实体监听器,可用于触发捕获审计信息。所以首先你必须注册你的AuditingEntityListener内部你orm.xml的持久化上下文中的所有实体:

示例104.审计配置orm.xml

  
    
       class="….data.jpa.domain.support.AuditingEntityListener" />
    
  

您还可以AuditingEntityListener使用@EntityListeners注释启用每个实体

@Entity
@EntityListeners(AuditingEntityListener.class)
public class MyEntity {

}

请注意审计功能需要spring-aspects.jar在类路径上。

有了这一点,激活审计功能只是将Spring Data JPA auditing命名空间元素添加到您的配置:

示例105.使用XML配置激活审计
 auditor-aware-ref="yourAuditorAwareBean" />

从Spring Data JPA 1.5开始,可以通过使用@EnableJpaAuditing批注注释配置类来启用审计。

示例106.通过Java配置激活审计
@Configuration
@EnableJpaAuditing
class Config {

  @Bean
  public AuditorAware<AuditableUser> auditorProvider() {
    return new AuditorAwareImpl();
  }
}

如果你暴露类型的豆AuditorAwareApplicationContext,审计基础设施会自动把它捡起来,并用它来确定当前用户要在域类型设置。如果您有多个注册的实现ApplicationContext,您可以通过显式设置auditorAwareRef属性来选择要使用的实现@EnableJpaAuditing

你可能感兴趣的:(文档翻译)