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>jmolecules</module>
|
||||
<module>jooq</module>
|
||||
<module>howto</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
|
||||
Reference in New Issue
Block a user