返回介绍

4.4 使用 Spring Data Repository

发布于 2025-04-22 19:57:18 字数 7482 浏览 0 评论 0 收藏

为了启用 Spring Data Repository 的功能,必须让 Spring Data Repository 的基础设施找到 Repository 接口。为了做到这一点,需要让 CustomerRepository 扩展自 Spring Data Repository 的标识接口。除此之外,还要保留了以前所声明的持久化方法,如示例 4-13 所示。

示例 4-13 Spring Data CustomerRepository 接口

c0413

save(…) 方法将会依靠通用的 SimpleJpaRepository,它实现了所有 CRUD 方法。我们所声明的查询方法将会依赖于查询衍生机制,如在 2.2.2 小节“衍生查询”中所提到的那样。我们还需要一种方式来启用 Spring Data Repository 的基础设施,关于这一点可以使用 XML,也可以使用 JavaConfig。对于 JavaConfig 方式来说,所要做的只是在配置类上添加 @EnableJpaRepositories 注解即可。在示例中移除了 @ComponentScan,因为不再需要查找手工实现了。同样地情况也适用于 @EnableTransactionManagement 注解。Spring Data Repository 的基础设施将自动处理对 Repository 的方法调用,会将其置于事务之中。关于事务设置的更多细节,可以参考 4.4.1 小节“事务性”。本来也可以保留这些注解以创建更为完整的应用,但是移除了它们,是因为我们不想造成这样一种印象,那就是构建简单的数据访问功能,这些注解也都是必需的。最终,ApplicationConfig 类的头部信息如示例 4-14 所示。

示例 4-14 使用 JavaConfig 启用 Spring Data Repository

c0414

如果使用 XML 配置的话,则需要添加 JPA 命名空间中的 repositories XML 命名空间元素,如示例 4-15 所示。

示例 4-15 通过 XML 命名空间启用 JPARepository

c0415

为了看到它的运行效果,请查看 CustomerRepositoryIntegrationTest。它使用了 AbstractIntegrationTest 中所构建的 Spring 配置,将 CustomerRepository 装配到测试用例之中并运行与 JpaCustomerRepositoryIntegrationTest 相同的测试,但是我们不需要为 Repository 接口提供任何的实现。让我们看一下 Repository 中定义的每个方法并简要介绍一下 Spring Data JPA 都为每个方法做了些什么,如示例 4-16 所示。

示例 4-16 为 Customer 所定义的 Repository 接口

c0416

findOne(...) 和 save(...) 方法依靠 SimpleJpaRepository 来实现,实际上就是这个类的实例支撑了 Spring Data 基础设施所创建的代理。所以,仅仅通过匹配方法签名,对这两个方法的调用就会被分发到该实现类上。如果想要暴露更完整的 CRUD 方法,只需要扩展 CrudRepository 接口来代替 Repository 就可以了,因为它已经包含了这些方法。要注意的是,如何避免暴露 delete(...) 方法以阻止删除 Customer 实例,如果扩展了 CrudRepository,这个方法是会暴露出来的。关于调节选项的更多细节,可参考 2.3.1 小节“调整 Repository 接口”。

最后要讨论的方法是 findByEmailAddress(…),它显然不是一个 CURD 方法,但是它的意图是要作为查询来执行的。因为我们没有做任何的手动声明,所以 Spring Data JPA 启动时就需要探查这个方法并试图根据它来衍生出查询。衍生机制(细节可以参考 2.2.2 小节“衍生查询”)将会发现 EmailAddress 是 Customer 所引用的合法属性,并且最终会创建一个 JPA Criteria API 查询,其等同的 JPQL 是“select c from Customer c where c.emailAddress = ?1”。因为方法会返回单个 Customer,所以查询执行的预期结果最多返回一个实体。如果没有找到 Customer,会得到 null;如果找到的结果不止一个,那么将会出现 IncorrectResultSizeDataAccessException 异常。

让我们继续看 ProductRepository 接口(如示例 4-17 所示)。可能会首先发现与 CustomerRepository 不同,在这里我们扩展 CrudRepository 接口,因为我们想要完整的 CRUD 方法集合。findByDescriptionContaining(...) 方法很明显是一个查询方法。这里有几点需要注意。首先,我们不仅引用了 Product 的 description 属性,而且用 Containing 关键字来限制断言。最终会给指定的 description 参数前后添加%字符,结果所形成的 String 将会通过 Like 操作符来进行限制。因此,查询如下:select p from Product p where p.description like ?1,如果给定的 description 是 Apple 的话,限制词就会是%Apple%。第二件有意思的事情就是我们使用了分页抽象(pagination abstraction),这样就能获取匹配条件的 Product 的一个子集。ProductRepository IntegrationTest 中的 lookupProductsByDescription() 测试用例展现了这些方法是如何被使用的(如示例 4-18 所示)。

示例 4-17 为 Product 所定义的 Repository 接口

c0417

示例 4-18 对 ProductRepository findByDescriptionContaining(...) 方法的测试用例

c0418

我们创建了一个新的 PageRequest 实例,用它来获取第一页的数据并声明每页的大小是一条,要按照 Product 的 name 进行降序排列。然后,我们将 Pageable 传递到方法之中,并确保返回的是 iPad、目前是第一页并且还有其他页可访问。可以看到,对分页方法的执行返回了必要的元数据信息,可以用来知道如果不使用分页的话会得到多少条记录。如果不使用 Spring Data 的话,要读取这些元数据需要手动编写额外的查询执行代码,它会基于实际的查询做一个求记录数的投射。关于使用 Repository 方法进行分页的更多细节,参见 2.2.3 小节“分页与排序”。

ProductRepository 的第二个方法是 findByAttributeAndValue(...)。我们希望获取到自定义属性为某个给定值的所有 Product。因为 attributes 被映射为 @ElementCollection(见示例 4-5),所以很遗憾无法使用查询衍生机制来为我们创建查询。为了手动定义要执行的查询,在这里使用了 @Query 注解。一般来讲,如果查询变得越来越复杂的话,这种方式也会带来便利。这是因为就算它们可以衍生出查询,但是方法的名字也会变得非常冗长。

最后,看一下 OrderRepository(如示例 4-19 所示),它看起来应该已经很熟悉了。查询方法 findByCustomer(...) 会触发查询衍生机制(如前面讲解所示)并且最终形成的查询为“select o from Order o where o.customer = ? 1”。与其他的 Repository 不同的是,我们扩展自 PagingAndSortingRepository 接口,而这个接口又是扩展自 CrudRepository 的。PagingAndSortingRepository 在 CrudRepository 已提供的 findAll(...) 之上又提供了分别接受 Sort 和 Pageable 参数的方法。这里的主要场景就是我们想逐页访问 Order,而不是一次加载所有的数据。

示例 4-19 为 Order 定义的 Repository 接口

c0419

4.4.1 事务性

基于 JPA EntityManager 的一些 CRUD 操作需要激活事务。为了让 Spring Data Repository 为 JPA 所提供的功能尽可能便利,CrudRepository 和 PagingAnd SortingRepository 背后的实现类已经添加了默认配置的 @Transactional 注解,这样的话 Spring 事务将会自动参与进来,甚至如果当前没有激活事务的话,它将会触发新的事务。要了解 Spring 事务的基本信息的话,可以查阅 Spring 参考文档( http://static.springsource.org/spring/docs/current/spring-framework-reference /html/ transaction.html )。

如果 Repository 实现类触发了事务的话,那么它会为 save(...) 和 delete(...) 操作创建默认的事务(存储默认的隔离级别、没有配置超时、只对运行时异常回滚),并对所有的查询方法包括分页创建只读的事务。为读取方法创建只读的事务有以下的好处:首先,这个标识将会传递给底层的 JDBC 驱动,这个驱动(取决于数据库供应商)将会进行优化,甚至阻止你意外执行修改式的查询。除此之外,Spring 事务的基础设施与 EntityManager 的生命周期进行了集成并且允许将它的 FlushMode 设置为 MANUAL,从而能够避免为持久化上下文中的每个实体检查状态变化(也就是所谓的脏检查),尤其是大量对象加载到持久化上下文中的情况下,这可以带来很明显的性能提升。

如果想为一些 CRUD 方法的事务配置做一些微调的话(如配置指定的超时时间),那么可以重新声明所需的 CRUD 方法并为其添加 @Transactional 注解,在这个注解中可以为方法声明进行自己的设置,它的优先级会高于 SimpleJpaRepository 中声明的默认配置,如示例 4-20 所示。

示例 4-20 在 CustomerRepository 接口中重新配置事务

c0420

使用自定义的 Repository 基础接口,当然也是可行的,可参见 2.3.1 小节“调整 Repository 接口”。

4.4.2 Repository 与 Querydsl 集成

既然已经看到了如何为 Repository 接口添加查询方法,那现在让我们来看一下如何使用 Querydsl 来为实体动态地创建断言并通过 Repository 抽象来执行它们。第 3 章曾总体介绍过 Querydsl 是什么以及它是如何工作的。

为了生成元模型类,要在 pom.xml 文件中配置 Querydsl Maven 插件,如示例 4-21 所示。

示例 4-21 为 JPA 构建 Querydsl APT 处理器

c0421

在这里,唯一特定于 JPA 的事情就是使用了 JPAAnnotationProcessor。它会让插件按照 JPA 的映射注解来查找实体、与其他实体的关系以及可嵌入式的内容(embeddable),等等。生成会在正常的构建过程中进行,生成的类将会位于 target 下的一个文件夹下。因此,每次清理构建的时候,它们会被清除掉,不要将它们签入到源码控制系统之中。

如果正在使用 Eclipse 并将这个插件添加到了工程之中,则需要触发 Maven 的工程更新(在工程上点击右键并选择“Maven→Update Project…”)。这会把配置的输出目录设置为源码文件夹,此时,使用生成类的代码就能正常编译了。

一旦这些准备就绪,就会看到生成的查询类,如 QCustomer、QProduct 等。让我们在 ProductRepository 上下文中探索一下这些生成类的功能。为了能够在 Repository 中执行 Querydsl 断言,我们将 QueryDslPredicateExecutor 添加到继承类型的列表中,如示例 4-22 所示。

示例 4-22 扩展了 QueryDslPredicateExecutor 的 ProductRepository 接口

c0422

这会将 findAll(Predicate predicate) 和 findOne(Predicate pred icate) 这样的方法加入到 API 之中。现在一切已经就绪,可以开始使用生成的类了。让我们看一下 QuerydslProduct RepositoryIntegrationTest,如示例 4-23 所示。

示例 4-23 使用 Querydsl 断言来查找 Product

c0423

首先,获得了 QProduct 元模型类的一个引用并将其放到 product 属性中。现在,我们可以使用它来导航生成的路径表达式(path expression),从而就能创建断言了。我们使用 product.name.eq("iPad") 来查找名为 iPad 的 Product 并将其保存为引用。我们所构建的第二个断言指明了要查找描述中包含 tablet 的 Product。然后,基于 Repository 执行这个 Predicate 实例,并且验证找到的 iPad 与前面的引用是相同的。

可以看到定义的断言非常易读和简洁。已构建的断言可以重新组合起来形成更高级别的断言,因此,能在实现灵活查询的同时又不增加复杂性。

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。