Slide 1

Slide 1 text

Maciej Walkowiak | @maciejwalkowiak Performance Oriented Spring Data JPA & Hibernate

Slide 2

Slide 2 text

Maciej Walkowiak | @maciejwalkowiak Why so slow?

Slide 3

Slide 3 text

Maciej Walkowiak | @maciejwalkowiak Why so slow? • Poor database connection management • Too many queries • Slow queries • Wrong JPA mappings • Fetching more than needed

Slide 4

Slide 4 text

Don’t guess

Slide 5

Slide 5 text

Maciej Walkowiak | @maciejwalkowiak

Slide 6

Slide 6 text

Maciej Walkowiak | @maciejwalkowiak

Slide 7

Slide 7 text

Database Connection Management

Slide 8

Slide 8 text

Maciej Walkowiak | @maciejwalkowiak Database JDBC Driver DataSource Application Database JDBC Driver DataSource Application get connection get connection get connection connection connection connection Network

Slide 9

Slide 9 text

Maciej Walkowiak | @maciejwalkowiak Database JDBC Driver DataSource Application Database JDBC Driver DataSource Application get connection get connection get connection connection connection connection Network • TCP Handshake • TLS negotiation • Authentication

Slide 10

Slide 10 text

Maciej Walkowiak | @maciejwalkowiak On the Database side (Postgres) • Each connection sparks a new OS process • Consumes 5-10MB of RAM • CPU context switching

Slide 11

Slide 11 text

3ms to execute query, 100ms to establish connection

Slide 12

Slide 12 text

Maciej Walkowiak | @maciejwalkowiak Connection Pooling

Slide 13

Slide 13 text

Maciej Walkowiak | @maciejwalkowiak • On application startup, creates a pool of physical connections • Reuses already open connections • Creates more connections, when pool is exhausted Connection Pooling

Slide 14

Slide 14 text

Maciej Walkowiak | @maciejwalkowiak Connection Pool DataSource Application Connection Pool DataSource Application get connection acquire connection connection connection

Slide 15

Slide 15 text

How big is your pool?

Slide 16

Slide 16 text

Maciej Walkowiak | @maciejwalkowiak • Tomcat handles requests with 200 threads • HikariCP default pool size is 10 How big is your pool?

Slide 17

Slide 17 text

If you have 10,000 front-end users, having a connection pool of 10,000 would be shear insanity. 1000 still horrible. Even 100 connections, overkill. You want a small pool of a few dozen connections at most, and you want the rest of the application threads blocked on the pool awaiting connections. https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

When is connection acquired? When is connection released?

Slide 20

Slide 20 text

Artur Havliukovskyi Vlad Mihalcea

Slide 21

Slide 21 text

Maciej Walkowiak | @maciejwalkowiak Demo 🔥

Slide 22

Slide 22 text

Maciej Walkowiak | @maciejwalkowiak • Turn off `spring.jpa.open-in-view` • Call external services outside of database transaction • Turn off auto-commit • Be very careful with @Transactional(propagation = REQUIRES_NEW) • Use TransactionTemplate when you need more control Summary

Slide 23

Slide 23 text

Maciej Walkowiak | @maciejwalkowiak Summary • Avoid select on insert with @Version or implement Persistable • Use Repository#getReferenceById when you need … references • Always use FetchType.LAZY on @ManyToOne, @ManyToMany • Explicitly fetch associations with fetch join or @EntityGraph • Use @DynamicUpdate for table with large number of columns

Slide 24

Slide 24 text

Maciej Walkowiak | @maciejwalkowiak How to bulletproof mappings?

Slide 25

Slide 25 text

Vlad Mihalcea Jean Bisutti

Slide 26

Slide 26 text

Maciej Walkowiak | @maciejwalkowiak Entities - goods and bads • Fetching more data than needed (even with LAZY fetch types) - 😢 • Loaded entities are stored in JPA Persistence Context - memory 🥺 • Dirty Tracking - CPU 😡 • The risk of N+1 - Database 😱 • Handles business logic and enforces invariants

Slide 27

Slide 27 text

Maciej Walkowiak | @maciejwalkowiak Fetch entity with an intention to modify it.

Slide 28

Slide 28 text

Maciej Walkowiak | @maciejwalkowiak Fetch projection for reading.

Slide 29

Slide 29 text

Maciej Walkowiak | @maciejwalkowiak @Entity public class Account { @Id private String id; private String iban; private String firstName; private String lastName; @ElementCollection @CollectionTable( name = "phone_number", joinColumns = @JoinColumn(name = “account_id") ) private List phoneNumbers; } {"id":"sender-id","firstName":"John","lastName":"Doe"} Expected Response: record NamesOnly(String id, String firstName, String lastName) {} Projection Class

Slide 30

Slide 30 text

Maciej Walkowiak | @maciejwalkowiak record NamesOnly(String id, String firstName, String lastName) {} Projection Class public interface AccountRepository extends JpaRepository { NamesOnly findNamesOnlyById(String id); } Repository

Slide 31

Slide 31 text

Maciej Walkowiak | @maciejwalkowiak NamesOnly findNamesOnlyById(String id); Columns to select where clause

Slide 32

Slide 32 text

Maciej Walkowiak | @maciejwalkowiak @Query("select new NamesOnly(a.id, a.firstName, a.lastName) from Account a where a.id = :id") NamesOnly findNamesOnlyById(String id);

Slide 33

Slide 33 text

Maciej Walkowiak | @maciejwalkowiak @Query(value = "select id, first_name, last_name from account where id = :id", nativeQuery = true) NamesOnly findNamesOnlyById(String id); interface NamesOnly { String getId(); String getFirstName(); String getLastName(); } 😳

Slide 34

Slide 34 text

Maciej Walkowiak | @maciejwalkowiak public interface AccountRepository extends JpaRepository { NamesOnly findNamesOnlyById(String id); }

Slide 35

Slide 35 text

Maciej Walkowiak | @maciejwalkowiak public interface AccountRepository extends JpaRepository { NamesOnly findNamesOnlyById(String id); AccountWithState findAccountWithStateById(String id); }

Slide 36

Slide 36 text

Maciej Walkowiak | @maciejwalkowiak public interface AccountRepository extends JpaRepository { NamesOnly findNamesOnlyById(String id); AccountWithState findAccountWithStateById(String id); AccountDTO findAccountDtoById(String id); }

Slide 37

Slide 37 text

Maciej Walkowiak | @maciejwalkowiak public interface AccountRepository extends JpaRepository { NamesOnly findNamesOnlyById(String id); AccountWithState findAccountWithStateById(String id); AccountDTO findAccountDtoById(String id); AccountLight findLightAccountById(String id); } 😳

Slide 38

Slide 38 text

Maciej Walkowiak | @maciejwalkowiak public interface AccountRepository extends JpaRepository { T findById(String id, Class clazz); } 😎

Slide 39

Slide 39 text

Maciej Walkowiak | @maciejwalkowiak public interface AccountRepository extends JpaRepository { T findById(String id, Class clazz); } 🥴 … but no custom query

Slide 40

Slide 40 text

Maciej Walkowiak | @maciejwalkowiak Just use SQL

Slide 41

Slide 41 text

Maciej Walkowiak | @maciejwalkowiak Takeaways • JPA & Hibernate are NOT easy • Get connections management right • Log queries during development • Consider getting Hibernate Optimiser from Vlad or using QuickPerf for testing • Use projections for reading data

Slide 42

Slide 42 text

Maciej Walkowiak | @maciejwalkowiak Vlad Mihalcea Thorben Janssen

Slide 43

Slide 43 text

Maciej Walkowiak | @maciejwalkowiak Thank you!