Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

JPA and Hibernate Performance Tips

182 views

Published on

Learn how to speed up your application with these JPA and Hibernate Performance Tips.

Published in: Software
  • Be the first to comment

  • Be the first to like this

JPA and Hibernate Performance Tips

  1. 1. @vlad_mihalcea vladmihalcea.com JPA and Hibernate Performance Tips
  2. 2. @vlad_mihalcea vladmihalcea.com About me vladmihalcea.com
  3. 3. @vlad_mihalcea vladmihalcea.com Mappings CC BY-SA 2.0 - https://www.flickr.com/photos/47515486@N05/44129053595/
  4. 4. @vlad_mihalcea vladmihalcea.com TABLE generator @Entity public class Post { @Id @GeneratedValue(strategy = GenerationType.TABLE) private Long id; private String title; //Getters and setters omitted for brevity }
  5. 5. @vlad_mihalcea vladmihalcea.com TABLE generator CREATE TABLE hibernate_sequences ( sequence_name (255) NOT NULL, next_val, PRIMARY KEY (sequence_name) ) CREATE TABLE post ( id NOT NULL, title (255), PRIMARY KEY (id) )
  6. 6. @vlad_mihalcea vladmihalcea.com TABLE generator public IntegralDataTypeHolder getNextValue() { return session.getTransactionCoordinator().createIsolationDelegate() .delegateWork(new AbstractReturningWork<IntegralDataTypeHolder>() { public IntegralDataTypeHolder execute( Connection connection) throws SQLException { … }, true ); } for (int i = 0; i < 3; i++) { Post post = new Post(); post.setTitle( String.format("High-Performance Java Persistence, Part %d", i + 1) ); entityManager.persist(post); }
  7. 7. @vlad_mihalcea vladmihalcea.com SELECT tbl.next_val FROM hibernate_sequences tbl WHERE tbl.sequence_name = 'default' FOR UPDATE INSERT INTO hibernate_sequences (sequence_name, next_val) VALUES ('default', 1) UPDATE hibernate_sequences SET next_val = 2 WHERE next_val = 1 AND sequence_name = 'default' SELECT tbl.next_val FROM hibernate_sequences tbl WHERE tbl.sequence_name = 'default' FOR UPDATE UPDATE hibernate_sequences SET next_val = 3 WHERE next_val = 2 AND sequence_name = 'default' SELECT tbl.next_val FROM hibernate_sequences tbl WHERE tbl.sequence_name = 'default' FOR UPDATE UPDATE hibernate_sequences SET next_val = 4 WHERE next_val = 3 AND sequence_name = 'default' TABLE generator
  8. 8. @vlad_mihalcea vladmihalcea.com INSERT INTO post (title, id) VALUES ('High-Performance Java Persistence, Part 1', 1) INSERT INTO post (title, id) VALUES ('High-Performance Java Persistence, Part 2', 2) INSERT INTO post (title, id) VALUES ('High-Performance Java Persistence, Part 3', 3) TABLE generator
  9. 9. @vlad_mihalcea vladmihalcea.com IDENTITY vs TABLE generator
  10. 10. @vlad_mihalcea vladmihalcea.com SEQUENCE vs TABLE generator
  11. 11. @vlad_mihalcea vladmihalcea.com AUTO Generator @Entity public class Post { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String title; //Getters and setters omitted for brevity }
  12. 12. @vlad_mihalcea vladmihalcea.com AUTO Generator • Prior to Hibernate 5 – native strategy. • Hibernate 5 – SequenceStyleGenerator strategy (falls back to TABLE)
  13. 13. @vlad_mihalcea vladmihalcea.com AUTO Generator – Hibernate 5 and MySQL @Id @GeneratedValue(generator="native") @GenericGenerator(name = "native", strategy = "native") private Long id; INSERT INTO post (title) VALUES ('High-Performance Java Persistence, Part 1') INSERT INTO post (title) VALUES ('High-Performance Java Persistence, Part 2') INSERT INTO post (title) VALUES ('High-Performance Java Persistence, Part 3')
  14. 14. @vlad_mihalcea vladmihalcea.com Identifier portability – annotation mapping @Entity(name = "Post") @Table(name = "post") public class Post { @Id @GeneratedValue(generator = "sequence", strategy = GenerationType.SEQUENCE) @SequenceGenerator(name = "sequence", allocationSize = 10) private Long id; private String title; //Getters and setters omitted for brevity }
  15. 15. @vlad_mihalcea vladmihalcea.com Identifier portability – XML mapping <?xml version="1.0" encoding="UTF-8"?> <entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm" xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm_2_2.xsd" version="2.2"> <package>com.vladmihalcea.book.hpjp.hibernate.identifier.global</package> <entity class="Post" access="FIELD"> <attributes> <id name="id"> <generated-value strategy="IDENTITY"/> </id> </attributes> </entity> </entity-mappings
  16. 16. @vlad_mihalcea vladmihalcea.com Custom types – IP address column • IP address stored in the Classless Inter-Domain Routing format • VARCHAR(18) • BIGINT column encoding – 4 bytes (e.g. 192.168.123.231) + 1 byte (e.g. 24) • PostgreSQL cidr or inet type (requires 7 bytes)
  17. 17. @vlad_mihalcea vladmihalcea.com Custom types – PostgreSQL inet column Event matchingEvent = (Event) entityManager .createNativeQuery( "SELECT e.* " + "FROM event e " + "WHERE " + " e.ip && CAST(:network AS inet) = TRUE") .setParameter("network", "192.168.0.1/24") .getSingleResult(); assertEquals("192.168.0.123", matchingEvent.getIp().getAddress());
  18. 18. @vlad_mihalcea vladmihalcea.com Custom types – PostgreSQL inet column @Entity(name = "Event") @Table(name = "event") @TypeDef(typeClass = IPv4Type.class, defaultForType = IPv4.class) public class Event { @Id @GeneratedValue private Long id; @Column(name = "ip", columnDefinition = "inet") private IPv4 ip; //Getters and setters omitted for brevity }
  19. 19. @vlad_mihalcea vladmihalcea.com The hibernate-types project <dependency> <groupId>com.vladmihalcea</groupId> <artifactId>hibernate-types-52</artifactId> <version>${hibernate-types.version}</version> </dependency> <dependency> <groupId>com.vladmihalcea</groupId> <artifactId>hibernate-types-5</artifactId> <version>${hibernate-types.version}</version> </dependency>
  20. 20. @vlad_mihalcea vladmihalcea.com JSON for unstructured data @Entity(name = "Book") @Table(name = "book") @TypeDef(typeClass = JsonNodeBinaryType.class, defaultForType = JsonNode.class) public class Book { @Id @GeneratedValue private Long id; @NaturalId private String isbn; @Column(columnDefinition = "jsonb") private JsonNode properties; //Getters and setters omitted for brevity }
  21. 21. @vlad_mihalcea vladmihalcea.com JSON for unstructured data Book book = new Book(); book.setIsbn("978-9730228236"); book.setProperties( JacksonUtil.toJsonNode( "{" + " "title": "High-Performance Java Persistence"," + " "author": "Vlad Mihalcea"," + " "publisher": "Amazon"," + " "price": 44.99" + "}" ) );
  22. 22. @vlad_mihalcea vladmihalcea.com JSON for unstructured data Book book = entityManager.unwrap(Session.class).bySimpleNaturalId(Book.class) .load("978-9730228236"); book.setProperties( JacksonUtil.toJsonNode( "{" + " "title": "High-Performance Java Persistence"," + " "author": "Vlad Mihalcea"," + " "publisher": "Amazon"," + " "price": 44.99," + " "url": "https://www.amzn.com/973022823X/"" + "}" ) );
  23. 23. @vlad_mihalcea vladmihalcea.com Statement caching CC BY 2.0 - https://www.flickr.com/photos/southbeachcars/16065861514/
  24. 24. @vlad_mihalcea vladmihalcea.com Execution plan cache
  25. 25. @vlad_mihalcea vladmihalcea.com Oracle server-side statement caching • Soft parse • Hard parse • Bind peeking • Adaptive cursor sharing (since 11g)
  26. 26. @vlad_mihalcea vladmihalcea.com SQL Server server-side statement caching • Execution plan cache • Parameter sniffing • Prepared statements should use the qualified object name SELECT * FROM etl.dbo.task WHERE status = ?
  27. 27. @vlad_mihalcea vladmihalcea.com PostgreSQL server-side statement caching • Prior to 9.2 – execution plan cache • 9.2 – deferred optimization • The prepareThreshold connection property
  28. 28. @vlad_mihalcea vladmihalcea.com MySQL server-side statement caching • No execution plan cache • Since Connector/J 5.0.5 PreparedStatements are emulated • To activate server-side prepared statements: • useServerPrepStmts • cachePrepStmts
  29. 29. @vlad_mihalcea vladmihalcea.com Client-side statement caching • Recycling Statement, PreparedStatement or CallableStatement objects • Reusing database cursors
  30. 30. @vlad_mihalcea vladmihalcea.com Oracle implicit client-side statement caching • Connection-level cache • PreparedStatement and CallabledStatement only • Caches metadata only connectionProperties.put( "oracle.jdbc.implicitStatementCacheSize", Integer.toString(cacheSize)); dataSource.setConnectionProperties(connectionProperties);
  31. 31. @vlad_mihalcea vladmihalcea.com Oracle implicit client-side statement caching • Once enabled, all statements are cached. • Can be disabled on a per statement basis if (statement.isPoolable()) { statement.setPoolable(false); }
  32. 32. @vlad_mihalcea vladmihalcea.com Oracle explicit client-side statement caching • Caches both metadata, execution state and data OracleConnection oracleConnection = (OracleConnection) connection; oracleConnection.setExplicitCachingEnabled(true); oracleConnection.setStatementCacheSize(cacheSize);
  33. 33. @vlad_mihalcea vladmihalcea.com Oracle explicit client-side statement caching PreparedStatement statement = oracleConnection. getStatementWithKey(SELECT_POST_KEY); if (statement == null) { statement = connection.prepareStatement(SELECT_POST); } try { statement.setInt(1, 10); statement.execute(); } finally { ((OraclePreparedStatement) statement).closeWithKey(SELECT_POST_KEY); }
  34. 34. @vlad_mihalcea vladmihalcea.com SQL Server client-side statement caching • The SQL Server JDBC driver version 6.3 added support for statement caching. • Prepared Statement caching is disabled by default. connection.setStatementPoolingCacheSize(10); connection.setDisableStatementPooling(false);
  35. 35. @vlad_mihalcea vladmihalcea.com PostgreSQL Server client-side statement caching • PostgreSQL JDBC Driver 9.4-1202 makes client-side statement connection-bound instead of statement-bound • Configurable: • preparedStatementCacheQueries (default is 256) • preparedStatementCacheSizeMiB (default is 5MB) • Statement.setPoolable(false) is not supported
  36. 36. @vlad_mihalcea vladmihalcea.com MySQL Server client-side statement caching • Configurable: • cachePrepStmts (default is false) Required for server-side statement caching as well • prepStmtCacheSize (default is 25) • prepStmtCacheSqlLimit (default is 256) • Statement.setPoolable(false) works for server-side statements only
  37. 37. @vlad_mihalcea vladmihalcea.com Statement caching gain (one minute interval) Database System No Caching Throughput (SPM) Caching Throughput (SPM) Percentage Gain DB_A 419 833 507 286 20.83% DB_B 194 837 303 100 55.56% DB_C 116 708 166 443 42.61% DB_D 15 522 15 550 0.18%
  38. 38. @vlad_mihalcea vladmihalcea.com Queries CC BY 2.0 - https://www.flickr.com/photos/justinbaeder/5317820857/
  39. 39. @vlad_mihalcea vladmihalcea.com Criteria API literal handling CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<Book> cq = cb.createQuery(Book.class); Root<Book> root = cq.from(Book.class); cq.select(root); cq.where(cb.equal(root.get("name"), "High-Performance Java Persistence")); Book book = entityManager.createQuery(cq).getSingleResult(); SELECT b.id AS id1_0_, b.isbn AS isbn2_0_, b.name AS name3_0_ FROM book b WHERE b.name = ?
  40. 40. @vlad_mihalcea vladmihalcea.com Criteria API literal handling CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<Book> cq = cb.createQuery(Book.class); Root<Book> root = cq.from(Book.class); cq.select(root); cq.where(cb.equal(root.get("isbn"), 978_9730228236L)); Book book = entityManager.createQuery(cq).getSingleResult(); SELECT b.id AS id1_0_, b.isbn AS isbn2_0_, b.name AS name3_0_ FROM book b WHERE b.isbn = 9789730228236
  41. 41. @vlad_mihalcea vladmihalcea.com Criteria API literal handling
  42. 42. @vlad_mihalcea vladmihalcea.com Criteria API literal handling <property name="hibernate.criteria.literal_handling_mode" value="bind" /> <property name="hibernate.criteria.literal_handling_mode" value="inline" /> <property name="hibernate.criteria.literal_handling_mode" value="auto" />
  43. 43. @vlad_mihalcea vladmihalcea.com IN query padding optimization List<Post> getPostByIds(EntityManager entityManager, Integer... ids) { return entityManager.createQuery( "select p " + "from Post p " + "where p.id in :ids", Post.class) .setParameter("ids", Arrays.asList(ids)) .getResultList(); }
  44. 44. @vlad_mihalcea vladmihalcea.com IN query padding optimization SELECT p.id AS id1_0_, p.title AS title2_0_ FROM post p WHERE p.id IN (?, ?, ?) SELECT p.id AS id1_0_, p.title AS title2_0_ FROM post p WHERE p.id IN (?, ?, ?, ?) assertEquals(3, getPostByIds(entityManager, 1, 2, 3).size()); assertEquals(4, getPostByIds(entityManager, 1, 2, 3, 4).size());
  45. 45. @vlad_mihalcea vladmihalcea.com IN query padding optimization SELECT p.id AS id1_0_, p.title AS title2_0_ FROM post p WHERE p.id IN (?, ?, ?, ?, ?) SELECT p.id AS id1_0_, p.title AS title2_0_ FROM post p WHERE p.id IN (?, ?, ?, ?, ?, ?) assertEquals(5, getPostByIds(entityManager, 1, 2, 3, 4, 5).size()); assertEquals(6, getPostByIds(entityManager, 1, 2, 3, 4, 5, 6).size());
  46. 46. @vlad_mihalcea vladmihalcea.com IN query padding optimization
  47. 47. @vlad_mihalcea vladmihalcea.com IN query padding optimization <property name="hibernate.query.in_clause_parameter_padding" value="true" /> SELECT p.id AS id1_0_, p.title AS title2_0_ FROM post p WHERE p.id IN (?, ?, ?, ?) -- Params: (1, 2, 3, 3) SELECT p.id AS id1_0_, p.title AS title2_0_ FROM post p WHERE p.id IN (?, ?, ?, ?) -- Params: (1, 2, 3, 4) assertEquals(3, getPostByIds(entityManager, 1, 2, 3).size()); assertEquals(4, getPostByIds(entityManager, 1, 2, 3, 4).size());
  48. 48. @vlad_mihalcea vladmihalcea.com IN query padding optimization assertEquals(5, getPostByIds(entityManager, 1, 2, 3, 4, 5).size()); SELECT p.id AS id1_0_, p.title AS title2_0_ FROM post p WHERE p.id IN (?, ?, ?, ?, ?, ?, ?, ?) -- Params: (1, 2, 3, 4, 5, 5, 5, 5) assertEquals(6, getPostByIds(entityManager, 1, 2, 3, 4, 5, 6).size()); SELECT p.id AS id1_0_, p.title AS title2_0_ FROM post p WHERE p.id IN (?, ?, ?, ?, ?, ?, ?, ?) -- Params: (1, 2, 3, 4, 5, 6, 6, 6)
  49. 49. @vlad_mihalcea vladmihalcea.com Query plan cache • Entity queries (JPQL, Criteria API) need to be compiled to SQL • Configurable: • hibernate.query.plan_cache_max_size (2048) • hibernate.query.plan_parameter_metadata_max_size (128)
  50. 50. @vlad_mihalcea vladmihalcea.com Entity query plan cache improvement
  51. 51. @vlad_mihalcea vladmihalcea.com Native SQL query plan cache improvement
  52. 52. @vlad_mihalcea vladmihalcea.com JPQL DISTINCT scalar query List<Integer> publicationYears = entityManager.createQuery( "select distinct year(p.createdOn) " + "from Post p " + "order by year(p.createdOn)", Integer.class) .getResultList(); SELECT DISTINCT extract(YEAR FROM p.created_on) AS col_0_0_ FROM post p ORDER BY (YEAR FROM p.created_on)
  53. 53. @vlad_mihalcea vladmihalcea.com JPQL DISTINCT entity query List<Post> posts = entityManager.createQuery( "select distinct p " + "from Post p " + "left join fetch p.comments " + "where p.title = :title", Post.class) .setParameter("title", "High-Performance Java Persistence") .getResultList(); SELECT DISTINCT p.id AS id1_0_0_, pc.id AS id1_1_1_, p.title AS title2_0_0_, pc.review AS review2_1_1_, pc.post_id AS post_id3_1_0__, pc.id AS id1_1_0__ FROM post p LEFT OUTER JOIN post_comment pc ON p.id=pc.post_id WHERE p.title = ?
  54. 54. @vlad_mihalcea vladmihalcea.com JPQL DISTINCT entity query
  55. 55. @vlad_mihalcea vladmihalcea.com JPQL DISTINCT entity query List<Post> posts = entityManager.createQuery( "select distinct p " + "from Post p " + "left join fetch p.comments " + "where p.title = :title", Post.class) .setParameter("title", "High-Performance Java Persistence") .setHint(QueryHints.HINT_PASS_DISTINCT_THROUGH, false) .getResultList(); SELECT p.id AS id1_0_0_, pc.id AS id1_1_1_, p.title AS title2_0_0_, pc.review AS review2_1_1_, pc.post_id AS post_id3_1_0__, pc.id AS id1_1_0__ FROM post p LEFT OUTER JOIN post_comment pc ON p.id=pc.post_id WHERE p.title = ?
  56. 56. @vlad_mihalcea vladmihalcea.com JPQL DISTINCT entity query
  57. 57. @vlad_mihalcea vladmihalcea.com Fetching CC BY 2.0 - https://www.flickr.com/photos/bala_/3544603505/
  58. 58. @vlad_mihalcea vladmihalcea.com Persistence Context – loaded state
  59. 59. @vlad_mihalcea vladmihalcea.com Persistence Context – dirty checking
  60. 60. @vlad_mihalcea vladmihalcea.com Persistence Context size //session-level configuration Session session = entityManager.unwrap(Session.class); session.setDefaultReadOnly(true); //query-level configuration List<Post> posts = entityManager.createQuery( "select p from Post p", Post.class) .setHint(QueryHints.HINT_READONLY, true) .getResultList();
  61. 61. @vlad_mihalcea vladmihalcea.com @Transactional(readOnly = true) public List<Post> findAllByTitle(String title) { List<Post> posts = postDAO.findByTitle(title); org.hibernate.engine.spi.PersistenceContext persistenceContext = getHibernatePersistenceContext(); for(Post post : posts) { assertTrue(entityManager.contains(post)); EntityEntry entityEntry = persistenceContext.getEntry(post); assertNull(entityEntry.getLoadedState()); } return posts; } Spring 5.1 read-only optimization
  62. 62. @vlad_mihalcea vladmihalcea.com Fetching – Pagination • JPA / Hibernate API works for both entity and native SQL queries List<PostCommentSummary> summaries = entityManager.createQuery( "select new PostCommentSummary( " + " p.id, p.title, c.review ) " + "from PostComment c " + "join c.post p") .setFirstResult(pageStart) .setMaxResults(pageSize) .getResultList();
  63. 63. @vlad_mihalcea vladmihalcea.com Fetching – 100k vs 100 rows Fetch all Fetch limit 0 500 1000 1500 2000 2500 3000 3500 4000 4500 5000 Time(ms) DB_A DB_B DB_C DB_D
  64. 64. @vlad_mihalcea vladmihalcea.com JPA 2.2 Streaming
  65. 65. @vlad_mihalcea vladmihalcea.com JPA 2.2 Streaming default Stream getResultStream() { return getResultList().stream(); } final ScrollableResultsImplementor scrollableResults = scroll( ScrollMode.FORWARD_ONLY );
  66. 66. @vlad_mihalcea vladmihalcea.com JDBC-level streaming support • You still need to consider the Statement.setFetchSize. • On MySQL, you need to set it to Integer.MIN_VALUE. • On PostgreSQL, you need to set it to a positive integer value. • What about the Execution Plan?
  67. 67. @vlad_mihalcea vladmihalcea.com JPA 2.2 Streaming – Execution Plans List<String> executionPlanLines = doInJPA(entityManager -> { try(Stream<String> postStream = entityManager .createNativeQuery( "EXPLAIN ANALYZE " + "SELECT p " + "FROM post p " + "ORDER BY p.created_on DESC") .setHint(QueryHints.HINT_FETCH_SIZE, 50) .getResultStream() ) { return postStream.limit(50).collect(Collectors.toList()); } }); CREATE INDEX idx_post_created_on ON post (created_on DESC)
  68. 68. @vlad_mihalcea vladmihalcea.com JPA 2.2 Streaming – Execution Plans LOGGER.info( "Execution plan: {}", executionPlanLines .stream() .collect(Collectors.joining( "n" )) ); Execution plan: Sort (cost=65.53..66.83 rows=518 width=564) (actual time=2.876..3.399 rows=5000 loops=1) Sort Key: created_on DESC Sort Method: quicksort Memory: 896kB -> Seq Scan on post p (cost=0.00..42.18 rows=518 width=564) (actual time=0.050..1.371 rows=5000 loops=1) Planning time: 1.586 ms Execution time: 4.061 ms
  69. 69. @vlad_mihalcea vladmihalcea.com JPA 2.2 Streaming – Execution Plans List<String> executionPlanLines = doInJPA(entityManager -> { return entityManager .createNativeQuery( "EXPLAIN ANALYZE " + "SELECT p " + "FROM post p " + "ORDER BY p.created_on DESC") .setMaxResults(50) .getResultList(); }); LOGGER.info("Execution plan: {}", executionPlanLines .stream() .collect(Collectors.joining("n")) );
  70. 70. @vlad_mihalcea vladmihalcea.com JPA 2.2 Streaming – Execution Plans Execution plan: Limit (cost=0.28..25.35 rows=50 width=564) (actual time=0.038..0.051 rows=50 loops=1) -> Index Scan using idx_post_created_on on post p (cost=0.28..260.04 rows=518 width=564) (actual time=0.037..0.049 rows=50 loops=1) Planning time: 1.511 ms Execution time: 0.148 ms
  71. 71. @vlad_mihalcea vladmihalcea.com Fetching – Open Session in View Anti-Pattern
  72. 72. @vlad_mihalcea vladmihalcea.com Fetching – Open Session in View Anti-Pattern
  73. 73. @vlad_mihalcea vladmihalcea.com Fetching – Temporary Session Anti-Pattern • “Band aid” for LazyInitializationException • One temporary Session/Connection for every lazily fetched association <property name="hibernate.enable_lazy_load_no_trans" value="true"/>
  74. 74. @vlad_mihalcea vladmihalcea.com Batching CC BY-SA 2.0 - https://www.flickr.com/photos/dozodomo/6975654335/
  75. 75. @vlad_mihalcea vladmihalcea.com JDBC Batch Updates
  76. 76. @vlad_mihalcea vladmihalcea.com Transactional write-behind cache
  77. 77. @vlad_mihalcea vladmihalcea.com Action queue
  78. 78. @vlad_mihalcea vladmihalcea.com Batch processing – flush-clear-commit try { entityManager.getTransaction().begin(); for ( int i = 0; i < entityCount; ++i ) { if ( i > 0 && i % batchSize == 0 ) { flush( entityManager ); } entityManager.persist( new Post( String.format( "Post %d", i + 1 ) ) ); } entityManager.getTransaction().commit(); } catch (RuntimeException e) { if ( entityManager.getTransaction().isActive()) { entityManager.getTransaction().rollback(); } throw e; } finally { entityManager.close(); }
  79. 79. @vlad_mihalcea vladmihalcea.com private void flush(EntityManager entityManager) { entityManager.flush(); entityManager.clear(); entityManager.getTransaction().commit(); entityManager.getTransaction().begin(); } Batch processing – flush and commit
  80. 80. @vlad_mihalcea vladmihalcea.com Hibernate batching – default behavior for (int i = 0; i < 3; i++) { entityManager.persist( new Post(String.format("Post no. %d", i + 1)) ); } INSERT INTO post (title, id) VALUES ('Post no. 1', 1) INSERT INTO post (title, id) VALUES ('Post no. 2', 2) INSERT INTO post (title, id) VALUES ('Post no. 3', 3)
  81. 81. @vlad_mihalcea vladmihalcea.com Enable JDBC batching <property name="hibernate.jdbc.batch_size" value="5"/> Query: ["INSERT INTO post (title, id) VALUES (?, ?)"], Params: [('Post no. 1', 1), ('Post no. 2', 2), ('Post no. 3', 3)] entityManager.unwrap(Session.class).setJdbcBatchSize(10); for (long i = 0; i < 10; ++i) { Post post = new Post(); post.setTitle(String.format("Post nr %d", i)); entityManager.persist(post); }
  82. 82. @vlad_mihalcea vladmihalcea.com PostgreSQL batch statements log_statement = 'all' LOG: execute S_2: insert into post (title, id) values ($1, $2) DETAIL: parameters: $1 = 'Post no. 1', $2 = '1' LOG: execute S_2: insert into post (title, id) values ($1, $2) DETAIL: parameters: $1 = 'Post no. 2', $2 = '2' LOG: execute S_2: insert into post (title, id) values ($1, $2) DETAIL: parameters: $1 = 'Post no. 3', $2 = '3'
  83. 83. @vlad_mihalcea vladmihalcea.com PostgreSQL rewrite batch statements PGSimpleDataSource dataSource = (PGSimpleDataSource) dataSource(); dataSource.setReWriteBatchedInserts(true); LOG: execute <unnamed>: insert into post (title, id) values ($1, $2),($3, $4),($5, $6) DETAIL: parameters: $1 = 'Post no. 1', $2 = '1’, $3 = 'Post no. 2', $4 = '2’, $5 = 'Post no. 3', $6 = '3'
  84. 84. @vlad_mihalcea vladmihalcea.com Default UPDATE - Post entity mapping @Entity(name = "Post") @Table(name = "post") public class Post { @Id private Long id; private String title; private long likes; //Getters and setters omitted for brevity }
  85. 85. @vlad_mihalcea vladmihalcea.com Default UPDATE - Post entity data Post post1 = new Post(); post1.setId(1L); post1.setTitle("High-Performance Java Persistence"); entityManager.persist(post1); Post post2 = new Post(); post2.setId(2L); post2.setTitle("Java Persistence with Hibernate"); entityManager.persist(post2);
  86. 86. @vlad_mihalcea vladmihalcea.com Default UPDATE - statement batching Post post1 = entityManager.find(Post.class, 1L); post1.setTitle("High-Performance Java Persistence 2nd Edition"); Post post2 = entityManager.find(Post.class, 2L); post2.setLikes(12); entityManager.flush(); Query :[ "update post set likes=?, title=? where id=?" ], Params:[ (0, High-Performance Java Persistence 2nd Edition, 1), (12, Java Persistence with Hibernate, 2) ]
  87. 87. @vlad_mihalcea vladmihalcea.com Default UPDATE disadvantages • Column size • Indexes • Replication • Undo and redo log • Triggers
  88. 88. @vlad_mihalcea vladmihalcea.com Hibernate dynamic update @Entity(name = "Post") @Table(name = "post") @DynamicUpdate public class Post { @Id private Long id; private String title; private long likes; //Getters and setters omitted for brevity }
  89. 89. @vlad_mihalcea vladmihalcea.com Hibernate dynamic update Query:["update post set title=? where id=?"], Params:[(High-Performance Java Persistence 2nd Edition, 1)] Query:["update post set likes=? where id=?"], Params:[(12, 2)] Post post1 = entityManager.find(Post.class, 1L); post1.setTitle("High-Performance Java Persistence 2nd Edition"); Post post2 = entityManager.find(Post.class, 2L); post2.setlikes(12); entityManager.flush();
  90. 90. @vlad_mihalcea vladmihalcea.com Updating detached entities - Post and PostComment List<Post> posts = doInJPA(entityManager -> { return entityManager.createQuery( "select p " + "from Post p " + "join fetch p.comments ", Post.class) .getResultList(); }); for (Post post: posts) { post.setTitle("Vlad Mihalcea's " + post.getTitle()); for (PostComment comment: post.getComments()) { comment.setReview(comment.getReview() + " read!"); } }
  91. 91. @vlad_mihalcea vladmihalcea.com • JPA merge • Hibernate update JPA merge and Hibernate update for (Post post: posts) { entityManager.merge(post); } Session session = entityManager.unwrap(Session.class); for (Post post: posts) { session.update(post); }
  92. 92. @vlad_mihalcea vladmihalcea.com JPA merge – SELECT statements SELECT p.id AS id1_0_1_, p.title AS title2_0_1_, c.post_id AS post_id3_1_3_, c.id AS id1_1_3_, c.review AS review2_1_0_ FROM post p LEFT OUTER JOIN post_comment c ON p.id = c.post_id WHERE p.id = 1 SELECT p.id AS id1_0_1_, p.title AS title2_0_1_, c.post_id AS post_id3_1_3_, c.id AS id1_1_3_, c.review AS review2_1_0_ FROM post p LEFT OUTER JOIN post_comment c ON p.id = c.post_id WHERE p.id = 3 SELECT p.id AS id1_0_1_, p.title AS title2_0_1_, c.post_id AS post_id3_1_3_, c.id AS id1_1_3_, c.review AS review2_1_0_ FROM post p LEFT OUTER JOIN post_comment c ON p.id = c.post_id WHERE p.id = 5 …
  93. 93. @vlad_mihalcea vladmihalcea.com JPA merge – UPDATE statements Query:[ "update post set title=? where id=?" ], Params:[ (Vlad Mihalcea's High-Performance Java Persistence, Part no. 0, 1), (Vlad Mihalcea's High-Performance Java Persistence, Part no. 1, 3), (Vlad Mihalcea's High-Performance Java Persistence, Part no. 2, 5) ] Query:[ "update post_comment set post_id=?, review=? where id=?" ], Params:[ (1, Excellent read!, 2), (3, Excellent read!, 4), (5, Excellent read!, 6) ]
  94. 94. @vlad_mihalcea vladmihalcea.com Hibernate-specific update operation Query:[ "update post set title=? where id=?" ], Params:[ (Vlad Mihalcea's High-Performance Java Persistence, Part no. 0, 1), (Vlad Mihalcea's High-Performance Java Persistence, Part no. 1, 3), (Vlad Mihalcea's High-Performance Java Persistence, Part no. 2, 5) ] Query:[ "update post_comment set post_id=?, review=? where id=?" ], Params:[ (1, Excellent read!, 2), (3, Excellent read!, 4), (5, Excellent read!, 6) ]
  95. 95. @vlad_mihalcea vladmihalcea.com Connections CC BY 2.0 - https://www.flickr.com/photos/justinbaeder/5317820857/
  96. 96. @vlad_mihalcea vladmihalcea.com Resource-local connection acquisition
  97. 97. @vlad_mihalcea vladmihalcea.com Immediate connection acquisition @PersistenceContext private EntityManager entityManager; @Transactional public void importForecasts(String dataFilePath) { Document forecastXmlDocument = readXmlDocument( dataFilePath ); List<Forecast> forecasts = parseForecasts(forecastXmlDocument); for(Forecast forecast : forecasts) { entityManager.persist( forecast ); } }
  98. 98. @vlad_mihalcea vladmihalcea.com Resource-local delay connection acquisition <property name="hibernate.connection.provider_disables_autocommit" value="true" /> HikariConfig hikariConfig = new HikariConfig(); hikariConfig.setDataSourceClassName(dataSourceClassName); hikariConfig.setDataSourceProperties(dataSourceProperties); hikariConfig.setMinimumPoolSize(minPoolSize); hikariConfig.setMaximumPoolSize(maxPoolSize); hikariConfig.setAutoCommit(false); DataSource datasource = new HikariDataSource(hikariConfig);
  99. 99. @vlad_mihalcea vladmihalcea.com Resource-local connection acquisition optimization
  100. 100. @vlad_mihalcea vladmihalcea.com Thank you • Twitter: @vlad_mihalcea • Blog: https://vladmihalcea.com • Courses: https://vladmihalcea.com/courses • Book: https://vladmihalcea.com/books/high-performance-java-persistence/

×