Adds an example for custom update strategies.
See https://spring.io/admin/blog/4540-spring-data-jdbc-how-do-update-just-the-aggregate-root-instead-of-all-the-referenced-entities
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
<module>bidirectionalinternal</module>
|
||||
<module>caching</module>
|
||||
<module>idgeneration</module>
|
||||
<module>selectiveupdate</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
|
||||
5
jdbc/howto/selectiveupdate/README.adoc
Normal file
5
jdbc/howto/selectiveupdate/README.adoc
Normal file
@@ -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.
|
||||
20
jdbc/howto/selectiveupdate/pom.xml
Normal file
20
jdbc/howto/selectiveupdate/pom.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-data-jdbc-how-to-selective-update</artifactId>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.data.examples</groupId>
|
||||
<artifactId>spring-data-jdbc-how-to</artifactId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<name>Spring Data JDBC - How to do selective updates</name>
|
||||
<description>Sample project for Spring Data JDBC demonstrating how to update only parts of an aggregate.
|
||||
</description>
|
||||
<url>https://projects.spring.io/spring-data-jdbc</url>
|
||||
<inceptionYear>2022</inceptionYear>
|
||||
|
||||
</project>
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.PersistenceConstructor;
|
||||
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<Toy> toys = new HashSet<>();
|
||||
@Version int version;
|
||||
|
||||
Minion(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@PersistenceConstructor
|
||||
private Minion(Long id, String name, Collection<Toy> 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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 org.springframework.data.jdbc.repository.query.Modifying;
|
||||
import org.springframework.data.jdbc.repository.query.Query;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
|
||||
/**
|
||||
* The normal MinionRepository.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
interface MinionRepository extends CrudRepository<Minion, Long>, PartyHatRepository {
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE MINION SET COLOR ='PURPLE', VERSION = VERSION +1 WHERE ID = :id")
|
||||
void turnPurple(Long id);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
|
||||
/**
|
||||
* A repository fragment to perform custom logic, including a special way to update minions in the database.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
public interface PartyHatRepository {
|
||||
|
||||
void addPartyHat(Minion minion);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Implementation of custom logic for a minion repository.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
class PartyHatRepositoryImpl implements PartyHatRepository {
|
||||
|
||||
|
||||
private final NamedParameterJdbcOperations template;
|
||||
|
||||
public PartyHatRepositoryImpl(NamedParameterJdbcOperations template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPartyHat(Minion minion) {
|
||||
|
||||
Map<String, Object> insertParams = new HashMap<>();
|
||||
insertParams.put("id", minion.id);
|
||||
insertParams.put("name", "Party Hat");
|
||||
template.update("INSERT INTO TOY (MINION, NAME) VALUES (:id, :name)", insertParams);
|
||||
|
||||
Map<String, Object> updateParams = new HashMap<>();
|
||||
updateParams.put("id", minion.id);
|
||||
updateParams.put("version", minion.version);
|
||||
final int updateCount = template.update("UPDATE MINION SET VERSION = :version + 1 WHERE ID = :id AND VERSION = :version", updateParams);
|
||||
if (updateCount != 1) {
|
||||
throw new OptimisticLockingFailureException("Minion was changed before a Party Hat was given");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.selectiveupdate;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.Version;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
|
||||
/**
|
||||
* A simplified minion which only has the attributes
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
@Table("MINION")
|
||||
class PlainMinion {
|
||||
@Id Long id;
|
||||
String name;
|
||||
Color color;
|
||||
@Version int version;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
* Repository for {@link PlainMinion}.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
interface PlainMinionRepository extends CrudRepository<PlainMinion, Long> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
|
||||
@SpringBootApplication
|
||||
class SelectiveUpdateApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SelectiveUpdateApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.Objects;
|
||||
|
||||
/**
|
||||
* Toys for minions to play with.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
class Toy {
|
||||
String name;
|
||||
|
||||
Toy(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Toy toy = (Toy) o;
|
||||
return Objects.equals(name, toy.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
logging.level.org.springframework.jdbc.core.JdbcTemplate=DEBUG
|
||||
13
jdbc/howto/selectiveupdate/src/main/resources/schema.sql
Normal file
13
jdbc/howto/selectiveupdate/src/main/resources/schema.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
CREATE TABLE MINION
|
||||
(
|
||||
ID IDENTITY PRIMARY KEY,
|
||||
NAME VARCHAR(255),
|
||||
COLOR VARCHAR(10),
|
||||
VERSION INT
|
||||
);
|
||||
|
||||
CREATE TABLE TOY
|
||||
(
|
||||
MINION BIGINT NOT NULL,
|
||||
NAME VARCHAR(255)
|
||||
);
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
|
||||
/**
|
||||
* Tests demonstrating various was for partial updates of an aggregate.
|
||||
*/
|
||||
@SpringBootTest
|
||||
class SelectiveUpdateApplicationTests {
|
||||
|
||||
@Autowired MinionRepository minions;
|
||||
@Autowired PlainMinionRepository plainMinions;
|
||||
|
||||
@Test
|
||||
void renameWithReducedView() {
|
||||
|
||||
Minion bob = new Minion("Bob").addToy(new Toy("Tiger Duck")).addToy(new Toy("Security blanket"));
|
||||
minions.save(bob);
|
||||
|
||||
PlainMinion plainBob = plainMinions.findById(bob.id).orElseThrow();
|
||||
plainBob.name = "Bob II.";
|
||||
plainMinions.save(plainBob);
|
||||
|
||||
Minion bob2 = minions.findById(bob.id).orElseThrow();
|
||||
|
||||
assertThat(bob2.toys).containsExactly(bob.toys.toArray(new Toy[] {}));
|
||||
assertThat(bob2.name).isEqualTo("Bob II.");
|
||||
assertThat(bob2.color).isEqualTo(Color.YELLOW);
|
||||
}
|
||||
|
||||
@Test
|
||||
void turnPurpleByDirectUpdate() {
|
||||
|
||||
Minion bob = new Minion("Bob").addToy(new Toy("Tiger Duck")).addToy(new Toy("Security blanket"));
|
||||
minions.save(bob);
|
||||
|
||||
minions.turnPurple(bob.id);
|
||||
|
||||
Minion bob2 = minions.findById(bob.id).orElseThrow();
|
||||
|
||||
assertThat(bob2.toys).containsExactly(bob.toys.toArray(new Toy[] {}));
|
||||
assertThat(bob2.name).isEqualTo("Bob");
|
||||
assertThat(bob2.color).isEqualTo(Color.PURPLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void grantPartyHat() {
|
||||
|
||||
Minion bob = new Minion("Bob").addToy(new Toy("Tiger Duck")).addToy(new Toy("Security blanket"));
|
||||
minions.save(bob);
|
||||
|
||||
minions.addPartyHat(bob);
|
||||
|
||||
Minion bob2 = minions.findById(bob.id).orElseThrow();
|
||||
|
||||
assertThat(bob2.toys).extracting("name").containsExactlyInAnyOrder("Tiger Duck", "Security blanket", "Party Hat");
|
||||
assertThat(bob2.name).isEqualTo("Bob");
|
||||
assertThat(bob2.color).isEqualTo(Color.YELLOW);
|
||||
assertThat(bob2.version).isEqualTo(bob.version + 1);
|
||||
|
||||
assertThatExceptionOfType(OptimisticLockingFailureException.class).isThrownBy(() -> minions.addPartyHat(bob));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user