Implement Query by Example.
Implement Spring Data's Query by Example feature. See #532 and https://github.com/spring-projects/spring-data-jdbc/issues/929.
This commit is contained in:
3
pom.xml
3
pom.xml
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<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">
|
||||
<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>
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
[[new-features]]
|
||||
= New & Noteworthy
|
||||
|
||||
[[new-features.1-3-0]]
|
||||
== What's New in Spring Data R2DBC 1.3.0
|
||||
|
||||
* Introduce <<r2dbc.repositories.queries.query-by-example,Query by Example support>>.
|
||||
|
||||
[[new-features.1-2-0]]
|
||||
== What's New in Spring Data R2DBC 1.2.0
|
||||
|
||||
|
||||
@@ -279,6 +279,47 @@ Extensions are retrieved from the application context at the time of SpEL evalua
|
||||
|
||||
TIP: When using SpEL expressions in combination with plain parameters, use named parameter notation instead of native bind markers to ensure a proper binding order.
|
||||
|
||||
[[r2dbc.repositories.queries.query-by-example]]
|
||||
=== Query By Example
|
||||
|
||||
Spring Data R2DBC also lets you use Query By Example to fashion queries.
|
||||
This technique allows you to use a "probe" object.
|
||||
Essentially, any field that isn't empty or `null` will be used to match.
|
||||
|
||||
Here's an example:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../{example-root}/QueryByExampleTests.java[tag=example]
|
||||
----
|
||||
<1> Create a domain object with the criteria (`null` fields will be ignored).
|
||||
<2> Using the domain object, create an `Example`.
|
||||
<3> Through the `R2dbcRepository`, execute query (use `findOne` for a `Mono`).
|
||||
====
|
||||
|
||||
This illustrates how to craft a simple probe using a domain object.
|
||||
In this case, it will query based on the `Employee` object's `name` field being equal to `Frodo`.
|
||||
`null` fields are ignored.
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../{example-root}/QueryByExampleTests.java[tag=example-2]
|
||||
----
|
||||
<1> Create a custom `ExampleMatcher` that matches on ALL fields (use `matchingAny()` to match on *ANY* fields)
|
||||
<2> For the `name` field, use a wildcard that matches against the end of the field
|
||||
<3> Match columns against `null` (don't forget that `NULL` doesn't equal `NULL` in relational databases).
|
||||
<4> Ignore the `role` field when forming the query.
|
||||
<5> Plug the custom `ExampleMatcher` into the probe.
|
||||
====
|
||||
|
||||
It's also possible to apply a `withTransform()` against any property, allowing you to transform a property before forming the query.
|
||||
For example, you can apply a `toUpperCase()` to a `String` -based property before the query is created.
|
||||
|
||||
Query By Example really shines when you you don't know all the fields needed in a query in advance.
|
||||
If you were building a filter on a web page where the user can pick the fields, Query By Example is a great way to flexibly capture that into an efficient query.
|
||||
|
||||
[[r2dbc.entity-persistence.state-detection-strategies]]
|
||||
=== Entity State Detection Strategies
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.r2dbc.repository;
|
||||
|
||||
import org.springframework.data.repository.NoRepositoryBean;
|
||||
import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor;
|
||||
import org.springframework.data.repository.reactive.ReactiveSortingRepository;
|
||||
|
||||
/**
|
||||
@@ -23,6 +24,7 @@ import org.springframework.data.repository.reactive.ReactiveSortingRepository;
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Stephen Cohen
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@NoRepositoryBean
|
||||
public interface R2dbcRepository<T, ID> extends ReactiveSortingRepository<T, ID> {}
|
||||
public interface R2dbcRepository<T, ID> extends ReactiveSortingRepository<T, ID>, ReactiveQueryByExampleExecutor<T> {}
|
||||
|
||||
@@ -21,16 +21,18 @@ import reactor.core.publisher.Mono;
|
||||
import java.util.List;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.r2dbc.convert.R2dbcConverter;
|
||||
import org.springframework.data.r2dbc.core.R2dbcEntityOperations;
|
||||
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
|
||||
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.core.query.Criteria;
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
import org.springframework.data.relational.repository.query.RelationalEntityInformation;
|
||||
import org.springframework.data.relational.repository.query.RelationalExampleMapper;
|
||||
import org.springframework.data.repository.reactive.ReactiveSortingRepository;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.data.util.Streamable;
|
||||
@@ -45,13 +47,15 @@ import org.springframework.util.Assert;
|
||||
* @author Jens Schauder
|
||||
* @author Mingyuan Wu
|
||||
* @author Stephen Cohen
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public class SimpleR2dbcRepository<T, ID> implements ReactiveSortingRepository<T, ID> {
|
||||
public class SimpleR2dbcRepository<T, ID> implements R2dbcRepository<T, ID> {
|
||||
|
||||
private final RelationalEntityInformation<T, ID> entity;
|
||||
private final R2dbcEntityOperations entityOperations;
|
||||
private final Lazy<RelationalPersistentProperty> idProperty;
|
||||
private final RelationalExampleMapper exampleMapper;
|
||||
|
||||
/**
|
||||
* Create a new {@link SimpleR2dbcRepository}.
|
||||
@@ -70,6 +74,7 @@ public class SimpleR2dbcRepository<T, ID> implements ReactiveSortingRepository<T
|
||||
.getMappingContext() //
|
||||
.getRequiredPersistentEntity(this.entity.getJavaType()) //
|
||||
.getRequiredIdProperty());
|
||||
this.exampleMapper = new RelationalExampleMapper(converter.getMappingContext());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,6 +95,7 @@ public class SimpleR2dbcRepository<T, ID> implements ReactiveSortingRepository<T
|
||||
.getMappingContext() //
|
||||
.getRequiredPersistentEntity(this.entity.getJavaType()) //
|
||||
.getRequiredIdProperty());
|
||||
this.exampleMapper = new RelationalExampleMapper(converter.getMappingContext());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,6 +118,7 @@ public class SimpleR2dbcRepository<T, ID> implements ReactiveSortingRepository<T
|
||||
.getMappingContext() //
|
||||
.getRequiredPersistentEntity(this.entity.getJavaType()) //
|
||||
.getRequiredIdProperty());
|
||||
this.exampleMapper = new RelationalExampleMapper(converter.getMappingContext());
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -372,6 +379,59 @@ public class SimpleR2dbcRepository<T, ID> implements ReactiveSortingRepository<T
|
||||
return this.entityOperations.select(Query.empty().sort(sort), this.entity.getJavaType());
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Methods from ReactiveQueryByExampleExecutor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public <S extends T> Mono<S> findOne(Example<S> example) {
|
||||
|
||||
Assert.notNull(example, "Example must not be null!");
|
||||
|
||||
Query query = this.exampleMapper.getMappedExample(example);
|
||||
|
||||
return this.entityOperations.selectOne(query, example.getProbeType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T> Flux<S> findAll(Example<S> example) {
|
||||
|
||||
Assert.notNull(example, "Example must not be null!");
|
||||
|
||||
return findAll(example, Sort.unsorted());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T> Flux<S> findAll(Example<S> example, Sort sort) {
|
||||
|
||||
Assert.notNull(example, "Example must not be null!");
|
||||
Assert.notNull(sort, "Sort must not be null!");
|
||||
|
||||
Query query = this.exampleMapper.getMappedExample(example).sort(sort);
|
||||
|
||||
return this.entityOperations.select(query, example.getProbeType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T> Mono<Long> count(Example<S> example) {
|
||||
|
||||
Assert.notNull(example, "Example must not be null!");
|
||||
|
||||
Query query = this.exampleMapper.getMappedExample(example);
|
||||
|
||||
return this.entityOperations.count(query, example.getProbeType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T> Mono<Boolean> exists(Example<S> example) {
|
||||
|
||||
Assert.notNull(example, "Example must not be null!");
|
||||
|
||||
Query query = this.exampleMapper.getMappedExample(example);
|
||||
|
||||
return this.entityOperations.exists(query, example.getProbeType());
|
||||
}
|
||||
|
||||
private RelationalPersistentProperty getIdProperty() {
|
||||
return this.idProperty.get();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.springframework.data.r2dbc.documentation;
|
||||
|
||||
import static org.springframework.data.domain.ExampleMatcher.*;
|
||||
import static org.springframework.data.domain.ExampleMatcher.GenericPropertyMatchers.*;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.ExampleMatcher;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
|
||||
public class QueryByExampleTests {
|
||||
|
||||
private EmployeeRepository repository;
|
||||
|
||||
@Test
|
||||
void queryByExampleSimple() {
|
||||
|
||||
// tag::example[]
|
||||
Employee employee = new Employee(); // <1>
|
||||
employee.setName("Frodo");
|
||||
|
||||
Example<Employee> example = Example.of(employee); // <2>
|
||||
|
||||
Flux<Employee> employees = repository.findAll(example); // <3>
|
||||
|
||||
// do whatever with the flux
|
||||
// end::example[]
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryByExampleCustomMatcher() {
|
||||
|
||||
// tag::example-2[]
|
||||
Employee employee = new Employee();
|
||||
employee.setName("Baggins");
|
||||
employee.setRole("ring bearer");
|
||||
|
||||
ExampleMatcher matcher = matching() // <1>
|
||||
.withMatcher("name", endsWith()) // <2>
|
||||
.withIncludeNullValues() // <3>
|
||||
.withIgnorePaths("role"); // <4>
|
||||
Example<Employee> example = Example.of(employee, matcher); // <5>
|
||||
|
||||
Flux<Employee> employees = repository.findAll(example);
|
||||
|
||||
// do whatever with the flux
|
||||
// end::example-2[]
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class Employee {
|
||||
|
||||
private @Id Integer id;
|
||||
private String name;
|
||||
private String role;
|
||||
}
|
||||
|
||||
public interface EmployeeRepository extends R2dbcRepository<Employee, Integer> {}
|
||||
}
|
||||
@@ -32,7 +32,6 @@ import javax.sql.DataSource;
|
||||
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.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
package org.springframework.data.r2dbc.repository.support;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.data.domain.ExampleMatcher.*;
|
||||
import static org.springframework.data.domain.ExampleMatcher.GenericPropertyMatchers.*;
|
||||
import static org.springframework.data.domain.ExampleMatcher.StringMatcher.*;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
@@ -33,12 +36,12 @@ import javax.sql.DataSource;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.Version;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.r2dbc.convert.MappingR2dbcConverter;
|
||||
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
|
||||
@@ -58,6 +61,7 @@ import org.springframework.r2dbc.core.DatabaseClient;
|
||||
* @author Bogdan Ilchyshyn
|
||||
* @author Stephen Cohen
|
||||
* @author Jens Schauder
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public abstract class AbstractSimpleR2dbcRepositoryIntegrationTests extends R2dbcIntegrationTestSupport {
|
||||
|
||||
@@ -68,16 +72,25 @@ public abstract class AbstractSimpleR2dbcRepositoryIntegrationTests extends R2db
|
||||
@Autowired private ReactiveDataAccessStrategy strategy;
|
||||
|
||||
SimpleR2dbcRepository<LegoSet, Integer> repository;
|
||||
SimpleR2dbcRepository<LegoSetWithNonScalarId, Integer> repositoryWithNonScalarId;
|
||||
JdbcTemplate jdbc;
|
||||
|
||||
@BeforeEach
|
||||
void before() {
|
||||
|
||||
MappingR2dbcConverter converter = new MappingR2dbcConverter(mappingContext);
|
||||
|
||||
RelationalEntityInformation<LegoSet, Integer> entityInformation = new MappingRelationalEntityInformation<>(
|
||||
(RelationalPersistentEntity<LegoSet>) mappingContext.getRequiredPersistentEntity(LegoSet.class));
|
||||
|
||||
this.repository = new SimpleR2dbcRepository<>(entityInformation, databaseClient,
|
||||
new MappingR2dbcConverter(mappingContext), strategy);
|
||||
this.repository = new SimpleR2dbcRepository<>(entityInformation, databaseClient, converter, strategy);
|
||||
|
||||
RelationalEntityInformation<LegoSetWithNonScalarId, Integer> boxedEntityInformation = new MappingRelationalEntityInformation<>(
|
||||
(RelationalPersistentEntity<LegoSetWithNonScalarId>) mappingContext
|
||||
.getRequiredPersistentEntity(LegoSetWithNonScalarId.class));
|
||||
|
||||
this.repositoryWithNonScalarId = new SimpleR2dbcRepository<>(boxedEntityInformation, databaseClient, converter,
|
||||
strategy);
|
||||
|
||||
this.jdbc = createJdbcTemplate(createDataSource());
|
||||
try {
|
||||
@@ -117,10 +130,8 @@ public abstract class AbstractSimpleR2dbcRepositoryIntegrationTests extends R2db
|
||||
|
||||
repository.save(new LegoSet(0, "SCHAUFELRADBAGGER", 12)) //
|
||||
.as(StepVerifier::create) //
|
||||
.consumeNextWith(actual -> {
|
||||
|
||||
assertThat(actual.getId()).isGreaterThan(0);
|
||||
}).verifyComplete();
|
||||
.consumeNextWith(actual -> assertThat(actual.getId()).isGreaterThan(0)) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // gh-93
|
||||
@@ -174,7 +185,10 @@ public abstract class AbstractSimpleR2dbcRepositoryIntegrationTests extends R2db
|
||||
.verifyComplete();
|
||||
|
||||
Map<String, Object> map = jdbc.queryForMap("SELECT * FROM legoset");
|
||||
assertThat(map).containsEntry("name", "SCHAUFELRADBAGGER").containsEntry("manual", 14).containsKey("id");
|
||||
assertThat(map) //
|
||||
.containsEntry("name", "SCHAUFELRADBAGGER") //
|
||||
.containsEntry("manual", 14) //
|
||||
.containsKey("id");
|
||||
}
|
||||
|
||||
@Test // gh-93
|
||||
@@ -194,7 +208,7 @@ public abstract class AbstractSimpleR2dbcRepositoryIntegrationTests extends R2db
|
||||
assertThat(legoSet.getVersion()).isEqualTo(43);
|
||||
|
||||
Map<String, Object> map = jdbc.queryForMap("SELECT * FROM legoset");
|
||||
assertThat(map)
|
||||
assertThat(map) //
|
||||
.containsEntry("name", "SCHAUFELRADBAGGER") //
|
||||
.containsEntry("manual", 14) //
|
||||
.containsEntry("version", 43) //
|
||||
@@ -311,10 +325,8 @@ public abstract class AbstractSimpleR2dbcRepositoryIntegrationTests extends R2db
|
||||
.map(LegoSet::getName) //
|
||||
.collectList() //
|
||||
.as(StepVerifier::create) //
|
||||
.assertNext(actual -> {
|
||||
|
||||
assertThat(actual).hasSize(2).contains("SCHAUFELRADBAGGER", "FORSCHUNGSSCHIFF");
|
||||
}).verifyComplete();
|
||||
.assertNext(actual -> assertThat(actual).containsExactly("SCHAUFELRADBAGGER", "FORSCHUNGSSCHIFF"))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // gh-407
|
||||
@@ -329,12 +341,12 @@ public abstract class AbstractSimpleR2dbcRepositoryIntegrationTests extends R2db
|
||||
.map(LegoSet::getName) //
|
||||
.collectList() //
|
||||
.as(StepVerifier::create) //
|
||||
.assertNext(actual -> assertThat(actual).containsExactly(
|
||||
"SCHAUFELRADBAGGER",
|
||||
"FORSCHUNGSSCHIFF",
|
||||
"RALLYEAUTO",
|
||||
"VOLTRON"
|
||||
)).verifyComplete();
|
||||
.assertNext(actual -> assertThat(actual).containsExactly( //
|
||||
"SCHAUFELRADBAGGER", //
|
||||
"FORSCHUNGSSCHIFF", //
|
||||
"RALLYEAUTO", //
|
||||
"VOLTRON"))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -480,21 +492,313 @@ public abstract class AbstractSimpleR2dbcRepositoryIntegrationTests extends R2db
|
||||
assertThat(count).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test // gh-538
|
||||
void shouldSelectByExampleUsingId() {
|
||||
|
||||
jdbc.execute("INSERT INTO legoset (name, manual) VALUES('SCHAUFELRADBAGGER', 12)");
|
||||
Integer id = jdbc.queryForObject("SELECT id FROM legoset", Integer.class);
|
||||
|
||||
LegoSetWithNonScalarId legoSet = new LegoSetWithNonScalarId();
|
||||
legoSet.setId(id);
|
||||
|
||||
Example<LegoSetWithNonScalarId> example = Example.of(legoSet);
|
||||
|
||||
repositoryWithNonScalarId.findOne(example) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(new LegoSetWithNonScalarId(id, "SCHAUFELRADBAGGER", 12, null)) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // gh-538
|
||||
void shouldSelectByExampleUsingName() {
|
||||
|
||||
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(0, 'SCHAUFELRADBAGGER', 12)");
|
||||
Integer id = jdbc.queryForObject("SELECT id FROM legoset", Integer.class);
|
||||
|
||||
LegoSetWithNonScalarId legoSet = new LegoSetWithNonScalarId();
|
||||
legoSet.setName("SCHAUFELRADBAGGER");
|
||||
|
||||
Example<LegoSetWithNonScalarId> example = Example.of(legoSet);
|
||||
|
||||
repositoryWithNonScalarId.findOne(example) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(new LegoSetWithNonScalarId(id, "SCHAUFELRADBAGGER", 12, null)) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // gh-538
|
||||
void shouldSelectByExampleUsingManual() {
|
||||
|
||||
jdbc.execute("INSERT INTO legoset (name, manual) VALUES('SCHAUFELRADBAGGER', 12)");
|
||||
Integer id = jdbc.queryForObject("SELECT id FROM legoset", Integer.class);
|
||||
|
||||
LegoSetWithNonScalarId legoSet = new LegoSetWithNonScalarId();
|
||||
legoSet.setManual(12);
|
||||
|
||||
Example<LegoSetWithNonScalarId> example = Example.of(legoSet);
|
||||
|
||||
repositoryWithNonScalarId.findOne(example) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(new LegoSetWithNonScalarId(id, "SCHAUFELRADBAGGER", 12, null)) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // gh-538
|
||||
void shouldSelectByExampleUsingGlobalStringMatcher() {
|
||||
|
||||
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(1, 'Moon space base', 12)");
|
||||
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(2, 'Mars space base', 13)");
|
||||
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(3, 'Moon construction kit', 14)");
|
||||
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(4, 'Mars construction kit', 15)");
|
||||
|
||||
LegoSetWithNonScalarId legoSet = new LegoSetWithNonScalarId();
|
||||
|
||||
legoSet.setName("Moon");
|
||||
Example<LegoSetWithNonScalarId> exampleByStarting = Example.of(legoSet, matching().withStringMatcher(STARTING));
|
||||
|
||||
repositoryWithNonScalarId.findAll(exampleByStarting) //
|
||||
.map(LegoSetWithNonScalarId::getName) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext("Moon space base") //
|
||||
.expectNext("Moon construction kit") //
|
||||
.verifyComplete();
|
||||
|
||||
legoSet.setName("base");
|
||||
Example<LegoSetWithNonScalarId> exampleByEnding = Example.of(legoSet, matching().withStringMatcher(ENDING));
|
||||
|
||||
repositoryWithNonScalarId.findAll(exampleByEnding) //
|
||||
.map(LegoSetWithNonScalarId::getName) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext("Moon space base") //
|
||||
.expectNext("Mars space base") //
|
||||
.verifyComplete();
|
||||
|
||||
legoSet.setName("construction");
|
||||
Example<LegoSetWithNonScalarId> exampleByContaining = Example.of(legoSet, matching().withStringMatcher(CONTAINING));
|
||||
|
||||
repositoryWithNonScalarId.findAll(exampleByContaining) //
|
||||
.map(LegoSetWithNonScalarId::getName) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext("Moon construction kit") //
|
||||
.expectNext("Mars construction kit") //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // gh-538
|
||||
void shouldSelectByExampleUsingFieldLevelStringMatcher() {
|
||||
|
||||
jdbc.execute("INSERT INTO legoset (name, manual) VALUES('Moon space base', 12)");
|
||||
jdbc.execute("INSERT INTO legoset (name, manual) VALUES('Mars space base', 13)");
|
||||
jdbc.execute("INSERT INTO legoset (name, manual) VALUES('Moon construction kit', 14)");
|
||||
jdbc.execute("INSERT INTO legoset (name, manual) VALUES('Mars construction kit', 15)");
|
||||
|
||||
LegoSetWithNonScalarId legoSet = new LegoSetWithNonScalarId();
|
||||
|
||||
legoSet.setName("Moon");
|
||||
Example<LegoSetWithNonScalarId> exampleByFieldBasedStartsWith = Example.of(legoSet,
|
||||
matching().withMatcher("name", startsWith()));
|
||||
|
||||
repositoryWithNonScalarId.findAll(exampleByFieldBasedStartsWith) //
|
||||
.map(LegoSetWithNonScalarId::getName) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext("Moon space base") //
|
||||
.expectNext("Moon construction kit") //
|
||||
.verifyComplete();
|
||||
|
||||
legoSet.setName("base");
|
||||
Example<LegoSetWithNonScalarId> exampleByFieldBasedEndsWith = Example.of(legoSet,
|
||||
matching().withMatcher("name", endsWith()));
|
||||
|
||||
repositoryWithNonScalarId.findAll(exampleByFieldBasedEndsWith) //
|
||||
.map(LegoSetWithNonScalarId::getName) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext("Moon space base") //
|
||||
.expectNext("Mars space base") //
|
||||
.verifyComplete();
|
||||
|
||||
legoSet.setName("construction");
|
||||
Example<LegoSetWithNonScalarId> exampleByFieldBasedConstruction = Example.of(legoSet,
|
||||
matching().withMatcher("name", contains()));
|
||||
|
||||
repositoryWithNonScalarId.findAll(exampleByFieldBasedConstruction) //
|
||||
.map(LegoSetWithNonScalarId::getName) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext("Moon construction kit") //
|
||||
.expectNext("Mars construction kit") //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // gh-538
|
||||
void shouldSelectByExampleIgnoringCase() {
|
||||
|
||||
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(1, 'Moon space base', 12)");
|
||||
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(2, 'Mars space base', 13)");
|
||||
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(3, 'Moon construction kit', 14)");
|
||||
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(4, 'Mars construction kit', 15)");
|
||||
|
||||
LegoSetWithNonScalarId legoSet = new LegoSetWithNonScalarId();
|
||||
|
||||
legoSet.setName("moon SPACE bAsE");
|
||||
Example<LegoSetWithNonScalarId> exampleIgnoreCase = Example.of(legoSet, matching().withIgnoreCase());
|
||||
|
||||
repositoryWithNonScalarId.findAll(exampleIgnoreCase) //
|
||||
.map(LegoSetWithNonScalarId::getName) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext("Moon space base") //
|
||||
.verifyComplete();
|
||||
|
||||
legoSet.setName("moon SPACE bAsE");
|
||||
Example<LegoSetWithNonScalarId> exampleByFieldBasedStartsWith = Example.of(legoSet,
|
||||
matching().withMatcher("name", ignoreCase()));
|
||||
|
||||
repositoryWithNonScalarId.findAll(exampleByFieldBasedStartsWith) //
|
||||
.map(LegoSetWithNonScalarId::getName) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext("Moon space base") //
|
||||
.verifyComplete();
|
||||
|
||||
}
|
||||
|
||||
@Test // gh-538
|
||||
void shouldFailSelectByExampleWhenUsingRegEx() {
|
||||
|
||||
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(1, 'Moon space base', 12)");
|
||||
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(2, 'Mars space base', 13)");
|
||||
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(3, 'Moon construction kit', 14)");
|
||||
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(4, 'Mars construction kit', 15)");
|
||||
|
||||
LegoSetWithNonScalarId legoSet = new LegoSetWithNonScalarId();
|
||||
|
||||
legoSet.setName("moon");
|
||||
|
||||
Example<LegoSetWithNonScalarId> exampleWithRegExGlobal = Example.of(legoSet, matching().withStringMatcher(REGEX));
|
||||
|
||||
assertThatIllegalStateException().isThrownBy(() -> {
|
||||
|
||||
repositoryWithNonScalarId.findAll(exampleWithRegExGlobal) //
|
||||
.map(LegoSetWithNonScalarId::getName) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext("Moon space base") //
|
||||
.verifyComplete();
|
||||
});
|
||||
|
||||
Example<LegoSetWithNonScalarId> exampleWithFieldRegEx = Example.of(legoSet,
|
||||
matching().withMatcher("name", regex()));
|
||||
|
||||
assertThatIllegalStateException().isThrownBy(() -> {
|
||||
|
||||
repositoryWithNonScalarId.findAll(exampleWithFieldRegEx) //
|
||||
.map(LegoSetWithNonScalarId::getName) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext("Moon space base") //
|
||||
.verifyComplete();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Test // gh-538
|
||||
void shouldSelectByExampleIncludingNull() {
|
||||
|
||||
jdbc.execute("INSERT INTO legoset (id, name, extra, manual) VALUES(1, 'Moon space base', 'base', 12)");
|
||||
jdbc.execute("INSERT INTO legoset (id, name, extra, manual) VALUES(2, 'Mars space base', 'base', 13)");
|
||||
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(3, 'Moon construction kit', 14)");
|
||||
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(4, 'Mars construction kit', 15)");
|
||||
|
||||
LegoSetWithNonScalarId legoSet = new LegoSetWithNonScalarId();
|
||||
legoSet.setExtra("base");
|
||||
|
||||
Example<LegoSetWithNonScalarId> exampleIncludingNull = Example.of(legoSet, matching().withIncludeNullValues());
|
||||
|
||||
repositoryWithNonScalarId.findAll(exampleIncludingNull) //
|
||||
.map(LegoSetWithNonScalarId::getName) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext("Moon space base", "Mars space base", "Moon construction kit", "Mars construction kit") //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // gh-538
|
||||
void shouldSelectByExampleWithAnyMatching() {
|
||||
|
||||
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(1, 'Moon space base', 12)");
|
||||
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(2, 'Mars space base', 13)");
|
||||
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(3, 'Moon construction kit', 14)");
|
||||
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(4, 'Mars construction kit', 15)");
|
||||
|
||||
LegoSet legoSet = new LegoSet();
|
||||
legoSet.setName("Moon space base");
|
||||
legoSet.setManual(15);
|
||||
|
||||
Example<LegoSet> exampleIncludingNull = Example.of(legoSet, matchingAny());
|
||||
|
||||
repository.findAll(exampleIncludingNull) //
|
||||
.map(LegoSet::getName) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext("Moon space base", "Mars construction kit") //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // gh-538
|
||||
void shouldCountByExampleUsingId() {
|
||||
|
||||
jdbc.execute("INSERT INTO legoset (name, manual) VALUES('SCHAUFELRADBAGGER', 12)");
|
||||
Integer id = jdbc.queryForObject("SELECT id FROM legoset", Integer.class);
|
||||
|
||||
LegoSetWithNonScalarId legoSet = new LegoSetWithNonScalarId();
|
||||
legoSet.setId(id);
|
||||
|
||||
Example<LegoSetWithNonScalarId> example = Example.of(legoSet);
|
||||
|
||||
repositoryWithNonScalarId.count(example) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // gh-538
|
||||
void shouldCheckExistenceByExampleUsingId() {
|
||||
|
||||
jdbc.execute("INSERT INTO legoset (name, manual) VALUES('SCHAUFELRADBAGGER', 12)");
|
||||
Integer id = jdbc.queryForObject("SELECT id FROM legoset", Integer.class);
|
||||
|
||||
LegoSetWithNonScalarId legoSet = new LegoSetWithNonScalarId();
|
||||
legoSet.setId(id);
|
||||
|
||||
Example<LegoSetWithNonScalarId> example = Example.of(legoSet);
|
||||
|
||||
repositoryWithNonScalarId.exists(example) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(true) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Data
|
||||
@Table("legoset")
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
static class LegoSet {
|
||||
|
||||
@Id int id;
|
||||
String name;
|
||||
Integer manual;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Table("legoset")
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
static class LegoSetWithNonScalarId {
|
||||
|
||||
@Id Integer id;
|
||||
String name;
|
||||
Integer manual;
|
||||
String extra;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Table("legoset")
|
||||
@NoArgsConstructor
|
||||
static class LegoSetVersionable extends LegoSet {
|
||||
|
||||
@Version Integer version;
|
||||
|
||||
LegoSetVersionable(int id, String name, Integer manual, Integer version) {
|
||||
@@ -507,6 +811,7 @@ public abstract class AbstractSimpleR2dbcRepositoryIntegrationTests extends R2db
|
||||
@Table("legoset")
|
||||
@NoArgsConstructor
|
||||
static class LegoSetPrimitiveVersionable extends LegoSet {
|
||||
|
||||
@Version int version;
|
||||
|
||||
LegoSetPrimitiveVersionable(int id, String name, Integer manual, int version) {
|
||||
|
||||
@@ -28,7 +28,6 @@ import javax.sql.DataSource;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
@@ -49,6 +48,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
* Integration tests for {@link SimpleR2dbcRepository} against H2.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@@ -86,7 +86,7 @@ public class H2SimpleR2dbcRepositoryIntegrationTests extends AbstractSimpleR2dbc
|
||||
|
||||
this.jdbc.execute("CREATE TABLE always_new (\n" //
|
||||
+ " id integer PRIMARY KEY,\n" //
|
||||
+ " name varchar(255) NOT NULL" //
|
||||
+ " name varchar(255) NOT NULL\n" //
|
||||
+ ");");
|
||||
|
||||
RelationalEntityInformation<AlwaysNew, Long> entityInformation = new MappingRelationalEntityInformation<>(
|
||||
|
||||
@@ -43,6 +43,7 @@ public class H2TestSupport {
|
||||
+ " id serial CONSTRAINT id PRIMARY KEY,\n" //
|
||||
+ " version integer NULL,\n" //
|
||||
+ " name varchar(255) NOT NULL,\n" //
|
||||
+ " extra varchar(255),\n" //
|
||||
+ " manual integer NULL\n" //
|
||||
+ ");";
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ public class PostgresTestSupport {
|
||||
+ " id serial CONSTRAINT id PRIMARY KEY,\n" //
|
||||
+ " version integer NULL,\n" //
|
||||
+ " name varchar(255) NOT NULL,\n" //
|
||||
+ " extra varchar(255),\n" //
|
||||
+ " manual integer NULL\n" //
|
||||
+ ");";
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ public class SqlServerTestSupport {
|
||||
+ " id integer IDENTITY(1,1) PRIMARY KEY,\n" //
|
||||
+ " version integer NULL,\n" //
|
||||
+ " name varchar(255) NOT NULL,\n" //
|
||||
+ " extra varchar(255),\n" //
|
||||
+ " manual integer NULL\n" //
|
||||
+ ");";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user