此文章为Spring Data JPA - Reference Documentation(2.1.9.RELEASE)的备忘录。
Reference
Repository Query Keyword
Working with Spring Data Repositories
Core concepts
Repository
Combine method using find, count, get, delete, remove, read, distinct, OrderBy*Asc, and, lessThan, IgnoreCase, AllIgnoreCase.
Extended:
- CrudRepository: some simple method about CRUD, i guess.
- PagingAndSortingRepository:
Additional parameter candidates: Sort, Pageable(usage: PageRequest.of(1, 20)), Page can be returned.
Query method
package-info.java
4.3 Defining Repository Interface
Repository -> CrudRepository -> PagingAndSortingRepository -> JpaRepository(MangoDbRepository)
PageRequest.of(1, 20)
- By extending annotation before.
- By annotating with @RepositoryDefinition
Selectively expose crud method
@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> 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);
}
Make sure that you add @NoRepositoryBean to all repositories for which should not create an instance at runtime.
Null handling for repository method
- Add @NonNullApi on the package-info.java to make non-null constrain work.
- Using @NonNull and @Nullable to fine-tune.
Distinguishing to Spring Data Module
- Extending @JpaRepository, using Spring Data JPA module.
- Domain annotated with @Entity, using Spring Data JPA module, MongoDb if @Document.
- Annotated with @EnableJpaRepositories and @EnableMongoRepositories with attributes basePackages to define packages to be scanned as the corresponding repository.
4.4 Defining Query Method
@EnableJpaRepositories
basePackages; queryLookupStrategy; repositoryBaseClass can be used to customize the base repository.
4.4.1 Query Lookup Strategies
4.4.2 Query Creation
findTop10ByLastnameAndFirstnameAllIgnoreCaseOrderByGenderAcs
findByAddressZipCode: AddressZip.Code -> Address.ZipCode
findByAddress_ZipCode: Address.ZipCodeSlice<User> findByLastname(String lastname, Pageable pageable, Sort sort);
Stream<User>(Jave8 String<T>) can be returned.Future<User>, CompletableFuture<User>, ListenableFuture<User> can be used with annotaion @Async
4.5 Creating Repository Instances
4.6 Custom Implementations for Spring Data Repository
interface HumanRepository {
void someHumanMethod(User user);
}
// Impl postfix. In namespace configuration, postfix can be configured with attributes **repository-impl-postfix** in **repositories** tag
// If this class is annotated with **@Camponent("beanNameImpl")**, thing changed.
class HumanRepositoryImpl implements HumanRepository {
public void someHumanMethod(User user) {
// Your custom implementation
}
}
interface ContactRepository {
void someContactMethod(User user);
User anotherContactMethod(User user);
}
class ContactRepositoryImpl implements ContactRepository {
public void someContactMethod(User user) {
// Your custom implementation
}
public User anotherContactMethod(User user) {
// Your custom implementation
}
}
// usage
interface UserRepository extends CrudRepository<User, Long>, HumanRepository, ContactRepository {
// Declare query methods here
}
Custom implementations have a higher priority than base implementation. this feature let you can override base repository.
4.7 Publishing Events from Aggregate Roots
Using @DomainEvents and @AfterDomainEventPublication on a method of aggregate root class to publish events.
Focus on Domain-Driven Design and search for more.
4.8 Spring Data Extensions
4.8.1 Querydsl Extension
http://www.querydsl.com
Extending QuerydslPredicateExecutor on repository interface to active and used as follow.
Predicate predicate = user.firstname.equalsIgnoreCase("dave")
.and(user.lastname.startsWithIgnoreCase("mathews"));
userRepository.findAll(predicate);
4.8.2 Basic Web Support
@EnableSpringDataWebSupport in JavaConfig configuration class.
Spring-HATEOAS
DomainClassConverter: Resolve domain class from request parameters or path variables.
@Controller
@RequestMapping("/users")
class UserController {
// request body is not include User information. Attention that repository of User domain has to implement CrudRepository. It will find User domain through calling fingById() on the repository.
@RequestMapping("/{id}")
String showUserForm(@PathVariable("id") User user, Model model) {
model.addAttribute("user", user);
return "userForm";
}
}
HandlerMethodArgumentResolvers for Pageable and Sort: Register a PageableHandlerMethodArgumentResolver as well as an instance of SortHandlerMethodArgumentResolver to enable Pageable, Sort as valid controller method arguments.
@Controller
@RequestMapping("/users")
class UserController {
private final UserRepository repository;
UserController(UserRepository repository) {
this.repository = repository;
}
@RequestMapping
String showUsers(Model model, Pageable pageable) {
model.addAttribute("users", repository.findAll(pageable));
return "users";
}
}
Request parameters
page: default to 0
size: default to 20
sort: default sort direction is ascending.sort=firstname&sort=lastname,asc
Something to do
Spring Data Example repository
Repository population
...To be
5. JPA repository
The basics of configuring Spring Data JPA
5.1.2 Annotation-based configuration
@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;
}
}
5.1.3 Bootstraps Mode
Spring support the initialization of the JPA EntityManagerFactory in a background thread because the process usually take a significant amount of startup time.
DEFAULT: instantiate eagerly.
LAZY(reasonable): instantiate upon first interaction with repository.
DEFERRED(decent in dev): instantiate in response to an ContextRefreshedEvent.
5.2 Persisting Entities
CrudRepository.save = entityManager.save or entityManager.merge
Entity state-detection strategies
- Id-Property inspection(default)
- Implementing Persistable ang override isNew method
- Implementing EntityInformation
5.3 Query Methods
Derived queries with the predicates IsStartingWith, StartingWith, StartsWith, IsEndingWith", EndingWith, EndsWith, IsNotContaining, NotContaining, NotContains, IsContaining, Containing, Contains the respective arguments for these queries will get sanitized.
Set escapeCharacter of @EnableJpaRepository annotation
5.3.3 Using JPA Named Query
Annotation-based Configuration
Recompile domain class for every new query declaration.
@Entity
@NamedQuery(name = "User.findByEmailAddress",
query = "select u from User u where u.emailAddress = ?1")
public class User {
}
// To active it
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByLastname(String lastname);
User findByEmailAddress(String emailAddress);
}
5.3.4 Using @Query
Take precedence over @NamedQuery
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);
@Query("select u from User u where u.firstname like %?1")
List<User> findByFirstnameEndsWith(String firstname);
}
You can use the Native Queries for pagination by specify the count 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);
}
5.3.5 Using Sort
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 :lastname%")
List<Object[]> findByAsArrayAndSort(@Param("lastname") String lastname, Sort sort);
}
// Valid Sort expression pointing to property in domain model.
repo.findByAndSort("lannister", new Sort("firstname"));
// Invalid Sort containing function call. Thows Exception.
repo.findByAndSort("stark", new Sort("LENGTH(firstname)"));
// Valid Sort containing explicitly unsafe Order.
repo.findByAndSort("targaryen", JpaSort.unsafe("LENGTH(firstname)"));
// Valid Sort expression pointing to aliased function.
repo.findByAsArrayAndSort("bolton", new Sort("fn_len"));
5.3.7 Using SpEL
@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> { … }
// escapeCharacter of the @EnableJpaRepositories annotation
@Query("select u from User u where u.firstname like %?#{escape([0])}% escape ?#{escapeCharacter()}")
List<User> findContainingEscaped(String namePart);`
And more
5.3.8 Modifying Queries
@Modifying
@Query("update User u set u.firstname = ?1 where u.lastname = ?2")
int setFixedFirstnameFor(String firstname, String lastname);
// executes a query and then deletes the returned instances one by one
// so @PreRemove can be used
void deleteByRoleId(long roleId);
@Modifying
@Query("delete from User u where user.role.id = ?1")
void deleteInBulkByRoleId(long roleId);
5.3.9 Applying Query Hint
https://thoughts-on-java.org/...
public interface UserRepository extends Repository<User, Long> {
// query but omit applying it to the count query triggered to calculate the total number of pages.
@QueryHints(value = { @QueryHint(name = "name", value = "value")},
forCounting = false)
Page<User> findByLastname(String lastname, Pageable pageable);
}
5.3.10 Configuring Fetch- and LoadGraphs
@Entity
@NamedEntityGraph(name = "GroupInfo.detail",
attributeNodes = @NamedAttributeNode("members"))
public class GroupInfo {
// default fetch mode is lazy.
@ManyToMany
List<GroupMember> members = new ArrayList<GroupMember>();
…
}
// usage
@Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {
@EntityGraph(value = "GroupInfo.detail", type = EntityGraphType.LOAD)
GroupInfo getByGroupName(String name);
}
Using ad hoc entity graph definition on a repository query method
@Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {
@EntityGraph(attributePaths = { "members" })
GroupInfo getByGroupName(String name);
}
5.3.11 Projections
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);
}
interface NamesOnly {
String getFirstname();
String getLastname();
}
interface PersonRepository extends Repository<Person, UUID> {
Collection<NamesOnly> findByLastname(String lastname);
}
interface PersonSummary {
String getFirstname();
String getLastname();
AddressSummary getAddress();
interface AddressSummary {
String getCity();
}
}
All above are closed projection
interface NamesOnly {
@Value("#{target.firstname + ' ' + target.lastname}")
String getFullName();
…
}
For very sample expressions, one option might be to resort to default(introduced in Java8)
interface NamesOnly {
String getFirstname();
String getLastname();
default String getFullName() {
return getFirstname.concat(" ").concat(getLastname());
}
}
And for more flexible
@Component
class MyBean {
String getFullName(Person person) {
…
}
}
interface NamesOnly {
@Value("#{@myBean.getFullName(target)}")
String getFullName();
…
}
And using parameters
interface NamesOnly {
@Value("#{args[0] + ' ' + target.firstname + '!'}")
String getSalutation(String prefix);
}
// no proxying happens and no nested projections can be applied.
// If the store optimizes the query execution by limiting the fields to be loaded, the fields to be loaded are determined from the parameter names of the constructor that is exposed.
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
}
Using https://projectlombok.org to avoid boilerplates code.
@Value
class NamesOnly {
String firstname, lastname;
}
Dynamic Projections
interface PersonRepository extends Repository<Person, UUID> {
<T> Collection<T> findByLastname(String lastname, Class<T> type);
}
void someMethod(PersonRepository people) {
Collection<Person> aggregates =
people.findByLastname("Matthews", Person.class);
Collection<NamesOnly> aggregates =
people.findByLastname("Matthews", NamesOnly.class);
}
5.4 Stored Procedures
5.5 Specifications
- Extended with JpaSpecificationExecutor interface and define recording repository method.
public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor {
List<T> findAll(Specification<T> spec);
}
Specification interface is defined as follow:
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
CriteriaBuilder builder);
}
- Define Specification class
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(MonetaryAmount value) {
return new Specification<Customer>() {
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
CriteriaBuilder builder) {
// build query here
}
};
}
}
- Using Specification
List<Customer> customers = customerRepository.findAll(isLongTermCustomer());
and shines when you combine them
MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR);
List<Customer> customers = customerRepository.findAll(
isLongTermCustomer().or(hasSalesOfMoreThan(amount)));
5.6 Query by Example
-
Extend QueryByExampleExecutor<T> in repository class
QueryByExampleExecutor looks like
public interface QueryByExampleExecutor<T> {
<S extends T> S findOne(Example<S> example);
<S extends T> Iterable<S> findAll(Example<S> example);
// … more functionality omitted.
}
- Simple using
Person person = new Person();
person.setFirstname("Dave");
Example<Person> example = Example.of(person);
- Using with Example Matchers
Person person = new Person();
person.setFirstname("Dave");
ExampleMatcher matcher = ExampleMatcher.matching()
.withIgnorePaths("lastname")
.withIncludeNullValues()
.withStringMatcherEnding();
Example<Person> example = Example.of(person, matcher);
ExampleMatcher.matchingAny().Specify behaviors for individual properties
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("firstname", endsWith())
.withMatcher("lastname", startsWith().ignoreCase());
}
// Java 8 lambda
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("firstname", match -> match.endsWith())
.withMatcher("firstname", match -> match.startsWith());
}
And more information, go for documents.
5.7 Transactions
For read operations, the transaction configuration readOnly flag is set to true. All others are configured with a plain @Transactional so that default transaction configuration applies.
For details, see JavaDoc of [https://docs.spring.io/spring...]
Tweek default transaction settings:
public interface UserRepository extends CrudRepository<User, Long> {
@Override
@Transactional(timeout = 10)
public List<User> findAll();
// Further query method declarations
}
@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();
}
The readOnly flag is instead propagated as a hint to underlying JPA providers.
For example, when used in hibernate, the flush mode is set to Never which case hibernate to skip dirty check(a noticeable improvement on large object trees)
5.8 Locking
interface UserRepository extends Repository<User, Long> {
// Plain query method
@Lock(LockModeType.READ)
List<User> findByLastname(String lastname);
}
5.9 Auditing
Spring data provides sophisticated support to transparently keep track of who created or updated an entity and when the change happens.
Plan A.
// @CreatedBy, @LastModifiedBy, @CreatedDate, @LastModifiedDate
class CustomerEntity {
@CreatedBy
private User user;
@CreatedDate
private DateTime createdDate;
// … further properties omitted
}
Plan B.
Implement Auditable or extend AbstractAuditable
Doing so increases the coupling of your domain class to Spring Data. It's more invasive and less flexible.
More detail have to be seached.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。