Spring数据存储库抽象的目标是显著减少为各种持久性存储实现数据访问层所需的样板代码量。
一、核心概念
Spring数据存储库抽象中的中心接口是repository。它将要管理的域类以及域类的ID类型作为类型参数。此接口主要用作标记接口,以捕获要使用的类型,并帮助您发现扩展此类型的接口。CrudRepository和ListCrudRepoository接口为所管理的实体类提供了复杂的CRUD功能。
Example 1. CrudRepository Interface
public interface CrudRepository<T, ID> extends Repository<T, ID> { <S extends T> S save(S entity); (1) Optional<T> findById(ID primaryKey); (2) Iterable<T> findAll(); (3) long count(); (4) void delete(T entity); (5) boolean existsById(ID primaryKey); (6) // … more functionality omitted. }
(1) 保存给定实体。
(2) 返回由给定ID标识的实体。
(3) 返回所有实体。
(4) 返回实体数。
(5) 删除给定实体。
(6) 指示是否存在具有给定ID的实体。
除了CrudRepository之外,还有一个PagingAndSortingRepository抽象,它添加了其他方法来简化对实体的分页访问:
示例2.PagingAndSortingRepository接口
public interface PagingAndSortingRepository<T, ID> { Iterable<T> findAll(Sort sort); Page<T> findAll(Pageable pageable); }
要以20的页面大小访问User的第二页,可以执行以下操作:
PagingAndSortingRepository<User, Long> repository = // … get access to a bean Page<User> users = repository.findAll(PageRequest.of(1, 20));
除了查询方法之外,还可以对计数查询和删除查询进行查询派生。 以下列表显示了派生计数查询的接口定义:
例 3.派生计数查询
interface UserRepository extends CrudRepository<User, Long> { long countByLastname(String lastname); }
以下清单显示了派生删除查询的接口定义:
例 4.派生删除查询
interface UserRepository extends CrudRepository<User, Long> { long deleteByLastname(String lastname); List<User> removeByLastname(String lastname); }
二、查询方法
标准 CRUD 功能存储库通常对基础数据存储具有查询。 使用 Spring Data,声明这些查询变成了一个四步过程:
- 声明扩展存储库或其子接口之一的接口,并将其键入应处理的域类和 ID 类型,如以下示例所示:
interface PersonRepository extends Repository<Person, Long> { … }
2.在接口上声明查询方法。
interface PersonRepository extends Repository<Person, Long> { List<Person> findByLastname(String lastname); }
3.设置Spring以使用JavaConfig或XML配置为这些接口创建代理实例。
import org.springframework.data.….repository.config.EnableJpaRepositories; @EnableJpaRepositories class Config { … }
本例中使用了JPA名称空间。如果您将存储库抽象用于任何其他存储,则需要将其更改为存储模块的适当名称空间声明。换言之,您应该交换jpa以支持例如mongodb。
注意,JavaConfig变量没有显式配置包,因为默认情况下使用带注释类的包。要自定义要扫描的包,请使用数据存储特定存储库的@EnableJpaRepositorys注释的basePackage…属性之一。
注入存储库实例并使用它,如下例所示:
class SomeClient { private final PersonRepository repository; SomeClient(PersonRepository repository) { this.repository = repository; } void doSomething() { List<Person> persons = repository.findByLastname("Matthews"); } }
三.定义存储库接口
要定义存储库接口,首先需要定义特定于域类的存储库接口。接口必须扩展Repository,并键入域类和ID类型。如果要公开该域类型的CRUD方法,可以扩展CrudRepository或其变体之一而不是Repository。
1. Fine-tuning Repository定义
如何开始使用存储库界面有几个变体。
典型的方法是扩展CrudRepository,它为CRUD功能提供了方法。CRUD代表创建、读取、更新和删除。在3.0版本中,我们还引入了ListCrudRepository,它与CrudRepoository非常相似,但对于那些返回多个实体的方法,它返回的是List而不是Iterable,这可能更容易使用。
如果您使用的是反应式存储,您可以选择ReactiveCrudRepository或RxJava3CrudRepository,具体取决于您使用的反应式框架。
此外,如果您需要允许指定排序抽象或在第一种情况下指定可分页抽象的方法,则可以扩展PagingAndSortingRepository、ReactiveSortingRepository、RxJava3SortingRepository或CoroutineSortingRepository。注意,不同的排序存储库不再像Spring Data Versions 3.0之前那样扩展各自的CRUD存储库。因此,如果您希望两个接口都具有功能,则需要扩展这两个接口。
如果不想扩展SpringData接口,还可以使用@RepositoryDefinition注释存储库接口。扩展其中一个CRUD存储库接口将公开一整套操作实体的方法。如果您希望对公开的方法有所选择,请将要公开的方法从CRUD存储库复制到域存储库中。执行此操作时,可以更改方法的返回类型。如果可能,Spring Data将采用返回类型。例如,对于返回多个实体的方法,可以选择Iterable<T>、List<T>,Collection<T>或VAVR列表。
如果应用程序中的许多存储库应该具有相同的方法集,则可以定义自己的基本接口以从中继承。这样的接口必须用@NoRepositoryBean注释。这防止了SpringData尝试直接创建它的实例并失败,因为它无法确定该存储库的实体,因为它仍然包含泛型类型变量。
以下示例显示了如何选择性地公开CRUD方法(本例中为findById和save):
示例5。选择性地公开CRUD方法:
@NoRepositoryBean interface MyBaseRepository<T, ID> extends Repository<T, ID> { Optional<T> findById(ID id); <S extends T> S save(S entity); } interface UserRepository extends MyBaseRepository<User, Long> { User findByEmailAddress(EmailAddress emailAddress); }
在前面的示例中,您为所有域存储库定义了一个公共的基本接口,并公开了findById(…)和save(…)。这些方法被路由到Spring Data提供的您选择的存储库的基本存储库实现中(例如,如果您使用JPA,则实现为SimpleJpaRepository),因为它们与CrudRepository中的方法签名匹配。因此,UserRepository现在可以保存用户,按ID查找单个用户,并触发查询以按电子邮件地址查找用户。
2.使用具有多个 Spring 数据模块的存储库
在应用程序中使用唯一的Spring Data模块使事情变得简单,因为定义范围内的所有存储库接口都绑定到Spring Data模块。有时,应用程序需要使用多个Spring Data模块。在这种情况下,存储库定义必须区分持久性技术。当它在类路径上检测到多个存储库工厂时,SpringData进入严格的存储库配置模式。严格配置使用存储库或域类的详细信息来决定存储库定义的Spring Data模块绑定:
- 如果存储库定义扩展了特定于模块的存储库,则它是特定SpringData模块的有效候选。
- 如果域类使用模块特定的类型注释进行了注释,则它是特定SpringData模块的有效候选。Spring Data模块接受第三方注释(如JPA的@Entity)或提供自己的注释(如@Document for Spring Data MongoDB和Spring Data Elasticsearch)。
以下示例显示了使用模块特定接口(本例中为JPA)的存储库:
例 6.使用特定于模块的接口的存储库定义
interface AmbiguousRepository extends Repository<User, Long> { … } @NoRepositoryBean interface MyBaseRepository<T, ID> extends CrudRepository<T, ID> { … } interface AmbiguousUserRepository extends MyBaseRepository<User, Long> { … }
MyRepository和UserRepository在其类型层次结构中扩展JpaRepository。它们是SpringDataJPA模块的有效候选。
以下示例显示了使用通用接口的存储库:
例 7.使用通用接口的存储库定义
interface AmbiguousRepository extends Repository<User, Long> { … } @NoRepositoryBean interface MyBaseRepository<T, ID> extends CrudRepository<T, ID> { … } interface AmbiguousUserRepository extends MyBaseRepository<User, Long> { … }
AmbiguousRepository和Ambiguous UserRepository在其类型层次结构中仅扩展Repository和CrudRepository。虽然这在使用唯一的Spring Data模块时很好,但多个模块无法区分这些存储库应该绑定到哪个特定的Spring Data。
以下示例显示了一个使用带有注释的域类的存储库:
例 8.使用带有注释的域类的存储库定义
interface PersonRepository extends Repository<Person, Long> { … } @Entity class Person { … } interface UserRepository extends Repository<User, Long> { … } @Document class User { … }
PersonRepository引用Person,Person使用JPA@Entity注释进行注释,因此该存储库显然属于SpringDataJPA。UserRepository引用User,该User使用Spring Data MongoDB的@Document注释进行注释。
以下错误示例显示了一个使用具有混合注释的域类的存储库:
例 9.使用具有混合注释的域类的存储库定义
interface JpaPersonRepository extends Repository<Person, Long> { … } interface MongoDBPersonRepository extends Repository<Person, Long> { … } @Entity @Document class Person { … }
这个示例显示了一个同时使用JPA和SpringDataMongoDB注释的域类。它定义了两个存储库,JpaPersonRepository和MongoDB PersonRepository。一个用于JPA,另一个用于MongoDB。SpringData不再能够区分存储库,这导致了未定义的行为。
存储库类型详细信息和区分域类注释用于严格的存储库配置,以识别特定Spring Data模块的候选存储库。在同一域类型上使用多个持久性技术特定的注释是可能的,并支持跨多个持久化技术重用域类型。然而,SpringData无法再确定绑定存储库的唯一模块。
区分存储库的最后一种方法是确定存储库基本包的范围。基本包定义了扫描存储库接口定义的起点,这意味着存储库定义位于适当的包中。默认情况下,注释驱动配置使用配置类的包。基于XML的配置中的基本包是必需的。
以下示例显示了基本包的注释驱动配置:
例 10.注释驱动的基本包配置
@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa") @EnableMongoRepositories(basePackages = "com.acme.repositories.mongo") class Configuration { … }
四、定义查询方法
存储库代理有两种方法可以从方法名称派生特定于存储的查询:
- 通过直接从方法名称派生查询。
- 通过使用手动定义的查询。
1. 查询查找策略
以下策略可用于存储库基础结构来解决查询。使用XML配置,您可以通过查询查找策略属性在命名空间中配置策略。对于Java配置,可以使用EnableJpaRepositorys注释的queryLookupStrategy属性。某些策略可能不支持特定的数据存储。
- CREATE尝试根据查询方法名称构造特定于存储的查询。一般的方法是从方法名中删除一组已知的前缀,并解析方法的其余部分。您可以在“查询创建”中阅读有关查询构造的更多信息。
- USE_DECLARED_QUERY尝试查找声明的查询,如果找不到则抛出异常。查询可以由某处的注释定义,也可以通过其他方式声明。请参阅特定商店的文档以查找该商店的可用选项。如果存储库基础结构在引导时未找到该方法的声明查询,则会失败。
- CREATE_IF_NOT_FOUND(默认值)结合了CREATE和USE_DECLARED_QUERY。它首先查找一个声明的查询,如果没有找到声明的查询则创建一个基于方法名的自定义查询。这是默认的查找策略,因此,如果未显式配置任何内容,则使用该策略。它允许通过方法名快速定义查询,但也可以根据需要引入声明的查询来定制这些查询。
2.查询创建
Spring 数据存储库基础结构中内置的查询生成器机制对于构建对存储库实体的约束查询非常有用。
下面的示例演示如何创建多个查询:
例 11.从方法名称创建查询
interface PersonRepository extends Repository<Person, Long> { List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname); // Enables the distinct flag for the query List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname); List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname); // Enabling ignoring case for an individual property List<Person> findByLastnameIgnoreCase(String lastname); // Enabling ignoring case for all suitable properties List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname); // Enabling static ORDER BY for a query List<Person> findByLastnameOrderByFirstnameAsc(String lastname); List<Person> findByLastnameOrderByFirstnameDesc(String lastname); }
3.特殊参数处理
要处理查询中的参数,请定义前面示例中已经看到的方法参数。除此之外,基础结构还识别某些特定类型,如Pageable和Sort,以动态地对查询应用分页和排序。以下示例演示了这些功能:
例 12.在查询方法中使用 、 和PageableSliceSort
Page<User> findByLastname(String lastname, Pageable pageable); Slice<User> findByLastname(String lastname, Pageable pageable); List<User> findByLastname(String lastname, Sort sort); List<User> findByLastname(String lastname, Pageable pageable);
4.分页和排序
可以使用属性名称定义简单的排序表达式。 您可以连接表达式以将多个条件收集到一个表达式中。
例 13.定义排序表达式
Sort sort = Sort.by("firstname").ascending() .and(Sort.by("lastname").descending());
有关定义排序表达式的更类型安全的方法,请从定义排序表达式的类型开始,并使用方法引用定义要排序的属性。
例 14。使用类型安全的 API 定义排序表达式
TypedSort<Person> person = Sort.sort(Person.class); Sort sort = person.by(Person::getFirstname).ascending() .and(person.by(Person::getLastname).descending());
如果您的商店实现支持 Querydsl,您还可以使用生成的元模型类型来定义排序表达式:
例 15。使用 Querydsl API 定义排序表达式
QSort sort = QSort.by(QPerson.firstname.asc()) .and(QSort.by(QPerson.lastname.desc()));
5.限制查询结果
您可以通过使用第一个或顶部关键字来限制查询方法的结果,这两个关键字可以互换使用。您可以在顶部或第一个附加一个可选的数值,以指定要返回的最大结果大小。如果省略了该数字,则假定结果大小为1。以下示例显示了如何限制查询大小:
例 16。使用 和 限制查询的结果大小TopFirst
User findFirstByOrderByLastnameAsc(); User findTopByOrderByAgeDesc(); Page<User> queryFirst10ByLastname(String lastname, Pageable pageable); Slice<User> findTop3ByLastname(String lastname, Pageable pageable); List<User> findFirst10ByLastname(String lastname, Sort sort); List<User> findTop10ByLastname(String lastname, Pageable pageable);
限制表达式还支持支持不同查询的数据存储的Distinct关键字。此外,对于将结果集限制为一个实例的查询,支持使用Optional关键字将结果包装为。
如果分页或切片应用于限制查询分页(以及可用页面数的计算),则在限制结果内应用。
五、使用示例
- 引入依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
2.示例代码
import java.util.Optional; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.PagingAndSortingRepository; /** * A Spring Data repository to persist {@link User}s. * * @author Oliver Gierke */ interface UserRepository extends CrudRepository<User, Long>, PagingAndSortingRepository<User, Long> { /** * Returns the user with the given {@link Username}. * * @param username can be {@literal null}. * @return */ Optional<User> findByUsername(Username username); }
import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.util.Assert; /** * Domain service to register {@link User}s in the system. * * @author Oliver Gierke */ @Transactional @Service @RequiredArgsConstructor public class UserManagement { private final UserRepository repository; private final PasswordEncoder encoder; /** * Registers a {@link User} with the given {@link Username} and {@link Password}. * * @param username must not be {@literal null}. * @param password must not be {@literal null}. * @return */ public User register(Username username, Password password) { Assert.notNull(username, "Username must not be null!"); Assert.notNull(password, "Password must not be null!"); repository.findByUsername(username).ifPresent(user -> { throw new IllegalArgumentException("User with that name already exists!"); }); var encryptedPassword = Password.encrypted(encoder.encode(password)); return repository.save(new User(username, encryptedPassword)); } /** * Returns a {@link Page} of {@link User} for the given {@link Pageable}. * * @param pageable must not be {@literal null}. * @return */ public Page<User> findAll(Pageable pageable) { Assert.notNull(pageable, "Pageable must not be null!"); return repository.findAll(pageable); } /** * Returns the {@link User} with the given {@link Username}. * * @param username must not be {@literal null}. * @return */ public Optional<User> findByUsername(Username username) { Assert.notNull(username, "Username must not be null!"); return repository.findByUsername(username); } /** * Creates a few sample users. */ @PostConstruct public void init() { IntStream.range(0, 41).forEach(index -> { register(new Username("user" + index), Password.raw("foobar")); }); } }
@Controller @RequiredArgsConstructor @RequestMapping("/users") class UserController { private final UserManagement userManagement; /** * Populates the model with a {@link Page} of {@link User}s. Spring Data automatically populates the {@link Pageable} from * request data according to the setup of {@link PageableHandlerMethodArgumentResolver}. Note how the defaults can be * tweaked by using {@link PageableDefault}. * * @param pageable will never be {@literal null}. * @return */ @ModelAttribute("users") public Page<User> users(@PageableDefault(size = 5) Pageable pageable) { return userManagement.findAll(pageable); } /** * Registers a new {@link User} for the data provided by the given {@link UserForm}. Note, how an interface is used to * bind request parameters. * * @param userForm the request data bound to the {@link UserForm} instance. * @param binding the result of the binding operation. * @param model the Spring MVC {@link Model}. * @return */ @RequestMapping(method = RequestMethod.POST) public Object register(UserForm userForm, BindingResult binding, Model model) { userForm.validate(binding, userManagement); if (binding.hasErrors()) { return "users"; } userManagement.register(new Username(userForm.getUsername()), Password.raw(userForm.getPassword())); var redirectView = new RedirectView("redirect:/users"); redirectView.setPropagateQueryParams(true); return redirectView; } /** * Populates the {@link Model} with the {@link UserForm} automatically created by Spring Data web components. It will * create a {@link Map}-backed proxy for the interface. * * @param model will never be {@literal null}. * @param userForm will never be {@literal null}. * @return */ @RequestMapping(method = RequestMethod.GET) public String listUsers(Model model, UserForm userForm) { model.addAttribute("userForm", userForm); return "users"; } /** * An interface to represent the form to be used * * @author Oliver Gierke */ interface UserForm { String getUsername(); String getPassword(); String getRepeatedPassword(); /** * Validates the {@link UserForm}. * * @param errors * @param userManagement */ default void validate(BindingResult errors, UserManagement userManagement) { rejectIfEmptyOrWhitespace(errors, "username", "user.username.empty"); rejectIfEmptyOrWhitespace(errors, "password", "user.password.empty"); rejectIfEmptyOrWhitespace(errors, "repeatedPassword", "user.repeatedPassword.empty"); if (!getPassword().equals(getRepeatedPassword())) { errors.rejectValue("repeatedPassword", "user.password.no-match"); } try { userManagement.findByUsername(new Username(getUsername())).ifPresent( user -> errors.rejectValue("username", "user.username.exists")); } catch (IllegalArgumentException o_O) { errors.rejectValue("username", "user.username.invalidFormat"); } } } }
官网:Doker 多克; 官方旗舰店:首页-Doker 多克 多克创新科技企业店-淘宝网全品优惠,期待您的支持!!!