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..b201ee2e6 --- /dev/null +++ b/jdbc/composite-ids/pom.xml @@ -0,0 +1,35 @@ + + 4.0.0 + + spring-data-jdbc-composite-ids + + + org.springframework.data.examples + spring-data-jdbc-examples + 2.0.0.BUILD-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 + + + org.springframework.data + spring-data-jdbc + 4.0.0-SNAPSHOT + + + org.springframework.data + spring-data-relational + 4.0.0-SNAPSHOT + + + + 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..20138d844 --- /dev/null +++ b/jdbc/composite-ids/src/main/java/example/springdata/jdbc/compositeid/EmployeeRepository.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 + * + * 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.ListCrudRepository; + + +/** + * Repositories for {@link Employee} instances. + * + * @author Jens Schauder + * @see InsertRepository + * @see InsertRepositoryImpl + */ +interface EmployeeRepository extends ListCrudRepository, InsertRepository { +} 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..a66d5b601 --- /dev/null +++ b/jdbc/composite-ids/src/main/java/example/springdata/jdbc/compositeid/InsertRepositoryImpl.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.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 { + @Autowired + private JdbcAggregateTemplate 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..57c291b6e --- /dev/null +++ b/jdbc/composite-ids/src/test/java/example/springdata/jdbc/compositeid/EmployeeTests.java @@ -0,0 +1,54 @@ +/* + * 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.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.data.jdbc.AutoConfigureDataJdbc; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.assertj.core.api.Assertions.*; + +/** + * 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).orElseThrow(); + + 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/pom.xml b/jdbc/pom.xml index 81dcfbe55..abbc0043f 100644 --- a/jdbc/pom.xml +++ b/jdbc/pom.xml @@ -18,6 +18,7 @@ basics + composite-ids howto immutables jmolecules