diff --git a/persistence-modules/spring-data-jpa-query-4/pom.xml b/persistence-modules/spring-data-jpa-query-4/pom.xml
index 3b843aed20..c605521cb3 100644
--- a/persistence-modules/spring-data-jpa-query-4/pom.xml
+++ b/persistence-modules/spring-data-jpa-query-4/pom.xml
@@ -1,7 +1,7 @@
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
spring-data-jpa-query-4
spring-data-jpa-query-4
@@ -27,6 +27,11 @@
spring-boot-starter-test
test
+
+ com.github.gavlyukovskiy
+ datasource-proxy-spring-boot-starter
+ ${datasource-proxy.version}
+
com.h2database
h2
@@ -37,9 +42,20 @@
${postgresql.version}
-
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 17
+
+
+
+
42.7.1
+ 1.9.1
-
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jpa-query-4/src/main/java/com/baeldung/spring/data/jpa/upsert/CreditCard.java b/persistence-modules/spring-data-jpa-query-4/src/main/java/com/baeldung/spring/data/jpa/upsert/CreditCard.java
new file mode 100644
index 0000000000..3db9f629c8
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-query-4/src/main/java/com/baeldung/spring/data/jpa/upsert/CreditCard.java
@@ -0,0 +1,58 @@
+package com.baeldung.spring.data.jpa.upsert;
+
+import javax.persistence.*;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+@Entity
+@Table(name = "credit_card")
+public class CreditCard {
+ @Id
+ @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "credit_card_id_seq")
+ @SequenceGenerator(name = "credit_card_id_seq", sequenceName = "credit_card_id_seq", allocationSize = 1)
+ private Long id;
+ private String cardNumber;
+ private String expiryDate;
+
+ private Long customerId;
+
+ public Long getCustomerId() {
+ return customerId;
+ }
+
+ public void setCustomerId(Long customerId) {
+ this.customerId = customerId;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getCardNumber() {
+ return cardNumber;
+ }
+
+ public void setCardNumber(String cardNumber) {
+ this.cardNumber = cardNumber;
+ }
+
+ public String getExpiryDate() {
+ return expiryDate;
+ }
+
+ public void setExpiryDate(String expiryDate) {
+ this.expiryDate = expiryDate;
+ }
+
+ public CreditCard() {
+ }
+
+ @Override
+ public String toString() {
+ return "CreditCard{" + "id=" + id + ", cardNumber='" + cardNumber + '\'' + ", expiryDate='" + expiryDate + '\'' + ", customerId=" + customerId + '}';
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-query-4/src/main/java/com/baeldung/spring/data/jpa/upsert/CreditCardLogic.java b/persistence-modules/spring-data-jpa-query-4/src/main/java/com/baeldung/spring/data/jpa/upsert/CreditCardLogic.java
new file mode 100644
index 0000000000..f0e2dc7072
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-query-4/src/main/java/com/baeldung/spring/data/jpa/upsert/CreditCardLogic.java
@@ -0,0 +1,62 @@
+package com.baeldung.spring.data.jpa.upsert;
+
+import java.math.BigInteger;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import javax.persistence.EntityManager;
+import javax.persistence.Query;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class CreditCardLogic {
+
+ @Autowired
+ private CreditCardRepository creditCardRepository;
+
+ @Autowired
+ private EntityManager em;
+
+ public void updateOrInsertUsingCustomLogic(CreditCard creditCard) {
+ CreditCard existingCard = creditCardRepository.findByCardNumber(creditCard.getCardNumber());
+ if (existingCard != null) {
+ existingCard.setExpiryDate(creditCard.getExpiryDate());
+ creditCardRepository.save(creditCard);
+ } else {
+ creditCardRepository.save(creditCard);
+ }
+ }
+
+ @Transactional
+ public void updateOrInsertUsingBuiltInFeature(CreditCard creditCard) {
+ Long id = creditCard.getId();
+ if (creditCard.getId() == null) {
+ BigInteger nextVal = (BigInteger) em.createNativeQuery("SELECT nextval('credit_card_id_seq')")
+ .getSingleResult();
+ id = nextVal.longValue();
+ }
+
+ String upsertQuery = """
+ MERGE INTO credit_card (id, card_number, expiry_date, customer_id)
+ KEY(card_number)
+ VALUES (?, ?, ?, ?)
+ """;
+
+ Query query = em.createNativeQuery(upsertQuery);
+ query.setParameter(1, id);
+ query.setParameter(2, creditCard.getCardNumber());
+ query.setParameter(3, creditCard.getExpiryDate());
+ query.setParameter(4, creditCard.getCustomerId());
+
+ query.executeUpdate();
+ }
+
+ public void updateOrInsertUsingRepository(CreditCard creditCard) {
+ creditCardRepository.updateOrInsert(creditCard);
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-query-4/src/main/java/com/baeldung/spring/data/jpa/upsert/CreditCardRepository.java b/persistence-modules/spring-data-jpa-query-4/src/main/java/com/baeldung/spring/data/jpa/upsert/CreditCardRepository.java
new file mode 100644
index 0000000000..6b909c6195
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-query-4/src/main/java/com/baeldung/spring/data/jpa/upsert/CreditCardRepository.java
@@ -0,0 +1,13 @@
+package com.baeldung.spring.data.jpa.upsert;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.transaction.annotation.Transactional;
+
+public interface CreditCardRepository extends JpaRepository {
+ @Transactional
+ default CreditCard updateOrInsert(CreditCard entity) {
+ return save(entity);
+ }
+
+ CreditCard findByCardNumber(String cardNumber);
+}
diff --git a/persistence-modules/spring-data-jpa-query-4/src/main/java/com/baeldung/spring/data/jpa/upsert/UpsertApplication.java b/persistence-modules/spring-data-jpa-query-4/src/main/java/com/baeldung/spring/data/jpa/upsert/UpsertApplication.java
new file mode 100644
index 0000000000..456b60c8a7
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-query-4/src/main/java/com/baeldung/spring/data/jpa/upsert/UpsertApplication.java
@@ -0,0 +1,12 @@
+package com.baeldung.spring.data.jpa.upsert;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class UpsertApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(UpsertApplication.class, args);
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-query-4/src/main/resources/application.properties b/persistence-modules/spring-data-jpa-query-4/src/main/resources/application.properties
index de0685b9bd..04b506d63c 100644
--- a/persistence-modules/spring-data-jpa-query-4/src/main/resources/application.properties
+++ b/persistence-modules/spring-data-jpa-query-4/src/main/resources/application.properties
@@ -1,3 +1,5 @@
+spring.jpa.defer-datasource-initialization=true
+logging.level.net.ttddyy.dsproxy.listener=debug
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=
@@ -7,4 +9,4 @@ spring.jpa.show-sql=true
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
-spring.jpa.properties.hibernate.format_sql=true
\ No newline at end of file
+spring.jpa.properties.hibernate.format_sql=true
diff --git a/persistence-modules/spring-data-jpa-query-4/src/test/java/com/baeldung/spring/data/jpa/upsert/UpdateOrInsertUnitTest.java b/persistence-modules/spring-data-jpa-query-4/src/test/java/com/baeldung/spring/data/jpa/upsert/UpdateOrInsertUnitTest.java
new file mode 100644
index 0000000000..89c91c7893
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-query-4/src/test/java/com/baeldung/spring/data/jpa/upsert/UpdateOrInsertUnitTest.java
@@ -0,0 +1,130 @@
+package com.baeldung.spring.data.jpa.upsert;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.jupiter.api.AfterEach;
+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;
+import org.springframework.transaction.annotation.Transactional;
+
+@SpringBootTest(classes = UpsertApplication.class)
+public class UpdateOrInsertUnitTest {
+
+ @Autowired
+ private CreditCardRepository creditCardRepository;
+
+ @Autowired
+ private CreditCardLogic creditCardLogic;
+
+ private CreditCard existingCard = null;
+
+ @BeforeEach
+ public void create() {
+ existingCard = createAndReturnCreditCards();
+ }
+
+ @AfterEach
+ public void clean() {
+ creditCardRepository.deleteAll();
+ }
+
+ @Test
+ void givenCreditCards_whenUpdateOrInsertUsingRepositoryExecuted_thenUpserted() {
+ // insert test
+ CreditCard newCreditCard = buildCreditCard();
+ CreditCard existingCardByCardNumber = creditCardRepository.findByCardNumber(newCreditCard.getCardNumber());
+ assertNull(existingCardByCardNumber);
+
+ creditCardLogic.updateOrInsertUsingRepository(newCreditCard);
+
+ existingCardByCardNumber = creditCardRepository.findByCardNumber(newCreditCard.getCardNumber());
+ assertNotNull(existingCardByCardNumber);
+
+ // update test
+ CreditCard cardForUpdate = existingCard;
+ String beforeExpiryDate = cardForUpdate.getExpiryDate();
+ cardForUpdate.setExpiryDate("2029-08-29");
+ existingCardByCardNumber = creditCardRepository.findByCardNumber(cardForUpdate.getCardNumber());
+ assertNotNull(existingCardByCardNumber);
+
+ creditCardLogic.updateOrInsertUsingRepository(cardForUpdate);
+
+ assertNotEquals("2029-08-29", beforeExpiryDate);
+ CreditCard updatedCard = creditCardRepository.findById(cardForUpdate.getId()).get();
+ assertEquals("2029-08-29", updatedCard.getExpiryDate());
+ }
+
+ @Test
+ void givenCreditCards_whenUpdateOrInsertUsingCustomLogicExecuted_thenUpserted() {
+ // insert test
+ CreditCard newCreditCard = buildCreditCard();
+ CreditCard existingCardByCardNumber = creditCardRepository.findByCardNumber(newCreditCard.getCardNumber());
+ assertNull(existingCardByCardNumber);
+
+ creditCardLogic.updateOrInsertUsingCustomLogic(newCreditCard);
+
+ existingCardByCardNumber = creditCardRepository.findByCardNumber(newCreditCard.getCardNumber());
+ assertNotNull(existingCardByCardNumber);
+
+ // update test
+ CreditCard cardForUpdate = existingCard;
+ String beforeExpiryDate = cardForUpdate.getExpiryDate();
+ cardForUpdate.setExpiryDate("2029-08-29");
+
+ creditCardLogic.updateOrInsertUsingCustomLogic(cardForUpdate);
+
+ assertNotEquals("2029-08-29", beforeExpiryDate);
+ CreditCard updatedCard = creditCardRepository.findById(cardForUpdate.getId()).get();
+ assertEquals("2029-08-29", updatedCard.getExpiryDate());
+ }
+
+ @Test
+ void givenCreditCards_whenUpdateOrInsertUsingBuiltInFeatureExecuted_thenUpserted() {
+ // insert test
+ CreditCard newCreditCard = buildCreditCard();
+ CreditCard existingCardByCardNumber = creditCardRepository.findByCardNumber(newCreditCard.getCardNumber());
+ assertNull(existingCardByCardNumber);
+
+ creditCardLogic.updateOrInsertUsingBuiltInFeature(newCreditCard);
+
+ existingCardByCardNumber = creditCardRepository.findByCardNumber(newCreditCard.getCardNumber());
+ assertNotNull(existingCardByCardNumber);
+
+ // update test
+ CreditCard cardForUpdate = existingCard;
+ String beforeExpiryDate = cardForUpdate.getExpiryDate();
+ cardForUpdate.setExpiryDate("2029-08-29");
+
+ creditCardLogic.updateOrInsertUsingBuiltInFeature(cardForUpdate);
+
+ assertNotEquals("2029-08-29", beforeExpiryDate);
+ CreditCard updatedCard = creditCardRepository.findById(cardForUpdate.getId()).get();
+ assertEquals("2029-08-29", updatedCard.getExpiryDate());
+ }
+
+ private CreditCard buildCreditCard() {
+ CreditCard card = new CreditCard();
+ card.setCardNumber("9994323432112222");
+ card.setExpiryDate("2024-06-21");
+ card.setCustomerId(10L);
+
+ return card;
+ }
+
+ private CreditCard createAndReturnCreditCards() {
+ CreditCard card = new CreditCard();
+ card.setCardNumber("3494323432112222");
+ card.setExpiryDate("2024-06-21");
+ card.setCustomerId(10L);
+ return creditCardRepository.save(card);
+ }
+}
diff --git a/persistence-modules/spring-data-jpa-query-4/src/test/resources/application.properties b/persistence-modules/spring-data-jpa-query-4/src/test/resources/application.properties
new file mode 100644
index 0000000000..c1970987af
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-query-4/src/test/resources/application.properties
@@ -0,0 +1 @@
+logging.level.net.ttddyy.dsproxy.listener=debug
\ No newline at end of file
diff --git a/persistence-modules/spring-data-jpa-query-4/src/test/resources/persistence-h2.properties b/persistence-modules/spring-data-jpa-query-4/src/test/resources/persistence-h2.properties
new file mode 100644
index 0000000000..e5853ae523
--- /dev/null
+++ b/persistence-modules/spring-data-jpa-query-4/src/test/resources/persistence-h2.properties
@@ -0,0 +1,8 @@
+jdbc.driverClassName=org.h2.Driver
+jdbc.url=jdbc:h2:mem:test
+jdbc.user=sa
+jdbc.pass=
+
+hibernate.dialect=org.hibernate.dialect.H2Dialect
+hibernate.show_sql=false
+hibernate.hbm2ddl.auto=create-drop