SlideShare a Scribd company logo
@vlad_mihalcea vladmihalcea.com
JPA and Hibernate
Performance Tips
@vlad_mihalcea vladmihalcea.com
About me
vladmihalcea.com
@vlad_mihalcea vladmihalcea.com
Mappings
CC BY-SA 2.0 - https://www.flickr.com/photos/47515486@N05/44129053595/
@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
}
@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)
)
@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);
}
@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
@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
@vlad_mihalcea vladmihalcea.com
IDENTITY vs TABLE generator
@vlad_mihalcea vladmihalcea.com
SEQUENCE vs TABLE generator
@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
}
@vlad_mihalcea vladmihalcea.com
AUTO Generator
• Prior to Hibernate 5 – native strategy.
• Hibernate 5 – SequenceStyleGenerator strategy (falls back to
TABLE)
@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')
@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
}
@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
@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)
@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());
@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
}
@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>
@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
}
@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" +
"}"
)
);
@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/"" +
"}"
)
);
@vlad_mihalcea vladmihalcea.com
Statement caching
CC BY 2.0 - https://www.flickr.com/photos/southbeachcars/16065861514/
@vlad_mihalcea vladmihalcea.com
Execution plan cache
@vlad_mihalcea vladmihalcea.com
Oracle server-side statement caching
• Soft parse
• Hard parse
• Bind peeking
• Adaptive cursor sharing (since 11g)
@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 = ?
@vlad_mihalcea vladmihalcea.com
PostgreSQL server-side statement caching
• Prior to 9.2 – execution plan cache
• 9.2 – deferred optimization
• The prepareThreshold connection property
@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
@vlad_mihalcea vladmihalcea.com
Client-side statement caching
• Recycling Statement, PreparedStatement or
CallableStatement objects
• Reusing database cursors
@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);
@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);
}
@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);
@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);
}
@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);
@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
@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
@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%
@vlad_mihalcea vladmihalcea.com
Queries
CC BY 2.0 - https://www.flickr.com/photos/justinbaeder/5317820857/
@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 = ?
@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
@vlad_mihalcea vladmihalcea.com
Criteria API literal handling
@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"
/>
@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();
}
@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());
@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());
@vlad_mihalcea vladmihalcea.com
IN query padding optimization
@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());
@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)
@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)
@vlad_mihalcea vladmihalcea.com
Entity query plan cache improvement
@vlad_mihalcea vladmihalcea.com
Native SQL query plan cache improvement
@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)
@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 = ?
@vlad_mihalcea vladmihalcea.com
JPQL DISTINCT entity query
@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 = ?
@vlad_mihalcea vladmihalcea.com
JPQL DISTINCT entity query
@vlad_mihalcea vladmihalcea.com
Fetching
CC BY 2.0 - https://www.flickr.com/photos/bala_/3544603505/
@vlad_mihalcea vladmihalcea.com
Persistence Context – loaded state
@vlad_mihalcea vladmihalcea.com
Persistence Context – dirty checking
@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();
@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
@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();
@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
@vlad_mihalcea vladmihalcea.com
JPA 2.2 Streaming
@vlad_mihalcea vladmihalcea.com
JPA 2.2 Streaming
default Stream getResultStream() {
return getResultList().stream();
}
final ScrollableResultsImplementor scrollableResults = scroll(
ScrollMode.FORWARD_ONLY
);
@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?
@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)
@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
@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"))
);
@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
@vlad_mihalcea vladmihalcea.com
Fetching – Open Session in View Anti-Pattern
@vlad_mihalcea vladmihalcea.com
Fetching – Open Session in View Anti-Pattern
@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"/>
@vlad_mihalcea vladmihalcea.com
Batching
CC BY-SA 2.0 - https://www.flickr.com/photos/dozodomo/6975654335/
@vlad_mihalcea vladmihalcea.com
JDBC Batch Updates
@vlad_mihalcea vladmihalcea.com
Transactional write-behind cache
@vlad_mihalcea vladmihalcea.com
Action queue
@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();
}
@vlad_mihalcea vladmihalcea.com
private void flush(EntityManager entityManager) {
entityManager.flush();
entityManager.clear();
entityManager.getTransaction().commit();
entityManager.getTransaction().begin();
}
Batch processing – flush and commit
@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)
@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);
}
@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'
@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'
@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
}
@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);
@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)
]
@vlad_mihalcea vladmihalcea.com
Default UPDATE disadvantages
• Column size
• Indexes
• Replication
• Undo and redo log
• Triggers
@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
}
@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();
@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!");
}
}
@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);
}
@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
…
@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)
]
@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)
]
@vlad_mihalcea vladmihalcea.com
Connections
CC BY 2.0 - https://www.flickr.com/photos/justinbaeder/5317820857/
@vlad_mihalcea vladmihalcea.com
Resource-local connection acquisition
@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 );
}
}
@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);
@vlad_mihalcea vladmihalcea.com
Resource-local connection acquisition optimization
@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/

More Related Content

PPTX
Solid principles
PPTX
React js
PPTX
Typescript ppt
PPTX
[Final] ReactJS presentation
PPT
JavaScript Tutorial
PPTX
Express js
PPTX
Rxjs ppt
PDF
TypeScript Introduction
Solid principles
React js
Typescript ppt
[Final] ReactJS presentation
JavaScript Tutorial
Express js
Rxjs ppt
TypeScript Introduction

What's hot (20)

PDF
Introduction to RxJS
PPTX
Coroutines in Kotlin
PDF
TypeScript - An Introduction
PPTX
Introducing type script
PDF
MongoDB Schema Design (Event: An Evening with MongoDB Houston 3/11/15)
PDF
Introduction to Redux
PPTX
Jsp with mvc
PPTX
Reactjs
PDF
Clean coding-practices
PPTX
Python/Flask Presentation
PDF
Quarkus k8s
PDF
Stored-Procedures-Presentation
PPTX
Spring data jpa
PPTX
PDF
What is JavaScript? Edureka
PDF
Clean Architecture
 
PPTX
CLASS OBJECT AND INHERITANCE IN PYTHON
PPTX
TypeScript VS JavaScript.pptx
PDF
Introduction to Spring Boot
Introduction to RxJS
Coroutines in Kotlin
TypeScript - An Introduction
Introducing type script
MongoDB Schema Design (Event: An Evening with MongoDB Houston 3/11/15)
Introduction to Redux
Jsp with mvc
Reactjs
Clean coding-practices
Python/Flask Presentation
Quarkus k8s
Stored-Procedures-Presentation
Spring data jpa
What is JavaScript? Edureka
Clean Architecture
 
CLASS OBJECT AND INHERITANCE IN PYTHON
TypeScript VS JavaScript.pptx
Introduction to Spring Boot
Ad

Similar to JPA and Hibernate Performance Tips (20)

PDF
Hibernate Reference
PDF
High-Performance Hibernate - JDK.io 2018
PDF
High-Performance JDBC Voxxed Bucharest 2016
PPT
Introduction to hibernate
PPTX
Hibernate
PDF
Hibernate Reference
PDF
Java persistence api 2.1
PDF
Cassandra drivers and libraries
PDF
Effiziente Datenpersistierung mit JPA 2.1 und Hibernate
PDF
JPA 2.1 performance tuning tips
PDF
High Performance Hibernate JavaZone 2016
PPT
Persisting Your Objects In The Database World @ AlphaCSP Professional OSS Con...
PDF
Hibernate reference
PDF
Introduction to JPA and Hibernate including examples
PPT
Basic Hibernate Final
PDF
Java e i database: da JDBC a JPA
PPTX
Effiziente persistierung
PPT
02 Hibernate Introduction
PPTX
Hibernate in Nutshell
PPTX
Advanced Hibernate V2
Hibernate Reference
High-Performance Hibernate - JDK.io 2018
High-Performance JDBC Voxxed Bucharest 2016
Introduction to hibernate
Hibernate
Hibernate Reference
Java persistence api 2.1
Cassandra drivers and libraries
Effiziente Datenpersistierung mit JPA 2.1 und Hibernate
JPA 2.1 performance tuning tips
High Performance Hibernate JavaZone 2016
Persisting Your Objects In The Database World @ AlphaCSP Professional OSS Con...
Hibernate reference
Introduction to JPA and Hibernate including examples
Basic Hibernate Final
Java e i database: da JDBC a JPA
Effiziente persistierung
02 Hibernate Introduction
Hibernate in Nutshell
Advanced Hibernate V2
Ad

Recently uploaded (20)

PDF
wealthsignaloriginal-com-DS-text-... (1).pdf
PPTX
ai tools demonstartion for schools and inter college
PPTX
VVF-Customer-Presentation2025-Ver1.9.pptx
PDF
PTS Company Brochure 2025 (1).pdf.......
PDF
Internet Downloader Manager (IDM) Crack 6.42 Build 42 Updates Latest 2025
PDF
Digital Strategies for Manufacturing Companies
PDF
Which alternative to Crystal Reports is best for small or large businesses.pdf
PDF
Navsoft: AI-Powered Business Solutions & Custom Software Development
PPTX
Agentic AI Use Case- Contract Lifecycle Management (CLM).pptx
PDF
Digital Systems & Binary Numbers (comprehensive )
PDF
top salesforce developer skills in 2025.pdf
PDF
Design an Analysis of Algorithms I-SECS-1021-03
PPTX
CHAPTER 2 - PM Management and IT Context
PDF
How to Choose the Right IT Partner for Your Business in Malaysia
PDF
Upgrade and Innovation Strategies for SAP ERP Customers
PPTX
Computer Software and OS of computer science of grade 11.pptx
PDF
Why TechBuilder is the Future of Pickup and Delivery App Development (1).pdf
PPTX
Reimagine Home Health with the Power of Agentic AI​
PDF
SAP S4 Hana Brochure 3 (PTS SYSTEMS AND SOLUTIONS)
PDF
How to Migrate SBCGlobal Email to Yahoo Easily
wealthsignaloriginal-com-DS-text-... (1).pdf
ai tools demonstartion for schools and inter college
VVF-Customer-Presentation2025-Ver1.9.pptx
PTS Company Brochure 2025 (1).pdf.......
Internet Downloader Manager (IDM) Crack 6.42 Build 42 Updates Latest 2025
Digital Strategies for Manufacturing Companies
Which alternative to Crystal Reports is best for small or large businesses.pdf
Navsoft: AI-Powered Business Solutions & Custom Software Development
Agentic AI Use Case- Contract Lifecycle Management (CLM).pptx
Digital Systems & Binary Numbers (comprehensive )
top salesforce developer skills in 2025.pdf
Design an Analysis of Algorithms I-SECS-1021-03
CHAPTER 2 - PM Management and IT Context
How to Choose the Right IT Partner for Your Business in Malaysia
Upgrade and Innovation Strategies for SAP ERP Customers
Computer Software and OS of computer science of grade 11.pptx
Why TechBuilder is the Future of Pickup and Delivery App Development (1).pdf
Reimagine Home Health with the Power of Agentic AI​
SAP S4 Hana Brochure 3 (PTS SYSTEMS AND SOLUTIONS)
How to Migrate SBCGlobal Email to Yahoo Easily

JPA and Hibernate Performance Tips

  • 1. @vlad_mihalcea vladmihalcea.com JPA and Hibernate Performance Tips
  • 3. @vlad_mihalcea vladmihalcea.com Mappings CC BY-SA 2.0 - https://www.flickr.com/photos/47515486@N05/44129053595/
  • 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. @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. @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. @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. @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
  • 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. @vlad_mihalcea vladmihalcea.com AUTO Generator • Prior to Hibernate 5 – native strategy. • Hibernate 5 – SequenceStyleGenerator strategy (falls back to TABLE)
  • 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. @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. @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. @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. @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. @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. @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. @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. @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. @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. @vlad_mihalcea vladmihalcea.com Statement caching CC BY 2.0 - https://www.flickr.com/photos/southbeachcars/16065861514/
  • 25. @vlad_mihalcea vladmihalcea.com Oracle server-side statement caching • Soft parse • Hard parse • Bind peeking • Adaptive cursor sharing (since 11g)
  • 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. @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. @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. @vlad_mihalcea vladmihalcea.com Client-side statement caching • Recycling Statement, PreparedStatement or CallableStatement objects • Reusing database cursors
  • 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. @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. @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. @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. @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. @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. @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. @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. @vlad_mihalcea vladmihalcea.com Queries CC BY 2.0 - https://www.flickr.com/photos/justinbaeder/5317820857/
  • 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. @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
  • 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. @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. @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. @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());
  • 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. @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. @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)
  • 51. @vlad_mihalcea vladmihalcea.com Native SQL query plan cache improvement
  • 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. @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 = ?
  • 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 = ?
  • 57. @vlad_mihalcea vladmihalcea.com Fetching CC BY 2.0 - https://www.flickr.com/photos/bala_/3544603505/
  • 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. @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. @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. @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
  • 65. @vlad_mihalcea vladmihalcea.com JPA 2.2 Streaming default Stream getResultStream() { return getResultList().stream(); } final ScrollableResultsImplementor scrollableResults = scroll( ScrollMode.FORWARD_ONLY );
  • 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. @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. @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. @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. @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. @vlad_mihalcea vladmihalcea.com Fetching – Open Session in View Anti-Pattern
  • 72. @vlad_mihalcea vladmihalcea.com Fetching – Open Session in View Anti-Pattern
  • 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. @vlad_mihalcea vladmihalcea.com Batching CC BY-SA 2.0 - https://www.flickr.com/photos/dozodomo/6975654335/
  • 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. @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. @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. @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. @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. @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. @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. @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. @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. @vlad_mihalcea vladmihalcea.com Default UPDATE disadvantages • Column size • Indexes • Replication • Undo and redo log • Triggers
  • 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. @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. @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. @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. @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. @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. @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. @vlad_mihalcea vladmihalcea.com Connections CC BY 2.0 - https://www.flickr.com/photos/justinbaeder/5317820857/
  • 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. @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);
  • 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/