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 + 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