0% found this document useful (0 votes)
3 views68 pages

Module 09 - Hibernate Optimization-upd (1)

Uploaded by

surafelbehailu90
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views68 pages

Module 09 - Hibernate Optimization-upd (1)

Uploaded by

surafelbehailu90
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 68

Module 09: Hibernate

Optimization
CS544: Enterprise Architecture

© 2014 Time2Master 1
Data Access Optimization
 By changing when and How Hibernate retrieves
data you can optimize its performance

 By default Hibernate tries to not load data that


you might not need – mostly uses lazy loading
 This gives good baseline performance
 Is not always the most optimal strategy

© 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

 Complex queries that use many joins


 May be more efficient to use several simple queries
 Cartesian Product problem is an example of this
 Caused by incorrect (over) optimization

© 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

Select * from Salesrep


Customer #1 Salesrep #1
where id = 1

Select * from Salesrep


Customer #2 Salesrep #2
where id = 2

Select * from Salesrep


Customer #3 Salesrep #3
where id = 3

Select * from Salesrep


Customer #4 Salesrep #4
where id = 4

Select * from Salesrep


Customer #5 Salesrep #5
where id = 5

© 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");

cust1.addBook(new Book("Harry Potter and the Deathly Hallows"));


cust1.addBook(new Book("Unseen Academicals (Discworld)"));
cust1.addBook(new Book("The Color of Magic (Discworld)"));
cust1.addMovie(new Movie("Shrek")); First customer has 3 books and 3 movies,
cust1.addMovie(new Movie("WALL-E"));
second customer has a single book, third
cust1.addMovie(new Movie("Howls Moving Castle"));
customer has a single movie
cust2.addBook(new Book("Twilight (The Twilight Saga, Book1)"));

cust3.addMovie(new Movie("Forgetting Sarah Marshall"));

Retrieve customers, and also try to


(eager) fetch the book and movie
collections for the customers
Hibernate:
select
customer0_.firstname as firstname0_0_,
customer0_.lastname as lastname0_0_,
books1_.title as title1_1_,
movies2_.title as title2_2_
from
Customer customer0_
left outer join
Book books1_
on customer0_.id=books1_.customer_id Outer Joining two or Row count per customers =
left outer join more collections creates related books * related movies
Movie movies2_
on customer0_.id=movies2_.customer_id
many redundant rows
© 2014 Time2Master 10
Cartesian Product
 Joining two collections creates: R x N x M
 Creating a very in-efficient resultset
Frank Brown Discworld Pixar

Frank Brown Discworld Dream Works

Frank Brown Discworld Studio Ghibli


9 rows, 3 columns to
Frank Brown Harry Potter Pixar give 7 pieces of data

Frank Brown Harry Potter Dream Works

Frank Brown Harry Potter Studio Ghibli

Frank Brown Twilight Pixar

Frank Brown Twilight Dream Works

Frank Brown Twilight Studio Ghibli


© 2014 Time2Master 11
Hibernate Optimization:

STRATEGY: LAZY LOADING

© 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

 <one-to-one> defaults to eager loading


 <many-to-one> defaults to lazy loading
public class Customer { Customer cust1 = (Customer)
private int id; session.get(Customer.class, 1);
private String firstname; Eagerly loads <one-to-one>
private String lastname; Hibernate: using a left outer join
private Address address; select
private SalesRep salesRep; customer0_.id as id0_1_,
customer0_.firstname as firstname0_1_,
... customer0_.lastname as lastname0_1_,
customer0_.salesRep as salesRep0_1_,
<hibernate-mapping package="when.objRefs"> address1_.id as id1_0_,
<class name="Customer"> address1_.street as street1_0_,
<id name="id"> address1_.apt as apt1_0_,
<generator class="native" /> address1_.city as city1_0_,
</id> address1_.state as state1_0_,
<property name="firstname" /> address1_.zip as zip1_0_,
<property name="lastname" /> address1_.customer as customer1_0_
<one-to-one name="address" cascade="persist" /> from
<many-to-one name="salesRep" /> Customer customer0_
</class> left outer join
</hibernate-mapping> Address address1_
on customer0_.id=address1_.id
where
© 2014 Time2Master customer0_.id=? 16
Collections
 By default the entire collection is retrieved
when .size(), .isEmpty(), or .contains() is used
 Good for small collections, bad for large collections
Customer with
a collection of
@Entity
Credit Cards
public class Customer {
@Id Retrieves all
@GeneratedValue
credit cards
private int id; Hibernate:
private String firstname; select
private String lastname; creditcard0_.customer_id as customer5_1_,
creditcard0_.id as id1_,
@OneToMany(mappedBy = "customer", creditcard0_.id as id1_0_,
cascade = CascadeType.PERSIST) creditcard0_.customer_id as
private Set<CreditCard> creditCards customer5_1_0_,
= new HashSet<CreditCard>(); creditcard0_.expiration as expiration1_0_,
creditcard0_.name as name1_0_,
... Check credit card creditcard0_.number as number1_0_
collection size from
CreditCard creditcard0_
customer.getCreditCards().size(); where
creditcard0_.customer_id=?
© 2014 Time2Master 17
Extra Lazy Collections
 Setting the collection to Extra Lazy solves this
problem for large collections
<hibernate-mapping package="entities">
<class name="Customer">
<id name="id">
<generator class="native" />
Extra Lazy Collection </id> Extra Lazy Collection
using annotations <property name="firstname" /> using XML mapping
<property name="lastname" />
@Entity <set name="creditCards" lazy="extra"
public class Customer { inverse="true" cascade="persist">
@Id <key column="customer" />
@GeneratedValue <one-to-many class="CreditCard" />
private int id; </set>
private String firstname; </class>
private String lastname; </hibernate-mapping>

@OneToMany(mappedBy = "customer", cascade = CascadeType.PERSIST)


@org.hibernate.annotations.LazyCollection( Only retrieves size
org.hibernate.annotations.LazyCollectionOption.EXTRA
Hibernate:
)
select
private Set<CreditCard> creditCards = new HashSet<CreditCard>();
count(id)
from
...
CreditCard
where
customer.getCreditCards().size(); customer_id =?

© 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>

<target name="instrument" depends="compile">


<taskdef name="instrument"
classname="org.hibernate.tool.instrument.cglib.InstrumentTask">
<classpath>
<fileset dir="c:/hibernatetraining/libraries/"> Requires the hibernate libraries
<include name="**/*.jar" />
</fileset>
</classpath>
</taskdef>
<instrument verbose="true">
<fileset dir="${build}/when/properties/"> Location of the files that
<include name="**/*.class" /> need to be instrumented
</fileset>
</instrument>
</target>
</project>

© 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

 Solution: fetch data more eagerly


 To which strategy you switch should depend on
the circumstance surrounding the association

© 2014 Time2Master 22
Hibernate Optimization:

STRATEGY: EAGER JOIN

© 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

 Similar but different:


 Eager specifies that a relation has to be loaded right
away if it hasn’t been loaded already
 Join Fetching retrieves a related entity or collection at
the same time using a SQL Join
 Joins are always eager, eager is not always with joins
 Will complain when multiple collections are eager

© 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

 Eager mapping specifies when not what or how


 After the query has executed, hibernate will fulfill
the eager specification using extra selects
 In other words, a guaranteed N+1 for all eager
associations not yet retrieved by the query
 Both for references and collections

© 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:

STRATEGY: JOIN FETCH QUERY

© 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

 Like Eager Joining, join fetch queries use SQL


joins to pre-cache additional data
 Extra data is not returned as part of the result set

© 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:

STRATEGY: BATCH FETCHING

© 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

Select * from Salesrep


Customer #1 Salesrep #1
where id in (1, 2, 3)

Customer #2 Salesrep #2

Customer #3 Salesrep #3

Select * from Salesrep


Customer #4 Salesrep #4
where id in (4, 5)

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

 Typical batch sizes are between 3 and 15

 Batch fetching reduces the N + 1 problem to


→ Ceil(N / Batch Size) + 1

© 2014 Time2Master 44
Hibernate Optimization:

STRATEGY: SUB SELECT

© 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

 Sub select is not available for references

© 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

 Internally Sub Select keeps track of the query


used to retrieve the original objects

© 2014 Time2Master 50
Hibernate Optimization:

2ND LEVEL CACHING

© 2014 Time2Master 51
2nd Level Caching
 By default Hibernate only Session
Cache
uses Session Caches DataBase

 Objects are cached for the


duration of the session

 You can enable a second


level cache Session SessionFactory
Cache 2nd Level Cache DataBase
 Lasts for the duration of
the SessionFactory
 Shared by all sessions

© 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

 Caching is a large and complex field


 We will cover Hibernates basic caching features
 Improper configuration can create problems that
are difficult to debug

© 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

 Good candidates for caching :


 Do not change, or change rarely
 Are modified only by your application
 Are non-critical to the application

 Typical examples include reference data


 Such as customer categories, or statuses

© 2014 Time2Master 54
Caching Strategies
 Four different caching strategies:
 Read Only: very fast caching strategy, but can only
Stricter and therefore Slower

be used for data that never changes


 Non Strict Read-Write: data may be stale for a
while, but it does get refreshed at timeout
 Read-Write: prevents stale data, but at a cost. Use
for read-mostly data in a non-clustered setup
 Transactional: Can prevent stale data in a clustered
environment. Can be used for read-mostly data

© 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

 Category entities – Read Only


 Typical reference data, categories are never updated

 SalesRep entities – Non Strict Read Write


 Not many Salesreps, always needed when editing customers
 SalesReps seldom change, stale SalesRep records are fine

 Customer entities – Not Cached


 Too many customers, customer are updated frequently

© 2014 Time2Master 57
Caching Example – Collections
Customer
Category SalesRep
1 * +id * 1
+id +firstname +id
+Description contains +lastname Mananged by +name

 Customer Collection for each category – Read Write


 Often used, try to avoid stale data as much as possible

 Customer Collection for each SalesRep – Not Cached


 Not used frequently enough to warrant caching

© 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>();

...

XML uses the <cache> tag


<hibernate-mapping package="cacheDemo">
<class name="SalesRep">
inside the <class> tag
<cache usage="nonstrict-read-write" />
<id name="id">
<generator class="native" />
</id>
<property name="name" />
<set name="customers" inverse="true" cascade="persist">
<key column="salesRep" />
<one-to-many class="Customer" />
</set>
</class>
</hibernate-mapping>

© 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>

<!-- Enable Second Level Cache -->


<property name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property>

<!-- Enable Statistics --> Enable 2nd level caching by


<property name="generate_statistics">true</property> specifying a caching provider

<!-- 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);

Statistics stats = sessionFactory.getStatistics();


Stats.clear();
stats.setStatisticsEnabled(true); Statistics can also be enabled
or disabled programmatically
...
allowing you to do more
stats.setStatisticsEnabled(false); targeted measurements
© 2014 Time2Master 63
Hibernate Optimization:

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

 Then after each change check the SQL again


<hibernate-configuration>
<session-factory> The following three property can
be used to check Hibernates SQL
<property name="show_sql">true</property>
<property name="format_sql">true</property>
<property name="use_sql_comments">true</property>

...

</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.

 Why doesn’t second level caching fix bad


fetching strategies?

© 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

You might also like