diff --git a/jdbc/howto/README.adoc b/jdbc/howto/README.adoc
new file mode 100644
index 00000000..d7afa08b
--- /dev/null
+++ b/jdbc/howto/README.adoc
@@ -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.
\ No newline at end of file
diff --git a/jdbc/howto/idgeneration/README.adoc b/jdbc/howto/idgeneration/README.adoc
new file mode 100644
index 00000000..57a7f61c
--- /dev/null
+++ b/jdbc/howto/idgeneration/README.adoc
@@ -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`.
diff --git a/jdbc/howto/idgeneration/pom.xml b/jdbc/howto/idgeneration/pom.xml
new file mode 100644
index 00000000..523a0279
--- /dev/null
+++ b/jdbc/howto/idgeneration/pom.xml
@@ -0,0 +1,20 @@
+
+ 4.0.0
+
+ spring-data-jdbc-how-to-id-generation
+
+
+ org.springframework.data.examples
+ spring-data-jdbc-how-to
+ 2.0.0.BUILD-SNAPSHOT
+ ../pom.xml
+
+
+ Spring Data JDBC - How to ID generation
+ 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
+ https://projects.spring.io/spring-data-jdbc
+ 2021
+
+
diff --git a/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/IdGenerationApplication.java b/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/IdGenerationApplication.java
new file mode 100644
index 00000000..3b76e189
--- /dev/null
+++ b/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/IdGenerationApplication.java
@@ -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 beforeSaveCallback() {
+
+ return (minion, mutableAggregateChange) -> {
+ if (minion.id == null) {
+ minion.id = UUID.randomUUID().toString();
+ }
+ return minion;
+ };
+ }
+
+}
diff --git a/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/Minion.java b/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/Minion.java
new file mode 100644
index 00000000..bbd921cf
--- /dev/null
+++ b/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/Minion.java
@@ -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;
+ }
+}
diff --git a/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/MinionRepository.java b/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/MinionRepository.java
new file mode 100644
index 00000000..1a2ee77f
--- /dev/null
+++ b/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/MinionRepository.java
@@ -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 {
+
+}
diff --git a/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/PersistableMinion.java b/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/PersistableMinion.java
new file mode 100644
index 00000000..9b3a88af
--- /dev/null
+++ b/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/PersistableMinion.java
@@ -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 {
+ @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;
+ }
+}
diff --git a/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/PersistableMinionRepository.java b/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/PersistableMinionRepository.java
new file mode 100644
index 00000000..69311209
--- /dev/null
+++ b/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/PersistableMinionRepository.java
@@ -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 {
+
+}
diff --git a/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/StringIdMinion.java b/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/StringIdMinion.java
new file mode 100644
index 00000000..98bf7c5d
--- /dev/null
+++ b/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/StringIdMinion.java
@@ -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;
+ }
+}
diff --git a/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/StringIdMinionRepository.java b/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/StringIdMinionRepository.java
new file mode 100644
index 00000000..884ee86d
--- /dev/null
+++ b/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/StringIdMinionRepository.java
@@ -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 {
+
+}
diff --git a/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/VersionedMinion.java b/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/VersionedMinion.java
new file mode 100644
index 00000000..7104bfe8
--- /dev/null
+++ b/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/VersionedMinion.java
@@ -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;
+ }
+}
diff --git a/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/VersionedMinionRepository.java b/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/VersionedMinionRepository.java
new file mode 100644
index 00000000..778e06f5
--- /dev/null
+++ b/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/VersionedMinionRepository.java
@@ -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 {
+
+}
diff --git a/jdbc/howto/idgeneration/src/main/resources/schema.sql b/jdbc/howto/idgeneration/src/main/resources/schema.sql
new file mode 100644
index 00000000..42496f02
--- /dev/null
+++ b/jdbc/howto/idgeneration/src/main/resources/schema.sql
@@ -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
+);
diff --git a/jdbc/howto/idgeneration/src/test/java/example/springdata/jdbc/howto/idgeneration/IdGenerationApplicationTests.java b/jdbc/howto/idgeneration/src/test/java/example/springdata/jdbc/howto/idgeneration/IdGenerationApplicationTests.java
new file mode 100644
index 00000000..bf235167
--- /dev/null
+++ b/jdbc/howto/idgeneration/src/test/java/example/springdata/jdbc/howto/idgeneration/IdGenerationApplicationTests.java
@@ -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");
+ }
+}
diff --git a/jdbc/howto/pom.xml b/jdbc/howto/pom.xml
new file mode 100644
index 00000000..d9cc9ee4
--- /dev/null
+++ b/jdbc/howto/pom.xml
@@ -0,0 +1,25 @@
+
+ 4.0.0
+
+ spring-data-jdbc-how-to
+ pom
+
+
+ org.springframework.data.examples
+ spring-data-jdbc-examples
+ 2.0.0.BUILD-SNAPSHOT
+ ../pom.xml
+
+
+ Spring Data JDBC - How to Examples
+ 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
+ https://projects.spring.io/spring-data-jdbc
+ 2021
+
+
+ idgeneration
+
+
+
diff --git a/jdbc/pom.xml b/jdbc/pom.xml
index 4cb80a13..3186a31d 100644
--- a/jdbc/pom.xml
+++ b/jdbc/pom.xml
@@ -22,6 +22,7 @@
immutables
jmolecules
jooq
+ howto