Merge JPA Java 8 examples with general examples.
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
```
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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!");
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user