#186 - Consider byte[] binary data when mapping entities.
We now exclude byte[] properties from being mapped to array types. To map data to a 1-dimensional BYTE[] Postgres type, properties can be declared as Collection<Byte> or Byte[].
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
[[mapping-chapter]]
|
||||
[[mapping]]
|
||||
= Mapping
|
||||
|
||||
Rich mapping support is provided by the `MappingR2dbcConverter`. `MappingR2dbcConverter` has a rich metadata model that allows mapping domain objects to a data row.
|
||||
@@ -8,7 +8,7 @@ The `MappingR2dbcConverter` also lets you map objects to rows without providing
|
||||
|
||||
This section describes the features of the `MappingR2dbcConverter`, including how to use conventions for mapping objects to rows and how to override those conventions with annotation-based mapping metadata.
|
||||
|
||||
[[mapping-conventions]]
|
||||
[[mapping.conventions]]
|
||||
== Convention-based Mapping
|
||||
|
||||
`MappingR2dbcConverter` has a few conventions for mapping objects to rows when no additional mapping metadata is provided.
|
||||
@@ -28,7 +28,7 @@ Public `JavaBean` properties are not used.
|
||||
Otherwise, the zero-argument constructor is used.
|
||||
If there is more than one non-zero-argument constructor, an exception is thrown.
|
||||
|
||||
[[mapping-configuration]]
|
||||
[[mapping.configuration]]
|
||||
== Mapping Configuration
|
||||
|
||||
By default (unless explicitly configured) an instance of `MappingR2dbcConverter` is created when you create a `DatabaseClient`.
|
||||
@@ -69,7 +69,7 @@ You can add additional converters to the converter by overriding the `r2dbcCusto
|
||||
|
||||
NOTE: `AbstractR2dbcConfiguration` creates a `DatabaseClient` instance and registers it with the container under the name of `databaseClient`.
|
||||
|
||||
[[mapping-usage]]
|
||||
[[mapping.usage]]
|
||||
== Metadata-based Mapping
|
||||
|
||||
To take full advantage of the object mapping functionality inside the Spring Data R2DBC support, you should annotate your mapped objects with the `@Table` annotation.
|
||||
@@ -100,8 +100,53 @@ public class Person {
|
||||
|
||||
IMPORTANT: The `@Id` annotation tells the mapper which property you want to use as the primary key.
|
||||
|
||||
[[mapping.types]]
|
||||
=== Default Type Mapping
|
||||
|
||||
[[mapping-usage-annotations]]
|
||||
The following table explains how property types of an entity affect mapping:
|
||||
|
||||
|===
|
||||
|Source Type | Target Type | Remarks
|
||||
|
||||
|Primitive types and wrapper types
|
||||
|Passthru
|
||||
|Can be customized using <<mapping.explicit.converters, Explicit Converters>>.
|
||||
|
||||
|JSR-310 Date/Time types
|
||||
|Passthru
|
||||
|Can be customized using <<mapping.explicit.converters, Explicit Converters>>.
|
||||
|
||||
|
||||
|`String`, `BigInteger`, `BigDecimal`, and `UUID`
|
||||
|Passthru
|
||||
|Can be customized using <<mapping.explicit.converters, Explicit Converters>>.
|
||||
|
||||
|`Blob` and `Clob`
|
||||
|Passthru
|
||||
|Can be customized using <<mapping.explicit.converters, Explicit Converters>>.
|
||||
|
||||
|`byte[]`, `ByteBuffer`
|
||||
|Passthru
|
||||
|Considered a binary payload.
|
||||
|
||||
|`Collection<T>`
|
||||
|Array of `T`
|
||||
|Conversion to Array type if supported by the configured <<r2dbc.drivers, driver>>, not supported otherwise.
|
||||
|
||||
|Arrays of primitive types, wrapper types and `String`
|
||||
|Array of wrapper type (e.g. `int[]` -> `Integer[]`)
|
||||
|Conversion to Array type if supported by the configured <<r2dbc.drivers, driver>>, not supported otherwise.
|
||||
|
||||
|Complex objects
|
||||
|Target type depends on registered `Converter`.
|
||||
|Requires a <<mapping.explicit.converters, Explicit Converters>>, not supported otherwise.
|
||||
|
||||
|===
|
||||
|
||||
NOTE: The native data type for a column depends on the R2DBC driver type mapping.
|
||||
Drivers can contribute additional simple types such as Geometry types.
|
||||
|
||||
[[mapping.usage.annotations]]
|
||||
=== Mapping Annotation Overview
|
||||
|
||||
The `MappingR2dbcConverter` can use metadata to drive the mapping of objects to rows. The following annotations are available:
|
||||
@@ -117,7 +162,7 @@ The mapping metadata infrastructure is defined in the separate `spring-data-comm
|
||||
Specific subclasses are used in the R2DBC support to support annotation based metadata.
|
||||
Other strategies can also be put in place (if there is demand).
|
||||
|
||||
[[mapping-custom-object-construction]]
|
||||
[[mapping.custom.object.construction]]
|
||||
=== Customized Object Construction
|
||||
|
||||
The mapping subsystem allows the customization of the object construction by annotating a constructor with the `@PersistenceConstructor` annotation. The values to be used for the constructor parameters are resolved in the following way:
|
||||
@@ -147,7 +192,7 @@ class OrderItem {
|
||||
----
|
||||
====
|
||||
|
||||
[[mapping-explicit-converters]]
|
||||
[[mapping.explicit.converters]]
|
||||
=== Overriding Mapping with Explicit Converters
|
||||
|
||||
When storing and querying your objects, it is often convenient to have a `R2dbcConverter` instance to handle the mapping of all Java types to `OutboundRow` instances.
|
||||
@@ -156,7 +201,7 @@ However, you may sometimes want the `R2dbcConverter` instances to do most of the
|
||||
To selectively handle the conversion yourself, register one or more one or more `org.springframework.core.convert.converter.Converter` instances with the `R2dbcConverter`.
|
||||
|
||||
You can use the `r2dbcCustomConversions` method in `AbstractR2dbcConfiguration` to configure converters.
|
||||
The examples <<mapping-configuration, at the beginning of this chapter>> show how to perform the configuration with Java.
|
||||
The examples <<mapping.configuration, at the beginning of this chapter>> show how to perform the configuration with Java.
|
||||
|
||||
NOTE: Custom top-level entity conversion requires asymmetric types for conversion. Inbound data is extracted from R2DBC's `Row`.
|
||||
Outbound data (to be used with `INSERT`/`UPDATE` statements) is represented as `OutboundRow` and later assembled to a statement.
|
||||
|
||||
@@ -203,7 +203,7 @@ When you run the main program, the preceding examples produce output similar to
|
||||
Even in this simple example, there are few things to notice:
|
||||
|
||||
* You can create an instance of the central helper class in Spring Data R2DBC (<<r2dbc.datbaseclient,`DatabaseClient`>>) by using a standard `io.r2dbc.spi.ConnectionFactory` object.
|
||||
* The mapper works against standard POJO objects without the need for any additional metadata (though you can, optionally, provide that information -- see <<mapping-chapter,here>>.).
|
||||
* The mapper works against standard POJO objects without the need for any additional metadata (though you can, optionally, provide that information -- see <<mapping,here>>.).
|
||||
* Mapping conventions can use field access. Notice that the `Person` class has only getters.
|
||||
* If the constructor argument names match the column names of the stored row, they are used to instantiate the object.
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ Flux<Person> all = client.execute("SELECT id, name FROM mytable")
|
||||
----
|
||||
====
|
||||
|
||||
`as(…)` applies <<mapping-conventions,Convention-based Object Mapping>> and maps the resulting columns to your POJO.
|
||||
`as(…)` applies <<mapping.conventions,Convention-based Object Mapping>> and maps the resulting columns to your POJO.
|
||||
|
||||
[[r2dbc.datbaseclient.mapping]]
|
||||
== Mapping Results
|
||||
|
||||
@@ -218,6 +218,10 @@ public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStra
|
||||
|
||||
private SettableValue getArrayValue(SettableValue value, RelationalPersistentProperty property) {
|
||||
|
||||
if (value.getType().equals(byte[].class)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
ArrayColumns arrayColumns = this.dialect.getArraySupport();
|
||||
|
||||
if (!arrayColumns.isSupported()) {
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.util.Objects;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* A database value that can be set in a statement.
|
||||
@@ -121,7 +122,7 @@ public class SettableValue {
|
||||
if (!(o instanceof SettableValue))
|
||||
return false;
|
||||
SettableValue value1 = (SettableValue) o;
|
||||
return Objects.equals(this.value, value1.value) && Objects.equals(this.type, value1.type);
|
||||
return ObjectUtils.nullSafeEquals(this.value, value1.value) && ObjectUtils.nullSafeEquals(this.type, value1.type);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -219,6 +219,36 @@ public abstract class AbstractDatabaseClientIntegrationTests extends R2dbcIntegr
|
||||
assertThat(jdbc.queryForMap("SELECT id, name, manual FROM legoset")).hasEntrySatisfying("id", numberOf(42055));
|
||||
}
|
||||
|
||||
@Test // gh-2
|
||||
public void insertTypedObjectWithBinary() {
|
||||
|
||||
LegoSet legoSet = new LegoSet();
|
||||
legoSet.setId(42055);
|
||||
legoSet.setName("SCHAUFELRADBAGGER");
|
||||
legoSet.setManual(12);
|
||||
legoSet.setCert(new byte[] { 1, 2, 3, 4, 5 });
|
||||
|
||||
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
|
||||
|
||||
databaseClient.insert().into(LegoSet.class)//
|
||||
.using(legoSet) //
|
||||
.fetch() //
|
||||
.rowsUpdated() //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(1) //
|
||||
.verifyComplete();
|
||||
|
||||
databaseClient.select().from(LegoSet.class) //
|
||||
.matching(where("name").is("SCHAUFELRADBAGGER")) //
|
||||
.fetch() //
|
||||
.first() //
|
||||
.as(StepVerifier::create) //
|
||||
.assertNext(actual -> {
|
||||
|
||||
assertThat(actual.getCert()).isEqualTo(new byte[] { 1, 2, 3, 4, 5 });
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // gh-64
|
||||
public void update() {
|
||||
|
||||
@@ -491,5 +521,6 @@ public abstract class AbstractDatabaseClientIntegrationTests extends R2dbcIntegr
|
||||
@Id int id;
|
||||
String name;
|
||||
Integer manual;
|
||||
byte[] cert;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import reactor.test.StepVerifier;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
@@ -83,6 +84,11 @@ public class MySqlDatabaseClientIntegrationTests extends AbstractDatabaseClientI
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Ignore("https://github.com/mirromutth/r2dbc-mysql/issues/62")
|
||||
@Test
|
||||
@Override
|
||||
public void insertTypedObjectWithBinary() {}
|
||||
|
||||
@Table("boolean_mapping")
|
||||
@Data
|
||||
static class BooleanMapping {
|
||||
|
||||
@@ -171,6 +171,11 @@ public abstract class ReactiveDataAccessStrategyTestSupport {
|
||||
testType(PrimitiveTypes::setUuid, PrimitiveTypes::getUuid, UUID.randomUUID(), "uuid");
|
||||
}
|
||||
|
||||
@Test // gh-186
|
||||
public void shouldReadAndWriteBinary() {
|
||||
testType(PrimitiveTypes::setBinary, PrimitiveTypes::getBinary, "hello".getBytes(), "binary");
|
||||
}
|
||||
|
||||
private <T> void testType(BiConsumer<PrimitiveTypes, T> setter, Function<PrimitiveTypes, T> getter, T testValue,
|
||||
String fieldname) {
|
||||
|
||||
@@ -224,6 +229,8 @@ public abstract class ReactiveDataAccessStrategyTestSupport {
|
||||
OffsetDateTime offsetDateTime;
|
||||
ZonedDateTime zonedDateTime;
|
||||
|
||||
byte[] binary;
|
||||
|
||||
UUID uuid;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,8 @@ public class H2TestSupport {
|
||||
public static String CREATE_TABLE_LEGOSET = "CREATE TABLE legoset (\n" //
|
||||
+ " id integer CONSTRAINT id PRIMARY KEY,\n" //
|
||||
+ " name varchar(255) NOT NULL,\n" //
|
||||
+ " manual integer NULL\n" //
|
||||
+ " manual integer NULL\n," //
|
||||
+ " cert bytea NULL\n" //
|
||||
+ ");";
|
||||
|
||||
public static String CREATE_TABLE_LEGOSET_WITH_ID_GENERATION = "CREATE TABLE legoset (\n" //
|
||||
|
||||
@@ -43,7 +43,8 @@ public class MySqlTestSupport {
|
||||
public static String CREATE_TABLE_LEGOSET = "CREATE TABLE legoset (\n" //
|
||||
+ " id integer PRIMARY KEY,\n" //
|
||||
+ " name varchar(255) NOT NULL,\n" //
|
||||
+ " manual integer NULL\n" //
|
||||
+ " manual integer NULL\n," //
|
||||
+ " cert varbinary(255) NULL\n" //
|
||||
+ ") ENGINE=InnoDB;";
|
||||
|
||||
public static String CREATE_TABLE_LEGOSET_WITH_ID_GENERATION = "CREATE TABLE legoset (\n" //
|
||||
|
||||
@@ -26,7 +26,8 @@ public class PostgresTestSupport {
|
||||
public static String CREATE_TABLE_LEGOSET = "CREATE TABLE legoset (\n" //
|
||||
+ " id integer CONSTRAINT id PRIMARY KEY,\n" //
|
||||
+ " name varchar(255) NOT NULL,\n" //
|
||||
+ " manual integer NULL\n" //
|
||||
+ " manual integer NULL\n," //
|
||||
+ " cert bytea NULL\n" //
|
||||
+ ");";
|
||||
|
||||
public static String CREATE_TABLE_LEGOSET_WITH_ID_GENERATION = "CREATE TABLE legoset (\n" //
|
||||
|
||||
@@ -18,7 +18,8 @@ public class SqlServerTestSupport {
|
||||
public static String CREATE_TABLE_LEGOSET = "CREATE TABLE legoset (\n" //
|
||||
+ " id integer PRIMARY KEY,\n" //
|
||||
+ " name varchar(255) NOT NULL,\n" //
|
||||
+ " manual integer NULL\n" //
|
||||
+ " manual integer NULL\n," //
|
||||
+ " cert varbinary(255) NULL\n" //
|
||||
+ ");";
|
||||
|
||||
public static String CREATE_TABLE_LEGOSET_WITH_ID_GENERATION = "CREATE TABLE legoset (\n" //
|
||||
|
||||
Reference in New Issue
Block a user