From fa88f6c181de7d234f9af53c35283b306ff079dc Mon Sep 17 00:00:00 2001 From: Thiago dos Santos Hora Date: Wed, 26 Jul 2023 17:23:08 +0200 Subject: [PATCH] [BAEL-4897] Shardingshere (#14470) * [BAEL-4897] Shardingshere * [BAEL-4897] Fixes * [BAEL-4897] delete read me --- persistence-modules/pom.xml | 1 + .../spring-data-shardingsphere/pom.xml | 80 +++++++++++++ .../com/baeldung/shardingsphere/Main.java | 12 ++ .../com/baeldung/shardingsphere/Order.java | 110 ++++++++++++++++++ .../shardingsphere/OrderController.java | 26 +++++ .../shardingsphere/OrderRepository.java | 5 + .../baeldung/shardingsphere/OrderService.java | 22 ++++ .../com/baeldung/shardingsphere/Status.java | 6 + .../src/main/resources/application.yml | 10 ++ .../src/main/resources/sharding.yml | 31 +++++ .../OrderServiceIntegrationTest.java | 86 ++++++++++++++ 11 files changed, 389 insertions(+) create mode 100644 persistence-modules/spring-data-shardingsphere/pom.xml create mode 100644 persistence-modules/spring-data-shardingsphere/src/main/java/com/baeldung/shardingsphere/Main.java create mode 100644 persistence-modules/spring-data-shardingsphere/src/main/java/com/baeldung/shardingsphere/Order.java create mode 100644 persistence-modules/spring-data-shardingsphere/src/main/java/com/baeldung/shardingsphere/OrderController.java create mode 100644 persistence-modules/spring-data-shardingsphere/src/main/java/com/baeldung/shardingsphere/OrderRepository.java create mode 100644 persistence-modules/spring-data-shardingsphere/src/main/java/com/baeldung/shardingsphere/OrderService.java create mode 100644 persistence-modules/spring-data-shardingsphere/src/main/java/com/baeldung/shardingsphere/Status.java create mode 100644 persistence-modules/spring-data-shardingsphere/src/main/resources/application.yml create mode 100644 persistence-modules/spring-data-shardingsphere/src/main/resources/sharding.yml create mode 100644 persistence-modules/spring-data-shardingsphere/src/test/java/com/baeldung/shardingsphere/OrderServiceIntegrationTest.java diff --git a/persistence-modules/pom.xml b/persistence-modules/pom.xml index 3569f83d7b..24a55491d3 100644 --- a/persistence-modules/pom.xml +++ b/persistence-modules/pom.xml @@ -97,6 +97,7 @@ spring-data-rest-2 spring-data-rest-querydsl spring-data-solr + spring-data-shardingsphere spring-jpa diff --git a/persistence-modules/spring-data-shardingsphere/pom.xml b/persistence-modules/spring-data-shardingsphere/pom.xml new file mode 100644 index 0000000000..1f37bed4cc --- /dev/null +++ b/persistence-modules/spring-data-shardingsphere/pom.xml @@ -0,0 +1,80 @@ + + + 4.0.0 + spring-data-shardingsphere + 1.0 + spring-data-shardingsphere + jar + + + com.baeldung + parent-boot-3 + 0.0.1-SNAPSHOT + ../../parent-boot-3 + + + + 5.3.2 + 8.0.33 + + + + + + org.testcontainers + testcontainers-bom + 1.18.3 + pom + import + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + mysql + mysql-connector-java + ${mysql.version} + + + org.apache.shardingsphere + shardingsphere-jdbc-core + ${shardingsphere.version} + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.testcontainers + junit-jupiter + test + + + org.testcontainers + mysql + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + + diff --git a/persistence-modules/spring-data-shardingsphere/src/main/java/com/baeldung/shardingsphere/Main.java b/persistence-modules/spring-data-shardingsphere/src/main/java/com/baeldung/shardingsphere/Main.java new file mode 100644 index 0000000000..76786088ff --- /dev/null +++ b/persistence-modules/spring-data-shardingsphere/src/main/java/com/baeldung/shardingsphere/Main.java @@ -0,0 +1,12 @@ +package com.baeldung.shardingsphere; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Main { + + public static void main(String[] args) { + SpringApplication.run(Main.class, args); + } +} diff --git a/persistence-modules/spring-data-shardingsphere/src/main/java/com/baeldung/shardingsphere/Order.java b/persistence-modules/spring-data-shardingsphere/src/main/java/com/baeldung/shardingsphere/Order.java new file mode 100644 index 0000000000..bb1d69f08a --- /dev/null +++ b/persistence-modules/spring-data-shardingsphere/src/main/java/com/baeldung/shardingsphere/Order.java @@ -0,0 +1,110 @@ +package com.baeldung.shardingsphere; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.Objects; + + +@Entity +@Table(name = "`order`") +public class Order { + + @Id + @Column(name = "order_id") + private Long orderId; + + @Column(name = "customer_id") + private Long customerId; + + @Column(name = "total_price") + private BigDecimal totalPrice; + + @Enumerated(EnumType.STRING) + @Column(name = "order_status") + private Status orderStatus; + + @Column(name = "order_date") + private LocalDate orderDate; + + @Column(name = "delivery_address") + private String deliveryAddress; + + public Long getOrderId() { + return orderId; + } + + public void setOrderId(Long orderId) { + this.orderId = orderId; + } + + public Long getCustomerId() { + return customerId; + } + + public void setCustomerId(Long customerId) { + this.customerId = customerId; + } + + public BigDecimal getTotalPrice() { + return totalPrice; + } + + public void setTotalPrice(BigDecimal totalPrice) { + this.totalPrice = totalPrice; + } + + public Status getOrderStatus() { + return orderStatus; + } + + public void setOrderStatus(Status orderStatus) { + this.orderStatus = orderStatus; + } + + public LocalDate getOrderDate() { + return orderDate; + } + + public void setOrderDate(LocalDate orderDate) { + this.orderDate = orderDate; + } + + public String getDeliveryAddress() { + return deliveryAddress; + } + + public void setDeliveryAddress(String deliveryAddress) { + this.deliveryAddress = deliveryAddress; + } + + protected Order() {} + + public Order(Long orderId, Long customerId, BigDecimal totalPrice, Status orderStatus, LocalDate orderDate, String deliveryAddress) { + this.orderId = orderId; + this.customerId = customerId; + this.totalPrice = totalPrice; + this.orderStatus = orderStatus; + this.orderDate = orderDate; + this.deliveryAddress = deliveryAddress; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Order order = (Order) o; + return Objects.equals(orderId, order.orderId); + } + + @Override + public int hashCode() { + return Objects.hash(orderId); + } +} diff --git a/persistence-modules/spring-data-shardingsphere/src/main/java/com/baeldung/shardingsphere/OrderController.java b/persistence-modules/spring-data-shardingsphere/src/main/java/com/baeldung/shardingsphere/OrderController.java new file mode 100644 index 0000000000..fe6294354e --- /dev/null +++ b/persistence-modules/spring-data-shardingsphere/src/main/java/com/baeldung/shardingsphere/OrderController.java @@ -0,0 +1,26 @@ +package com.baeldung.shardingsphere; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/orders") +public class OrderController { + + private final OrderService orderService; + + public OrderController(OrderService orderService) { + this.orderService = orderService; + } + + @PostMapping + public ResponseEntity createOrder(@RequestBody Order order) { + return ResponseEntity.ok(orderService.createOrder(order)); + } + + @GetMapping("/{id}") + public ResponseEntity getOrder(@PathVariable Long id) { + return ResponseEntity.ok(orderService.getOrder(id)); + } +} diff --git a/persistence-modules/spring-data-shardingsphere/src/main/java/com/baeldung/shardingsphere/OrderRepository.java b/persistence-modules/spring-data-shardingsphere/src/main/java/com/baeldung/shardingsphere/OrderRepository.java new file mode 100644 index 0000000000..54f9e4e140 --- /dev/null +++ b/persistence-modules/spring-data-shardingsphere/src/main/java/com/baeldung/shardingsphere/OrderRepository.java @@ -0,0 +1,5 @@ +package com.baeldung.shardingsphere; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface OrderRepository extends JpaRepository { } diff --git a/persistence-modules/spring-data-shardingsphere/src/main/java/com/baeldung/shardingsphere/OrderService.java b/persistence-modules/spring-data-shardingsphere/src/main/java/com/baeldung/shardingsphere/OrderService.java new file mode 100644 index 0000000000..dd796f1b9a --- /dev/null +++ b/persistence-modules/spring-data-shardingsphere/src/main/java/com/baeldung/shardingsphere/OrderService.java @@ -0,0 +1,22 @@ +package com.baeldung.shardingsphere; + +import org.springframework.stereotype.Service; + +@Service +public class OrderService { + + private final OrderRepository orderRepository; + + public OrderService(OrderRepository orderRepository) { + this.orderRepository = orderRepository; + } + + public Order createOrder(Order order) { + return orderRepository.save(order); + } + + public Order getOrder(Long id) { + return orderRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("Order not found")); + } +} diff --git a/persistence-modules/spring-data-shardingsphere/src/main/java/com/baeldung/shardingsphere/Status.java b/persistence-modules/spring-data-shardingsphere/src/main/java/com/baeldung/shardingsphere/Status.java new file mode 100644 index 0000000000..9bd6ad4f93 --- /dev/null +++ b/persistence-modules/spring-data-shardingsphere/src/main/java/com/baeldung/shardingsphere/Status.java @@ -0,0 +1,6 @@ +package com.baeldung.shardingsphere; + + +public enum Status { + PROCESSING, SHIPPED, DELIVERED, CANCELLED +} diff --git a/persistence-modules/spring-data-shardingsphere/src/main/resources/application.yml b/persistence-modules/spring-data-shardingsphere/src/main/resources/application.yml new file mode 100644 index 0000000000..ec2d12b63f --- /dev/null +++ b/persistence-modules/spring-data-shardingsphere/src/main/resources/application.yml @@ -0,0 +1,10 @@ +spring: + datasource: + driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver + url: jdbc:shardingsphere:classpath:sharding.yml + jpa: + properties: + hibernate: + dialect: org.hibernate.dialect.MySQL8Dialect + hibernate: + ddl-auto: create-drop \ No newline at end of file diff --git a/persistence-modules/spring-data-shardingsphere/src/main/resources/sharding.yml b/persistence-modules/spring-data-shardingsphere/src/main/resources/sharding.yml new file mode 100644 index 0000000000..3d5702f6e2 --- /dev/null +++ b/persistence-modules/spring-data-shardingsphere/src/main/resources/sharding.yml @@ -0,0 +1,31 @@ +dataSources: + ds0: + dataSourceClassName: com.zaxxer.hikari.HikariDataSource + driverClassName: com.mysql.jdbc.Driver + jdbcUrl: jdbc:mysql://localhost:13306/ds0?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8 + username: test + password: test + ds1: + dataSourceClassName: com.zaxxer.hikari.HikariDataSource + driverClassName: com.mysql.jdbc.Driver + jdbcUrl: jdbc:mysql://localhost:13307/ds1?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8 + username: test + password: test +rules: + - !SHARDING + tables: + order: + actualDataNodes: ds${0..1}.order + defaultDatabaseStrategy: + standard: + shardingColumn: order_id + shardingAlgorithmName: database_inline + defaultTableStrategy: + none: + shardingAlgorithms: + database_inline: + type: INLINE + props: + algorithm-expression: ds${order_id % 2} +props: + sql-show: false \ No newline at end of file diff --git a/persistence-modules/spring-data-shardingsphere/src/test/java/com/baeldung/shardingsphere/OrderServiceIntegrationTest.java b/persistence-modules/spring-data-shardingsphere/src/test/java/com/baeldung/shardingsphere/OrderServiceIntegrationTest.java new file mode 100644 index 0000000000..938d250058 --- /dev/null +++ b/persistence-modules/spring-data-shardingsphere/src/test/java/com/baeldung/shardingsphere/OrderServiceIntegrationTest.java @@ -0,0 +1,86 @@ +package com.baeldung.shardingsphere; + +import 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.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.MySQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDate; +import java.util.List; + +@Testcontainers +@SpringBootTest +class OrderServiceIntegrationTest { + + @Container + static MySQLContainer mySQLContainer1 = new MySQLContainer<>("mysql:8.0.23") + .withDatabaseName("ds0") + .withUsername("test") + .withPassword("test"); + + @Container + static MySQLContainer mySQLContainer2 = new MySQLContainer<>("mysql:8.0.23") + .withDatabaseName("ds1") + .withUsername("test") + .withPassword("test"); + + static { + mySQLContainer2.setPortBindings(List.of("13307:3306")); + mySQLContainer1.setPortBindings(List.of("13306:3306")); + } + @Autowired + private OrderService orderService; + + @Autowired + private OrderRepository orderRepository; + + @DynamicPropertySource + static void setProperties(DynamicPropertyRegistry registry) { + registry.add("spring.jpa.hibernate.ddl-auto", () -> "create-drop"); + } + + @Test + void shouldFindOrderInCorrectShard() { + // given + Order order1 = new Order(1L, 1L, BigDecimal.TEN, Status.PROCESSING, LocalDate.now(), "123 Main St"); + Order order2 = new Order(2L, 2L, BigDecimal.valueOf(12.5), Status.SHIPPED, LocalDate.now(), "456 Main St"); + + // when + Order savedOrder1 = orderService.createOrder(order1); + Order savedOrder2 = orderService.createOrder(order2); + + // then + // Assuming the sharding strategy is based on the order id, data for order1 should be present only in ds0 + // and data for order2 should be present only in ds1 + Assertions.assertThat(orderService.getOrder(savedOrder1.getOrderId())).isEqualTo(savedOrder1); + Assertions.assertThat(orderService.getOrder(savedOrder2.getOrderId())).isEqualTo(savedOrder2); + + // Verify that the orders are not present in the wrong shards. + // You would need to implement these methods in your OrderService. + // They should use a JdbcTemplate or EntityManager to execute SQL directly against each shard. + Assertions.assertThat(assertOrderInShard(savedOrder1, mySQLContainer2)).isTrue(); + Assertions.assertThat(assertOrderInShard(savedOrder2, mySQLContainer1)).isTrue(); + } + + private boolean assertOrderInShard(Order order, MySQLContainer container) { + try (Connection conn = DriverManager.getConnection(container.getJdbcUrl(), container.getUsername(), container.getPassword())) { + PreparedStatement stmt = conn.prepareStatement("SELECT * FROM `order` WHERE order_id = ?"); + stmt.setLong(1, order.getOrderId()); + ResultSet rs = stmt.executeQuery(); + return rs.next(); + } catch (SQLException ex) { + throw new RuntimeException("Error querying order in shard", ex); + } + } +}