diff --git a/jdbc/howto/pom.xml b/jdbc/howto/pom.xml
index 5da1ec32..0ac931b3 100644
--- a/jdbc/howto/pom.xml
+++ b/jdbc/howto/pom.xml
@@ -24,6 +24,7 @@
caching
idgeneration
selectiveupdate
+ schema-generation
diff --git a/jdbc/howto/schema-generation/.gitignore b/jdbc/howto/schema-generation/.gitignore
new file mode 100644
index 00000000..a718e994
--- /dev/null
+++ b/jdbc/howto/schema-generation/.gitignore
@@ -0,0 +1 @@
+cs-*.yaml
\ No newline at end of file
diff --git a/jdbc/howto/schema-generation/README.adoc b/jdbc/howto/schema-generation/README.adoc
new file mode 100644
index 00000000..3f27e40f
--- /dev/null
+++ b/jdbc/howto/schema-generation/README.adoc
@@ -0,0 +1,5 @@
+== Spring Data JDBC How To Generate the Database Schema.
+
+Spring Data JDBC offers an API to generate Liquibase Changesets from your domain model and optionally an existing database.
+
+This project demonstrates how to do this.
diff --git a/jdbc/howto/schema-generation/pom.xml b/jdbc/howto/schema-generation/pom.xml
new file mode 100644
index 00000000..29f5c92e
--- /dev/null
+++ b/jdbc/howto/schema-generation/pom.xml
@@ -0,0 +1,33 @@
+
+ 4.0.0
+
+ spring-data-jdbc-how-to-schema-generation
+
+
+ org.springframework.data.examples
+ spring-data-jdbc-how-to
+ 2.0.0.BUILD-SNAPSHOT
+ ../pom.xml
+
+
+ Spring Data JDBC - How to do schema generation
+ Sample project for Spring Data JDBC demonstrating how it can be used to generate and update your
+ schema.
+
+ https://projects.spring.io/spring-data-jdbc
+ 2023
+
+
+ 2023.1.0-M2
+
+
+
+
+
+ org.liquibase
+ liquibase-core
+
+
+
+
diff --git a/jdbc/howto/schema-generation/src/main/java/example.springdata/jdbc/howto/caching/Minion.java b/jdbc/howto/schema-generation/src/main/java/example.springdata/jdbc/howto/caching/Minion.java
new file mode 100644
index 00000000..0ac2e9fb
--- /dev/null
+++ b/jdbc/howto/schema-generation/src/main/java/example.springdata/jdbc/howto/caching/Minion.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2023 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.caching;
+
+import org.springframework.data.annotation.Id;
+import org.springframework.data.relational.core.mapping.Table;
+
+
+/**
+ * Entity we use as a fixture for demonstrating Schema Generation.
+ * See SchemaGenerationTest in the test directory for the interesting stuff.
+ *
+ * @author Jens Schauder
+ * @since 3.2
+ */
+@Table
+class Minion {
+
+ @Id
+ Long id;
+
+ @Name
+ String name;
+
+ Minion(String name) {
+ this.name = name;
+ }
+
+ public Long getId() {
+ return id;
+ }
+}
diff --git a/jdbc/howto/schema-generation/src/main/java/example.springdata/jdbc/howto/caching/Name.java b/jdbc/howto/schema-generation/src/main/java/example.springdata/jdbc/howto/caching/Name.java
new file mode 100644
index 00000000..91d4158f
--- /dev/null
+++ b/jdbc/howto/schema-generation/src/main/java/example.springdata/jdbc/howto/caching/Name.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2023 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.caching;
+
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Annotation to mark a property as a Name which should have a very specify database type.
+ *
+ * @author Jens Schauder
+ */
+@Varchar(20)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Name {
+}
diff --git a/jdbc/howto/schema-generation/src/main/java/example.springdata/jdbc/howto/caching/SchemaGenerationExampleApplication.java b/jdbc/howto/schema-generation/src/main/java/example.springdata/jdbc/howto/caching/SchemaGenerationExampleApplication.java
new file mode 100644
index 00000000..75fe10ef
--- /dev/null
+++ b/jdbc/howto/schema-generation/src/main/java/example.springdata/jdbc/howto/caching/SchemaGenerationExampleApplication.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2023 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.caching;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cache.annotation.EnableCaching;
+
+/**
+ * Boot application that we use as a fixture for demonstrating Schema Generation.
+ * See SchemaGenerationTest in the test directory for the interesting stuff.
+ *
+ * @author Jens Schauder
+ * @since 3.2
+ */
+@EnableCaching
+@SpringBootApplication
+class SchemaGenerationExampleApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SchemaGenerationExampleApplication.class, args);
+ }
+
+}
diff --git a/jdbc/howto/schema-generation/src/main/java/example.springdata/jdbc/howto/caching/Varchar.java b/jdbc/howto/schema-generation/src/main/java/example.springdata/jdbc/howto/caching/Varchar.java
new file mode 100644
index 00000000..626af7e1
--- /dev/null
+++ b/jdbc/howto/schema-generation/src/main/java/example.springdata/jdbc/howto/caching/Varchar.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2023 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.caching;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Marks a property as to be represented by a VARCHAR database type.
+ *
+ * @author Jens Schauder
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Varchar {
+
+ /**
+ * the size of the varchar.
+ */
+ int value();
+}
diff --git a/jdbc/howto/schema-generation/src/main/resources/application.properties b/jdbc/howto/schema-generation/src/main/resources/application.properties
new file mode 100644
index 00000000..07ddc16f
--- /dev/null
+++ b/jdbc/howto/schema-generation/src/main/resources/application.properties
@@ -0,0 +1,2 @@
+# Disable Liquibase when you don't have changesets configured yet.
+# spring.liquibase.enabled=false
\ No newline at end of file
diff --git a/jdbc/howto/schema-generation/src/main/resources/db/changelog/db.changelog-master.yaml b/jdbc/howto/schema-generation/src/main/resources/db/changelog/db.changelog-master.yaml
new file mode 100644
index 00000000..615ec854
--- /dev/null
+++ b/jdbc/howto/schema-generation/src/main/resources/db/changelog/db.changelog-master.yaml
@@ -0,0 +1,32 @@
+databaseChangeLog:
+- changeSet:
+ id: '1692725820135'
+ author: Spring Data Relational
+ objectQuotingStrategy: LEGACY
+ changes:
+ - createTable:
+ columns:
+ - column:
+ autoIncrement: true
+ constraints:
+ nullable: true
+ primaryKey: true
+ name: id
+ type: BIGINT
+ - column:
+ constraints:
+ nullable: true
+ name: firstname
+ type: VARCHAR(255 BYTE)
+ - column:
+ constraints:
+ nullable: true
+ name: lastname
+ type: VARCHAR(255 BYTE)
+ - column:
+ constraints:
+ nullable: true
+ name: special
+ type: VARCHAR(255 BYTE)
+ tableName: minion
+
diff --git a/jdbc/howto/schema-generation/src/test/java/example/springdata/jdbc/howto/caching/SchemaGenerationTest.java b/jdbc/howto/schema-generation/src/test/java/example/springdata/jdbc/howto/caching/SchemaGenerationTest.java
new file mode 100644
index 00000000..5f915e43
--- /dev/null
+++ b/jdbc/howto/schema-generation/src/test/java/example/springdata/jdbc/howto/caching/SchemaGenerationTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2023 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.caching;
+
+import liquibase.database.Database;
+import liquibase.database.core.HsqlDatabase;
+import liquibase.database.jvm.JdbcConnection;
+import liquibase.exception.LiquibaseException;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.data.jdbc.core.mapping.schema.DefaultSqlTypeMapping;
+import org.springframework.data.jdbc.core.mapping.schema.LiquibaseChangeSetWriter;
+import org.springframework.data.jdbc.core.mapping.schema.SqlTypeMapping;
+import org.springframework.data.relational.core.mapping.RelationalMappingContext;
+
+import javax.sql.DataSource;
+import java.io.File;
+import java.io.IOException;
+import java.sql.SQLException;
+import java.util.Collections;
+
+/**
+ * Example code demonstrating how to use the Schema Generation Feature.
+ *
+ * @author Jens Schauder
+ * @since 3.2
+ */
+@SpringBootTest
+class SchemaGenerationTest {
+
+ @Autowired
+ RelationalMappingContext context;
+
+ @Autowired
+ DataSource ds;
+
+ @Test
+ void minimumExample() throws IOException {
+
+ // the change set will get appended, so we delete any pre existing file.
+ new File("cs-minimum.yaml").delete();
+
+ context.setInitialEntitySet(Collections.singleton(Minion.class));
+ LiquibaseChangeSetWriter writer = new LiquibaseChangeSetWriter(context);
+
+ writer.writeChangeSet(new FileSystemResource("cs-minimum.yaml"));
+ }
+
+
+ @Test
+ void diffing() {
+
+ // the change set will get appended, so we delete any pre existing file.
+ new File("cs-diff.yaml").delete();
+
+ context.setInitialEntitySet(Collections.singleton(Minion.class));
+ LiquibaseChangeSetWriter writer = new LiquibaseChangeSetWriter(context);
+
+ // drop unused columns
+ writer.setDropColumnFilter((table, column) -> !column.equalsIgnoreCase("special"));
+
+ // for comparison with existing schema
+ try (Database db = new HsqlDatabase()) {
+
+ db.setConnection(new JdbcConnection(ds.getConnection()));
+
+ writer.writeChangeSet(new FileSystemResource("cs-diff.yaml"), db);
+
+ } catch (IOException | SQLException | LiquibaseException e) {
+ throw new RuntimeException("Changeset generation failed", e);
+ }
+ }
+
+ @Test
+ void customizingTypes() throws IOException {
+
+ // the change set will get appended, so we delete any pre existing file.
+ new File("cs-custom.yaml").delete();
+
+ context.setInitialEntitySet(Collections.singleton(Minion.class));
+ LiquibaseChangeSetWriter writer = new LiquibaseChangeSetWriter(context);
+
+ writer.setSqlTypeMapping(((SqlTypeMapping) property -> {
+ if (property.getName().equalsIgnoreCase("name")) {
+ return "VARCHAR(500)";
+ }
+ return null;
+ }).and(new DefaultSqlTypeMapping()));
+
+ writer.writeChangeSet(new FileSystemResource("cs-custom.yaml"));
+
+ }
+
+ @Test
+ void customizingTypesUsingAnnotations() throws IOException {
+
+ // the change set will get appended, so we delete any pre existing file.
+ new File("cs-annotation.yaml").delete();
+
+ context.setInitialEntitySet(Collections.singleton(Minion.class));
+ LiquibaseChangeSetWriter writer = new LiquibaseChangeSetWriter(context);
+
+ writer.setSqlTypeMapping(((SqlTypeMapping) property -> {
+
+ if (!property.getType().equals(String.class)) {
+ return null;
+ }
+
+ // findAnnotation will find meta annotations
+ Varchar varchar = property.findAnnotation(Varchar.class);
+ int value = varchar.value();
+
+ if (varchar == null) {
+ return null;
+ }
+ return "VARCHAR(" +
+ varchar.value() +
+ ")";
+
+ }).and(new DefaultSqlTypeMapping()));
+
+ writer.writeChangeSet(new FileSystemResource("cs-annotation.yaml"));
+
+ }
+}