Merge JPA Java 8 examples with general examples.

This commit is contained in:
Mark Paluch
2021-04-29 08:51:31 +02:00
parent 8c7e85366c
commit 743937c45f
12 changed files with 146 additions and 450 deletions

View File

@@ -37,7 +37,6 @@ We have separate folders for the samples of individual modules:
* `eclipselink` - Sample project to show how to use Spring Data JPA with Spring Boot and [Eclipselink](https://www.eclipse.org/eclipselink/).
* `example` - Probably the project you want to have a look at first. Contains a variety of sample packages, showcasing the different levels at which you can use Spring Data JPA. Have a look at the `simple` package for the most basic setup.
* `interceptors` - Example of how to enrich the repositories with AOP.
* `java8` - Example of how to use Spring Data JPA auditing with Java 8 date time types as well as the usage of `Optional` as return type for repository methods. Note, this project requires to be build with JDK 8.
* `jpa21` - Shows support for JPA 2.1 specific features (stored procedures support).
* `multiple-datasources` - Examples of how to use Spring Data JPA with multiple `DataSource`s.
* `query-by-example` - Example project showing usage of Query by Example with Spring Data JPA.

View File

@@ -15,14 +15,19 @@
*/
package example.springdata.jpa.simple;
import example.springdata.jpa.projections.Customer;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.scheduling.annotation.Async;
/**
* Simple repository interface for {@link User} instances. The interface is used to declare so called query methods,
@@ -130,4 +135,34 @@ public interface SimpleUserRepository extends CrudRepository<User, Long> {
*/
@Query("select u from User u where u.firstname = :#{#user.firstname} or u.lastname = :#{#user.lastname}")
Iterable<User> findByFirstnameOrLastname(User user);
/**
* Sample default method.
*
* @param user
* @return
*/
default List<User> findByLastname(User user) {
return findByLastname(user == null ? null : user.getLastname());
}
/**
* Sample method to demonstrate support for {@link Stream} as a return type with a custom query. The query is executed
* in a streaming fashion which means that the method returns as soon as the first results are ready.
*
* @return
*/
@Query("select u from User u")
Stream<User> streamAllCustomers();
/**
* Sample method to demonstrate support for {@link Stream} as a return type with a derived query. The query is
* executed in a streaming fashion which means that the method returns as soon as the first results are ready.
*
* @return
*/
Stream<User> findAllByLastnameIsNotNull();
@Async
CompletableFuture<List<User>> readAllBy();
}

View File

@@ -49,6 +49,11 @@ public class User extends AbstractPersistable<Long> {
this.setId(id);
}
public User(String firstname, String lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
/**
* Returns the username.
*

View File

@@ -15,24 +15,32 @@
*/
package example.springdata.jpa.simple;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.data.domain.Sort.Direction.ASC;
import static org.springframework.data.domain.Sort.Direction.DESC;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.domain.Sort.Direction.*;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
@@ -47,6 +55,7 @@ import org.springframework.transaction.annotation.Transactional;
@ExtendWith(SpringExtension.class)
@Transactional
@SpringBootTest
@Slf4j
public class SimpleUserRepositoryTests {
@Autowired SimpleUserRepository repository;
@@ -99,11 +108,11 @@ public class SimpleUserRepositoryTests {
public void removeByLastname() {
// create a 2nd user with the same lastname as user
User user2 = new User();
var user2 = new User();
user2.setLastname(user.getLastname());
// create a 3rd user as control group
User user3 = new User();
var user3 = new User();
user3.setLastname("no-positive-match");
repository.saveAll(Arrays.asList(user, user2, user3));
@@ -118,12 +127,12 @@ public class SimpleUserRepositoryTests {
repository.deleteAll();
// int repository with some values that can be ordered
int totalNumberUsers = 11;
var totalNumberUsers = 11;
List<User> source = new ArrayList<User>(totalNumberUsers);
for (int i = 1; i <= totalNumberUsers; i++) {
for (var i = 1; i <= totalNumberUsers; i++) {
User user = new User();
var user = new User();
user.setLastname(this.user.getLastname());
user.setUsername(user.getLastname() + "-" + String.format("%03d", i));
source.add(user);
@@ -131,7 +140,7 @@ public class SimpleUserRepositoryTests {
repository.saveAll(source);
Slice<User> users = repository.findByLastnameOrderByUsernameAsc(this.user.getLastname(), PageRequest.of(1, 5));
var users = repository.findByLastnameOrderByUsernameAsc(this.user.getLastname(), PageRequest.of(1, 5));
assertThat(users).containsAll(source.subList(5, 10));
}
@@ -139,19 +148,19 @@ public class SimpleUserRepositoryTests {
@Test
public void findFirst2ByOrderByLastnameAsc() {
User user0 = new User();
var user0 = new User();
user0.setLastname("lastname-0");
User user1 = new User();
var user1 = new User();
user1.setLastname("lastname-1");
User user2 = new User();
var user2 = new User();
user2.setLastname("lastname-2");
// we deliberatly save the items in reverse
repository.saveAll(Arrays.asList(user2, user1, user0));
List<User> result = repository.findFirst2ByOrderByLastnameAsc();
var result = repository.findFirst2ByOrderByLastnameAsc();
assertThat(result).containsExactly(user0, user1);
}
@@ -159,23 +168,23 @@ public class SimpleUserRepositoryTests {
@Test
public void findTop2ByWithSort() {
User user0 = new User();
var user0 = new User();
user0.setLastname("lastname-0");
User user1 = new User();
var user1 = new User();
user1.setLastname("lastname-1");
User user2 = new User();
var user2 = new User();
user2.setLastname("lastname-2");
// we deliberately save the items in reverse
repository.saveAll(Arrays.asList(user2, user1, user0));
List<User> resultAsc = repository.findTop2By(Sort.by(ASC, "lastname"));
var resultAsc = repository.findTop2By(Sort.by(ASC, "lastname"));
assertThat(resultAsc).containsExactly(user0, user1);
List<User> resultDesc = repository.findTop2By(Sort.by(DESC, "lastname"));
var resultDesc = repository.findTop2By(Sort.by(DESC, "lastname"));
assertThat(resultDesc).containsExactly(user2, user1);
}
@@ -183,22 +192,95 @@ public class SimpleUserRepositoryTests {
@Test
public void findByFirstnameOrLastnameUsingSpEL() {
User first = new User();
var first = new User();
first.setLastname("lastname");
User second = new User();
var second = new User();
second.setFirstname("firstname");
User third = new User();
var third = new User();
repository.saveAll(Arrays.asList(first, second, third));
User reference = new User();
var reference = new User();
reference.setFirstname("firstname");
reference.setLastname("lastname");
Iterable<User> users = repository.findByFirstnameOrLastname(reference);
var users = repository.findByFirstnameOrLastname(reference);
assertThat(users).containsExactly(first, second);
}
/**
* Streaming data from the store by using a repository method that returns a {@link Stream}. Note, that since the
* resulting {@link Stream} contains state it needs to be closed explicitly after use!
*/
@Test
public void useJava8StreamsWithCustomQuery() {
var user1 = repository.save(new User("Customer1", "Foo"));
var user2 = repository.save(new User("Customer2", "Bar"));
try (var stream = repository.streamAllCustomers()) {
assertThat(stream.collect(Collectors.toList())).contains(user1, user2);
}
}
/**
* Streaming data from the store by using a repository method that returns a {@link Stream} with a derived query.
* Note, that since the resulting {@link Stream} contains state it needs to be closed explicitly after use!
*/
@Test
public void useJava8StreamsWithDerivedQuery() {
var user1 = repository.save(new User("Customer1", "Foo"));
var user2 = repository.save(new User("Customer2", "Bar"));
try (var stream = repository.findAllByLastnameIsNotNull()) {
assertThat(stream.collect(Collectors.toList())).contains(user1, user2);
}
}
/**
* Query methods using streaming need to be used inside a surrounding transaction to keep the connection open while
* the stream is consumed. We simulate that not being the case by actively disabling the transaction here.
*/
@Test
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void rejectsStreamExecutionIfNoSurroundingTransactionActive() {
Assertions.assertThrows(InvalidDataAccessApiUsageException.class, () -> {
repository.findAllByLastnameIsNotNull();
});
}
/**
* Here we demonstrate the usage of {@link CompletableFuture} as a result wrapper for asynchronous repository query
* methods. Note, that we need to disable the surrounding transaction to be able to asynchronously read the written
* data from from another thread within the same test method.
*/
@Test
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void supportsCompletableFuturesAsReturnTypeWrapper() throws Exception {
repository.save(new User("Customer1", "Foo"));
repository.save(new User("Customer2", "Bar"));
var future = repository.readAllBy().thenAccept(users -> {
assertThat(users).hasSize(2);
users.forEach(customer -> log.info(customer.toString()));
log.info("Completed!");
});
while (!future.isDone()) {
log.info("Waiting for the CompletableFuture to finish...");
TimeUnit.MILLISECONDS.sleep(500);
}
future.get();
log.info("Done!");
repository.deleteAll();
}
}

View File

@@ -1,65 +0,0 @@
# Spring Data - Java 8 examples
This project contains samples of Java 8 specific features of Spring Data (JPA).
## Support for JDK 8's `Optional` for repository methods
```java
interface CustomerRepository extends Repository<Customer, Long> {
// CRUD method using Optional
Optional<Customer> findOne(Long id);
// Query method using Optional
Optional<Customer> findByLastname(String lastname);
}
```
* `CustomerRepository.findOne(Long)` effectively "overrides" the default `CrudRepository.findOne(…)` to return `Optional.empty()` instead of `null` in case no unique element satisfying the query can be found.
* `CustomerRepository.findByLastname(…)` does the same for a normal query method.
## Support for JDK 8's date/time types in the auditing sub-system
* `Customer` extends `AbstractEntity` which contains fields of JDK 8's new date/time API and those can be populated the same way legacy types like `Date` and `Calendar` can.
## Support for JDK 8' `Stream` in repository methods
JPA repositories can now use `Stream` as return type for query methods to trigger streamed execution of the query. This will cause Spring Data JPA to use persistence provider specific API to traverse the query result one-by-one.
```java
interface CustomerRepository extends Repository<Customer, Long> {
@Query("select c from Customer c")
Stream<Customer> streamAllCustomers();
}
try (Stream<Customer> customers = repository.streamAllCustomers()) {
// use the stream here
}
```
Note how the returned `Stream` has to be used in a try-with-resources clause as the underlying resources have to be closed once we finished iterating over the result.
## Support for JDK 8' `CompletableFuture` in `@Async` repository methods
JPA repositories can now use `CompletableFuture` as return type for query methods for async execution of the query with support for fluent processing.
Note that: Support for `CompletableFuture` in combination with `@Async` is available in Spring Framework 4.2.x.
```java
interface CustomerRepository extends Repository<Customer, Long> {
@Async
CompletableFuture<List<Customer>> readAllBy();
}
CompletableFuture<List<Customer>> future = repository.readAllBy().thenApply(this::doSomethingWithCustomers);
while (!future.isDone()) {
log.info("Waiting for the CompletableFuture to finish...");
TimeUnit.MILLISECONDS.sleep(500);
}
List<Customer> processedCustomers = future.get();
```

View File

@@ -1,15 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.data.examples</groupId>
<artifactId>spring-data-jpa-examples</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-data-jpa-java8</artifactId>
<name>Spring Data JPA - Java 8 specific features</name>
</project>

View File

@@ -1,40 +0,0 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.jpa.java8;
import java.time.LocalDateTime;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
/**
* @author Oliver Gierke
*/
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class AbstractEntity {
@Id @GeneratedValue Long id;
@CreatedDate LocalDateTime createdDate;
@LastModifiedDate LocalDateTime modifiedDate;
}

View File

@@ -1,25 +0,0 @@
/*
* Copyright 2014-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.jpa.java8;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableAsync;
@EnableAsync
@SpringBootApplication
@EnableJpaAuditing
class AuditingConfiguration {}

View File

@@ -1,40 +0,0 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.jpa.java8;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import javax.persistence.Entity;
/**
* @author Oliver Gierke
* @author Thomas Darimont
*/
@Entity
@Getter
@ToString
@AllArgsConstructor
public class Customer extends AbstractEntity {
String firstname, lastname;
protected Customer() {
this.firstname = null;
this.lastname = null;
}
}

View File

@@ -1,89 +0,0 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.jpa.java8;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.Repository;
import org.springframework.scheduling.annotation.Async;
/**
* Repository to manage {@link Customer} instances.
*
* @author Oliver Gierke
* @author Thomas Darimont
*/
public interface CustomerRepository extends Repository<Customer, Long> {
/**
* Special customization of {@link CrudRepository#findOne(java.io.Serializable)} to return a JDK 8 {@link Optional}.
*
* @param id
* @return
*/
Optional<Customer> findById(Long id);
/**
* Saves the given {@link Customer}.
*
* @param customer
* @return
*/
<S extends Customer> S save(S customer);
/**
* Sample method to derive a query from using JDK 8's {@link Optional} as return type.
*
* @param lastname
* @return
*/
Optional<Customer> findByLastname(String lastname);
/**
* Sample default method to show JDK 8 feature support.
*
* @param customer
* @return
*/
default Optional<Customer> findByLastname(Customer customer) {
return findByLastname(customer == null ? null : customer.lastname);
}
/**
* Sample method to demonstrate support for {@link Stream} as a return type with a custom query. The query is executed
* in a streaming fashion which means that the method returns as soon as the first results are ready.
*
* @return
*/
@Query("select c from Customer c")
Stream<Customer> streamAllCustomers();
/**
* Sample method to demonstrate support for {@link Stream} as a return type with a derived query. The query is
* executed in a streaming fashion which means that the method returns as soon as the first results are ready.
*
* @return
*/
Stream<Customer> findAllByLastnameIsNotNull();
@Async
CompletableFuture<List<Customer>> readAllBy();
}

View File

@@ -1,150 +0,0 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.jpa.java8;
import static org.assertj.core.api.Assertions.*;
import lombok.extern.slf4j.Slf4j;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* Integration test to show the usage of Java 8 date time APIs with Spring Data JPA auditing.
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Divya Srivastava
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest
@Transactional
@Slf4j
public class Java8IntegrationTests {
@Autowired CustomerRepository repository;
@Test
public void providesFindOneWithOptional() {
Customer carter = repository.save(new Customer("Carter", "Beauford"));
assertThat(repository.findById(carter.id)).isPresent();
assertThat(repository.findById(carter.id + 1)).isEmpty();
}
@Test
public void auditingSetsJdk8DateTimeTypes() {
Customer customer = repository.save(new Customer("Dave", "Matthews"));
assertThat(customer.createdDate).isNotNull();
assertThat(customer.modifiedDate).isNotNull();
}
@Test
public void invokesDefaultMethod() {
Customer customer = repository.save(new Customer("Dave", "Matthews"));
Optional<Customer> result = repository.findByLastname(customer);
assertThat(result).hasValue(customer);
}
/**
* Streaming data from the store by using a repository method that returns a {@link Stream}. Note, that since the
* resulting {@link Stream} contains state it needs to be closed explicitly after use!
*/
@Test
public void useJava8StreamsWithCustomQuery() {
Customer customer1 = repository.save(new Customer("Customer1", "Foo"));
Customer customer2 = repository.save(new Customer("Customer2", "Bar"));
try (Stream<Customer> stream = repository.streamAllCustomers()) {
assertThat(stream.collect(Collectors.toList())).contains(customer1, customer2);
}
}
/**
* Streaming data from the store by using a repository method that returns a {@link Stream} with a derived query.
* Note, that since the resulting {@link Stream} contains state it needs to be closed explicitly after use!
*/
@Test
public void useJava8StreamsWithDerivedQuery() {
Customer customer1 = repository.save(new Customer("Customer1", "Foo"));
Customer customer2 = repository.save(new Customer("Customer2", "Bar"));
try (Stream<Customer> stream = repository.findAllByLastnameIsNotNull()) {
assertThat(stream.collect(Collectors.toList())).contains(customer1, customer2);
}
}
/**
* Query methods using streaming need to be used inside a surrounding transaction to keep the connection open while
* the stream is consumed. We simulate that not being the case by actively disabling the transaction here.
*/
@Test
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void rejectsStreamExecutionIfNoSurroundingTransactionActive() {
Assertions.assertThrows(InvalidDataAccessApiUsageException.class, () -> {
repository.findAllByLastnameIsNotNull();
});
}
/**
* Here we demonstrate the usage of {@link CompletableFuture} as a result wrapper for asynchronous repository query
* methods. Note, that we need to disable the surrounding transaction to be able to asynchronously read the written
* data from from another thread within the same test method.
*/
@Test
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void supportsCompletableFuturesAsReturnTypeWrapper() throws Exception {
repository.save(new Customer("Customer1", "Foo"));
repository.save(new Customer("Customer2", "Bar"));
CompletableFuture<Void> future = repository.readAllBy().thenAccept(customers -> {
assertThat(customers).hasSize(2);
customers.forEach(customer -> log.info(customer.toString()));
log.info("Completed!");
});
while (!future.isDone()) {
log.info("Waiting for the CompletableFuture to finish...");
TimeUnit.MILLISECONDS.sleep(500);
}
future.get();
log.info("Done!");
}
}

View File

@@ -20,7 +20,6 @@
<module>example</module>
<module>showcase</module>
<module>interceptors</module>
<module>java8</module>
<module>jpa21</module>
<module>security</module>
<module>multiple-datasources</module>