Module 09 - Hibernate Optimization-upd (1)
Module 09 - Hibernate Optimization-upd (1)
Optimization
CS544: Enterprise Architecture
© 2014 Time2Master 1
Data Access Optimization
By changing when and How Hibernate retrieves
data you can optimize its performance
© 2014 Time2Master 2
When and How
When data is retrieved is closely related to
how data is retrieved
Hibernate provides several fetching strategies
Strategy Description
Lazy Loading Loads data only when needed, using a select for each item
Batch Fetching Loads more of the same entity / collection when one is needed
Sub Select Loads all collections for retrieved entities when one is needed
Eager Join Immediately loads related data, using joins if possible else selects
Join Fetch Query A query that also loads related entities or collections
© 2014 Time2Master 3
Hibernate Optimization:
PERFORMANCE PROBLEMS
© 2014 Time2Master 4
Slow? → What to Look For
There are two main problem categories:
Many selects to get similar, or closely related data
These selects can probably be combined
The N + 1 problem is an example of this
Caused by inappropriate lazy loading of data
© 2014 Time2Master 5
The N + 1 Problem
4 Customers each with
their own salesrep Gets the customers (1 query), then
session = sessionFactory.openSession(); executes another query for each
tx = session.beginTransaction();
Hibernate:
salesrep (N queries). Total N + 1
select
Customer cust1 = new Customer("Frank", "Brown");
customer0_.id as id0_,
Customer cust2 = new Customer("Jane", "Terrien"); customer0_.firstname as firstname0_,
Customer cust3 = new Customer("John", "Doe"); customer0_.lastname as lastname0_,
Customer cust4 = new Customer("Carol", "Reno"); customer0_.salesRep_id as salesRep4_0_
cust1.setSalesRep(new SalesRep("John Willis")); from
Customer customer0_
cust2.setSalesRep(new SalesRep("Mary Long"));
Hibernate:
cust3.setSalesRep(new SalesRep("Ted Walker")); select
cust4.setSalesRep(new SalesRep("Keith Rogers")); salesrep0_.id as id1_0_,
salesrep0_.name as name1_0_
session.persist(cust1); from
SalesRep salesrep0_
session.persist(cust2);
where
session.persist(cust3); salesrep0_.id=?
session.persist(cust4); Hibernate:
select
tx.commit(); salesrep0_.id as id1_0_,
salesrep0_.name as name1_0_
List<Customer> customers = from
SalesRep salesrep0_
session.createQuery("from Customer").list(); where
SalesRep salesrep = null; salesrep0_.id=?
for (Customer customer : customers) { Hibernate:
salesrep = customer.getSalesRep(); select
// do something with the salesrep salesrep0_.id as id1_0_,
salesrep0_.name as name1_0_
salesrep.getName(); from
} SalesRep salesrep0_
Retrieve customers and then where
work with related salesrep salesrep0_.id=?
© 2014 Time2Master 6
N + 1 with References (to-one)
1 Select
Each select returns
Select * from Customer N Selects a single object
© 2014 Time2Master 7
N + 1 with Collections
Gets the sales reps (1 query), and then
2 Sales Reps, each with a
executes another query for each sales
collection of customers
rep (N queries). Total N + 1 queries
session = sessionFactory.openSession(); Hibernate:
tx = session.beginTransaction(); select
salesrep0_.id as id1_,
salesrep0_.name as name1_
SalesRep sr1 = new SalesRep("John Willis"); from
SalesRep sr2 = new SalesRep("Mary Long"); SalesRep salesrep0_
Hibernate:
sr1.addCustomer(new Customer("Frank", "Brown")); select
customers0_.salesRep_id as salesRep4_1_,
sr1.addCustomer(new Customer("Jane", "Terrien")); customers0_.id as id1_,
sr2.addCustomer(new Customer("John", "Doe")); customers0_.id as id0_0_,
sr2.addCustomer(new Customer("Carol", "Reno")); customers0_.firstname as firstname0_0_,
customers0_.lastname as lastname0_0_,
session.persist(sr1); customers0_.salesRep_id as salesRep4_0_0_
from
session.persist(sr2); Customer customers0_
where
tx.commit(); customers0_.salesRep_id=?
Hibernate:
select
List<SalesRep> salesReps = customers0_.salesRep_id as salesRep4_1_,
session.createQuery("from SalesRep").list(); customers0_.id as id1_,
for (SalesRep s : salesReps) { customers0_.id as id0_0_,
Set<Customer> customers = s.getCustomers(); customers0_.firstname as firstname0_0_,
for (Customer c : customers) { customers0_.lastname as lastname0_0_,
customers0_.salesRep_id as salesRep4_0_0_
// do something with the customer from
} Customer customers0_
} where
Retrieve sales reps, and then customers0_.salesRep_id=?
work with related customers
© 2014 Time2Master 8
N + 1 with Collections (to-many)
1 Select
Each select returns
Select * from Salesrep N Selects a collection
Customer #1
Select * from Customer Customer #2
Salesrep #1
where salesrep_id = 1 Customer #3
Customer #4
Select * from Customer Customer #5
Salesrep #2
where salesrep_id = 2 Customer #6
Customer #7
Select * from Customer
Salesrep #3 Customer #8
where salesrep_id = 3 Customer #9
Customer #10
Select * from Customer
Salesrep #4 Customer #11
where salesrep_id = 4 Customer #12
Customer #13
Select * from Customer
Salesrep #5 Customer #14
where salesrep_id = 5 Customer #15
The amount of customers retrieved is
irrelevant, the amount of selects is important
© 2014 Time2Master 9
Cartesian Product Problem
Customer cust1 = new Customer("Frank", "Brown"); Customers have a set of books,
Customer cust2 = new Customer("Jane", "Terrien"); and a set of movies that they like.
Customer cust3 = new Customer("John", "Doe");
© 2014 Time2Master 12
Lazy Loading
Lazy loading can be specified for:
Object References
one-to-one and many-to-one associations
Generally don’t default to lazy loading (good thing)
Collections
one-to-many and many-to-many associations
Have the option to use ‘extra-lazy’ loading
Large Properties
CLOBs and BLOBs, e.g. large texts or image data
Need byte code instrumentation to use lazy loading
© 2014 Time2Master 13
Annotations
The JPA specifies that both @ManyToOne and
@OneToOne default to eager loading
@Entity Hibernate:
public class Customer { select
@Id customer0_.id as id0_2_,
@GeneratedValue customer0_.address_id as address4_0_2_,
private int id; customer0_.firstname as firstname0_2_,
customer0_.lastname as lastname0_2_,
private String firstname; customer0_.salesRep_id as salesRep5_0_2_,
private String lastname; address1_.id as id1_0_,
address1_.apt as apt1_0_,
@OneToOne(cascade=CascadeType.PERSIST) address1_.city as city1_0_,
private Address address; address1_.state as state1_0_,
address1_.street as street1_0_,
address1_.zip as zip1_0_,
@ManyToOne salesrep2_.id as id3_1_,
private SalesRep salesRep; salesrep2_.name as name3_1_
from
Customer customer0_
Customer cust1 = (Customer) left outer join
session.get(Customer.class, 1); Address address1_
on customer0_.address_id=address1_.id
left outer join
SalesRep salesrep2_
on customer0_.salesRep_id=salesrep2_.id
Hibernate retrieves the customer, where
the address, and the salesrep customer0_.address_id=?
© 2014 Time2Master 14
Specifying Lazy
Although it is possible to set @OneToOne and
@ManyToOne to FetchType.LAZY
@Entity
public class Customer {
@Id
@GeneratedValue
private int id;
private String firstname;
private String lastname; FetchType.LAZY Hibernate again only retrieves
the customer object
@OneToOne(fetch = FetchType.LAZY,
cascade=CascadeType.PERSIST) Hibernate:
private Address address; FetchType.LAZY select
customer0_.id as id0_0_,
@ManyToOne(fetch = FetchType.LAZY) customer0_.address_id as address4_0_0_,
private SalesRep salesRep; customer0_.firstname as firstname0_0_,
customer0_.lastname as lastname0_0_,
... customer0_.salesRep_id as salesRep5_0_0_
from
Customer cust1 = (Customer) Customer customer0_
session.get(Customer.class, 1); where
customer0_.id=?
© 2014 Time2Master 15
XML Can only be changed with
byte-code instrumentation
© 2014 Time2Master 18
Large Properties
Certain Properties may be so large that you
only want to load them when really necessary
Lazy loading of properties is only available with
byte-code instrumentation
<hibernate-mapping package="when.lazyprops">
public class Book {
<class name="Book"> Without byte-code
private String isbn;
<id name="isbn" /> instr. lazy=true
private String title;
<property name="title" />
private String author; doesn’t do anything
<property name="author" />
private java.sql.Clob summary;
<property name="summary" type="clob" lazy="true" />
private java.sql.Blob cover;
<property name="cover" type="blob" lazy="true" />
</class>
...
</hibernate-mapping>
Hibernate:
select
book0_.isbn as isbn0_0_,
Book b = (Book)session.get(Book.class, "978-0545139700"); book0_.title as title0_0_,
book0_.author as author0_0_
from
Summary and cover Book book0_
are not loaded (lazy) where
book0_.isbn=? 19
© 2014 Time2Master
Byte-Code Instrumentation Ant File
<?xml version="1.0" encoding="UTF-8"?>
<project name="ByteCodeInstrument" default="instrument">
<description>Byte Code instrument example</description>
<property name="src" location="src" />
<property name="build" location="bin" /> Code needs to be
compiled before it
<target name="compile">
<javac srcdir="${src}" destdir="${build}" />
can be instrumented
</target>
© 2014 Time2Master 20
Annotations – Lazy Properties
Requires property access for lazy loading
Book b = (Book)session.get(Book.class, "978-0545139700");
System.out.println(b.getTitle());
Only loads summary
java.sql.Clob sumData = b.getSummary(); when needed
int length = (int)sumData.length(); Hibernate:
System.out.println(sumData.getSubString(1, length)); select
book0_.isbn as isbn0_0_,
book0_.title as title0_0_,
@Entity book0_.author as author0_0_
public class Book { Annotations on getter
from
... methods instead of fields Book book0_
for property access where
@Id book0_.isbn=?
public String getIsbn() { return isbn; } Harry Potter and the Deathly Hallows
public String getTitle() { return title; } Hibernate:
public String getAuthor() { return author; } select
book_.summary as summary0_,
@Basic(fetch=FetchType.LAZY) book_.cover as cover0_
public java.sql.Clob getSummary() { from
return summary; Book book_
} Both Summary and
where
@Basic(fetch=FetchType.LAZY) Cover are loaded book_.isbn=?
public java.sql.Blob getCover() { Readers beware. The brilliant,
return cover; breathtaking conclusion to J.K.
} Rowling's spellbinding series is not for
the faint of heart
...
© 2014 Time2Master
21
Lazy Loading
Lazy loading gives good baseline performance
But it is also the cause of N + 1 problems
Can be slow because of too many small selects
© 2014 Time2Master 22
Hibernate Optimization:
© 2014 Time2Master 23
Eager Join
The eager join strategy is a combination of (non
lazy) Eager behavior and Join Fetching
Using XML these are two separate things
Using annotations they can not be separated
© 2014 Time2Master 24
Eager - XML
Hibernate:
<hibernate-mapping package="eager.xml"> select
<class name="Customer"> customer0_.id as id0_0_,
<id name="id"> Eager Many-to-one and customer0_.firstname as firstname0_0_,
<generator class="native" /> customer0_.lastname as lastname0_0_,
One-To-Many relation
</id> customer0_.salesRep as salesRep0_0_
<property name="firstname" /> from
<property name="lastname" /> Customer customer0_
<many-to-one name="salesRep" lazy="false" /> where
<set name="creditCards" inverse="true" customer0_.id=?
cascade="persist" lazy="false"> Hibernate:
<key column="customer" /> select
<one-to-many class="CreditCard" /> salesrep0_.id as id1_0_,
</set> salesrep0_.name as name1_0_
</class> from
</hibernate-mapping> SalesRep salesrep0_
where
salesrep0_.id=?
SalesRep
Hibernate:
Customer
+name select
+firstname * 1
+lastname creditcard0_.customer as customer1_,
CreditCard creditcard0_.id as id1_,
1 *
+number creditcard0_.id as id2_0_,
creditcard0_.number as number2_0_,
creditcard0_.name as name2_0_,
creditcard0_.expiration as expiration2_0_,
session.get(Customer.class, 1);
creditcard0_.customer as customer2_0_
from
Get() a single customer creates 3 selects CreditCard creditcard0_
where
© 2014 Time2Master creditcard0_.customer=? 25
Join Fetching – XML
<hibernate-mapping package="how.always.join">
<class name="Customer">
<id name="id"> Join Fetching for Many-To-One
<generator class="native" /> and One-To-Many
</id>
<property name="firstname" /> Hibernate:
<property name="lastname" /> select
<many-to-one name="salesRep" fetch="join" /> customer0_.id as id0_2_,
<set name="creditCards" inverse="true" customer0_.firstname as firstname0_2_,
cascade="persist" fetch="join"> customer0_.lastname as lastname0_2_,
<key column="customer" /> customer0_.salesRep_id as salesRep4_0_2_,
<one-to-many class="CreditCard" /> creditcard1_.customer_id as customer5_4_,
</set> creditcard1_.id as id4_,
</class> creditcard1_.id as id2_0_,
</hibernate-mapping> creditcard1_.customer_id as customer5_2_0_,
creditcard1_.expiration as expiration2_0_,
creditcard1_.name as name2_0_,
creditcard1_.number as number2_0_,
salesrep2_.id as id1_1_,
salesrep2_.name as name1_1_
Joined collections can lead from
to a Cartesian product Customer customer0_
left outer join
CreditCard creditcard1_
session.get(Customer.class, 1); on customer0_.id=creditcard1_.customer_id
left outer join
SalesRep salesrep2_
on customer0_.salesRep_id=salesrep2_.id
Get() loads three entities using a single select
where
customer0_.id=?
© 2014 Time2Master 26
Eager and Join - Annotations
@Entity
public class Customer {
@Id
Specify eager and join
@GeneratedValue using FetchType.EAGER
private int id; Hibernate:
private String firstname; select
private String lastname; customer0_.id as id0_2_,
customer0_.firstname as firstname0_2_,
@ManyToOne(fetch=FetchType.EAGER) customer0_.lastname as lastname0_2_,
private SalesRep salesRep; customer0_.salesRep_id as salesRep4_0_2_,
creditcard1_.customer_id as customer5_4_,
@OneToMany(fetch=FetchType.EAGER, creditcard1_.id as id4_,
mappedBy="customer", creditcard1_.id as id2_0_,
cascade=CascadeType.PERSIST) creditcard1_.customer_id as customer5_2_0_,
private Set<CreditCard> creditCards = creditcard1_.expiration as expiration2_0_,
new HashSet<CreditCard>(); creditcard1_.name as name2_0_,
creditcard1_.number as number2_0_,
... salesrep2_.id as id1_1_,
salesrep2_.name as name1_1_
from
Joined collections are Customer customer0_
dangerous left outer join
CreditCard creditcard1_
on customer0_.id=creditcard1_.customer_id
session.get(Customer.class, 1); left outer join
SalesRep salesrep2_
on customer0_.salesRep_id=salesrep2_.id
All three entities are loaded where
using a single joined select customer0_.id=?
© 2014 Time2Master 27
No Problem?
So far combining Eager and Join in the same
specification doesn’t seem like a problem
Eager without join seems silly, just following the
references would give the same result
Joins are always eager anyway
Surely there is no problem in combining these!
© 2014 Time2Master 28
Eager and Queries
Queries only fetch what is specified in the query
Mapping based optimizations (what / how to
retrieve) are ignored when executing a query
© 2014 Time2Master 29
Annotations Eager Join & Query
@Entity Hibernate:
public class Customer { select
@Id customer0_.id as id0_,
@GeneratedValue customer0_.firstname as firstname0_,
private int id; customer0_.lastname as lastname0_,
private String firstname; customer0_.salesRep_id as salesRep4_0_
private String lastname; from
Customer customer0_
@ManyToOne(fetch = FetchType.EAGER)
N selects, one for each
private SalesRep salesRep; Hibernate:
select customer retrieved
@OneToMany(fetch=FetchType.EAGER, salesrep0_.id as id1_0_,
mappedBy="customer", salesrep0_.name as name1_0_
cascade=CascadeType.PERSIST) from
private Set<CreditCard> creditCards = SalesRep salesrep0_
new HashSet<CreditCard>(); where
salesrep0_.id=?
... N selects, one for each
Hibernate: customer retrieved
select
creditcard0_.customer_id as customer5_1_,
creditcard0_.id as id1_,
creditcard0_.id as id2_0_,
Creates an N+1 problems creditcard0_.customer_id as customer5_2_0_,
without even having a loop! creditcard0_.expiration as expiration2_0_,
creditcard0_.name as name2_0_,
Query query = creditcard0_.number as number2_0_
session.createQuery("from Customer"); from
List<Customer> customers = query.list(); CreditCard creditcard0_
where
© 2014 Time2Master creditcard0_.customer_id=? 30
XML Join & Query
<hibernate-mapping package="how.always.join">
<class name="Customer">
<id name="id">
<generator class="native" />
</id>
<property name="firstname" />
<property name="lastname" />
<many-to-one name="salesRep" fetch="join" />
<set name="creditCards" cascade="persist"
inverse="true" fetch="join" >
<key column="customer" />
<one-to-many class="CreditCard" />
</set>
</class>
</hibernate-mapping>
Query query =
session.createQuery("from Customer");
List<Customer> customers = query.list();
Hibernate:
select
customer0_.id as id0_,
customer0_.firstname as firstname0_,
XML join mappings default to
customer0_.lastname as lastname0_,
lazy=“true” thereby not causing customer0_.salesRep as salesRep0_
the N+1 problems from
Customer customer0_
Unfortunately no such option
exists for annotations
© 2014 Time2Master 31
Beware: Eager Join
Because of the problems eager join can create
we mostly recommend against using it
There are still valid reasons why you might want
to use join fetching, especially with XML
Just beware that eager can cause N+1 problems
© 2014 Time2Master 32
Hibernate Optimization:
© 2014 Time2Master 33
Join Fetch Query
A Join Fetch Query is the most flexible strategy
Other strategies are defined in mapping data
→Mapping data is always used by all use cases
Join Fetch Queries are defined in code
→Only executed in the use case that it is defined in
© 2014 Time2Master 34
Join Fetch Queries
Queries can safely join multiple referenced objects
Should not join more than one collection
Even for a single collection ‘distinct’ is needed
Multiple collections create a Cartesian product
Query query = session.createQuery("select distinct p " Fetch joins are outer joins even if you do
+ "from Person p left join fetch p.accounts"); not specify LEFT or OUTER
List<Person> people = query.list();
Criteria criteria = session.createCriteria(Person.class)
.setFetchMode("accounts", FetchMode.JOIN)
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
Hibernate:
List<Person> people = criteria.list();
select
distinct person0_.id as id0_0_,
accounts1_.number as number1_1_,
person0_.firstname as firstname0_0_,
person0_.lastname as lastname0_0_,
accounts1_.balance as balance1_1_,
accounts1_.owner_id as owner3_1_1_,
accounts1_.owner_id as owner3_0__,
accounts1_.number as number0__
from
Person person0_ Loads person objects and pre-cache the
left outer join associated accounts using a single select
Account accounts1_
on person0_.id=accounts1_.owner_id © 2014 Time2Master 35
Hibernate Optimization:
© 2014 Time2Master 36
Batch Fetching
References N+1:
We also saw how N+1 loads the salesrep for each
customer in a separate select
Batch fetching helps: by loading the salesrep for
several customers in one go – loading a batch
When the first reference is needed
Collections N+1:
We saw how N+1 loads the customer list for each
salesrep using a separate select
Batch fetching helps: by loading the customer lists for
several salesreps in one go – loading a batch
When the first collection is needed
© 2014 Time2Master 37
Batch Fetching References
1 Select
Each select returns
N / batch
Select * from Customer a batch of objects
Selects
Customer #2 Salesrep #2
Customer #3 Salesrep #3
Customer #5 Salesrep #5
© 2014 Time2Master 38
Batch References
@Entity
@org.hibernate.annotations.BatchSize(size=3)
public class SalesRep {
@Id SalesRep will be loaded
@GeneratedValue in batches of 3 or less,
@Entity private int id; when possible
public class Customer { private String name;
@Id
@GeneratedValue @OneToMany(mappedBy="salesRep”)
private int id; private Set<Customer> customers = new HashSet<Customer>();
private String firstname;
private String lastname; ...
Hibernate:
@ManyToOne(fetch = FetchType.LAZY) select
private SalesRep salesRep; customer0_.id as id0_,
customer0_.firstname as firstname0_,
... customer0_.lastname as lastname0_,
customer0_.salesRep_id as salesRep4_0_
from
List<Customer> customers =
Customer customer0_
session.createQuery("from Customer").list();
Hibernate:
SalesRep salesrep = null;
select
for (Customer customer : customers) {
salesrep0_.id as id1_0_,
salesrep = customer.getSalesRep();
salesrep0_.name as name1_0_
// do something with the salesrep
from
salesrep.getName();
SalesRep salesrep0_
}
where
Batch fetching works because customers
salesrep0_.id in (
with un-retrieved salesrep have been loaded ?, ?, ?
© 2014 Time2Master ) 39
Batch References– XML
<hibernate-mapping package="how.always.batch.entity">
<class name="SalesRep" batch-size="3">
<id name="id"> batch-size attribute
<generator class="native" /> on the <class> tag
</id>
<property name="name" />
<hibernate-mapping > <set name="customers" inverse="true" cascade="persist">
<class name="Customer"> <key column="salesRep" />
<id name="id"> <one-to-many class="Customer" />
<generator class="native" /> </set>
</id> </class>
<property name="firstname" /> </hibernate-mapping>
<property name="lastname" />
<many-to-one name="salesRep" /> Hibernate:
</class> select
</hibernate-mapping> customer0_.id as id0_,
customer0_.firstname as firstname0_,
customer0_.lastname as lastname0_,
customer0_.salesRep_id as salesRep4_0_
List<Customer> customers = from
session.createQuery("from Customer").list(); Customer customer0_
SalesRep salesrep = null; Hibernate:
for (Customer customer : customers) { select
salesrep = customer.getSalesRep(); salesrep0_.id as id1_0_,
// do something with the salesrep salesrep0_.name as name1_0_
salesrep.getName(); from
} SalesRep salesrep0_
Batch fetching works when Hibernate where
knows about un-retrieved salereps salesrep0_.id in (
?, ?, ?
© 2014 Time2Master ) 40
Batch Fetching Collections
1 Select
Each select returns a
N / batch
Select * from Salesrep batch of collections
Selects
Customer #1
Select * from Customer Customer #2
Salesrep #1
where salesrep_id in (1, 2, 3) Customer #3
Customer #4
Salesrep #2 Customer #5
Customer #6
Customer #7
Salesrep #3 Customer #8
Customer #9
Customer #10
Select * from Customer
Salesrep #4 Customer #11
where salesrep_id in (4, 5) Customer #12
Customer #13
Salesrep #5 Customer #14
Customer #15
© 2014 Time2Master 41
Batch Fetching – Collections
@Entity
public class SalesRep {
@Id
@GeneratedValue
private int id; Try to load the customer collection
private String name;
@Entity for 3 salesreps when possible
public class Customer {
@OneToMany(mappedBy="salesRep", cascade=CascadeType.PERSIST)
@Id
@org.hibernate.annotations.BatchSize(size=3)
@GeneratedValue
private Set<Customer> customers = new HashSet<Customer>();
private int id;
private String firstname;
...
private String lastname;
Hibernate:
@ManyToOne(fetch = FetchType.LAZY)
select
private SalesRep salesRep;
salesrep0_.id as id1_,
salesrep0_.name as name1_
...
from
SalesRep salesrep0_
Hibernate:
select
customers0_.salesRep_id as salesRep4_1_,
List<SalesRep> salesreps = customers0_.id as id1_,
session.createQuery("from SalesRep").list(); customers0_.id as id0_0_,
Set<Customer> customers = null; customers0_.firstname as firstname0_0_,
for (SalesRep s : salesreps) { customers0_.lastname as lastname0_0_,
customers = s.getCustomers(); customers0_.salesRep_id as salesRep4_0_0_
for (Customer c : customers) { from
// do something with the customer Customer customers0_
} where
Batch fetching only works because
} customers0_.salesRep_id in (
Hibernate knows of un-retrieved
?, ?, ?
customer collections
© 2014 Time2Master ) 42
Batch Collections – XML <hibernate-mapping package="how.always.batch.collection">
<class name="SalesRep">
<id name="id">
<generator class="native" /> XML uses the batch-
</id> size attribute on
<hibernate-mapping > <property name="name" /> collection tags
<class name="Customer"> <set name="customers" batch-size="3"
<id name="id"> inverse="true" cascade="persist">
<generator class="native" /> <key column="salesRep" />
</id> <one-to-many class="Customer" />
<property name="firstname" /> </set>
<property name="lastname" /> </class>
<many-to-one name="salesRep" /> </hibernate-mapping>
</class> Hibernate:
</hibernate-mapping> select
salesrep0_.id as id1_,
salesrep0_.name as name1_
from
SalesRep salesrep0_
Hibernate:
List<SalesRep> salesreps = select
session.createQuery("from SalesRep").list(); customers0_.salesRep_id as salesRep4_1_,
Set<Customer> customers = null; customers0_.id as id1_,
for (SalesRep s : salesreps) { customers0_.id as id0_0_,
customers = s.getCustomers(); customers0_.firstname as firstname0_0_,
for (Customer c : customers) { customers0_.lastname as lastname0_0_,
// do something with the customer customers0_.salesRep_id as salesRep4_0_0_
} from
Batch fetching only works because Customer customers0_
}
Hibernate knows of un-retrieved where
customer collections customers0_.salesRep_id in (
?, ?, ?
© 2014 Time2Master ) 43
Batch Fetching
Batch fetching is an easy and safe optimization
If un-needed data is retrieved it’s never much
No joins are involved, no Cartesian Product
Can be specified for references and collections
© 2014 Time2Master 44
Hibernate Optimization:
© 2014 Time2Master 45
Sub Select
The sub select strategy is a specialized form of
the batch fetching strategy for collections
Instead of loading a batch of collections it loads
all related collections in one select
Just like batch fetching it doesn’t retrieve anything
until the first time a collection is needed
© 2014 Time2Master 46
Batch Fetching Collections
1 Select Single select returns all
customer collections for the
Select * from Salesrep 1 Select Salesreps in the first select
Customer #1
Select * from Customer Customer #2
Salesrep #1
where salesrep_id in Customer #3
(Select id from Salesrep)
Customer #4
Salesrep #2 Customer #5
Customer #6
Customer #7
Salesrep #3 Customer #8
Customer #9
Customer #10
Salesrep #4 Customer #11
Customer #12
Customer #13
Salesrep #5 Customer #14
Customer #15
© 2014 Time2Master 47
Sub Select Collections Hibernate:
select
salesrep0_.id as id1_,
Keeps track of the query used salesrep0_.name as name1_
from
to retrieve the salesreps
SalesRep salesrep0_
where
salesrep0_.id<1000
List<SalesRep> salesreps = session.createQuery(Hibernate:
"from SalesRep where id < 1000").list(); select
Set<Customer> customers = null; customers0_.salesRep_id as salesRep4_1_,
for (SalesRep s : salesreps) { customers0_.id as id1_,
customers = s.getCustomers(); customers0_.id as id0_0_,
for (Customer c : customers) { customers0_.firstname as firstname0_0_,
// do something with the customer customers0_.lastname as lastname0_0_,
} customers0_.salesRep_id as salesRep4_0_0_
} Sub-Select eager fetching from
Customer customers0_
only works for collections
where
customers0_.salesRep_id in (
select
@Entity salesrep0_.id Re-uses that
public class SalesRep { from query as a
@Id SalesRep salesrep0_
sub select to
@GeneratedValue where
private int id; salesrep0_.id<1000 get the
private String name; ) customer
collections
@OneToMany(mappedBy="salesRep", cascade=CascadeType.PERSIST) for those
@org.hibernate.annotations.Fetch( salesreps
org.hibernate.annotations.FetchMode.SUBSELECT FetchMode.SUBSELECT
)
private Set<Customer> customers = new HashSet<Customer>(); © 2014 Time2Master 48
Sub Select – XML Hibernate:
select
salesrep0_.id as id1_,
Keeps track of the query used salesrep0_.name as name1_
from
to retrieve the salesreps
SalesRep salesrep0_
where
salesrep0_.id<1000
List<SalesRep> salesreps = session.createQuery(Hibernate:
"from SalesRep where id < 1000").list(); select
Set<Customer> customers = null; customers0_.salesRep_id as salesRep4_1_,
for (SalesRep s : salesreps) { customers0_.id as id1_,
customers = s.getCustomers(); customers0_.id as id0_0_,
for (Customer c : customers) { customers0_.firstname as firstname0_0_,
// do something with the customer customers0_.lastname as lastname0_0_,
} customers0_.salesRep_id as salesRep4_0_0_
} Sub-Select eager fetching from
Customer customers0_
only works for collections
where
customers0_.salesRep_id in (
<hibernate-mapping package="how.always.subselect"> select
<class name="SalesRep"> salesrep0_.id Re-uses that
<id name="id"> from query as a
<generator class="native" /> SalesRep salesrep0_
</id> sub select to
Fetch=“subselect” where
<property name="name" /> salesrep0_.id<1000 get the
<set name="customers" fetch="subselect" ) customer
inverse="true" cascade="persist"> collections
<key column="salesRep" /> for those
<one-to-many class="Customer" />
salesreps
</set>
</class>
</hibernate-mapping>
© 2014 Time2Master 49
Sub Select
The Sub Select strategy solves the N + 1
problem by turning it into a 1 + 1
Only available for collections, not references
May retrieve too much data if you did not actually
need to work with the collections
Like batch fetching, no joins, no Cartesian Product
© 2014 Time2Master 50
Hibernate Optimization:
© 2014 Time2Master 51
2nd Level Caching
By default Hibernate only Session
Cache
uses Session Caches DataBase
© 2014 Time2Master 52
Caching and Optimization
2nd Level caching should never be used as an
alternative to fetch optimizations
Can not solve problems, can attempt to hide them
Should be used to help scale the application
© 2014 Time2Master 53
What to Cache
Hibernate can cache entity objects and
collections (collections of entity IDs)
But not all of them will benefit from being cached
© 2014 Time2Master 54
Caching Strategies
Four different caching strategies:
Read Only: very fast caching strategy, but can only
Stricter and therefore Slower
© 2014 Time2Master 55
Cache Providers
The following open source cache providers are
bundled with Hibernate
Only a single cache provider per SessionFactory
Provider Read Only Non Strict Read Write Read Write Transactional
EHCache
OSCache
SwarmCache
JBoss Cache 1.x
JBoss Cache 2.x
© 2014 Time2Master 56
Caching Example – Entities
Customer
Category SalesRep
1 * +id * 1
+id +firstname +id
+Description contains +lastname Mananged by +name
© 2014 Time2Master 57
Caching Example – Collections
Customer
Category SalesRep
1 * +id * 1
+id +firstname +id
+Description contains +lastname Mananged by +name
© 2014 Time2Master 58
Category
Mutable=false indicates to Hibernate
@Entity
@org.hibernate.annotations.Entity(mutable=false)
that Categories can never change
@org.hibernate.annotations.Cache(usage=
CacheConcurrencyStrategy.READ_ONLY Specify read only caching
) for Category Entities
public class Category {
@Id
private String abbreviation;
private String description;
Specify read write caching
@OneToMany(mappedBy="category") for the collection of
@org.hibernate.annotations.Cache(usage=
CacheConcurrencyStrategy.READ_WRITE
customers each category has
)
private Set<Customer> customers = new HashSet<Customer>();
...
Mutable=false insinde <class> tag
<hibernate-mapping package="cacheDemo">
<class name="Category" mutable="false"> XML uses <cache> tag inside <class>
<cache usage="read-only" />
to specify category entity caching
<id name="abbreviation" />
<property name="description" />
<set name="customers" inverse="true" cascade="persist">
<cache usage="read-write" />
<key column="salesRep" /> <cache> tag inside <set> for
<one-to-many class="Customer" /> the customers collection
</set>
</class>
</hibernate-mapping>
© 2014 Time2Master 59
SalesRep
@Entity Specify non strict read write
@org.hibernate.annotations.Cache(usage=
CacheConcurrencyStrategy.NONSTRICT_READ_WRITE
caching for SalesRep Entities
)
public class SalesRep {
@Id
@GeneratedValue
private int id;
private String name;
@OneToMany(mappedBy="salesRep", cascade=CascadeType.PERSIST)
private Set<Customer> customers = new HashSet<Customer>();
...
© 2014 Time2Master 60
Enabling Caching (EHCache)
<hibernate-configuration>
<session-factory>
<!-- HSQL DB running on localhost -->
<property name="connection.url">jdbc:hsqldb:hsql://localhost/trainingdb</property>
<property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
<property name="connection.username">sa</property>
<property name="connection.password"></property>
<property name="dialect">org.hibernate.dialect.HSQLDialect</property>
<!-- Hibernate XML mapping files - Cache --> Optionally enable statistics
<mapping resource="cacheDemo/Customer.hbm.xml" />
<mapping resource="cacheDemo/SalesRep.hbm.xml" />
<mapping resource="cacheDemo/Category.hbm.xml" />
</session-factory>
</hibernate-configuration>
© 2014 Time2Master 61
Configuring EHCache – Cache Eviction
<ehcache>
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
EHCache General configuration
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true" />
<cache name="cacheDemo.Category"
maxElementsInMemory="50"
eternal=“true" Sets up a cache region for category entities
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false" />
<cache name="cacheDemo.Category.customers"
maxElementsInMemory="50" Sets up a cache region for the customer
eternal="false" collections inside the category entities
timeToIdleSeconds="3600"
timeToLiveSeconds="7200"
overflowToDisk="false" />
<cache name="cacheDemo.SalesRep"
maxElementsInMemory="500"
eternal="false" cache region for the SalesRep entities
timeToIdleSeconds="1800"
timeToLiveSeconds="10800"
overflowToDisk="false" />
</ehcache>
© 2014 Time2Master 62
Hibernate Statistics
Stats object also holds
general statistics for many
Hibernate subsystems
General 2nd level cache statistics
Statistics stats = sessionFactory.getStatistics();
long hits = stats.getSecondLevelCacheHitCount();
long misses = stats.getSecondLevelCacheMissCount();
long puts = stats.getSecondLevelCachePutCount();
System.out.printf("\nGeneral 2nd Level Cache Stats\n");
System.out.printf("Hit: %d Miss: %d Put: %d\n", hits, misses, puts);
SecondLevelCacheStatistics salesRepStats =
stats.getSecondLevelCacheStatistics("cacheDemo.SalesRep"); cache statistics for a
long srCurrent = salesRepStats.getElementCountInMemory(); specific cache region
long srMemsize = salesRepStats.getSizeInMemory();
long srHits = salesRepStats.getHitCount();
long srMisses = salesRepStats.getMissCount();
long srPuts = salesRepStats.getPutCount();
System.out.printf("\nSalesRep Cache Region - Size: %d Holds: %d\n", srMemsize, srCurrent);
System.out.printf("Hit: %d Miss: %d Put: %d\n", srHits, srMisses, srPuts);
WRAPPING UP
© 2014 Time2Master 64
Analyze SQL
Before changing any fetching strategies
Analyze the SQL Hibernate uses for all use cases
Look for things that can actually cause problems
Don’t over optimize, only update real problem areas
...
</session-factory>
</hibernate-configuration>
© 2014 Time2Master 65
Optimization Steps
1. Update Fetching Strategies by:
a) Analyzing SQL used by Hibernate
b) Updating a Fetching Strategy
c) Checking SQL to ensure effectiveness
2. Enable Second Level Caching
Fine-tuning the cache settings in production
© 2014 Time2Master 66
Active Learning
Describe the difference between batch
fetching and sub select optimization.
© 2014 Time2Master 67
Module Summary
Data Access Optimization changes when and
how Hibernate retrieves data
Hibernate mostly defaults to lazy loading
Lazy loading can lead to too many small selects
Incorrect eager loading can lead to slow queries
2nd level caching should not be used as an
alternative to fetch optimizations
Caching can help boost performance under load
Incorrectly configured cache can create problems
© 2014 Time2Master 68