Add example for using Immutables with Spring Data JDBC.
Original pull request #624
This commit is contained in:
committed by
Jens Schauder
parent
6e55e3d27a
commit
744ee32425
@@ -44,6 +44,8 @@ Local Elasticsearch instance must be running to run the tests.
|
||||
## Spring Data JDBC
|
||||
|
||||
* `basic` - Basic usage of Spring Data JDBC.
|
||||
* `immutables` - Showing Spring Data JDBC usage
|
||||
with [Immutables](https://immutables.github.io/)
|
||||
|
||||
## Spring Data JPA
|
||||
|
||||
|
||||
27
jdbc/immutables/README.adoc
Normal file
27
jdbc/immutables/README.adoc
Normal file
@@ -0,0 +1,27 @@
|
||||
== Spring Data JDBC with Immutables
|
||||
|
||||
This example show how to use https://immutables.github.io/[Immutables] with Spring Data.
|
||||
The core concept of Immutables is to define an interface (or abstract class) for which Immutables generates an immutable implementation that can then be used by application code.
|
||||
|
||||
Persisting immutable objects and associating the saved object with generated identifiers works out of the box.
|
||||
The reading side requires a redirection of the to be created object type, see `ImmutablesJdbcConfiguration`.
|
||||
|
||||
Limitations:
|
||||
|
||||
Immutables tends to generate additional constructors when using `@Value.Style(allParameters = true)` or `@Value.Parameter`.
|
||||
This conflicts with Spring Data's constructor resolution as Spring Data cannot identify reliably a persistence constructor.
|
||||
|
||||
To run the tests, run:
|
||||
|
||||
[indent=0]
|
||||
----
|
||||
$ mvn test
|
||||
----
|
||||
|
||||
The code generator is automatically run when executing the tests.
|
||||
If you want to rerun the code generator manually, just run the following command:
|
||||
|
||||
[indent=0]
|
||||
----
|
||||
$ mvn clean generate-sources
|
||||
----
|
||||
28
jdbc/immutables/pom.xml
Normal file
28
jdbc/immutables/pom.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<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/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-data-jdbc-immutables</artifactId>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.data.examples</groupId>
|
||||
<artifactId>spring-data-jdbc-examples</artifactId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<name>Spring Data JDBC - Usage with Immutables</name>
|
||||
<description>Sample project demonstrating Spring Data JDBC features</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.immutables</groupId>
|
||||
<artifactId>value</artifactId>
|
||||
<version>2.8.8</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 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
|
||||
*
|
||||
* http://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.jdbc.immutables;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
|
||||
import org.springframework.data.jdbc.core.convert.DefaultJdbcTypeFactory;
|
||||
import org.springframework.data.jdbc.core.convert.Identifier;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
|
||||
import org.springframework.data.jdbc.core.convert.RelationResolver;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||
import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.relational.core.dialect.Dialect;
|
||||
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Configuration stub.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@SpringBootApplication
|
||||
class Application {
|
||||
|
||||
/**
|
||||
* Name scheme how Immutables generates implementations from interface/class definitions.
|
||||
*/
|
||||
public static final String IMMUTABLE_IMPLEMENTATION_CLASS = "%s.Immutable%s";
|
||||
|
||||
@Configuration
|
||||
static class ImmutablesJdbcConfiguration extends AbstractJdbcConfiguration {
|
||||
|
||||
private final ResourceLoader resourceLoader;
|
||||
|
||||
public ImmutablesJdbcConfiguration(ResourceLoader resourceLoader) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link JdbcConverter} that redirects entities to be instantiated towards the implementation. See
|
||||
* {@link #IMMUTABLE_IMPLEMENTATION_CLASS} and
|
||||
* {@link #getImplementationEntity(JdbcMappingContext, RelationalPersistentEntity)}.
|
||||
*
|
||||
* @param mappingContext
|
||||
* @param operations
|
||||
* @param relationResolver
|
||||
* @param conversions
|
||||
* @param dialect
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public JdbcConverter jdbcConverter(JdbcMappingContext mappingContext, NamedParameterJdbcOperations operations,
|
||||
RelationResolver relationResolver, JdbcCustomConversions conversions, Dialect dialect) {
|
||||
|
||||
var jdbcTypeFactory = new DefaultJdbcTypeFactory(operations.getJdbcOperations());
|
||||
|
||||
return new BasicJdbcConverter(mappingContext, relationResolver, conversions, jdbcTypeFactory,
|
||||
dialect.getIdentifierProcessing()) {
|
||||
|
||||
@Override
|
||||
public <T> T mapRow(RelationalPersistentEntity<T> entity, ResultSet resultSet, Object key) {
|
||||
return super.mapRow(getImplementationEntity(mappingContext, entity), resultSet, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T mapRow(PersistentPropertyPathExtension path, ResultSet resultSet, Identifier identifier,
|
||||
Object key) {
|
||||
|
||||
return super.mapRow(new DelegatePersistentPropertyPathExtension(mappingContext,
|
||||
path.getRequiredPersistentPropertyPath(), getImplementationEntity(mappingContext, path.getLeafEntity())),
|
||||
resultSet, identifier, key);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> RelationalPersistentEntity<T> getImplementationEntity(JdbcMappingContext mappingContext,
|
||||
RelationalPersistentEntity<T> entity) {
|
||||
|
||||
var type = entity.getType();
|
||||
if (type.isInterface()) {
|
||||
|
||||
var immutableClass = String.format(IMMUTABLE_IMPLEMENTATION_CLASS, type.getPackageName(), type.getSimpleName());
|
||||
if (ClassUtils.isPresent(immutableClass, resourceLoader.getClassLoader())) {
|
||||
|
||||
return (RelationalPersistentEntity<T>) mappingContext
|
||||
.getPersistentEntity(ClassUtils.resolveClassName(immutableClass, resourceLoader.getClassLoader()));
|
||||
}
|
||||
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect {@link #getLeafEntity()} to a different entity type.
|
||||
*/
|
||||
static class DelegatePersistentPropertyPathExtension extends PersistentPropertyPathExtension {
|
||||
|
||||
private final RelationalPersistentEntity<?> leafEntity;
|
||||
|
||||
public DelegatePersistentPropertyPathExtension(
|
||||
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context,
|
||||
PersistentPropertyPath<? extends RelationalPersistentProperty> path, RelationalPersistentEntity<?> leafEntity) {
|
||||
super(context, path);
|
||||
this.leafEntity = leafEntity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RelationalPersistentEntity<?> getLeafEntity() {
|
||||
return leafEntity;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 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
|
||||
*
|
||||
* http://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.jdbc.immutables;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.immutables.value.Value;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.relational.core.mapping.MappedCollection;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Value.Immutable
|
||||
@Table("ENIGMA")
|
||||
public interface Enigma {
|
||||
|
||||
@Nullable
|
||||
@Id
|
||||
Long getId();
|
||||
|
||||
String getModel();
|
||||
|
||||
// Explicit keys to not derive key names from the implementation class.
|
||||
@MappedCollection(idColumn = "ENIGMA_ID", keyColumn = "ROTOR_KEY")
|
||||
List<Rotor> getRotors();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 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
|
||||
*
|
||||
* http://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.jdbc.immutables;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
* Repository for {@link Enigma} instances.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public interface EnigmaRepository extends CrudRepository<Enigma, Long> {
|
||||
|
||||
Enigma findByModel(String name);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 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
|
||||
*
|
||||
* http://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.jdbc.immutables;
|
||||
|
||||
import org.immutables.value.Value;
|
||||
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Value.Immutable
|
||||
@Table("ROTOR")
|
||||
public interface Rotor {
|
||||
|
||||
String getName();
|
||||
|
||||
String getWiring();
|
||||
|
||||
char getNotch();
|
||||
|
||||
/**
|
||||
* Factory method for {@link Rotor} as using {@code @Value.Style} and {@code @Value.Parameter} conflicts with Spring
|
||||
* Data's constructor discovery rules.
|
||||
*
|
||||
* @param name
|
||||
* @param wiring
|
||||
* @param notch
|
||||
* @return
|
||||
*/
|
||||
static Rotor of(String name, String wiring, char notch) {
|
||||
return ImmutableRotor.builder().name(name).wiring(wiring).notch(notch).build();
|
||||
}
|
||||
|
||||
}
|
||||
34
jdbc/immutables/src/main/resources/schema.sql
Normal file
34
jdbc/immutables/src/main/resources/schema.sql
Normal file
@@ -0,0 +1,34 @@
|
||||
CREATE TABLE IF NOT EXISTS ENIGMA
|
||||
(
|
||||
ID
|
||||
INTEGER
|
||||
IDENTITY
|
||||
PRIMARY
|
||||
KEY,
|
||||
MODEL
|
||||
VARCHAR
|
||||
(
|
||||
100
|
||||
)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ROTOR
|
||||
(
|
||||
ENIGMA_ID
|
||||
INTEGER,
|
||||
ROTOR_KEY
|
||||
INTEGER,
|
||||
NAME
|
||||
VARCHAR
|
||||
(
|
||||
100
|
||||
),
|
||||
WIRING VARCHAR
|
||||
(
|
||||
26
|
||||
),
|
||||
NOTCH CHAR
|
||||
(
|
||||
1
|
||||
)
|
||||
);
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 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
|
||||
*
|
||||
* http://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.jdbc.immutables;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest;
|
||||
|
||||
/**
|
||||
* Integration tests using immutable types assisted by Immutables. Calling code can use the actual immutable
|
||||
* interface/abstract class while Spring Data materializes the actual implementation.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @see <a href="https://immutables.github.io/">https://immutables.github.io/</a>
|
||||
*/
|
||||
@DataJdbcTest
|
||||
class ImmutablesTests {
|
||||
|
||||
@Autowired EnigmaRepository repository;
|
||||
|
||||
@Test
|
||||
void shouldInsertAndRetrieveObject() {
|
||||
|
||||
var enigmaA = ImmutableEnigma.builder().model("A") //
|
||||
.addRotors(Rotor.of("I", "DMTWSILRUYQNKFEJCAZBPGXOHV", 'Q')) //
|
||||
.addRotors(Rotor.of("II", "HQZGPJTMOBLNCIFDYAWVEUSRXL", 'E')) //
|
||||
.build();
|
||||
|
||||
var saved = repository.save(enigmaA);
|
||||
|
||||
assertThat(saved.getId()).isNotNull();
|
||||
|
||||
var loaded = repository.findByModel("A");
|
||||
|
||||
assertThat(loaded).isEqualTo(saved).isInstanceOf(ImmutableEnigma.class);
|
||||
assertThat(loaded.getRotors()).hasSize(2);
|
||||
assertThat(loaded.getRotors().get(0)).isInstanceOf(ImmutableRotor.class);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
<modules>
|
||||
<module>basics</module>
|
||||
<module>mybatis</module>
|
||||
<module>immutables</module>
|
||||
<module>jmolecules</module>
|
||||
<module>jooq</module>
|
||||
</modules>
|
||||
|
||||
Reference in New Issue
Block a user