Add examples for various way to generate IDs for Spring Data JDBC.

Original pull request #628
This commit is contained in:
Jens Schauder
2021-09-06 14:43:37 +02:00
parent 58593632a7
commit 22548d45e1
16 changed files with 439 additions and 0 deletions

5
jdbc/howto/README.adoc Normal file
View File

@@ -0,0 +1,5 @@
== Spring Data JDBC How Tos
=== ID Generation
Demonstrates the various ways how the user might control generation of IDs when the default of database generated IDs is not sufficient.

View File

@@ -0,0 +1,24 @@
== Spring Data JDBC How To ID Generation
There are multiple ways how the generation of IDs can be controlled.
1. The default is to let the database create the ID by using a `AUTOINCREMENT`, `SERIAL` or `IDENTITY` column.
If you try to save a new aggregate root with a preset ID you will receive an exception.
See `IdGenerationApplicationTest.cantSaveNewAggregateWithPresetId`.
The reason is that Spring Data JDBC will inspect the aggregate root, notes that the ID is not `null` and tries to perform an update which will update 0 rows and cause an exception.
2. You can manually set the id of an aggregate root to a value of your choice if you use `JdbcAggregateTemplate.insert`.
This bypasses the check if an update or insert is to be performed and always performs an insert.
See `IdGenerationApplicationTest.insertNewAggregateWithPresetIdUsingTemplate`.
3. You may use a `BeforeSaveEntityCallBack` to set the id of aggregate roots with null ID.
This has the benefit of being transparent in to your domain code, as it should be since IDs are normally not relevant to it.
See `IdGenerationApplicationTest.idByCallBack` and `IdGenerationApplication.beforeSaveCallback`.
As long as your entity is mutable you might as well use an `BeforeSaveEntityListener`, but since the callback works for both cases it is the recommended approach.
4. If you add a version attribute, i.e. one annotated with `@Version` that attribute is used to determine if the aggregate is new or not, leaving you free to set the ID as you see fit.
See `IdGenerationApplicationTest.determineIsNewPerVersion`.
5. The final option is to let your aggregate root implement `Persistable` which allows you to define your own `isNew` method, which controls if we perform an insert or an update.
See `IdGenerationApplicationTest.determineIsNewPerPersistable`.

View File

@@ -0,0 +1,20 @@
<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>
<artifactId>spring-data-jdbc-how-to-id-generation</artifactId>
<parent>
<groupId>org.springframework.data.examples</groupId>
<artifactId>spring-data-jdbc-how-to</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<name>Spring Data JDBC - How to ID generation</name>
<description>Sample project for Spring Data JDBC demonstrating the various options to use user defined IDs in Spring Data JDBC aggregates.
It serves as a source code repository for a How To article on the Spring Blog</description>
<url>https://projects.spring.io/spring-data-jdbc</url>
<inceptionYear>2021</inceptionYear>
</project>

View File

@@ -0,0 +1,28 @@
package example.springdata.jdbc.howto.idgeneration;
import java.util.UUID;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.relational.core.mapping.event.BeforeSaveCallback;
@SpringBootApplication
class IdGenerationApplication {
public static void main(String[] args) {
SpringApplication.run(IdGenerationApplication.class, args);
}
@Bean
BeforeSaveCallback<StringIdMinion> beforeSaveCallback() {
return (minion, mutableAggregateChange) -> {
if (minion.id == null) {
minion.id = UUID.randomUUID().toString();
}
return minion;
};
}
}

View File

@@ -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
*
* 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.jdbc.howto.idgeneration;
import org.springframework.data.annotation.Id;
class Minion {
@Id
Long id;
String name;
Minion(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,22 @@
/*
* 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
*
* 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.jdbc.howto.idgeneration;
import org.springframework.data.repository.CrudRepository;
interface MinionRepository extends CrudRepository<Minion, Long> {
}

View File

@@ -0,0 +1,42 @@
/*
* 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
*
* 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.jdbc.howto.idgeneration;
import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Persistable;
import org.springframework.data.relational.core.mapping.Table;
@Table("MINION")
class PersistableMinion implements Persistable<Long> {
@Id Long id;
String name;
PersistableMinion(Long id, String name) {
this.id = id;
this.name = name;
}
@Override
public Long getId() {
return id;
}
@Override
public boolean isNew() {
// this implementation is most certainly not suitable for production use
return true;
}
}

View File

@@ -0,0 +1,22 @@
/*
* 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
*
* 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.jdbc.howto.idgeneration;
import org.springframework.data.repository.CrudRepository;
interface PersistableMinionRepository extends CrudRepository<PersistableMinion, Long> {
}

View File

@@ -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
*
* 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.jdbc.howto.idgeneration;
import org.springframework.data.annotation.Id;
class StringIdMinion {
@Id
String id;
String name;
StringIdMinion(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,22 @@
/*
* 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
*
* 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.jdbc.howto.idgeneration;
import org.springframework.data.repository.CrudRepository;
interface StringIdMinionRepository extends CrudRepository<StringIdMinion, String> {
}

View File

@@ -0,0 +1,32 @@
/*
* 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
*
* 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.jdbc.howto.idgeneration;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
class VersionedMinion {
@Id Long id;
String name;
@Version Integer version;
VersionedMinion(long id, String name) {
this.id = id;
this.name = name;
}
}

View File

@@ -0,0 +1,22 @@
/*
* 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
*
* 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.jdbc.howto.idgeneration;
import org.springframework.data.repository.CrudRepository;
interface VersionedMinionRepository extends CrudRepository<VersionedMinion, Long> {
}

View File

@@ -0,0 +1,18 @@
CREATE TABLE MINION
(
ID IDENTITY PRIMARY KEY,
NAME VARCHAR(255),
);
CREATE TABLE STRING_ID_MINION
(
ID VARCHAR(255) PRIMARY KEY,
NAME VARCHAR(255)
);
CREATE TABLE VERSIONED_MINION
(
ID INT PRIMARY KEY,
NAME VARCHAR(255),
VERSION INT
);

View File

@@ -0,0 +1,100 @@
package example.springdata.jdbc.howto.idgeneration;
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.context.SpringBootTest;
import org.springframework.dao.IncorrectUpdateSemanticsDataAccessException;
import org.springframework.data.jdbc.core.JdbcAggregateTemplate;
@SpringBootTest
class IdGenerationApplicationTests {
@Autowired
MinionRepository minions;
@Autowired
StringIdMinionRepository stringions;
@Autowired
VersionedMinionRepository versionedMinions;
@Autowired
PersistableMinionRepository persistableMinions;
@Autowired
JdbcAggregateTemplate template;
@Test
void saveWithNewIdFromDb() {
Minion before = new Minion("Bob");
assertThat(before.id).isNull();
Minion after = minions.save(before);
assertThat(after.id).isNotNull();
}
@Test
void cantSaveNewAggregateWithPresetId() {
Minion before = new Minion("Stuart");
before.id = 42L;
// We can't save this because Spring Data JDBC thinks it has to do an update.
assertThatThrownBy(() -> minions.save(before)).getRootCause().isInstanceOf(IncorrectUpdateSemanticsDataAccessException.class);
}
@Test
void insertNewAggregateWithPresetIdUsingTemplate() {
Minion before = new Minion("Stuart");
before.id = 42L;
template.insert(before);
Minion reloaded = minions.findById(42L).get();
assertThat(reloaded.name).isEqualTo("Stuart");
}
@Test
void idByCallBack() {
StringIdMinion before = new StringIdMinion("Kevin");
stringions.save(before);
assertThat(before.id).isNotNull();
StringIdMinion reloaded = stringions.findById(before.id).get();
assertThat(reloaded.name).isEqualTo("Kevin");
}
@Test
void determineIsNewPerVersion() {
VersionedMinion before = new VersionedMinion(23L, "Bob");
assertThat(before.id).isNotNull();
versionedMinions.save(before);
// It's saved!
VersionedMinion reloaded = versionedMinions.findById(before.id).get();
assertThat(reloaded.name).isEqualTo("Bob");
}
@Test
void determineIsNewPerPersistable() {
PersistableMinion before = new PersistableMinion(23L, "Dave");
persistableMinions.save(before);
// It's saved!
PersistableMinion reloaded = persistableMinions.findById(before.id).get();
assertThat(reloaded.name).isEqualTo("Dave");
}
}

25
jdbc/howto/pom.xml Normal file
View File

@@ -0,0 +1,25 @@
<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>
<artifactId>spring-data-jdbc-how-to</artifactId>
<packaging>pom</packaging>
<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 - How to Examples</name>
<description>Sample projects for Spring Data JDBC demonstrating various features and options.
It serves as a source code repository for a series of How To articles on the Spring Blog</description>
<url>https://projects.spring.io/spring-data-jdbc</url>
<inceptionYear>2021</inceptionYear>
<modules>
<module>idgeneration</module>
</modules>
</project>

View File

@@ -22,6 +22,7 @@
<module>immutables</module>
<module>jmolecules</module>
<module>jooq</module>
<module>howto</module>
</modules>
<dependencies>