Add examples for various way to generate IDs for Spring Data JDBC.
Original pull request #628
This commit is contained in:
5
jdbc/howto/README.adoc
Normal file
5
jdbc/howto/README.adoc
Normal 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.
|
||||||
24
jdbc/howto/idgeneration/README.adoc
Normal file
24
jdbc/howto/idgeneration/README.adoc
Normal 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`.
|
||||||
20
jdbc/howto/idgeneration/pom.xml
Normal file
20
jdbc/howto/idgeneration/pom.xml
Normal 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>
|
||||||
@@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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> {
|
||||||
|
|
||||||
|
}
|
||||||
18
jdbc/howto/idgeneration/src/main/resources/schema.sql
Normal file
18
jdbc/howto/idgeneration/src/main/resources/schema.sql
Normal 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
|
||||||
|
);
|
||||||
@@ -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
25
jdbc/howto/pom.xml
Normal 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>
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
<module>immutables</module>
|
<module>immutables</module>
|
||||||
<module>jmolecules</module>
|
<module>jmolecules</module>
|
||||||
<module>jooq</module>
|
<module>jooq</module>
|
||||||
|
<module>howto</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|||||||
Reference in New Issue
Block a user