dings = repository.findProjectedByNameContaining("dings");
+ dings.forEach(it -> System.out.println("Name: %s, Description: %s, Combined: %s".formatted(it.getName(),
+ it.getDescription(), it.getNameAndDescription())));
+ };
+ }
+}
diff --git a/jdbc/aot-optimization/src/main/java/example/springdata/aot/Category.java b/jdbc/aot-optimization/src/main/java/example/springdata/aot/Category.java
new file mode 100644
index 000000000..479dbe509
--- /dev/null
+++ b/jdbc/aot-optimization/src/main/java/example/springdata/aot/Category.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2025 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
+ *
+ * http://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.aot;
+
+import java.time.LocalDateTime;
+
+import org.springframework.data.annotation.Id;
+import org.springframework.data.annotation.PersistenceCreator;
+
+/**
+ * Coarse classification.
+ *
+ * @author Jens Schauder
+ */
+public class Category {
+
+ private final @Id Long id;
+ private String name, description;
+ private LocalDateTime created;
+ private Long inserted;
+
+ public Category(String name, String description) {
+
+ this.id = null;
+ this.name = name;
+ this.description = description;
+ this.created = LocalDateTime.now();
+ }
+
+ @PersistenceCreator
+ Category(Long id, String name, String description, LocalDateTime created, Long inserted) {
+ this.id = id;
+ this.name = name;
+ this.description = description;
+ this.created = created;
+ this.inserted = inserted;
+ }
+
+ public void timeStamp() {
+
+ if (inserted == 0) {
+ inserted = System.currentTimeMillis();
+ }
+ }
+
+ public Long getId() {
+ return this.id;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public String getDescription() {
+ return this.description;
+ }
+
+ public LocalDateTime getCreated() {
+ return this.created;
+ }
+
+ public Long getInserted() {
+ return this.inserted;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public void setCreated(LocalDateTime created) {
+ this.created = created;
+ }
+
+ public void setInserted(Long inserted) {
+ this.inserted = inserted;
+ }
+
+ public Category withId(Long id) {
+ return this.id == id ? this : new Category(id, this.name, this.description, this.created, this.inserted);
+ }
+
+ @Override
+ public String toString() {
+ return "Category(id=" + this.getId() + ", name=" + this.getName() + ", description=" + this.getDescription()
+ + ", created=" + this.getCreated() + ", inserted=" + this.getInserted() + ")";
+ }
+}
diff --git a/jdbc/aot-optimization/src/main/java/example/springdata/aot/CategoryConfiguration.java b/jdbc/aot-optimization/src/main/java/example/springdata/aot/CategoryConfiguration.java
new file mode 100644
index 000000000..67006ef99
--- /dev/null
+++ b/jdbc/aot-optimization/src/main/java/example/springdata/aot/CategoryConfiguration.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2025 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
+ *
+ * http://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.aot;
+
+import org.springframework.context.ApplicationEvent;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.relational.core.mapping.event.BeforeSaveCallback;
+import org.springframework.data.relational.core.mapping.event.RelationalEvent;
+
+/**
+ * Contains infrastructure necessary for creating repositories, listeners and EntityCallbacks.
+ *
+ * Not that a listener may change an entity without any problem.
+ *
+ * @author Jens Schauder
+ * @author Mark Paluch
+ */
+@Configuration
+public class CategoryConfiguration {
+
+ /**
+ * @return {@link ApplicationListener} for {@link RelationalEvent}s.
+ */
+ @Bean
+ public ApplicationListener> loggingListener() {
+
+ return (ApplicationListener) event -> {
+ if (event instanceof RelationalEvent) {
+ System.out.println("Received an event: " + event);
+ }
+ };
+ }
+
+ /**
+ * @return {@link BeforeSaveCallback} for {@link Category}.
+ */
+ @Bean
+ public BeforeSaveCallback timeStampingSaveTime() {
+
+ return (entity, aggregateChange) -> {
+
+ entity.timeStamp();
+
+ return entity;
+ };
+ }
+}
diff --git a/jdbc/aot-optimization/src/main/java/example/springdata/aot/CategoryProjection.java b/jdbc/aot-optimization/src/main/java/example/springdata/aot/CategoryProjection.java
new file mode 100644
index 000000000..3a314cc1c
--- /dev/null
+++ b/jdbc/aot-optimization/src/main/java/example/springdata/aot/CategoryProjection.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2025 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
+ *
+ * http://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.aot;
+
+/**
+ * @author Mark Paluch
+ */
+public interface CategoryProjection {
+
+ String getName();
+
+ String getDescription();
+
+ default String getNameAndDescription() {
+ return getName() + " - " + getDescription();
+ }
+}
diff --git a/jdbc/aot-optimization/src/main/java/example/springdata/aot/CategoryRepository.java b/jdbc/aot-optimization/src/main/java/example/springdata/aot/CategoryRepository.java
new file mode 100644
index 000000000..8efb47d70
--- /dev/null
+++ b/jdbc/aot-optimization/src/main/java/example/springdata/aot/CategoryRepository.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2025 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
+ *
+ * http://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.aot;
+
+import java.util.List;
+
+import org.springframework.data.jdbc.repository.query.Query;
+import org.springframework.data.repository.CrudRepository;
+
+/**
+ * Repository for Categories.
+ *
+ * @author Mark Paluch
+ */
+interface CategoryRepository extends CrudRepository {
+
+ List findAllByNameContaining(String name);
+
+ List findProjectedByNameContaining(String name);
+
+ @Query("SELECT * FROM category WHERE name = :name")
+ List findWithDeclaredQuery(String name);
+
+}
diff --git a/jdbc/aot-optimization/src/main/resources/application.properties b/jdbc/aot-optimization/src/main/resources/application.properties
new file mode 100644
index 000000000..4e2092bc7
--- /dev/null
+++ b/jdbc/aot-optimization/src/main/resources/application.properties
@@ -0,0 +1,2 @@
+#logging.level.org.springframework.data.repository.aot.generate.RepositoryContributor=trace
+
diff --git a/geode/security/src/main/java/example/springdata/geode/client/security/server/ApacheShiroIniConfiguration.java b/jdbc/aot-optimization/src/main/resources/data.sql
similarity index 55%
rename from geode/security/src/main/java/example/springdata/geode/client/security/server/ApacheShiroIniConfiguration.java
rename to jdbc/aot-optimization/src/main/resources/data.sql
index 53f546112..f6c4d9ea3 100644
--- a/geode/security/src/main/java/example/springdata/geode/client/security/server/ApacheShiroIniConfiguration.java
+++ b/jdbc/aot-optimization/src/main/resources/data.sql
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 the original author or authors.
+ * Copyright 2025 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.
@@ -13,13 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package example.springdata.geode.client.security.server;
-
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Profile;
-import org.springframework.data.gemfire.config.annotation.EnableSecurity;
-
-@Configuration
-@EnableSecurity(shiroIniResourcePath = "shiro.ini")
-@Profile("shiro-ini-configuration")
-public class ApacheShiroIniConfiguration {}
+INSERT INTO category(name, description, created, inserted)
+VALUES ('Cars', 'Anything that has approximately 4 wheels.', now(), 1);
+INSERT INTO category(name, description, created, inserted)
+VALUES ('Buildings', 'Walls, anyone?', now(), 2);
+INSERT INTO category(name, description, created, inserted)
+VALUES ('Chemistry Labs', 'Heisenberg calling', now(), 3);
diff --git a/jdbc/aot-optimization/src/main/resources/schema.sql b/jdbc/aot-optimization/src/main/resources/schema.sql
new file mode 100644
index 000000000..369e89873
--- /dev/null
+++ b/jdbc/aot-optimization/src/main/resources/schema.sql
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2025 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
+ *
+ * http://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.
+ */
+CREATE TABLE IF NOT EXISTS category
+(
+ id INTEGER IDENTITY PRIMARY KEY,
+ name VARCHAR(100),
+ description VARCHAR(2000),
+ created DATETIME,
+ inserted BIGINT
+);
diff --git a/jdbc/basics/README.adoc b/jdbc/basics/README.adoc
index 1ec4b6f2a..94b0af2b1 100644
--- a/jdbc/basics/README.adoc
+++ b/jdbc/basics/README.adoc
@@ -6,9 +6,9 @@ This example demonstrate basic usage of JDBC based repositories.
* The `SimpleEntityTests` demonstrate CRUD operations for an entity without references, just simple properties of various types.
-* The `CategoryContext` shows how to configure an application context so that Spring Data JDBC can create repositories.
+* The `CategoryConfiguration` shows how to configure an application context so that Spring Data JDBC can create repositories.
-* The `ApplicationListener` registered in `CategoryContext` demonstrate how to react to events published by Spring Data JDBC and how entities can get manipulated in such event listeners.
+* The `ApplicationListener` registered in `CategoryConfiguration` demonstrate how to react to events published by Spring Data JDBC and how entities can get manipulated in such event listeners.
=== AggregateTests
@@ -27,4 +27,4 @@ This is achieved by providing a custom `NamingStrategy` which maps both to the s
* `LegoSetRepository` has methods that utilize `@Query` annotations.
-* Note that `Model` is a value class, i.e. it is immutable, and doesn't have an ID.
\ No newline at end of file
+* Note that `Model` is a value class, i.e. it is immutable, and doesn't have an ID.
diff --git a/jdbc/basics/pom.xml b/jdbc/basics/pom.xml
index cc22e6a9e..526ce850d 100644
--- a/jdbc/basics/pom.xml
+++ b/jdbc/basics/pom.xml
@@ -8,7 +8,7 @@
org.springframework.data.examples
spring-data-jdbc-examples
- 2.0.0.BUILD-SNAPSHOT
+ 4.0.0-SNAPSHOT
../pom.xml
diff --git a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/AgeGroup.java b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/AgeGroup.java
index f4ccb4cce..64d7c3867 100644
--- a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/AgeGroup.java
+++ b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/AgeGroup.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2018 the original author or authors.
+ * Copyright 2017-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.
diff --git a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/AggregateConfiguration.java b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/AggregateConfiguration.java
index 8b76ffb6a..e64525cbd 100644
--- a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/AggregateConfiguration.java
+++ b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/AggregateConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2018 the original author or authors.
+ * Copyright 2017-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.
@@ -29,7 +29,7 @@
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration;
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
-import org.springframework.data.relational.core.mapping.event.BeforeSaveEvent;
+import org.springframework.data.relational.core.mapping.event.BeforeConvertEvent;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
@@ -51,7 +51,7 @@ public class AggregateConfiguration extends AbstractJdbcConfiguration {
@Bean
public ApplicationListener> idSetting() {
- return (ApplicationListener) event -> {
+ return (ApplicationListener) event -> {
if (event.getEntity() instanceof LegoSet) {
setIds((LegoSet) event.getEntity());
@@ -65,7 +65,7 @@ private void setIds(LegoSet legoSet) {
legoSet.setId(id.incrementAndGet());
}
- Manual manual = legoSet.getManual();
+ var manual = legoSet.getManual();
if (manual != null) {
manual.setId((long) legoSet.getId());
@@ -102,11 +102,11 @@ public NamedParameterJdbcTemplate namedParameterJdbcTemplate(JdbcOperations oper
@Bean
DataSourceInitializer initializer(DataSource dataSource) {
- DataSourceInitializer initializer = new DataSourceInitializer();
+ var initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource);
- ClassPathResource script = new ClassPathResource("schema.sql");
- ResourceDatabasePopulator populator = new ResourceDatabasePopulator(script);
+ var script = new ClassPathResource("schema.sql");
+ var populator = new ResourceDatabasePopulator(script);
initializer.setDatabasePopulator(populator);
return initializer;
diff --git a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/LegoSet.java b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/LegoSet.java
index 95275b7c4..8d4a27380 100644
--- a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/LegoSet.java
+++ b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/LegoSet.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2018 the original author or authors.
+ * Copyright 2017-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.
@@ -92,7 +92,7 @@ private static Period toPeriod(int years) {
public void addModel(String name, String description) {
- Model model = new Model(name, description);
+ var model = new Model(name, description);
models.put(name, model);
}
}
diff --git a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/LegoSetRepository.java b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/LegoSetRepository.java
index 02fc4663b..37ccd6750 100644
--- a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/LegoSetRepository.java
+++ b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/LegoSetRepository.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2018 the original author or authors.
+ * Copyright 2017-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.
@@ -15,13 +15,13 @@
*/
package example.springdata.jdbc.basics.aggregate;
+import java.util.List;
+
import org.springframework.data.jdbc.repository.query.Modifying;
import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
-import java.util.List;
-
/**
* A repository for {@link LegoSet}.
*
@@ -29,11 +29,13 @@
*/
interface LegoSetRepository extends CrudRepository {
- @Query("SELECT m.name model_name, m.description, l.name set_name" +
- " FROM model m" +
- " JOIN lego_set l" +
- " ON m.lego_set = l.id" +
- " WHERE :age BETWEEN l.min_age and l.max_age")
+ @Query("""
+ SELECT m.name model_name, m.description, l.name set_name
+ FROM model m
+ JOIN lego_set l
+ ON m.lego_set = l.id
+ WHERE :age BETWEEN l.min_age and l.max_age
+ """)
List reportModelForAge(@Param("age") int age);
/**
@@ -41,9 +43,11 @@ interface LegoSetRepository extends CrudRepository {
* @param name
* @return
*/
- @Query("select a.*, b.handbuch_id as manual_handbuch_id, b.author as manual_author, b.text as manual_text from lego_set a " +
- "join handbuch b on a.id = b.handbuch_id " +
- "where a.name = :name")
+ @Query("""
+ select a.*, b.handbuch_id as manual_handbuch_id, b.author as manual_author, b.text as manual_text from lego_set a
+ join handbuch b on a.id = b.handbuch_id
+ where a.name = :name
+ """)
List findByName(@Param("name") String name);
@Modifying
diff --git a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/Manual.java b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/Manual.java
index 5329f70bc..c50ea0b07 100644
--- a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/Manual.java
+++ b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/Manual.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2018 the original author or authors.
+ * Copyright 2017-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.
diff --git a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/Model.java b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/Model.java
index a39ab8025..3cefa9d0d 100644
--- a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/Model.java
+++ b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/Model.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2018 the original author or authors.
+ * Copyright 2017-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.
@@ -24,8 +24,5 @@
*
* @author Jens Schauder
*/
-@Value
-@With(AccessLevel.PACKAGE)
-public class Model {
- String name, description;
+public record Model(String name, String description) {
}
diff --git a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/ModelReport.java b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/ModelReport.java
index 51b9849fc..c5a19dcf5 100644
--- a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/ModelReport.java
+++ b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/ModelReport.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 the original author or authors.
+ * Copyright 2018-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.
@@ -17,13 +17,10 @@
import lombok.AccessLevel;
import lombok.Value;
-import lombok.experimental.Wither;
+import lombok.With;
/**
* @author Jens Schauder
*/
-@Value
-@Wither(AccessLevel.PACKAGE)
-public class ModelReport {
- String modelName, description, setName;
+public record ModelReport(String modelName, String description, String setName) {
}
diff --git a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/simpleentity/Category.java b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/simpleentity/Category.java
index b6bbee29a..d8232a161 100644
--- a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/simpleentity/Category.java
+++ b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/simpleentity/Category.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2018 the original author or authors.
+ * Copyright 2017-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.
@@ -21,12 +21,12 @@
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Setter;
-import lombok.experimental.Wither;
+import lombok.With;
import java.time.LocalDateTime;
import org.springframework.data.annotation.Id;
-import org.springframework.data.annotation.PersistenceConstructor;
+import org.springframework.data.annotation.PersistenceCreator;
/**
* Coarse classification for {@link LegoSet}s, like "Car", "Plane", "Building" and so on.
@@ -34,10 +34,10 @@
* @author Jens Schauder
*/
@Data
-@AllArgsConstructor(access = AccessLevel.PRIVATE, onConstructor = @__(@PersistenceConstructor))
+@AllArgsConstructor(access = AccessLevel.PRIVATE, onConstructor = @__(@PersistenceCreator))
public class Category {
- private final @Id @Wither Long id;
+ private final @Id @With Long id;
private String name, description;
private LocalDateTime created;
private @Setter long inserted;
diff --git a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/simpleentity/CategoryConfiguration.java b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/simpleentity/CategoryConfiguration.java
index 3f75e65c6..abb95942b 100644
--- a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/simpleentity/CategoryConfiguration.java
+++ b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/simpleentity/CategoryConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2018 the original author or authors.
+ * Copyright 2017-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.
diff --git a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/simpleentity/CategoryRepository.java b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/simpleentity/CategoryRepository.java
index 709272863..88bc671be 100644
--- a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/simpleentity/CategoryRepository.java
+++ b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/simpleentity/CategoryRepository.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2018 the original author or authors.
+ * Copyright 2017-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.
diff --git a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/simpleentity/WithInsert.java b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/simpleentity/WithInsert.java
index 66faeecdf..937a0f831 100644
--- a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/simpleentity/WithInsert.java
+++ b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/simpleentity/WithInsert.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 the original author or authors.
+ * Copyright 2019-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.
diff --git a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/simpleentity/WithInsertImpl.java b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/simpleentity/WithInsertImpl.java
index 207f9172f..a65993bf8 100644
--- a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/simpleentity/WithInsertImpl.java
+++ b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/simpleentity/WithInsertImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 the original author or authors.
+ * Copyright 2019-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.
diff --git a/jdbc/basics/src/main/resources/application.properties b/jdbc/basics/src/main/resources/application.properties
index 2804353df..912eeb5f4 100644
--- a/jdbc/basics/src/main/resources/application.properties
+++ b/jdbc/basics/src/main/resources/application.properties
@@ -1,2 +1,2 @@
logging.level.org.springframework.data=INFO
-logging.level.org.springframework.jdbc.core.JdbcTemplate=DEBUG
\ No newline at end of file
+logging.level.org.springframework.jdbc.core.JdbcTemplate=DEBUG
diff --git a/jdbc/basics/src/test/java/example/springdata/jdbc/basics/Output.java b/jdbc/basics/src/test/java/example/springdata/jdbc/basics/Output.java
index 5803b42b7..fbdb5fcbb 100644
--- a/jdbc/basics/src/test/java/example/springdata/jdbc/basics/Output.java
+++ b/jdbc/basics/src/test/java/example/springdata/jdbc/basics/Output.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2018 the original author or authors.
+ * Copyright 2017-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.
@@ -32,7 +32,7 @@ public class Output {
public static void list(Iterable> categories, String title) {
- StringBuilder message = new StringBuilder(String.format("==== %s ====\n", title));
+ var message = new StringBuilder(String.format("==== %s ====\n", title));
categories.forEach(category -> message.append(category.toString().replace(", ", ",\n\t")));
diff --git a/jdbc/basics/src/test/java/example/springdata/jdbc/basics/aggregate/AggregateTests.java b/jdbc/basics/src/test/java/example/springdata/jdbc/basics/aggregate/AggregateTests.java
index 30b470d38..513c98997 100644
--- a/jdbc/basics/src/test/java/example/springdata/jdbc/basics/aggregate/AggregateTests.java
+++ b/jdbc/basics/src/test/java/example/springdata/jdbc/basics/aggregate/AggregateTests.java
@@ -1,11 +1,11 @@
/*
- * Copyright 2017-2018 the original author or authors.
+ * Copyright 2025 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
+ * http://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,
@@ -21,40 +21,38 @@
import java.time.Period;
import java.util.Arrays;
-import java.util.List;
import org.assertj.core.groups.Tuple;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.Test;
+
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.data.jdbc.AutoConfigureDataJdbc;
+import org.springframework.boot.data.jdbc.test.autoconfigure.AutoConfigureDataJdbc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
-import org.springframework.test.context.junit4.SpringRunner;
/**
* Demonstrates various possibilities to customize the behavior of a repository.
*
* @author Jens Schauder
+ * @author Divya Srivastava
*/
-@RunWith(SpringRunner.class)
@SpringBootTest(classes = AggregateConfiguration.class)
@AutoConfigureDataJdbc
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
-public class AggregateTests {
+class AggregateTests {
@Autowired LegoSetRepository repository;
@Test
- public void exerciseSomewhatComplexEntity() {
+ void exerciseSomewhatComplexEntity() {
- LegoSet smallCar = createLegoSet("Small Car 01", 5, 12);
+ var smallCar = createLegoSet("Small Car 01", 5, 12);
smallCar.setManual(new Manual("Just put all the pieces together in the right order", "Jens Schauder"));
smallCar.addModel("suv", "SUV with sliding doors.");
smallCar.addModel("roadster", "Slick red roadster.");
repository.save(smallCar);
- Iterable legoSets = repository.findAll();
+ var legoSets = repository.findAll();
Output.list(legoSets, "Original LegoSet");
checkLegoSets(legoSets, "Just put all the pieces together in the right order", 2);
@@ -75,20 +73,20 @@ public void exerciseSomewhatComplexEntity() {
}
@Test
- public void customQueries() {
+ void customQueries() {
- String smallCarsSetName = "Small Car - 01";
- LegoSet smallCars = createLegoSet(smallCarsSetName, 5, 10);
+ var smallCarsSetName = "Small Car - 01";
+ var smallCars = createLegoSet(smallCarsSetName, 5, 10);
smallCars.setManual(new Manual("Just put all the pieces together in the right order", "Jens Schauder"));
smallCars.addModel("SUV", "SUV with sliding doors.");
smallCars.addModel("roadster", "Slick red roadster.");
- LegoSet f1Racer = createLegoSet("F1 Racer", 6, 15);
+ var f1Racer = createLegoSet("F1 Racer", 6, 15);
f1Racer.setManual(new Manual("Build a helicopter or a plane", "M. Shoemaker"));
f1Racer.addModel("F1 Ferrari 2018", "A very fast red car.");
- LegoSet constructionVehicles = createLegoSet("Construction Vehicles", 3, 6);
+ var constructionVehicles = createLegoSet("Construction Vehicles", 3, 6);
constructionVehicles.setManual(
new Manual("Build a Road Roler, a Mobile Crane, a Tracked Dumper, or a Backhoe Loader ", "Bob the Builder"));
@@ -100,23 +98,23 @@ public void customQueries() {
repository.saveAll(Arrays.asList(smallCars, f1Racer, constructionVehicles));
- List report = repository.reportModelForAge(6);
+ var report = repository.reportModelForAge(6);
Output.list(report, "Model Report");
assertThat(report).hasSize(7)
- .allMatch(m -> m.getDescription() != null && m.getModelName() != null && m.getSetName() != null);
+ .allMatch(m -> m.description() != null && m.modelName() != null && m.setName() != null);
- int updated = repository.lowerCaseMapKeys();
+ var updated = repository.lowerCaseMapKeys();
// SUV, F1 Ferrari 2018 and Muck get updated
assertThat(updated).isEqualTo(3);
- final List legoSetsByName = repository.findByName(smallCarsSetName);
+ var legoSetsByName = repository.findByName(smallCarsSetName);
assertThat(legoSetsByName).hasSize(1);
}
private LegoSet createLegoSet(String name, int minimumAge, int maximumAge) {
- LegoSet smallCar = new LegoSet();
+ var smallCar = new LegoSet();
smallCar.setName(name);
smallCar.setMinimumAge(Period.ofYears(minimumAge));
diff --git a/jdbc/basics/src/test/java/example/springdata/jdbc/basics/simpleentity/SimpleEntityTests.java b/jdbc/basics/src/test/java/example/springdata/jdbc/basics/simpleentity/SimpleEntityTests.java
index bf1ecfedb..bd2d83384 100644
--- a/jdbc/basics/src/test/java/example/springdata/jdbc/basics/simpleentity/SimpleEntityTests.java
+++ b/jdbc/basics/src/test/java/example/springdata/jdbc/basics/simpleentity/SimpleEntityTests.java
@@ -1,11 +1,11 @@
/*
- * Copyright 2017-2018 the original author or authors.
+ * Copyright 2025 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
+ * http://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,
@@ -20,31 +20,30 @@
import example.springdata.jdbc.basics.Output;
import example.springdata.jdbc.basics.aggregate.AgeGroup;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.Test;
+
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureJdbc;
+import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureJdbc;
import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.junit4.SpringRunner;
/**
* Demonstrates simple CRUD operations with a simple entity without any references.
*
* @author Jens Schauder
+ * @author Divya Srivastava
*/
-@RunWith(SpringRunner.class)
@SpringBootTest(classes = CategoryConfiguration.class)
@AutoConfigureJdbc
-public class SimpleEntityTests {
+class SimpleEntityTests {
@Autowired CategoryRepository repository;
@Test
- public void exerciseRepositoryForSimpleEntity() {
+ void exerciseRepositoryForSimpleEntity() {
// create some categories
- Category cars = repository.save(new Category("Cars", "Anything that has approximately 4 wheels.", AgeGroup._3to8));
- Category buildings = repository.save(new Category("Buildings", null, AgeGroup._12andOlder));
+ var cars = repository.save(new Category("Cars", "Anything that has approximately 4 wheels.", AgeGroup._3to8));
+ var buildings = repository.save(new Category("Buildings", null, AgeGroup._12andOlder));
// save categories
Output.list(repository.findAll(), "`Cars` and `Buildings` got saved");
@@ -63,9 +62,9 @@ public void exerciseRepositoryForSimpleEntity() {
}
@Test
- public void directInsert(){
+ void directInsert() {
- Category cars = new Category("Cars", "Anything that has approximately 4 wheels.", AgeGroup._3to8).withId(23L);
+ var cars = new Category("Cars", "Anything that has approximately 4 wheels.", AgeGroup._3to8).withId(23L);
repository.insert(cars);
Output.list(repository.findAll(), "`Cars` inserted with id 23L");
diff --git a/jdbc/composite-ids/README.adoc b/jdbc/composite-ids/README.adoc
new file mode 100644
index 000000000..739d1d369
--- /dev/null
+++ b/jdbc/composite-ids/README.adoc
@@ -0,0 +1,8 @@
+== Spring Data JDBC Composite Id
+
+=== EmployeeTest
+
+Demonstrates saving an entity with composite id.
+
+Once by using a direct insert, via a custom `insert` method in the repository, backed by `JdbcAggregateTemplate.insert` and once by a custom id generating callback.
+See `CompositeConfiguration.idGeneration()`.
\ No newline at end of file
diff --git a/jdbc/composite-ids/pom.xml b/jdbc/composite-ids/pom.xml
new file mode 100644
index 000000000..bd73353b3
--- /dev/null
+++ b/jdbc/composite-ids/pom.xml
@@ -0,0 +1,25 @@
+
+ 4.0.0
+
+ spring-data-jdbc-composite-ids
+
+
+ org.springframework.data.examples
+ spring-data-jdbc-examples
+ 4.0.0-SNAPSHOT
+ ../pom.xml
+
+
+ Spring Data JDBC - Examples using composite ids
+ Sample project demonstrating Spring Data JDBCs support for custom ids
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+
diff --git a/jdbc/composite-ids/src/main/java/example/springdata/jdbc/compositeid/CompositeConfiguration.java b/jdbc/composite-ids/src/main/java/example/springdata/jdbc/compositeid/CompositeConfiguration.java
new file mode 100644
index 000000000..776d4a65a
--- /dev/null
+++ b/jdbc/composite-ids/src/main/java/example/springdata/jdbc/compositeid/CompositeConfiguration.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2025 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.compositeid;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration;
+import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
+import org.springframework.data.relational.core.mapping.event.BeforeConvertCallback;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+
+/**
+ * Configuration for the demonstration of composite ids.
+ *
+ * Registers a {@link BeforeConvertCallback} for generating ids.
+ *
+ * @author Jens Schauder
+ */
+@Configuration
+@EnableJdbcRepositories
+public class CompositeConfiguration extends AbstractJdbcConfiguration {
+
+ @Bean
+ BeforeConvertCallback idGeneration() {
+ return new BeforeConvertCallback<>() {
+ AtomicLong counter = new AtomicLong();
+
+ @Override
+ public Employee onBeforeConvert(Employee employee) {
+ if (employee.id == null) {
+ employee.id = new EmployeeId(Organization.RND, counter.addAndGet(1));
+ }
+ return employee;
+ }
+ };
+ }
+}
diff --git a/jdbc/composite-ids/src/main/java/example/springdata/jdbc/compositeid/Employee.java b/jdbc/composite-ids/src/main/java/example/springdata/jdbc/compositeid/Employee.java
new file mode 100644
index 000000000..070614f6e
--- /dev/null
+++ b/jdbc/composite-ids/src/main/java/example/springdata/jdbc/compositeid/Employee.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2025 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.compositeid;
+
+import org.springframework.data.annotation.Id;
+import org.springframework.data.annotation.PersistenceCreator;
+
+/**
+ * A simple entity sporting a compostite id.
+ *
+ * @author Jens Schauder
+ */
+class Employee {
+
+ @Id
+ EmployeeId id;
+
+ String name;
+
+ @PersistenceCreator
+ Employee(EmployeeId id, String name) {
+
+ this.id = id;
+ this.name = name;
+ }
+
+ Employee(String name) {
+ this.name = name;
+ }
+}
diff --git a/jdbc/composite-ids/src/main/java/example/springdata/jdbc/compositeid/EmployeeId.java b/jdbc/composite-ids/src/main/java/example/springdata/jdbc/compositeid/EmployeeId.java
new file mode 100644
index 000000000..835ae6a62
--- /dev/null
+++ b/jdbc/composite-ids/src/main/java/example/springdata/jdbc/compositeid/EmployeeId.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2025 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.compositeid;
+
+/**
+ * Composite id for {@link Employee} instances.
+ *
+ * @author Jens Schauder
+ */
+record EmployeeId(
+ Organization organization,
+ Long employeeNumber) {
+}
diff --git a/jdbc/composite-ids/src/main/java/example/springdata/jdbc/compositeid/EmployeeRepository.java b/jdbc/composite-ids/src/main/java/example/springdata/jdbc/compositeid/EmployeeRepository.java
new file mode 100644
index 000000000..066208a58
--- /dev/null
+++ b/jdbc/composite-ids/src/main/java/example/springdata/jdbc/compositeid/EmployeeRepository.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2025 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.compositeid;
+
+import org.springframework.data.repository.Repository;
+
+
+/**
+ * Repositories for {@link Employee} instances.
+ *
+ * @author Jens Schauder
+ * @see InsertRepository
+ * @see InsertRepositoryImpl
+ */
+interface EmployeeRepository extends Repository, InsertRepository {
+ Employee findById(EmployeeId id);
+
+ Employee save(Employee employee);
+}
diff --git a/jdbc/composite-ids/src/main/java/example/springdata/jdbc/compositeid/InsertRepository.java b/jdbc/composite-ids/src/main/java/example/springdata/jdbc/compositeid/InsertRepository.java
new file mode 100644
index 000000000..562b26ffb
--- /dev/null
+++ b/jdbc/composite-ids/src/main/java/example/springdata/jdbc/compositeid/InsertRepository.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2025 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.compositeid;
+
+
+/**
+ * Interface for repositories supporting an {@literal insert} operation, that always performs an insert on the database
+ * and does not check the instance.
+ *
+ * @author Jens Schauder
+ */
+interface InsertRepository {
+ E insert(E employee);
+}
diff --git a/jdbc/composite-ids/src/main/java/example/springdata/jdbc/compositeid/InsertRepositoryImpl.java b/jdbc/composite-ids/src/main/java/example/springdata/jdbc/compositeid/InsertRepositoryImpl.java
new file mode 100644
index 000000000..2735a6024
--- /dev/null
+++ b/jdbc/composite-ids/src/main/java/example/springdata/jdbc/compositeid/InsertRepositoryImpl.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2025 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.compositeid;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.jdbc.core.JdbcAggregateTemplate;
+
+/**
+ * Fragment implementing the {@literal insert} operation using a {@link JdbcAggregateTemplate}.
+ *
+ * @author Jens Schauder
+ */
+class InsertRepositoryImpl implements InsertRepository {
+
+ private JdbcAggregateTemplate template;
+
+ InsertRepositoryImpl(JdbcAggregateTemplate template) {
+ this.template = template;
+ }
+
+ @Override
+ public E insert(E employee) {
+ return template.insert(employee);
+ }
+}
diff --git a/jdbc/composite-ids/src/main/java/example/springdata/jdbc/compositeid/Organization.java b/jdbc/composite-ids/src/main/java/example/springdata/jdbc/compositeid/Organization.java
new file mode 100644
index 000000000..d53191357
--- /dev/null
+++ b/jdbc/composite-ids/src/main/java/example/springdata/jdbc/compositeid/Organization.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2025 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.compositeid;
+
+/**
+ * Just an enum to be part of the composite id, to demonstrate that one may use various datatypes.
+ *
+ * @author Jens Schauder
+ */
+enum Organization {
+ RND,
+ SALES,
+ MARKETING,
+ PURCHASING
+}
diff --git a/jdbc/composite-ids/src/main/java/example/springdata/jdbc/compositeid/package-info.java b/jdbc/composite-ids/src/main/java/example/springdata/jdbc/compositeid/package-info.java
new file mode 100644
index 000000000..b7ef233b4
--- /dev/null
+++ b/jdbc/composite-ids/src/main/java/example/springdata/jdbc/compositeid/package-info.java
@@ -0,0 +1,4 @@
+@NonNullApi
+package example.springdata.jdbc.compositeid;
+
+import org.springframework.lang.NonNullApi;
\ No newline at end of file
diff --git a/jdbc/composite-ids/src/main/resources/application.properties b/jdbc/composite-ids/src/main/resources/application.properties
new file mode 100644
index 000000000..2804353df
--- /dev/null
+++ b/jdbc/composite-ids/src/main/resources/application.properties
@@ -0,0 +1,2 @@
+logging.level.org.springframework.data=INFO
+logging.level.org.springframework.jdbc.core.JdbcTemplate=DEBUG
\ No newline at end of file
diff --git a/jdbc/composite-ids/src/main/resources/schema.sql b/jdbc/composite-ids/src/main/resources/schema.sql
new file mode 100644
index 000000000..73e9ee996
--- /dev/null
+++ b/jdbc/composite-ids/src/main/resources/schema.sql
@@ -0,0 +1,6 @@
+create table employee
+(
+ organization varchar(20),
+ employee_number int,
+ name varchar(100)
+);
diff --git a/jdbc/composite-ids/src/test/java/example/springdata/jdbc/compositeid/EmployeeTests.java b/jdbc/composite-ids/src/test/java/example/springdata/jdbc/compositeid/EmployeeTests.java
new file mode 100644
index 000000000..c59e95c32
--- /dev/null
+++ b/jdbc/composite-ids/src/test/java/example/springdata/jdbc/compositeid/EmployeeTests.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2025 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
+ *
+ * http://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.compositeid;
+
+import static org.assertj.core.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.data.jdbc.test.autoconfigure.AutoConfigureDataJdbc;
+import org.springframework.boot.test.context.SpringBootTest;
+
+/**
+ * Test demonstrating the use of composite ids.
+ *
+ * @author Jens Schauder
+ */
+@SpringBootTest(classes = CompositeConfiguration.class)
+@AutoConfigureDataJdbc
+class EmployeeTests {
+
+ @Autowired
+ EmployeeRepository repository;
+
+ @Test
+ void employeeDirectInsert() {
+
+ Employee employee = repository.insert(new Employee(new EmployeeId(Organization.RND, 23L), "Jens Schauder"));
+
+ Employee reloaded = repository.findById(employee.id);
+
+ assertThat(reloaded.name).isEqualTo(employee.name);
+ }
+
+ @Test
+ void employeeIdGeneration() {
+
+ Employee employee = repository.save(new Employee("Mark Paluch"));
+
+ assertThat(employee.id).isNotNull();
+ }
+}
diff --git a/jdbc/graalvm-native/README.adoc b/jdbc/graalvm-native/README.adoc
new file mode 100644
index 000000000..3aa10ed9b
--- /dev/null
+++ b/jdbc/graalvm-native/README.adoc
@@ -0,0 +1,47 @@
+== Spring Data JDBC - GraalVM native image
+
+This example compiles a basic Spring Data JDBC application into a GraalVM native image.
+
+=== Install GraalVM & native image tooling
+
+Download and install GraalVM using https://sdkman.io/[SDKMAN!].
+
+```
+$> sdk install java .r17-grl
+$> gu install native-image
+```
+
+=== Compile to native image
+
+The maven build uses a dedicated profile `native` to trigger the native image creation.
+
+```
+$> maven clean package -P native
+```
+
+This will create the native executable in the target folder.
+
+=== Run the image
+
+Run the image directly from your console as shown below.
+This will print results of crud functions invoked via a `CommandLineRunner`.
+
+```
+$> ./target/spring-data-jdbc-graalvm-native
+
+ . ____ _ __ _ _
+ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
+( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
+ \\/ ___)| |_)| | | | | || (_| | ) ) ) )
+ ' |____| .__|_| |_|_| |_\__, | / / / /
+ =========|_|==============|___/=/_/_/_/
+ :: Spring Boot :: (v3.0.0-SNAPSHOT)
+
+INFO 82562 --- [ main] e.s.j.g.GraalvmNativeApplication : Starting GraalvmNativeApplication using Java 17.0.4 with PID 82562
+INFO 82562 --- [ main] e.s.j.g.GraalvmNativeApplication : Started GraalvmNativeApplication in 0.042 seconds (process running for 0.061)
+insertAuthors(): author1 = Author{name='Josh Long'}
+insertAuthors(): author2 = Author{name='Martin Kleppmann'}
+listAllAuthors(): author = Author{name='Josh Long'}
+ Book{title='Reactive Spring'}
+...
+```
diff --git a/jdbc/graalvm-native/pom.xml b/jdbc/graalvm-native/pom.xml
new file mode 100644
index 000000000..4450cd3dc
--- /dev/null
+++ b/jdbc/graalvm-native/pom.xml
@@ -0,0 +1,54 @@
+
+ 4.0.0
+
+ spring-data-jdbc-graalvm-native
+
+
+ org.springframework.data.examples
+ spring-data-examples
+ 4.0.0-SNAPSHOT
+ ../../pom.xml
+
+
+ Spring Data JDBC - GraalVM native sample
+ Sample project demonstrating Spring Data JDBC features running as GraalVM native image
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jdbc
+
+
+
+ com.h2database
+ h2
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+
+
+ native
+
+
+
+ org.graalvm.buildtools
+ native-maven-plugin
+
+
+
+
+
+
+
diff --git a/geode/events/src/main/java/example/springdata/geode/server/events/Address.java b/jdbc/graalvm-native/src/main/java/example/springdata/jdbc/graalvmnative/AuthorRepository.java
old mode 100755
new mode 100644
similarity index 50%
rename from geode/events/src/main/java/example/springdata/geode/server/events/Address.java
rename to jdbc/graalvm-native/src/main/java/example/springdata/jdbc/graalvmnative/AuthorRepository.java
index 47300101e..e9b66bb4e
--- a/geode/events/src/main/java/example/springdata/geode/server/events/Address.java
+++ b/jdbc/graalvm-native/src/main/java/example/springdata/jdbc/graalvmnative/AuthorRepository.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 the original author or authors.
+ * Copyright 2022 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.
@@ -13,29 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package example.springdata.geode.server.events;
+package example.springdata.jdbc.graalvmnative;
-import lombok.Data;
+import java.util.Optional;
-import java.io.Serializable;
+import example.springdata.jdbc.graalvmnative.model.Author;
+import org.springframework.data.jdbc.repository.query.Query;
+import org.springframework.data.repository.ListCrudRepository;
-/**
- * An address used in the examples.
- *
- * @author Oliver Gierke
- * @author Udo Kohlmeyer
- * @author Patrick Johnson
- */
-@Data
-public class Address implements Serializable {
+public interface AuthorRepository extends ListCrudRepository {
- private String street;
- private String city;
- private String country;
+ Optional findByNameContainingIgnoreCase(String partialName);
- public Address(String street, String city, String country) {
- this.street = street;
- this.city = city;
- this.country = country;
- }
+ @Query("SELECT * FROM author a WHERE a.name = :name LIMIT 1")
+ Optional queryFindByName(String name);
}
diff --git a/jdbc/graalvm-native/src/main/java/example/springdata/jdbc/graalvmnative/CLR.java b/jdbc/graalvm-native/src/main/java/example/springdata/jdbc/graalvmnative/CLR.java
new file mode 100644
index 000000000..785906499
--- /dev/null
+++ b/jdbc/graalvm-native/src/main/java/example/springdata/jdbc/graalvmnative/CLR.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2022 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.graalvmnative;
+
+import java.util.List;
+import java.util.Set;
+
+import example.springdata.jdbc.graalvmnative.model.Author;
+import example.springdata.jdbc.graalvmnative.model.Book;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Christoph Strobl
+ * @since 2022/10
+ */
+@Component
+public class CLR implements CommandLineRunner {
+
+ private final AuthorRepository authorRepository;
+
+ CLR(AuthorRepository authorRepository) {
+ this.authorRepository = authorRepository;
+ }
+
+ @Override
+ public void run(String... args) {
+ insertAuthors();
+ listAllAuthors();
+ findById();
+ findByPartialName();
+ queryFindByName();
+ deleteAll();
+ }
+
+ private void deleteAll() {
+ this.authorRepository.deleteAll();
+ long count = this.authorRepository.count();
+ System.out.printf("deleteAll(): count = %d%n", count);
+ }
+
+ private void queryFindByName() {
+ Author author1 = this.authorRepository.queryFindByName("Josh Long").orElse(null);
+ Author author2 = this.authorRepository.queryFindByName("Martin Kleppmann").orElse(null);
+
+ System.out.printf("queryFindByName(): author1 = %s%n", author1);
+ System.out.printf("queryFindByName(): author2 = %s%n", author2);
+ }
+
+ private void findByPartialName() {
+ Author author1 = this.authorRepository.findByNameContainingIgnoreCase("sh lo").orElse(null);
+ Author author2 = this.authorRepository.findByNameContainingIgnoreCase("in kl").orElse(null);
+
+ System.out.printf("findByPartialName(): author1 = %s%n", author1);
+ System.out.printf("findByPartialName(): author2 = %s%n", author2);
+ }
+
+ private void findById() {
+ Author author1 = this.authorRepository.findById(1L).orElse(null);
+ Author author2 = this.authorRepository.findById(2L).orElse(null);
+
+ System.out.printf("findById(): author1 = %s%n", author1);
+ System.out.printf("findById(): author2 = %s%n", author2);
+ }
+
+ private void listAllAuthors() {
+ List authors = this.authorRepository.findAll();
+ for (Author author : authors) {
+ System.out.printf("listAllAuthors(): author = %s%n", author);
+ for (Book book : author.getBooks()) {
+ System.out.printf("\t%s%n", book);
+ }
+ }
+ }
+
+ private void insertAuthors() {
+ Author author1 = this.authorRepository.save(new Author(null, "Josh Long",
+ Set.of(new Book(null, "Reactive Spring"), new Book(null, "Cloud Native Java"))));
+ Author author2 = this.authorRepository.save(
+ new Author(null, "Martin Kleppmann", Set.of(new Book(null, "Designing Data Intensive Applications"))));
+
+ System.out.printf("insertAuthors(): author1 = %s%n", author1);
+ System.out.printf("insertAuthors(): author2 = %s%n", author2);
+ }
+}
diff --git a/geode/transactions/src/main/java/example/springdata/geode/client/transactions/server/TransactionalServer.java b/jdbc/graalvm-native/src/main/java/example/springdata/jdbc/graalvmnative/GraalvmNativeApplication.java
old mode 100755
new mode 100644
similarity index 63%
rename from geode/transactions/src/main/java/example/springdata/geode/client/transactions/server/TransactionalServer.java
rename to jdbc/graalvm-native/src/main/java/example/springdata/jdbc/graalvmnative/GraalvmNativeApplication.java
index 791e879ef..f78bd031b
--- a/geode/transactions/src/main/java/example/springdata/geode/client/transactions/server/TransactionalServer.java
+++ b/jdbc/graalvm-native/src/main/java/example/springdata/jdbc/graalvmnative/GraalvmNativeApplication.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 the original author or authors.
+ * Copyright 2022 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.
@@ -13,20 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package example.springdata.geode.client.transactions.server;
+package example.springdata.jdbc.graalvmnative;
-import org.springframework.boot.WebApplicationType;
+import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.boot.builder.SpringApplicationBuilder;
-/**
- * @author Patrick Johnson
- */
@SpringBootApplication
-public class TransactionalServer {
+public class GraalvmNativeApplication {
public static void main(String[] args) {
- new SpringApplicationBuilder(TransactionalServer.class).web(WebApplicationType.NONE).build().run(args);
+ SpringApplication.run(GraalvmNativeApplication.class, args);
}
-
}
diff --git a/jdbc/graalvm-native/src/main/java/example/springdata/jdbc/graalvmnative/model/Author.java b/jdbc/graalvm-native/src/main/java/example/springdata/jdbc/graalvmnative/model/Author.java
new file mode 100644
index 000000000..29f476511
--- /dev/null
+++ b/jdbc/graalvm-native/src/main/java/example/springdata/jdbc/graalvmnative/model/Author.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2022 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.graalvmnative.model;
+
+import java.util.Objects;
+import java.util.Set;
+
+import org.springframework.data.annotation.Id;
+
+public class Author {
+
+ @Id
+ private final Long id;
+
+ private final String name;
+
+ private final Set books;
+
+ public Author(Long id, String name, Set books) {
+ this.id = id;
+ this.name = name;
+ this.books = books;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Set getBooks() {
+ return books;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Author author = (Author) o;
+ return Objects.equals(id, author.id) && Objects.equals(name, author.name)
+ && Objects.equals(books, author.books);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, name, books);
+ }
+
+ @Override
+ public String toString() {
+ return "Author{" + "name='" + name + '\'' + '}';
+ }
+}
diff --git a/jdbc/graalvm-native/src/main/java/example/springdata/jdbc/graalvmnative/model/Book.java b/jdbc/graalvm-native/src/main/java/example/springdata/jdbc/graalvmnative/model/Book.java
new file mode 100644
index 000000000..ea3d59163
--- /dev/null
+++ b/jdbc/graalvm-native/src/main/java/example/springdata/jdbc/graalvmnative/model/Book.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2022 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.graalvmnative.model;
+
+import java.util.Objects;
+
+import org.springframework.data.annotation.Id;
+
+public class Book {
+
+ @Id
+ private final Long id;
+
+ private final String title;
+
+ public Book(Long id, String title) {
+ this.id = id;
+ this.title = title;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Book book = (Book) o;
+ return Objects.equals(id, book.id) && Objects.equals(title, book.title);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, title);
+ }
+
+ @Override
+ public String toString() {
+ return "Book{" + "title='" + title + '\'' + '}';
+ }
+}
diff --git a/jdbc/graalvm-native/src/main/resources/application.properties b/jdbc/graalvm-native/src/main/resources/application.properties
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/jdbc/graalvm-native/src/main/resources/application.properties
@@ -0,0 +1 @@
+
diff --git a/jdbc/graalvm-native/src/main/resources/schema.sql b/jdbc/graalvm-native/src/main/resources/schema.sql
new file mode 100644
index 000000000..14e51639f
--- /dev/null
+++ b/jdbc/graalvm-native/src/main/resources/schema.sql
@@ -0,0 +1,12 @@
+create table author
+(
+ id bigint auto_increment primary key,
+ name varchar not null
+);
+
+create table book
+(
+ id bigint auto_increment primary key,
+ author bigint not null references author (id),
+ title varchar not null
+);
diff --git a/jdbc/graalvm-native/src/test/java/example/springdata/jdbc/graalvmnative/GraalvmNativeApplicationTests.java b/jdbc/graalvm-native/src/test/java/example/springdata/jdbc/graalvmnative/GraalvmNativeApplicationTests.java
new file mode 100644
index 000000000..90520e4ad
--- /dev/null
+++ b/jdbc/graalvm-native/src/test/java/example/springdata/jdbc/graalvmnative/GraalvmNativeApplicationTests.java
@@ -0,0 +1,13 @@
+package example.springdata.jdbc.graalvmnative;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class GraalvmNativeApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
diff --git a/jdbc/howto/README.adoc b/jdbc/howto/README.adoc
new file mode 100644
index 000000000..0be538297
--- /dev/null
+++ b/jdbc/howto/README.adoc
@@ -0,0 +1,13 @@
+== 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.
+
+=== Bidirectional aggregate internal relationships.
+
+Demonstrates how to maintain a bidirectional relationship within a single aggregate.
+
+=== Bidirectional relationships between aggregates.
+
+Demonstrates how to maintain a bidirectional relationship between aggregates.
\ No newline at end of file
diff --git a/jdbc/howto/bidirectionalexternal/README.adoc b/jdbc/howto/bidirectionalexternal/README.adoc
new file mode 100644
index 000000000..96de5745f
--- /dev/null
+++ b/jdbc/howto/bidirectionalexternal/README.adoc
@@ -0,0 +1,6 @@
+== Spring Data JDBC How To Bidirectional External Relationship
+
+An external relationship is one that links one aggregate from another one.
+Spring Data JDBC models such a relationship by referencing the aggregate by id, possibly wrapped in an `AggregateReference`.
+
+For a bidirectional navigation it turns out you just need an additional query method.
\ No newline at end of file
diff --git a/jdbc/howto/bidirectionalexternal/pom.xml b/jdbc/howto/bidirectionalexternal/pom.xml
new file mode 100644
index 000000000..8f4c77d81
--- /dev/null
+++ b/jdbc/howto/bidirectionalexternal/pom.xml
@@ -0,0 +1,21 @@
+
+
+
+ org.springframework.data.examples
+ spring-data-jdbc-how-to
+ 4.0.0-SNAPSHOT
+ ../pom.xml
+
+
+ 4.0.0
+
+ spring-data-jdbc-how-to-bidirectional-external
+
+ Spring Data JDBC - How to model bidirectional relationships between aggregates
+ Sample project for Spring Data JDBC demonstrating how to model bidirectional relationships between aggregates.
+ It serves as a source code repository for a How-To article on the Spring Blog
+ https://spring.io/spring-data-jdbc
+ 2021
+
diff --git a/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/BidirectionalApplication.java b/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/BidirectionalApplication.java
new file mode 100644
index 000000000..12c3eea2d
--- /dev/null
+++ b/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/BidirectionalApplication.java
@@ -0,0 +1,13 @@
+package example.springdata.jdbc.howto.bidirectionalexternal;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+class BidirectionalApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(BidirectionalApplication.class, args);
+ }
+
+}
diff --git a/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/Minion.java b/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/Minion.java
new file mode 100644
index 000000000..1e2411fc7
--- /dev/null
+++ b/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/Minion.java
@@ -0,0 +1,31 @@
+/*
+ * 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.bidirectionalexternal;
+
+import org.springframework.data.annotation.Id;
+import org.springframework.data.jdbc.core.mapping.AggregateReference;
+
+class Minion {
+ @Id
+ Long id;
+ String name;
+ AggregateReference evilMaster;
+
+ Minion(String name, AggregateReference evilMaster) {
+ this.name = name;
+ this.evilMaster = evilMaster;
+ }
+}
diff --git a/geode/security/src/main/java/example/springdata/geode/client/security/client/CustomerRepository.java b/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/MinionRepository.java
old mode 100755
new mode 100644
similarity index 63%
rename from geode/security/src/main/java/example/springdata/geode/client/security/client/CustomerRepository.java
rename to jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/MinionRepository.java
index 3d782b667..d80307efb
--- a/geode/security/src/main/java/example/springdata/geode/client/security/client/CustomerRepository.java
+++ b/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/MinionRepository.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 the original author or authors.
+ * 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.
@@ -13,20 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package example.springdata.geode.client.security.client;
+package example.springdata.jdbc.howto.bidirectionalexternal;
-import example.springdata.geode.client.security.Customer;
-
-import java.util.List;
+import java.util.Collection;
+import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.repository.CrudRepository;
-/**
- * @author Patrick Johnson
- */
-public interface CustomerRepository extends CrudRepository {
-
- @Override
- List findAll();
+interface MinionRepository extends CrudRepository {
+ @Query("SELECT * FROM MINION WHERE EVIL_MASTER = :id")
+ Collection findByEvilMaster(Long id);
}
diff --git a/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/Person.java b/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/Person.java
new file mode 100644
index 000000000..cd0640241
--- /dev/null
+++ b/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/Person.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.bidirectionalexternal;
+
+import org.springframework.data.annotation.Id;
+
+class Person {
+ @Id
+ Long id;
+ String name;
+
+ Person(String name) {
+ this.name = name;
+ }
+}
diff --git a/geode/events/src/main/java/example/springdata/geode/server/events/OrderRepository.java b/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/PersonRepository.java
old mode 100755
new mode 100644
similarity index 75%
rename from geode/events/src/main/java/example/springdata/geode/server/events/OrderRepository.java
rename to jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/PersonRepository.java
index 6d391f09d..6a6f0c981
--- a/geode/events/src/main/java/example/springdata/geode/server/events/OrderRepository.java
+++ b/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/PersonRepository.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 the original author or authors.
+ * 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.
@@ -13,11 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package example.springdata.geode.server.events;
+package example.springdata.jdbc.howto.bidirectionalexternal;
import org.springframework.data.repository.CrudRepository;
-/**
- * @author Patrick Johnson
- */
-public interface OrderRepository extends CrudRepository {}
+interface PersonRepository extends CrudRepository {
+
+}
diff --git a/jdbc/howto/bidirectionalexternal/src/main/resources/application.properties b/jdbc/howto/bidirectionalexternal/src/main/resources/application.properties
new file mode 100644
index 000000000..3a7895a8b
--- /dev/null
+++ b/jdbc/howto/bidirectionalexternal/src/main/resources/application.properties
@@ -0,0 +1 @@
+logging.level.org.springframework.jdbc.core.JdbcTemplate=debug
diff --git a/jdbc/howto/bidirectionalexternal/src/main/resources/schema.sql b/jdbc/howto/bidirectionalexternal/src/main/resources/schema.sql
new file mode 100644
index 000000000..379fed64e
--- /dev/null
+++ b/jdbc/howto/bidirectionalexternal/src/main/resources/schema.sql
@@ -0,0 +1,13 @@
+CREATE TABLE PERSON
+(
+ ID IDENTITY PRIMARY KEY,
+ NAME VARCHAR(255)
+);
+
+CREATE TABLE MINION
+(
+ ID IDENTITY PRIMARY KEY,
+ NAME VARCHAR(255),
+ EVIL_MASTER BIGINT,
+ CONSTRAINT FK_MINION_PERSON FOREIGN KEY (EVIL_MASTER) REFERENCES PERSON
+);
diff --git a/jdbc/howto/bidirectionalexternal/src/test/java/example/springdata/jdbc/howto/bidirectionalexternal/BidirectionalApplicationTests.java b/jdbc/howto/bidirectionalexternal/src/test/java/example/springdata/jdbc/howto/bidirectionalexternal/BidirectionalApplicationTests.java
new file mode 100644
index 000000000..91b0497e9
--- /dev/null
+++ b/jdbc/howto/bidirectionalexternal/src/test/java/example/springdata/jdbc/howto/bidirectionalexternal/BidirectionalApplicationTests.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2025 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
+ *
+ * http://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.bidirectionalexternal;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.Collection;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.data.jdbc.test.autoconfigure.DataJdbcTest;
+import org.springframework.data.jdbc.core.JdbcAggregateTemplate;
+import org.springframework.data.jdbc.core.mapping.AggregateReference;
+
+@DataJdbcTest
+class BidirectionalApplicationTests {
+
+ @Autowired
+ MinionRepository minions;
+
+ @Autowired
+ PersonRepository persons;
+
+ @Autowired
+ JdbcAggregateTemplate template;
+
+ @Test
+ void bidi() {
+
+
+ AggregateReference scarletReference = AggregateReference.to(persons.save(new Person("Scarlet")).id);
+
+ Minion bob = minions.save(new Minion("Bob", scarletReference));
+ Minion kevin = minions.save(new Minion("Kevin", scarletReference));
+ Minion stuart = minions.save(new Minion("Stuart", scarletReference));
+
+ AggregateReference gruReference = AggregateReference.to(persons.save(new Person("Gru")).id);
+ Minion tim = minions.save(new Minion("Tim", gruReference));
+
+ Collection minionsOfScarlet = minions.findByEvilMaster(scarletReference.getId());
+
+ assertThat(minionsOfScarlet).extracting(m -> m.name).containsExactlyInAnyOrder("Bob", "Kevin", "Stuart");
+ }
+
+
+}
diff --git a/jdbc/howto/bidirectionalinternal/README.adoc b/jdbc/howto/bidirectionalinternal/README.adoc
new file mode 100644
index 000000000..9c33b54f8
--- /dev/null
+++ b/jdbc/howto/bidirectionalinternal/README.adoc
@@ -0,0 +1,6 @@
+== Spring Data JDBC How To Bidirectional Internal Relationship
+
+An internal relationship is one that links entities within an aggregate.
+Spring Data JDBC doesn't have special mechanics to maintain such a relationship.
+Instead you may choose to maintain the reverse relationship while creating the aggregate.
+This takes care of the construction during initial creation or modification of the aggregate, and also of the construction when loading an aggregate from the database.
\ No newline at end of file
diff --git a/jdbc/howto/bidirectionalinternal/pom.xml b/jdbc/howto/bidirectionalinternal/pom.xml
new file mode 100644
index 000000000..45d91d248
--- /dev/null
+++ b/jdbc/howto/bidirectionalinternal/pom.xml
@@ -0,0 +1,21 @@
+
+
+
+ org.springframework.data.examples
+ spring-data-jdbc-how-to
+ 4.0.0-SNAPSHOT
+ ../pom.xml
+
+
+ 4.0.0
+
+ spring-data-jdbc-how-to-bidirectional-internal
+
+ Spring Data JDBC - How to model aggregate internal bidirectional relationships
+ Sample project for Spring Data JDBC demonstrating how to model aggregate internal bidirectional relationships.
+ It serves as a source code repository for a How-To article on the Spring Blog
+ https://spring.io/spring-data-jdbc
+ 2021
+
diff --git a/jdbc/howto/bidirectionalinternal/src/main/java/example/springdata/jdbc/howto/bidirectionalinternal/BiDiInternalApplication.java b/jdbc/howto/bidirectionalinternal/src/main/java/example/springdata/jdbc/howto/bidirectionalinternal/BiDiInternalApplication.java
new file mode 100644
index 000000000..a53130f0c
--- /dev/null
+++ b/jdbc/howto/bidirectionalinternal/src/main/java/example/springdata/jdbc/howto/bidirectionalinternal/BiDiInternalApplication.java
@@ -0,0 +1,13 @@
+package example.springdata.jdbc.howto.bidirectionalinternal;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+class BiDiInternalApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(BiDiInternalApplication.class, args);
+ }
+
+}
diff --git a/jdbc/howto/bidirectionalinternal/src/main/java/example/springdata/jdbc/howto/bidirectionalinternal/Minion.java b/jdbc/howto/bidirectionalinternal/src/main/java/example/springdata/jdbc/howto/bidirectionalinternal/Minion.java
new file mode 100644
index 000000000..a5905b26b
--- /dev/null
+++ b/jdbc/howto/bidirectionalinternal/src/main/java/example/springdata/jdbc/howto/bidirectionalinternal/Minion.java
@@ -0,0 +1,51 @@
+/*
+ * 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.bidirectionalinternal;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.springframework.data.annotation.Id;
+import org.springframework.data.annotation.PersistenceCreator;
+
+class Minion {
+ @Id
+ Long id;
+ String name;
+ final Set toys = new HashSet<>();
+
+ Minion(String name) {
+ this.name = name;
+ }
+
+ @PersistenceCreator
+ private Minion(Long id, String name, Collection toys) {
+
+ this.id = id;
+ this.name = name;
+ toys.forEach(this::addToy);
+ }
+
+ public void addToy(Toy toy) {
+ toys.add(toy);
+ toy.minion = this;
+ }
+
+ public void showYourToys() {
+ toys.forEach(Toy::sayHello);
+ }
+}
diff --git a/geode/storage/src/main/java/example/springdata/geode/server/storage/OrderRepository.java b/jdbc/howto/bidirectionalinternal/src/main/java/example/springdata/jdbc/howto/bidirectionalinternal/MinionRepository.java
old mode 100755
new mode 100644
similarity index 75%
rename from geode/storage/src/main/java/example/springdata/geode/server/storage/OrderRepository.java
rename to jdbc/howto/bidirectionalinternal/src/main/java/example/springdata/jdbc/howto/bidirectionalinternal/MinionRepository.java
index d42d690cd..4c4fb5ab7
--- a/geode/storage/src/main/java/example/springdata/geode/server/storage/OrderRepository.java
+++ b/jdbc/howto/bidirectionalinternal/src/main/java/example/springdata/jdbc/howto/bidirectionalinternal/MinionRepository.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 the original author or authors.
+ * 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.
@@ -13,11 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package example.springdata.geode.server.storage;
+package example.springdata.jdbc.howto.bidirectionalinternal;
import org.springframework.data.repository.CrudRepository;
-/**
- * @author Patrick Johnson
- */
-public interface OrderRepository extends CrudRepository {}
+public interface MinionRepository extends CrudRepository {
+}
diff --git a/geode/events/src/main/java/example/springdata/geode/server/events/EmailAddress.java b/jdbc/howto/bidirectionalinternal/src/main/java/example/springdata/jdbc/howto/bidirectionalinternal/Toy.java
old mode 100755
new mode 100644
similarity index 59%
rename from geode/events/src/main/java/example/springdata/geode/server/events/EmailAddress.java
rename to jdbc/howto/bidirectionalinternal/src/main/java/example/springdata/jdbc/howto/bidirectionalinternal/Toy.java
index 1fc0b8ac2..dc55c1f87
--- a/geode/events/src/main/java/example/springdata/geode/server/events/EmailAddress.java
+++ b/jdbc/howto/bidirectionalinternal/src/main/java/example/springdata/jdbc/howto/bidirectionalinternal/Toy.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 the original author or authors.
+ * 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.
@@ -13,25 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package example.springdata.geode.server.events;
+package example.springdata.jdbc.howto.bidirectionalinternal;
-import lombok.Data;
+import org.springframework.data.annotation.Transient;
-import java.io.Serializable;
+class Toy {
+ String name;
-/**
- * Value object to represent email addresses.
- *
- * @author Udo Kohlmeyer
- * @author Patrick Johnson
- */
-@Data
-public class EmailAddress implements Serializable {
+ @Transient // org.SPRINGframework.DATA...
+ Minion minion;
- private String value;
-
- public EmailAddress(String value) {
+ Toy(String name) {
+ this.name = name;
+ }
- this.value = value;
+ public void sayHello() {
+ System.out.println("I'm " + name + " and I'm a toy of " + minion.name);
}
}
diff --git a/jdbc/howto/bidirectionalinternal/src/main/resources/application.properties b/jdbc/howto/bidirectionalinternal/src/main/resources/application.properties
new file mode 100644
index 000000000..3a7895a8b
--- /dev/null
+++ b/jdbc/howto/bidirectionalinternal/src/main/resources/application.properties
@@ -0,0 +1 @@
+logging.level.org.springframework.jdbc.core.JdbcTemplate=debug
diff --git a/jdbc/howto/bidirectionalinternal/src/main/resources/schema.sql b/jdbc/howto/bidirectionalinternal/src/main/resources/schema.sql
new file mode 100644
index 000000000..7f67813ef
--- /dev/null
+++ b/jdbc/howto/bidirectionalinternal/src/main/resources/schema.sql
@@ -0,0 +1,11 @@
+CREATE TABLE MINION
+(
+ ID IDENTITY PRIMARY KEY,
+ NAME VARCHAR(255),
+);
+
+CREATE TABLE TOY
+(
+ MINION BIGINT NOT NULL,
+ NAME VARCHAR(255)
+);
diff --git a/jdbc/howto/bidirectionalinternal/src/test/java/example/springdata/jdbc/howto/bidirectionalinternal/BiDiInternalApplicationTests.java b/jdbc/howto/bidirectionalinternal/src/test/java/example/springdata/jdbc/howto/bidirectionalinternal/BiDiInternalApplicationTests.java
new file mode 100644
index 000000000..999232895
--- /dev/null
+++ b/jdbc/howto/bidirectionalinternal/src/test/java/example/springdata/jdbc/howto/bidirectionalinternal/BiDiInternalApplicationTests.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2025 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
+ *
+ * http://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.bidirectionalinternal;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.data.jdbc.test.autoconfigure.DataJdbcTest;
+
+@DataJdbcTest
+class BiDiInternalApplicationTests {
+
+ @Autowired
+ MinionRepository minions;
+
+ @Test
+ void biDi() {
+
+ Minion bob = new Minion("Bob");
+ bob.addToy(new Toy("Dolphin"));
+ bob.addToy(new Toy("Tiger Duck"));
+
+ minions.save(bob);
+
+ Minion reloaded = minions.findById(bob.id).get();
+
+ reloaded.showYourToys();
+ }
+}
diff --git a/jdbc/howto/caching/README.adoc b/jdbc/howto/caching/README.adoc
new file mode 100644
index 000000000..00a518570
--- /dev/null
+++ b/jdbc/howto/caching/README.adoc
@@ -0,0 +1,6 @@
+== Spring Data JDBC How To cache.
+
+Spring Data JDBC doesn't include any caching for entities.
+But it is trivially combined with Springs Cache abstraction.
+
+This project demonstrates this.
diff --git a/jdbc/howto/caching/pom.xml b/jdbc/howto/caching/pom.xml
new file mode 100644
index 000000000..4220eae88
--- /dev/null
+++ b/jdbc/howto/caching/pom.xml
@@ -0,0 +1,30 @@
+
+ 4.0.0
+
+ spring-data-jdbc-how-to-caching
+
+
+ org.springframework.data.examples
+ spring-data-jdbc-how-to
+ 4.0.0-SNAPSHOT
+ ../pom.xml
+
+
+ Spring Data JDBC - How to do caching
+ Sample project for Spring Data JDBC demonstrating how it can be used with Springs caching
+ abstraction.
+
+ https://spring.io/spring-data-jdbc
+ 2021
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-cache
+
+
+
+
+
diff --git a/jdbc/howto/caching/src/main/java/example.springdata/jdbc/howto/caching/CachingApplication.java b/jdbc/howto/caching/src/main/java/example.springdata/jdbc/howto/caching/CachingApplication.java
new file mode 100644
index 000000000..7d7c705c7
--- /dev/null
+++ b/jdbc/howto/caching/src/main/java/example.springdata/jdbc/howto/caching/CachingApplication.java
@@ -0,0 +1,15 @@
+package example.springdata.jdbc.howto.caching;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cache.annotation.EnableCaching;
+
+@EnableCaching
+@SpringBootApplication
+class CachingApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(CachingApplication.class, args);
+ }
+
+}
diff --git a/jdbc/howto/caching/src/main/java/example.springdata/jdbc/howto/caching/Minion.java b/jdbc/howto/caching/src/main/java/example.springdata/jdbc/howto/caching/Minion.java
new file mode 100644
index 000000000..841756e66
--- /dev/null
+++ b/jdbc/howto/caching/src/main/java/example.springdata/jdbc/howto/caching/Minion.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.caching;
+
+import org.springframework.data.annotation.Id;
+
+public class Minion {
+ @Id
+ Long id;
+ String name;
+
+ Minion(String name) {
+ this.name = name;
+ }
+
+ public Long getId(){
+ return id;
+ }
+}
diff --git a/jdbc/howto/caching/src/main/java/example.springdata/jdbc/howto/caching/MinionRepository.java b/jdbc/howto/caching/src/main/java/example.springdata/jdbc/howto/caching/MinionRepository.java
new file mode 100644
index 000000000..9b73b0cb3
--- /dev/null
+++ b/jdbc/howto/caching/src/main/java/example.springdata/jdbc/howto/caching/MinionRepository.java
@@ -0,0 +1,33 @@
+/*
+ * 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.caching;
+
+import java.util.Optional;
+
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.data.repository.CrudRepository;
+
+interface MinionRepository extends CrudRepository {
+
+ @Override
+ @CacheEvict(value="minions",beforeInvocation = false,key = "#result.id")
+ S save(S s);
+
+ @Override
+ @Cacheable("minions")
+ Optional findById(Long aLong);
+}
diff --git a/jdbc/howto/caching/src/main/resources/application.properties b/jdbc/howto/caching/src/main/resources/application.properties
new file mode 100644
index 000000000..813de42f6
--- /dev/null
+++ b/jdbc/howto/caching/src/main/resources/application.properties
@@ -0,0 +1 @@
+logging.level.org.springframework.jdbc.core.JdbcTemplate=DEBUG
\ No newline at end of file
diff --git a/jdbc/howto/caching/src/main/resources/schema.sql b/jdbc/howto/caching/src/main/resources/schema.sql
new file mode 100644
index 000000000..5e75d2286
--- /dev/null
+++ b/jdbc/howto/caching/src/main/resources/schema.sql
@@ -0,0 +1,5 @@
+CREATE TABLE MINION
+(
+ ID IDENTITY PRIMARY KEY,
+ NAME VARCHAR(255),
+);
diff --git a/jdbc/howto/caching/src/test/java/example/springdata/jdbc/howto/caching/CachingApplicationTests.java b/jdbc/howto/caching/src/test/java/example/springdata/jdbc/howto/caching/CachingApplicationTests.java
new file mode 100644
index 000000000..4043391fc
--- /dev/null
+++ b/jdbc/howto/caching/src/test/java/example/springdata/jdbc/howto/caching/CachingApplicationTests.java
@@ -0,0 +1,39 @@
+package example.springdata.jdbc.howto.caching;
+
+import java.util.Optional;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class CachingApplicationTests {
+
+ private Long bobsId;
+ @Autowired MinionRepository minions;
+
+ @BeforeEach
+ void setup() {
+
+ Minion bob = minions.save(new Minion("Bob"));
+ bobsId = bob.id;
+ }
+
+ @Test
+ void saveloadMultipleTimes() {
+
+ Optional bob = null;
+ for (int i = 0; i < 10; i++) {
+ bob = minions.findById(bobsId);
+ }
+
+ minions.save(bob.get());
+
+ for (int i = 0; i < 10; i++) {
+ bob = minions.findById(bobsId);
+ }
+
+ }
+
+}
diff --git a/jdbc/howto/idgeneration/README.adoc b/jdbc/howto/idgeneration/README.adoc
new file mode 100644
index 000000000..57a7f61c2
--- /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 000000000..9ac1666e7
--- /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
+ 4.0.0-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://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 000000000..4b75d8d34
--- /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.BeforeConvertCallback;
+
+@SpringBootApplication
+class IdGenerationApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(IdGenerationApplication.class, args);
+ }
+
+ @Bean
+ BeforeConvertCallback beforeSaveCallback() {
+
+ return (minion) -> {
+ 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 000000000..bbd921cf7
--- /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/geode/events/src/main/java/example/springdata/geode/server/events/ProductRepository.java b/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/MinionRepository.java
old mode 100755
new mode 100644
similarity index 75%
rename from geode/events/src/main/java/example/springdata/geode/server/events/ProductRepository.java
rename to jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/MinionRepository.java
index efb9484de..1a2ee77fd
--- a/geode/events/src/main/java/example/springdata/geode/server/events/ProductRepository.java
+++ b/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/MinionRepository.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 the original author or authors.
+ * 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.
@@ -13,11 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package example.springdata.geode.server.events;
+package example.springdata.jdbc.howto.idgeneration;
import org.springframework.data.repository.CrudRepository;
-/**
- * @author Patrick Johnson
- */
-public interface ProductRepository extends CrudRepository {}
+interface MinionRepository extends CrudRepository {
+
+}
diff --git a/geode/wan/src/main/java/example/springdata/geode/server/wan/event/server/EvenNumberedKeyWanEventFilter.java b/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/PersistableMinion.java
old mode 100755
new mode 100644
similarity index 50%
rename from geode/wan/src/main/java/example/springdata/geode/server/wan/event/server/EvenNumberedKeyWanEventFilter.java
rename to jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/PersistableMinion.java
index 5446face6..9b3a88af0
--- a/geode/wan/src/main/java/example/springdata/geode/server/wan/event/server/EvenNumberedKeyWanEventFilter.java
+++ b/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/PersistableMinion.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 the original author or authors.
+ * 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.
@@ -13,29 +13,30 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package example.springdata.jdbc.howto.idgeneration;
-package example.springdata.geode.server.wan.event.server;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.domain.Persistable;
+import org.springframework.data.relational.core.mapping.Table;
-import org.apache.geode.cache.wan.GatewayEventFilter;
-import org.apache.geode.cache.wan.GatewayQueueEvent;
-import org.springframework.stereotype.Component;
+@Table("MINION")
+class PersistableMinion implements Persistable {
+ @Id Long id;
+ String name;
-/**
- * @author Patrick Johnson
- */
-@Component
-public class EvenNumberedKeyWanEventFilter implements GatewayEventFilter {
+ PersistableMinion(Long id, String name) {
+ this.id = id;
+ this.name = name;
+ }
@Override
- public boolean beforeEnqueue(GatewayQueueEvent event) {
- return (Long) event.getKey() % 2 == 0;
+ public Long getId() {
+ return id;
}
@Override
- public boolean beforeTransmit(GatewayQueueEvent event) {
+ public boolean isNew() {
+ // this implementation is most certainly not suitable for production use
return true;
}
-
- @Override
- public void afterAcknowledgement(GatewayQueueEvent event) {}
}
diff --git a/geode/events/src/main/java/example/springdata/geode/server/events/CustomerRepository.java b/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/PersistableMinionRepository.java
old mode 100755
new mode 100644
similarity index 75%
rename from geode/events/src/main/java/example/springdata/geode/server/events/CustomerRepository.java
rename to jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/PersistableMinionRepository.java
index 1613b694b..69311209b
--- a/geode/events/src/main/java/example/springdata/geode/server/events/CustomerRepository.java
+++ b/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/PersistableMinionRepository.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 the original author or authors.
+ * 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.
@@ -13,12 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package example.springdata.geode.server.events;
+package example.springdata.jdbc.howto.idgeneration;
import org.springframework.data.repository.CrudRepository;
-/**
- * @author Patrick Johnson
- */
-public interface CustomerRepository extends 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 000000000..98bf7c5d2
--- /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 000000000..884ee86d8
--- /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/mongodb/java8/src/main/java/example/springdata/mongodb/people/Person.java b/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/VersionedMinion.java
similarity index 62%
rename from mongodb/java8/src/main/java/example/springdata/mongodb/people/Person.java
rename to jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/VersionedMinion.java
index 83ad4d504..7104bfe8f 100644
--- a/mongodb/java8/src/main/java/example/springdata/mongodb/people/Person.java
+++ b/jdbc/howto/idgeneration/src/main/java/example.springdata/jdbc/howto/idgeneration/VersionedMinion.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-2018 the original author or authors.
+ * 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.
@@ -13,23 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package example.springdata.mongodb.people;
-
-import lombok.Data;
-import lombok.RequiredArgsConstructor;
+package example.springdata.jdbc.howto.idgeneration;
import org.springframework.data.annotation.Id;
+import org.springframework.data.annotation.Version;
-/**
- * An entity to represent a Person.
- *
- * @author Thomas Darimont
- */
-@Data
-@RequiredArgsConstructor
-public class Person {
+class VersionedMinion {
+
+ @Id Long id;
+ String name;
+ @Version Integer version;
+
+ VersionedMinion(long id, String name) {
- private @Id String id;
- private final String firstname;
- private final String lastname;
+ 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 000000000..778e06f58
--- /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 000000000..42496f025
--- /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 000000000..de0ef9fde
--- /dev/null
+++ b/jdbc/howto/idgeneration/src/test/java/example/springdata/jdbc/howto/idgeneration/IdGenerationApplicationTests.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2025 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
+ *
+ * http://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 static org.assertj.core.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.data.jdbc.test.autoconfigure.DataJdbcTest;
+import org.springframework.dao.IncorrectUpdateSemanticsDataAccessException;
+import org.springframework.data.jdbc.core.JdbcAggregateTemplate;
+
+@DataJdbcTest
+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)).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 000000000..fcdff5dbd
--- /dev/null
+++ b/jdbc/howto/pom.xml
@@ -0,0 +1,30 @@
+
+ 4.0.0
+
+ spring-data-jdbc-how-to
+ pom
+
+
+ org.springframework.data.examples
+ spring-data-jdbc-examples
+ 4.0.0-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://spring.io/spring-data-jdbc
+ 2021
+
+
+ bidirectionalexternal
+ bidirectionalinternal
+ caching
+ idgeneration
+ selectiveupdate
+ schema-generation
+
+
+
diff --git a/jdbc/howto/schema-generation/.gitignore b/jdbc/howto/schema-generation/.gitignore
new file mode 100644
index 000000000..a718e9947
--- /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 000000000..3f27e40fe
--- /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 000000000..73e8ad543
--- /dev/null
+++ b/jdbc/howto/schema-generation/pom.xml
@@ -0,0 +1,29 @@
+
+ 4.0.0
+
+ spring-data-jdbc-how-to-schema-generation
+
+
+ org.springframework.data.examples
+ spring-data-jdbc-how-to
+ 4.0.0-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://spring.io/spring-data-jdbc
+ 2023
+
+
+
+
+ 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 000000000..0ac2e9fbf
--- /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 000000000..91d4158fb
--- /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 000000000..3fd9634d5
--- /dev/null
+++ b/jdbc/howto/schema-generation/src/main/java/example.springdata/jdbc/howto/caching/SchemaGenerationExampleApplication.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2025 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
+ *
+ * http://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;
+
+/**
+ * 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
+ */
+@SpringBootApplication
+class SchemaGenerationExampleApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SchemaGenerationExampleApplication.class, args);
+ }
+
+}
diff --git a/geode/security/src/main/java/example/springdata/geode/client/security/EmailAddress.java b/jdbc/howto/schema-generation/src/main/java/example.springdata/jdbc/howto/caching/Varchar.java
old mode 100755
new mode 100644
similarity index 59%
rename from geode/security/src/main/java/example/springdata/geode/client/security/EmailAddress.java
rename to jdbc/howto/schema-generation/src/main/java/example.springdata/jdbc/howto/caching/Varchar.java
index 85f60acd9..626af7e15
--- a/geode/security/src/main/java/example/springdata/geode/client/security/EmailAddress.java
+++ b/jdbc/howto/schema-generation/src/main/java/example.springdata/jdbc/howto/caching/Varchar.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 the original author or authors.
+ * 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.
@@ -13,24 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package example.springdata.geode.client.security;
+package example.springdata.jdbc.howto.caching;
-import lombok.Data;
-
-import java.io.Serializable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
- * Value object to represent email addresses.
+ * Marks a property as to be represented by a VARCHAR database type.
*
- * @author Udo Kohlmeyer
- * @author Patrick Johnson
+ * @author Jens Schauder
*/
-@Data
-public class EmailAddress implements Serializable {
- private String value;
-
- public EmailAddress(String value) {
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Varchar {
- this.value = value;
- }
+ /**
+ * 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 000000000..07ddc16f1
--- /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 000000000..615ec854d
--- /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 000000000..1ca47de31
--- /dev/null
+++ b/jdbc/howto/schema-generation/src/test/java/example/springdata/jdbc/howto/caching/SchemaGenerationTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2025 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
+ *
+ * http://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 java.io.File;
+import java.io.IOException;
+import java.sql.SQLException;
+import java.util.Collections;
+
+import javax.sql.DataSource;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.data.jdbc.test.autoconfigure.DataJdbcTest;
+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;
+
+/**
+ * Example code demonstrating how to use the Schema Generation Feature.
+ *
+ * @author Jens Schauder
+ * @since 3.2
+ */
+@DataJdbcTest
+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"));
+
+ }
+}
diff --git a/jdbc/howto/selectiveupdate/README.adoc b/jdbc/howto/selectiveupdate/README.adoc
new file mode 100644
index 000000000..f3d6335ef
--- /dev/null
+++ b/jdbc/howto/selectiveupdate/README.adoc
@@ -0,0 +1,5 @@
+== Spring Data JDBC How To perform selective updates.
+
+Spring Data JDBC normally persists complete aggregates, which is wasteful if only few things have changed.
+
+This project demonstrates alternatives that require a little more work from the developer but are much more efficient.
diff --git a/jdbc/howto/selectiveupdate/pom.xml b/jdbc/howto/selectiveupdate/pom.xml
new file mode 100644
index 000000000..8ea758760
--- /dev/null
+++ b/jdbc/howto/selectiveupdate/pom.xml
@@ -0,0 +1,20 @@
+
+ 4.0.0
+
+ spring-data-jdbc-how-to-selective-update
+
+
+ org.springframework.data.examples
+ spring-data-jdbc-how-to
+ 4.0.0-SNAPSHOT
+ ../pom.xml
+
+
+ Spring Data JDBC - How to do selective updates
+ Sample project for Spring Data JDBC demonstrating how to update only parts of an aggregate.
+
+ https://spring.io/spring-data-jdbc
+ 2022
+
+
diff --git a/jdbc/howto/selectiveupdate/src/main/java/example.springdata/jdbc/howto/selectiveupdate/Color.java b/jdbc/howto/selectiveupdate/src/main/java/example.springdata/jdbc/howto/selectiveupdate/Color.java
new file mode 100644
index 000000000..399e66d8d
--- /dev/null
+++ b/jdbc/howto/selectiveupdate/src/main/java/example.springdata/jdbc/howto/selectiveupdate/Color.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2022 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.selectiveupdate;
+
+/**
+ * Simple enum for the color of a minion.
+ *
+ * @author Jens Schauder
+ */
+public enum Color {
+ YELLOW, PURPLE
+}
diff --git a/jdbc/howto/selectiveupdate/src/main/java/example.springdata/jdbc/howto/selectiveupdate/Minion.java b/jdbc/howto/selectiveupdate/src/main/java/example.springdata/jdbc/howto/selectiveupdate/Minion.java
new file mode 100644
index 000000000..cf8ed6d96
--- /dev/null
+++ b/jdbc/howto/selectiveupdate/src/main/java/example.springdata/jdbc/howto/selectiveupdate/Minion.java
@@ -0,0 +1,58 @@
+/*
+ * 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.selectiveupdate;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.springframework.data.annotation.Id;
+import org.springframework.data.annotation.PersistenceCreator;
+import org.springframework.data.annotation.Version;
+
+
+/**
+ * A minion. The main entity and aggregate root for this example.
+ *
+ * @author Jens Schauder
+ */
+class Minion {
+
+ @Id Long id;
+ String name;
+ Color color = Color.YELLOW;
+ Set toys = new HashSet<>();
+ @Version int version;
+
+ Minion(String name) {
+ this.name = name;
+ }
+
+ @PersistenceCreator
+ private Minion(Long id, String name, Collection toys, int version) {
+
+ this.id = id;
+ this.name = name;
+ this.toys.addAll(toys);
+ this.version = version;
+ }
+
+ Minion addToy(Toy toy) {
+
+ toys.add(toy);
+ return this;
+ }
+}
diff --git a/mongodb/java8/src/main/java/example/springdata/mongodb/people/PersonRepository.java b/jdbc/howto/selectiveupdate/src/main/java/example.springdata/jdbc/howto/selectiveupdate/MinionRepository.java
similarity index 56%
rename from mongodb/java8/src/main/java/example/springdata/mongodb/people/PersonRepository.java
rename to jdbc/howto/selectiveupdate/src/main/java/example.springdata/jdbc/howto/selectiveupdate/MinionRepository.java
index 7ff09b0e8..0c97d7e8e 100644
--- a/mongodb/java8/src/main/java/example/springdata/mongodb/people/PersonRepository.java
+++ b/jdbc/howto/selectiveupdate/src/main/java/example.springdata/jdbc/howto/selectiveupdate/MinionRepository.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-2018 the original author or authors.
+ * 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.
@@ -13,23 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package example.springdata.mongodb.people;
+package example.springdata.jdbc.howto.selectiveupdate;
-import java.util.List;
-import java.util.stream.Stream;
-
-import org.springframework.data.mongodb.repository.Query;
+import org.springframework.data.jdbc.repository.query.Modifying;
+import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.repository.CrudRepository;
+
/**
- * Repository interface to manage {@link Person} instances.
+ * The normal MinionRepository.
*
- * @author Thomas Darimont
+ * @author Jens Schauder
*/
-public interface PersonRepository extends CrudRepository {
+interface MinionRepository extends CrudRepository, PartyHatRepository {
- List