Slide 1

Slide 1 text

MACIEJWALKOWIAK THE STATE OF JAVA RELATIONAL PERSISTENCE Maciej Walkowiak

Slide 2

Slide 2 text

MACIEJWALKOWIAK SF 2009, FIRST NO SQL MEETUP

Slide 3

Slide 3 text

MACIEJWALKOWIAK Relational databases give you too much. They force you to twist your object data to fit a RDBMS… NoSQL-based alternatives "just give you what you need". Jon Travis, principal engineer at SpringSource

Slide 4

Slide 4 text

MACIEJWALKOWIAK ! SCALABILITY "INFLEXIBILITY

Slide 5

Slide 5 text

MACIEJWALKOWIAK PostgreSQL • almost eliminated need for table locks • JSON data type support • geospatial queries • full text search

Slide 6

Slide 6 text

MACIEJWALKOWIAK MySQL • foreign keys • ACID (yay) • since 8.0 - JSON support • suitable for serious projects

Slide 7

Slide 7 text

MACIEJWALKOWIAK AWS Aurora • “the relational database for the cloud” • 10x faster than MySQL • db.x1e.32large has 128 vCPUs and 3904GB RAM • serverless

Slide 8

Slide 8 text

MACIEJWALKOWIAK RELATIONAL DATABASES ARE MORE FORGIVING

Slide 9

Slide 9 text

MACIEJWALKOWIAK

Slide 10

Slide 10 text

MACIEJWALKOWIAK MACIEJ WALKOWIAK - independent consultant - Open Source contributor

Slide 11

Slide 11 text

MACIEJWALKOWIAK MACIEJ WALKOWIAK - independent consultant - Open Source contributor

Slide 12

Slide 12 text

MACIEJWALKOWIAK

Slide 13

Slide 13 text

MACIEJWALKOWIAK https:/ /youtube.com/c/springacademy

Slide 14

Slide 14 text

MACIEJWALKOWIAK THE STATE OF JAVA RELATIONAL PERSISTENCE

Slide 15

Slide 15 text

MACIEJWALKOWIAK THE STATE OF JAVA RELATIONAL PERSISTENCE JDBC JPA HIBERNATE SPRING DATA R2DBC JOOQ JDBI FLYWAY DATABASE RIDER TEST CONTAINERS

Slide 16

Slide 16 text

MACIEJWALKOWIAK LET’S CLARIFY SOME BASICS

Slide 17

Slide 17 text

MACIEJWALKOWIAK

Slide 18

Slide 18 text

MACIEJWALKOWIAK

Slide 19

Slide 19 text

MACIEJWALKOWIAK network

Slide 20

Slide 20 text

MACIEJWALKOWIAK network select * from orders where id = 10

Slide 21

Slide 21 text

MACIEJWALKOWIAK network select * from orders where id = 10 JDBC Driver

Slide 22

Slide 22 text

MACIEJWALKOWIAK

Slide 23

Slide 23 text

MACIEJWALKOWIAK network JDBC Driver select * from orders where id = 10

Slide 24

Slide 24 text

MACIEJWALKOWIAK network Employee emp = new Employee(“Maria”); database.save(emp); JDBC Driver

Slide 25

Slide 25 text

MACIEJWALKOWIAK network Employee emp = new Employee(“Maria”); database.save(emp); JDBC Driver

Slide 26

Slide 26 text

MACIEJWALKOWIAK network Employee emp = new Employee(“Maria”); database.save(emp); JDBC Driver JPA

Slide 27

Slide 27 text

MACIEJWALKOWIAK JPA IS SIMPLE ..?

Slide 28

Slide 28 text

MACIEJWALKOWIAK class Order { private Long id; private Set items; } class OrderItem { private Long id; private int quantity; private Product product; } class Product { private Long id; private String name; }

Slide 29

Slide 29 text

MACIEJWALKOWIAK @Entity class Order { @Id @GeneratedValue private Long id; @OneToMany private Set items; } @Entity class OrderItem { @Id @GeneratedValue private Long id; private int quantity; @ManyToOne private Product product; } @Entity class Product { @Id @GeneratedValue private Long id; private String name; }

Slide 30

Slide 30 text

MACIEJWALKOWIAK @Entity @Table(name = "orders") class Order { @Id @GeneratedValue private Long id; @OneToMany private Set items; } @Entity class OrderItem { @Id @GeneratedValue private Long id; private int quantity; @ManyToOne private Product product; } @Entity class Product { @Id @GeneratedValue private Long id; private String name; }

Slide 31

Slide 31 text

MACIEJWALKOWIAK @Entity @Table(name = "orders") class Order { @Id @GeneratedValue private Long id; @OneToMany private Set items; } @Entity class OrderItem { @Id @GeneratedValue private Long id; private int quantity; @ManyToOne private Product product; } @Entity class Product { @Id @GeneratedValue private Long id; private String name; } Product product1 = productRepostory.save( new Product("Accelerate")); Product product2 = productRepostory.save( new Product("Lean Enterprise")); Order order = new Order(); order.addItem(product1, 1); order.addItem(product2, 2); orderRepostory.save(order);

Slide 32

Slide 32 text

MACIEJWALKOWIAK @Entity @Table(name = "orders") class Order { @Id @GeneratedValue private Long id; @OneToMany private Set items; } @Entity class OrderItem { @Id @GeneratedValue private Long id; private int quantity; @ManyToOne private Product product; } @Entity class Product { @Id @GeneratedValue private Long id; private String name; } Product product1 = productRepostory.save( new Product("Accelerate")); Product product2 = productRepostory.save( new Product("Lean Enterprise")); Order order = new Order(); order.addItem(product1, 1); order.addItem(product2, 2); orderRepostory.save(order); org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.maciejwalkowiak.springio.ordersjpa.OrderItem;

Slide 33

Slide 33 text

MACIEJWALKOWIAK @Entity @Table(name = "orders") class Order { @Id @GeneratedValue private Long id; @OneToMany(cascade = CascadeType.ALL) private Set items; } @Entity class OrderItem { @Id @GeneratedValue private Long id; private int quantity; @ManyToOne private Product product; } @Entity class Product { @Id @GeneratedValue private Long id; private String name; } Product product1 = productRepostory.save( new Product("Accelerate")); Product product2 = productRepostory.save( new Product("Lean Enterprise")); Order order = new Order(); order.addItem(product1, 1); order.addItem(product2, 2); orderRepostory.save(order); #

Slide 34

Slide 34 text

MACIEJWALKOWIAK @Entity @Table(name = "orders") class Order { @Id @GeneratedValue private Long id; @OneToMany(cascade = CascadeType.ALL) private Set items; } @Entity class OrderItem { @Id @GeneratedValue private Long id; private int quantity; @ManyToOne private Product product; } @Entity class Product { @Id @GeneratedValue private Long id; private String name; }

Slide 35

Slide 35 text

MACIEJWALKOWIAK @Entity @Table(name = "orders") class Order { @Id @GeneratedValue private Long id; @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "order_id") private Set items; } @Entity class OrderItem { @Id @GeneratedValue private Long id; private int quantity; @ManyToOne private Product product; } @Entity class Product { @Id @GeneratedValue private Long id; private String name;

Slide 36

Slide 36 text

MACIEJWALKOWIAK #AskVlad

Slide 37

Slide 37 text

MACIEJWALKOWIAK #AskVlad

Slide 38

Slide 38 text

MACIEJWALKOWIAK #AskVlad So, the bidirectional @OneToMany association is the best way to map a one-to-many database relationship when we really need the collection on the parent side of the association.

Slide 39

Slide 39 text

MACIEJWALKOWIAK #AskVlad

Slide 40

Slide 40 text

MACIEJWALKOWIAK #AskVlad as a Service

Slide 41

Slide 41 text

MACIEJWALKOWIAK #AskVlad as a Service

Slide 42

Slide 42 text

MACIEJWALKOWIAK io.hypersistence hypersistence-optimizer 1.1.1-SNAPSHOT

Slide 43

Slide 43 text

MACIEJWALKOWIAK io.hypersistence hypersistence-optimizer 1.1.1-SNAPSHOT @RunWith(SpringRunner.class) @SpringBootTest public class OrdersJpaApplicationTests { @PersistenceContext private EntityManager entityManager; @Test public void testOptimizer() { final ListEventHandler listEventListener = new ListEventHandler(); new HypersistenceOptimizer( new JpaConfig(entityManager.getEntityManagerFactory()) .setEventHandler(new ChainEventHandler( Arrays.asList(listEventListener, LogEventHandler.INSTANCE) )) ).init(); } }

Slide 44

Slide 44 text

MACIEJWALKOWIAK CRITICAL - UnidirectionalOneToManyEvent - The [items] one-to-many association in the [com.maciejwalkowiak.springio.ordersjpa.Order] entity is unidirectional and does not render very efficient SQL statements. Consider using a bidirectional one-to-many association instead. For more info about this event, check out this User Guide link - https:// vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#UnidirectionalOneToManyEvent CRITICAL - EagerFetchingEvent - The [product] attribute in the [com.maciejwalkowiak.springio.ordersjpa.OrderItem] entity uses eager fetching. Consider using a lazy fetching which, not only that is more efficient, but it is way more flexible when it comes to fetching data. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/#EagerFetchingEvent MAJOR - SkipAutoCommitCheckEvent - You should set the [hibernate.connection.provider_disables_autocommit] configuration property to [true] while also making sure that the underlying DataSource is configured to disable the auto-commit flag whenever a new Connection is being acquired. For more info about this event, check out this User Guide link - https://vladmihalcea.com/hypersistence-optimizer/docs/user-guide/ #SkipAutoCommitCheckEvent

Slide 45

Slide 45 text

MACIEJWALKOWIAK @Entity @Table(name = "orders") class Order { @Id @GeneratedValue private Long id; @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "order_id") private Set items; } @Entity class OrderItem { @Id @GeneratedValue private Long id; private int quantity; @ManyToOne private Product product; } @Entity class Product { @Id @GeneratedValue private Long id; private String name;

Slide 46

Slide 46 text

MACIEJWALKOWIAK @Entity @Table(name = "orders") class Order { @Id @GeneratedValue private Long id; @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "order_id") private Set items; } @Entity class OrderItem { @Id @GeneratedValue private Long id; private int quantity; @ManyToOne private Product product; } @Entity class Product { @Id @GeneratedValue private Long id; private String name; • which entity should have corresponding repository?

Slide 47

Slide 47 text

MACIEJWALKOWIAK @Entity @Table(name = "orders") class Order { @Id @GeneratedValue private Long id; @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "order_id") private Set items; } @Entity class OrderItem { @Id @GeneratedValue private Long id; private int quantity; @ManyToOne private Product product; } @Entity class Product { @Id @GeneratedValue private Long id; private String name; • which entity should have corresponding repository? • should OrderItem have a reference to product?

Slide 48

Slide 48 text

MACIEJWALKOWIAK @Entity @Table(name = "orders") class Order { @Id @GeneratedValue private Long id; @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "order_id") private Set items; } @Entity class OrderItem { @Id @GeneratedValue private Long id; private int quantity; @ManyToOne private Product product; } @Entity class Product { @Id @GeneratedValue private Long id; private String name; • which entity should have corresponding repository? • should OrderItem have a reference to product? • should OrderItem have a reference to Order?

Slide 49

Slide 49 text

MACIEJWALKOWIAK @Entity @Table(name = "orders") class Order { @Id @GeneratedValue private Long id; @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "order_id") private Set items; } @Entity class OrderItem { @Id @GeneratedValue private Long id; private int quantity; @ManyToOne private Product product; } @Entity class Product { @Id @GeneratedValue private Long id; private String name; • which entity should have corresponding repository? • should OrderItem have a reference to product? • should OrderItem have a reference to Order?

Slide 50

Slide 50 text

MACIEJWALKOWIAK JDBC JPA HIBERNATE SPRING DATA R2DBC JOOQ JDBI FLYWAY DATABASE RIDER TEST CONTAINERS

Slide 51

Slide 51 text

MACIEJWALKOWIAK

Slide 52

Slide 52 text

MACIEJWALKOWIAK PUBLISHING DOMAIN EVENTS FROM AGGREGATE ROOTS WITH SPRING DATA

Slide 53

Slide 53 text

MACIEJWALKOWIAK void confirmOrder(Long orderId) { Order order = orderRepostory.findById(orderId) .orElseThrow(() -> …); order.confirmed(); orderRepostory.save(order); } @Entity @Table(name = "orders") class Order { … public void confirmed() { this.status = OrderStatus.CONFIRMED; } }

Slide 54

Slide 54 text

MACIEJWALKOWIAK void confirmOrder(Long orderId) { Order order = orderRepostory.findById(orderId) .orElseThrow(() -> …); order.confirmed(); orderRepostory.save(order); mailSender.sendConfirmOrderEmail(order.getId()); }

Slide 55

Slide 55 text

MACIEJWALKOWIAK void confirmOrder(Long orderId) { Order order = orderRepostory.findById(orderId) .orElseThrow(() -> …); order.confirmed(); orderRepostory.save(order); mailSender.sendConfirmOrderEmail(order.getId()); pushNotifications.notifyCustomer(order.getId()); }

Slide 56

Slide 56 text

MACIEJWALKOWIAK class OrderConfirmed { private final Long orderId; … } private ApplicationEventPublisher eventPublisher; void confirmOrder(Long orderId) { Order order = orderRepostory.findById(orderId) .orElseThrow(() -> …); order.confirmed(); orderRepostory.save(order); eventPublisher .publishEvent(new OrderConfirmed(order.getId())); }

Slide 57

Slide 57 text

MACIEJWALKOWIAK @Component public class PushNotifications { @EventListener void handle(OrderConfirmed event) { LOGGER.info("Confirmed", event); } } @Component public class MailSender { @EventListener void handle(OrderConfirmed event) { LOGGER.info("Confirmed", event); } }

Slide 58

Slide 58 text

MACIEJWALKOWIAK class OrderConfirmed { private final Long orderId; … } private ApplicationEventPublisher eventPublisher; void confirmOrder(Long orderId) { Order order = orderRepostory.findById(orderId) .orElseThrow(() -> …); order.confirmed(); orderRepostory.save(order); eventPublisher .publishEvent(new OrderConfirmed(order.getId())); }

Slide 59

Slide 59 text

MACIEJWALKOWIAK @Entity @Table(name = "orders") class Order extends AbstractAggregateRoot { public void confirmed() { this.status = OrderStatus.CONFIRMED; registerEvent(new OrderConfirmed(id)); } }

Slide 60

Slide 60 text

MACIEJWALKOWIAK void confirmOrder(Long orderId) { Order order = orderRepostory.findById(orderId) .orElseThrow(() -> …); order.confirmed(); orderRepostory.save(order); eventPublisher .publishEvent(new OrderConfirmed(order.getId())); }

Slide 61

Slide 61 text

MACIEJWALKOWIAK void confirmOrder(Long orderId) { Order order = orderRepostory.findById(orderId) .orElseThrow(() -> …); order.confirmed(); orderRepostory.save(order); } MACIEJWALKOWIAK

Slide 62

Slide 62 text

MACIEJWALKOWIAK void confirmOrder(Long orderId) { Order order = orderRepostory.findById(orderId) .orElseThrow(() -> …); order.confirmed(); orderRepostory.save(order); } MACIEJWALKOWIAK

Slide 63

Slide 63 text

MACIEJWALKOWIAK

Slide 64

Slide 64 text

MACIEJWALKOWIAK @Entity @Table(name = "orders") class Order { @Id @GeneratedValue private Long id; @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "order_id") private Set items = new HashSet<>(); void addItem(Product product, int quantity) { this.items.add(new OrderItem(quantity, product)); } int sumQuantities() { return this.items .stream() .mapToInt(OrderItem::getQuantity) .sum(); } }

Slide 65

Slide 65 text

MACIEJWALKOWIAK select order0_.id as id1_1_ from orders order0_ select items0_.order_id as order_id4_0_0_, items0_.id as id1_0_0_, items0_.id as id1_0_1_, items0_.product_id as product_3_0_1_, items0_.quantity as quantity2_0_1_, product1_.id as id1_2_2_, product1_.name as name2_2_2_ from order_item items0_ left outer join product product1_ on items0_.product_id=product1_.id where items0_.order_id=? select items0_.order_id as order_id4_0_0_, items0_.id as id1_0_0_, items0_.id as id1_0_1_, items0_.product_id as product_3_0_1_, items0_.quantity as quantity2_0_1_, product1_.id as id1_2_2_, product1_.name as name2_2_2_ from order_item items0_ left outer join product product1_ on items0_.product_id=product1_.id where items0_.order_id=? select items0_.order_id as order_id4_0_0_, items0_.id as id1_0_0_, items0_.id as id1_0_1_, items0_.product_id as product_3_0_1_, items0_.quantity as quantity2_0_1_, product1_.id as id1_2_2_, product1_.name as name2_2_2_ from order_item items0_ left outer join product product1_ on items0_.product_id=product1_.id where items0_.order_id=?

Slide 66

Slide 66 text

MACIEJWALKOWIAK select order0_.id as id1_1_ from orders order0_ select items0_.order_id as order_id4_0_0_, items0_.id as id1_0_0_, items0_.id as id1_0_1_, items0_.product_id as product_3_0_1_, items0_.quantity as quantity2_0_1_, product1_.id as id1_2_2_, product1_.name as name2_2_2_ from order_item items0_ left outer join product product1_ on items0_.product_id=product1_.id where items0_.order_id=? select items0_.order_id as order_id4_0_0_, items0_.id as id1_0_0_, items0_.id as id1_0_1_, items0_.product_id as product_3_0_1_, items0_.quantity as quantity2_0_1_, product1_.id as id1_2_2_, product1_.name as name2_2_2_ from order_item items0_ left outer join product product1_ on items0_.product_id=product1_.id where items0_.order_id=? select items0_.order_id as order_id4_0_0_, items0_.id as id1_0_0_, items0_.id as id1_0_1_, items0_.product_id as product_3_0_1_, items0_.quantity as quantity2_0_1_, product1_.id as id1_2_2_, product1_.name as name2_2_2_ from order_item items0_ left outer join product product1_ on items0_.product_id=product1_.id where items0_.order_id=? select order_id, sum(quantity) from order_item group by order_id;

Slide 67

Slide 67 text

MACIEJWALKOWIAK HIBERNATE & JPA IS NOT THE ALTERNATIVE TO SQL. SQL IS YOUR FRIEND.

Slide 68

Slide 68 text

MACIEJWALKOWIAK @Entity @Table(name = "orders") @SqlResultSetMapping( name = "Order.getOrdersWithQuantity", classes = { @ConstructorResult( targetClass = OrderWithQuantity.class, columns = { @ColumnResult(name = "orderId"), @ColumnResult(name = "quantity") } ) } ) @NamedNativeQuery(name = "Order.getOrdersWithQuantity", query = "select order_id as orderId, " + "sum(quantity) as quantity " + "from order_item " + "group by id") class Order extends AbstractAggregateRoot { … }

Slide 69

Slide 69 text

MACIEJWALKOWIAK interface OrderRepository extends CrudRepository { @Query(value = "select order_id as orderId, " + "sum(quantity) as quantity " + "from order_item " + "group by id", nativeQuery = true) List findOrdersWithQuantity(); } interface OrderWithQuantity { Long getOrderId(); int getQuantity(); }

Slide 70

Slide 70 text

MACIEJWALKOWIAK

Slide 71

Slide 71 text

MACIEJWALKOWIAK List searchForProperties(SearchCriteria searchCriteria) { Set conditions = new HashSet<>(); if (searchCriteria.getPrice() != null) { if (searchCriteria.getPrice().getMin() != null) { conditions.add("price > :priceMin"); } if (searchCriteria.getPrice().getMax() != null) { conditions.add("price < :priceMax"); } } String query = "select * from property_ad where " + conditions.stream() .collect(Collectors.joining(" AND ")); Query q = entityManager.createNativeQuery(query, PropertyAd.class); if (searchCriteria.getPrice() != null) { if (searchCriteria.getPrice().getMin() != null) { q.setParameter("priceMin", searchCriteria.getPrice().getMin()); } if (searchCriteria.getPrice().getMax() != null) { q.setParameter("priceMax", searchCriteria.getPrice().getMax()); } } return q.getResultList(); }

Slide 72

Slide 72 text

MACIEJWALKOWIAK

Slide 73

Slide 73 text

MACIEJWALKOWIAK jOOQ generates Java code from your database and lets you build type safe SQL queries through its fluent API.

Slide 74

Slide 74 text

MACIEJWALKOWIAK • is not an ORM • is not a Spring Data project • is a library for building type-safe SQL queries in Java • it’s great for reading data from the database JOOQ

Slide 75

Slide 75 text

MACIEJWALKOWIAK select order_id, sum(quantity) from order_item group by order_id;

Slide 76

Slide 76 text

MACIEJWALKOWIAK dslContext.select(ORDER_ITEM.ORDER_ID, sum(ORDER_ITEM.QUANTITY)) .from(ORDER_ITEM) .where() .groupBy(ORDER_ITEM.ORDER_ID) .fetchInto(JooqOrderWithQuantity.class); select order_id, sum(quantity) from order_item group by order_id;

Slide 77

Slide 77 text

MACIEJWALKOWIAK List searchForProperties(SearchCriteria searchCriteria) { Set conditions = new HashSet<>(); if (searchCriteria.getPrice() != null) { if (searchCriteria.getPrice().getMin() != null) { conditions.add("price > :priceMin"); } if (searchCriteria.getPrice().getMax() != null) { conditions.add("price < :priceMax"); } } String query = "select * from property_ad where " + conditions.stream() .collect(Collectors.joining(" AND ")); Query q = entityManager.createNativeQuery(query, PropertyAd.class); if (searchCriteria.getPrice() != null) { if (searchCriteria.getPrice().getMin() != null) { q.setParameter("priceMin", searchCriteria.getPrice().getMin()); } if (searchCriteria.getPrice().getMax() != null) { q.setParameter("priceMax", searchCriteria.getPrice().getMax()); } } return q.getResultList(); }

Slide 78

Slide 78 text

MACIEJWALKOWIAK List searchForProperties(SearchCriteria searchCriteria) { Set conditions = new HashSet<>(); if (searchCriteria.getPrice() != null) { if (searchCriteria.getPrice().getMin() != null) { conditions.add(PROPERTY_AD.PRICE.gt(searchCriteria.getPrice().getMin())); } if (searchCriteria.getPrice().getMax() != null) { conditions.add(PROPERTY_AD.PRICE.lt(searchCriteria.getPrice().getMax())); } } return dslContext.selectFrom(PROPERTY_AD) .where(conditions) .fetchInto(PropertyAd.class); }

Slide 79

Slide 79 text

MACIEJWALKOWIAK WHERE TO START? • http:/ /www.jooq.org/ • https:/ /www.youtube.com/watch?v=j5QqHSIEcPE - Spring Tips: JOOQ • https:/ /www.youtube.com/watch?v=4pwTd6NEuN0 
 - Database centric applications with Spring Boot and jOOQ - Michael Simons • https:/ /start.spring.io/ - and select JOOQ starter

Slide 80

Slide 80 text

MACIEJWALKOWIAK SPRING DATA JDBC

Slide 81

Slide 81 text

MACIEJWALKOWIAK SPRING DATA JDBC Jens Schauder, Spring Data Team • simple ORM • embraces Domain Driven Design

Slide 82

Slide 82 text

MACIEJWALKOWIAK @Entity @Table(name = "orders") class Order { @Id @GeneratedValue private Long id; @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "order_id") private Set items; } @Entity class OrderItem { @Id @GeneratedValue private Long id; private int quantity; @ManyToOne private Product product; } @Entity class Product { @Id @GeneratedValue private Long id; private String name; @Table("orders") class Order { @Id private Long id; private Set items; private OrderStatus status; } class OrderItem { @Id private Long id; private int quantity; private Long productId; } class Product { @Id private Long id; private String name; }

Slide 83

Slide 83 text

MACIEJWALKOWIAK @Table("orders") class Order { @Id private Long id; private Set items; private OrderStatus status; } class OrderItem { @Id private Long id; private int quantity; private Long productId; } class Product { @Id private Long id; private String name; }

Slide 84

Slide 84 text

MACIEJWALKOWIAK interface ProductRepository extends CrudRepository {} interface OrderRepository extends CrudRepository {}

Slide 85

Slide 85 text

MACIEJWALKOWIAK interface ProductRepository extends CrudRepository {} interface OrderRepository extends CrudRepository { Iterable findByStatus(OrderStatus status); }

Slide 86

Slide 86 text

MACIEJWALKOWIAK interface ProductRepository extends CrudRepository {} interface OrderRepository extends CrudRepository { @Query("select * from orders where status = :status") Iterable findByStatus(@Param("status") OrderStatus status); }

Slide 87

Slide 87 text

MACIEJWALKOWIAK interface ProductRepository extends CrudRepository {} interface OrderRepository extends CrudRepository { @Query("select * from orders where status = :status") Iterable findByStatus(@Param("status") OrderStatus status); } @Transactional void confirm(Long orderId) { Order order = orderRepository.findById(orderId) .orElseThrow(() -> new OrderNotFoundException(orderId)); order.confirmed(); orderRepository.save(order); }

Slide 88

Slide 88 text

MACIEJWALKOWIAK interface ProductRepository extends CrudRepository {} interface OrderRepository extends CrudRepository { @Query("select * from orders where status = :status") Iterable findByStatus(@Param("status") OrderStatus status); } @Transactional void confirm(Long orderId) { Order order = orderRepository.findById(orderId) .orElseThrow(() -> new OrderNotFoundException(orderId)); order.confirmed(); orderRepository.save(order); }

Slide 89

Slide 89 text

MACIEJWALKOWIAK @Table("orders") class Order extends AbstractAggregateRoot { @Id private Long id; private Set items = new HashSet<>(); private OrderStatus status; public void confirmed() { this.status = OrderStatus.CONFIRMED; registerEvent(new OrderConfirmed(id)); } }

Slide 90

Slide 90 text

MACIEJWALKOWIAK • No dirty tracking, lazy loading • No caching • No Many to One and Many to Many WHAT IS MISSING?

Slide 91

Slide 91 text

MACIEJWALKOWIAK • Simplicity • Predictibility • Embraces SQL • Enforced good design … BUT INSTEAD YOU GET

Slide 92

Slide 92 text

MACIEJWALKOWIAK SPRING DATA JDBC

Slide 93

Slide 93 text

MACIEJWALKOWIAK SPRING DATA JDBC BASED ON JDBC

Slide 94

Slide 94 text

MACIEJWALKOWIAK SPRING DATA JDBC BASED ON JDBC BLOCKING

Slide 95

Slide 95 text

MACIEJWALKOWIAK R2DBC REACTIVE RELATIONAL DATABASE CONNECTIVITY

Slide 96

Slide 96 text

MACIEJWALKOWIAK Mono count = Mono.from(connectionFactory.create()) .flatMapMany(it -> it.createStatement( "INSERT INTO legoset (id, name, manual) " + "VALUES($1, $2, $3)") .bind("$1", 42055) .bind("$2", "Description") .bindNull("$3", Integer.class) .execute()) .flatMap(io.r2dbc.spi.Result::getRowsUpdated) .next(); Flux> rows = Mono.from(connectionFactory.create()) .flatMapMany(it -> it.createStatement( "SELECT id, name, manual FROM legoset").execute()) .flatMap(it -> it.map((row, rowMetadata) -> collectToMap(row, rowMetadata))); R2DBC SPI

Slide 97

Slide 97 text

MACIEJWALKOWIAK R2dbc r2dbc = new R2dbc(connectionFactory); Flux count = r2dbc.inTransaction(handle -> handle.createQuery( "INSERT INTO legoset (id, name, manual) " + "VALUES($1, $2, $3)") .bind("$1", 42055) .bind("$2", "Description") .bindNull("$3", Integer.class) .mapResult(io.r2dbc.spi.Result::getRowsUpdated)); Flux> rows = r2dbc .inTransaction(handle -> handle.select( "SELECT id, name, manual FROM legoset") .mapRow((row, rowMetadata) -> collectToMap(row, rowMetadata)); } R2DBC API

Slide 98

Slide 98 text

MACIEJWALKOWIAK DatabaseClient databaseClient = DatabaseClient.create(connectionFactory); Mono count = databaseClient.execute() .sql( "INSERT INTO legoset (id, name, manual) " + "VALUES($1, $2, $3)") .bind("$1", 42055) .bind("$2", "Description") .bindNull("$3", Integer.class) .fetch() .rowsUpdated(); Flux> rows = databaseClient.execute() .sql("SELECT id, name, manual FROM legoset") .fetch() .all(); SPRING DATA R2DBC

Slide 99

Slide 99 text

MACIEJWALKOWIAK class LegoSet { @Id private Integer id; private String name; private String manual; } interface LegoSetRepostory extends ReactiveCrudRepository {} Mono legoSet = legoSetRepository.save(new LegoSet(42055, "Some name", null)); Flux allSets = legoSetRepository.findAll(); SPRING DATA R2DBC

Slide 100

Slide 100 text

MACIEJWALKOWIAK • Alpha stage • PostgreSQL, MySQL, MSSQL, H2 • Plays nice with JOOQ R2DBC

Slide 101

Slide 101 text

MACIEJWALKOWIAK HOW CAN WE TEST IT?

Slide 102

Slide 102 text

MACIEJWALKOWIAK HOW CAN WE TEST IT? TESTCONTAINERS

Slide 103

Slide 103 text

MACIEJWALKOWIAK org.testcontainers postgresql 1.11.2 test

Slide 104

Slide 104 text

MACIEJWALKOWIAK org.testcontainers postgresql 1.11.2 test 
 
 spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver spring.datasource.url=jdbc:tc:postgresql:9.6.8://hostname/databasename spring.test.database.replace=none

Slide 105

Slide 105 text

MACIEJWALKOWIAK org.testcontainers postgresql 1.11.2 test 
 
 spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver spring.datasource.url=jdbc:tc:postgresql:9.6.8://hostname/databasename spring.test.database.replace=none 
 @RunWith(SpringRunner.class) @DataJdbcTest public class OrderRepositoryTest { @Autowired private OrderRepository orderRepository; @Test public void foo() { System.out.println(orderRepository.findAll()); } }

Slide 106

Slide 106 text

MACIEJWALKOWIAK org.testcontainers postgresql 1.11.2 test 
 
 spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver spring.datasource.url=jdbc:tc:postgresql:9.6.8://hostname/databasename spring.test.database.replace=none 
 @RunWith(SpringRunner.class) @DataJdbcTest public class OrderRepositoryTest { @Autowired private OrderRepository orderRepository; @Test public void foo() { System.out.println(orderRepository.findAll()); } }

Slide 107

Slide 107 text

MACIEJWALKOWIAK KEY TAKEAWAYS • There is a world beyond JPA • Embrace SQL • Consider Spring Data JDBC • Watch R2DBC progress • Use TestContainers!

Slide 108

Slide 108 text

MACIEJWALKOWIAK THANK YOU! https:/ /youtube.com/c/springacademy https:/ /twitter.com/maciejwalkowiak https:/ /maciejwalkowiak.com http:/ /speakerdeck.com/maciejwalkowiak