Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

Lazy vs. Eager Loading Strategies in JPA 2.1

Java Day Kiev 2015

  • Identifiez-vous pour voir les commentaires

Lazy vs. Eager Loading Strategies in JPA 2.1

  1. 1. Lazy vs. Eager Loading Strategies for JPA 2.1 Patrycja Wegrzynowicz Java Day Kiev 2015
  2. 2. About Me •  15+ professional experience –  SoGware engineer, architect, head of soGware R&D •  Author and speaker –  JavaOne, Devoxx, JavaZone, TheServerSide Java Symposium, Jazoon, OOPSLA, ASE, others •  Finalizing PhD in Computer Science •  Founder and CTO of Yonita –  Bridge the gap between the industry and the academia –  Automated detecUon and refactoring of soGware defects –  Security, performance, concurrency, databases •  TwiVer: @yonlabs
  3. 3. Outline •  MoUvaUon •  Why? –  Expect unexpected! •  What? –  Use cases and corner cases –  Hints on strategies •  How? –  JPA 2.1 •  Conclusion
  4. 4. Database
  5. 5. Database The Mordor of Java Devs
  6. 6. Hibernate JPA Provider Heads of Hydra @Entity public class Hydra { private Long id; private List<Head> heads = new ArrayList<Head>(); @Id @GeneratedValue public Long getId() {...} protected void setId() {...} @OneToMany(cascade=CascadeType.ALL) public List<Head> getHeads() { return Collections.unmodifiableList(heads); } protected void setHeads() {...} } // new EntityManager and new transaction: creates and persists the hydra with 3 heads // new EntityManager and new transaction Hydra found = em.find(Hydra.class, hydra.getId());
  7. 7. How Many Queries in 2nd Tx? @Entity public class Hydra { private Long id; private List<Head> heads = new ArrayList<Head>(); @Id @GeneratedValue public Long getId() {...} protected void setId() {...} @OneToMany(cascade=CascadeType.ALL) public List<Head> getHeads() { return Collections.unmodifiableList(heads); } protected void setHeads() {...} } // new EntityManager and new transaction: creates and persists the hydra with 3 heads // new EntityManager and new transaction Hydra found = em.find(Hydra.class, hydra.getId()); (a) 1 select (b) 2 selects (c) 1+3 selects (d) 2 selects, 1 delete, 3 inserts (e) None of the above
  8. 8. How Many Queries in 2nd Tx? (a) 1 select (b) 2 selects (c) 1+3 selects (d) 2 selects, 1 delete, 3 inserts (e) None of the above During commit hibernate checks whether the collection property is dirty (needs to be re-created) by comparing Java identities (object references).
  9. 9. Another Look @Entity public class Hydra { private Long id; private List<Head> heads = new ArrayList<Head>(); @Id @GeneratedValue public Long getId() {...} protected void setId() {...} @OneToMany(cascade=CascadeType.ALL) public List<Head> getHeads() { return Collections.unmodifiableList(heads); } protected void setHeads() {...} } // new EntityManager and new transaction: creates and persists the hydra with 3 heads // new EntityManager and new transaction // during find only 1 select (hydra) Hydra found = em.find(Hydra.class, hydra.getId()); // during commit 1 select (heads),1 delete (heads),3 inserts (heads)
  10. 10. Lessons Learned • Expect unexpected ;-) • Prefer field access mappings • Operate on collection objects returned by hibernate –Don’t change collection references unless you know what you’re doing
  11. 11. Lessons Learned • Expect unexpected ;-) • Prefer field access mappings • Operate on collection objects returned by hibernate –Don’t change collection references unless you know what you’re doing List<Head> newHeads = new List<>(hydra.getHeads()); Hydra.setHeads(newHeads);
  12. 12. Other Providers? • EcpliseLink – 1 select • Datanucleus – 1 select • „A Performance Comparison of JPA Providers”
  13. 13. Lessons Learned • A lot of depends on a JPA Provider! • JPA is a spec – A great spec, but only a spec – It says what to implement, not how to implement • You need to tune an application in a concrete environment
  14. 14. I do love JPA!
  15. 15. I do love JPA! But as in every relationship we have our ups and downs.
  16. 16. My Dear JPA and Its Providers 
  17. 17. My Dear JPA and Its Providers 
  18. 18. My Dear JPA and Its Providers 
  19. 19. LisUng and ReporUng
  20. 20. LisUng and ReporUng AnU-PaVerns •  Different contexts •  Direct usage of an object-oriented domain model •  Too much data loaded •  Heavy processing on the Java side
  21. 21. ReporUng AnU-PaVerns Example
  22. 22. ReporUng AnU-PaVerns Employee EnUty @Entity public class Employee { @Id @GeneratedValue private Long id; private String firstName; private String lastName; private BigDecimal salary; private BigDecimal bonus; @Temporal(TemporalType.DATE) private Date startDate; @Temporal(TemporalType.DATE) private Date endDate; @ManyToOne @JoinColumn(name = "manager_id") private Employee manager; @OneToOne @JoinColumn(name = "address_id") private Address address; private String country; @OneToMany(mappedBy = "owner") private Collection<Phone> phones; @ManyToMany(mappedBy = "employees”) private Collection<Project> projects; … }
  23. 23. Sum of Salaries By Country Select All (1) TypedQuery<Employee> query = em.createQuery( "SELECT e FROM Employee e", Employee.class); List<Employee> list = query.getResultList(); // calculate sum of salaries by country // map: country->sum Map<String, BigDecimal> results = new HashMap<>(); for (Employee e : list) { String country = e.getAddress().getCountry(); BigDecimal total = results.get(country); if (total == null) total = BigDecimal.ZERO; total = total.add(e.getSalary()); results.put(country, total); }
  24. 24. Sum of Salaries by Country Select Join Fetch (2) TypedQuery<Employee> query = em.createQuery( "SELECT e FROM Employee e JOIN FETCH e.address", Employee.class); List<Employee> list = query.getResultList(); // calculate sum of salaries by country // map: country->sum Map<String, BigDecimal> results = new HashMap<>(); for (Employee e : list) { String country = e.getAddress().getCountry(); BigDecimal total = results.get(country); if (total == null) total = BigDecimal.ZERO; total = total.add(e.getSalary()); results.put(country, total); }
  25. 25. ReporUng AnU-PaVerns ProjecUon (3) Query query = em.createQuery( "SELECT e.salary, e.address.country FROM Employee e”); List<Object[]> list = query.getResultList(); // calculate sum of salaries by country // map: country->sum Map<String, BigDecimal> results = new HashMap<>(); for (Object[] e : list) { String country = (String) e[1]; BigDecimal total = results.get(country); if (total == null) total = BigDecimal.ZERO; total = total.add((BigDecimal) e[0]); results.put(country, total); }
  26. 26. ReporUng AnU-PaVerns AggregaUon JPQL (4) Query query = em.createQuery( "SELECT SUM(e.salary), e.address.country FROM Employee e GROUP BY e.address.country”); List<Object[]> list = query.getResultList(); // already calculated!
  27. 27. ReporUng AnU-PaVerns AggregaUon SQL (5) Query query = em.createNativeQuery( "SELECT SUM(e.salary), a.country FROM employee e JOIN address a ON e.address_id = a.id GROUP BY a.country"); List list = query.getResultList(); // already calculated!
  28. 28. Comparison 1-5 100 000 employees, EclipseLink MySQL PostgreSQL (1) Select all (N+1) 25704ms 18120ms (2) Select join fetch 6211ms 3954ms (3) ProjecUon 533ms 569ms (4) Aggreg. JPQL 410ms 380ms (5) Aggreg. SQL 380ms 409ms
  29. 29. ProjecUon JPQL -> Value Object Query query = em.createQuery( "SELECT new com.yonita.jpa.vo.EmployeeVO( e.salary, e.address.country) FROM Employee e”); // List<EmployeeVO> List list = query.getResultList();
  30. 30. ProjecUon JPQL -> Value Object Query query = em.createQuery( "SELECT new com.yonita.jpa.CountryStatVO( sum(e.salary), e.address.country) FROM Employee e GROUP BY e.address.country"”); // List<CountryStatVO> List list = query.getResultList();
  31. 31. ProjecUon SQL -> Value Object @SqlResultSetMapping( name = "countryStatVO", classes = { @ConstructorResult( targetClass = CountryStatVO.class, columns = { @ColumnResult(name = "ssum", type = BigDecimal.class), @ColumnResult(name = "country", type = String.class) }) })
  32. 32. ProjecUon SQL -> Value Object Query query = em.createNativeQuery( "SELECT SUM(e.salary), a.country FROM employee e JOIN address a ON e.address_id = a.id GROUP BY a.country", "countryStatVO"); // List<CountryStatVO> List list = query.getResultList();
  33. 33. ProjecUon Wrap-up •  JPA 2.0 –  Only JPQL query to directly produce a value object! •  JPA 2.1 –  JPQL and naUve queries to directly produce a value object! •  Managed object –  Sync with database –  L1/L2 cache •  Use cases for Direct Value Object –  ReporUng, staUsUcs, history –  Read-only data, GUI data –  Performance: •  No need for managed objects •  Rich (or fat) managed objects •  Subset of aVributes required •  Gain speed •  Offload an app server
  34. 34. AggregaUon Wrap-up •  JPA 2.0 –  Selected aggregaUon funcUons: COUNT, SUM, AVG, MIN, MAX •  JPA 2.1 –  All funcUon as supported by a database –  Call any database funcUon with new FUNCTION keyword •  Database-specific aggregate funcUons –  MS SQL: STDEV, STDEVP, VAR, VARP,… –  MySQL: BIT_AND, BIT_OR, BIT_XOR,… –  Oracle: MEDIAN, PERCENTILE,… –  More… •  Use cases –  ReporUng, staUsUcs –  Performance •  Gain speed •  Offload an app server to a database!
  35. 35. Loading Strategy: EAGER for sure! • We know what we want – Known range of required data in a future execution path • We want a little – A relatively small entity, no need to divide it into tiny pieces
  36. 36. Loading strategy: Usually Better EAGER! • Network latency to a database – Lower number of round-trips to a database with EAGER loading
  37. 37. Loading Strategy: LAZY for sure! • We don’t know what we want – Load only required data – „I’ll think about that tomorrow” • We want a lot – Divide and conquer – Load what’s needed in the first place
  38. 38. Large Objects • Lazy Property Fetching • @Basic(fetch = FetchType.LAZY) • Recommended usage – Blobs – Clobs – Formulas • Remember about byte-code instrumentation, – Otherwise will not work – Silently ignores
  39. 39. Large Objects • Lazy Property Fetching • @Basic(fetch = FetchType.LAZY) • Recommended usage – Blobs – Clobs – Formulas • Remember about byte-code instrumentation, – Otherwise will not work – Silently ignores
  40. 40. Large Objects • Something smells here • Do you really need them?
  41. 41. Large Objects • Something smells here • Do you really need them? • But do you really need them?
  42. 42. Large Objects • Something smells here • Do you really need them? • But do you really need them? • Ponder on your object model and use cases, otherwise it’s not gonna work
  43. 43. Large Collections • Divide and conquer! • Definitely lazy • You don’t want a really large collection in the memory • Batch size – JPA Provider specific configuration
  44. 44. Hibernate: Plant a Tree @Entity public class Forest { @Id @GeneratedValue private Long id; @OneToMany private Collection<Tree> trees = new HashSet<Tree>(); public void plantTree(Tree tree) { return trees.add(tree); } } // new EntityManager and new transaction: creates and persists a forest with 10.000 trees // new EntityManager and new transaction Tree tree = new Tree(“oak”); em.persist(tree); Forest forest = em.find(Forest.class, id); forest.plantTree(tree);
  45. 45. How Many Queries in 2nd Tx? @Entity public class Forest { @Id @GeneratedValue private Long id; @OneToMany private Collection<Tree> trees = new HashSet<Tree>(); public void plantTree(Tree tree) { return trees.add(tree); } } // new EntityManager and new transaction: creates and persists a forest with 10.000 trees // new EntityManager and new transaction Tree tree = new Tree(“oak”); em.persist(tree); Forest forest = em.find(Forest.class, id); forest.plantTree(tree); (a) 1 select, 2 inserts (b) 2 selects, 2 inserts (c) 2 selects, 1 delete, 10.000+2 inserts (d) 2 selects, 10.000 deletes, 10.000+2 inserts (e) Even more ;-)
  46. 46. How Many Queries in 2nd Tx? (a) 1 select, 2 inserts (b) 2 selects, 2 inserts (c) 2 selects, 1 delete, 10.000+2 inserts (d) 2 selects, 10.000 deletes, 10.000+2 inserts (e) Even more ;-) The combination of OneToMany and Collection enables a bag semantic. That’s why the collection is re-created.
  47. 47. Plant a Tree Revisited @Entity public class Orchard { @Id @GeneratedValue private Long id; @OneToMany private List<Tree> trees = new ArrayList<Tree>(); public void plantTree(Tree tree) { return trees.add(tree); } } // creates and persists a forest with 10.000 trees // new EntityManager and new transaction Tree tree = new Tree(“apple tree”); em.persist(tree); Orchard orchard = em.find(Orchard.class, id); orchard.plantTree(tree); STILL BAG SEMANTIC Use OrderColumn or IndexColumn for list semantic.
  48. 48. Plant a Tree @Entity public class Forest { @Id @GeneratedValue private Long id; @OneToMany private Set<Tree> trees = new HashSet<Tree>(); public void plantTree(Tree tree) { return trees.add(tree); } } // new EntityManager and new transaction: creates and persists a forest with 10.000 trees // new EntityManager and new transaction Tree tree = new Tree(“oak”); em.persist(tree); Forest forest = em.find(Forest.class, id); forest.plantTree(tree); 1. Collection elements loaded into memory 2. Possibly unnecessary queries 3. Transaction and locking schema problems: version, optimistic locking
  49. 49. Plant a Tree @Entity public class Forest { @Id @GeneratedValue private Long id; @OneToMany(mappedBy = „forest”) private Set<Tree> trees = new HashSet<Tree>(); public void plantTree(Tree tree) { return trees.add(tree); } } @Entity public class Tree { @Id @GeneratedValue private Long id; private String name; @ManyToOne private Forest forest; public void setForest(Forest forest) { this.forest = forest; Forest.plantTree(this); } } Set semantic on the inverse side forces of loading all trees.
  50. 50. Other Providers? • EclipseLink – 2 selects/2 inserts • OpenJPA • 3 selects/1 update/2inserts • Datanucleus • 3 selects/1 update/2inserts
  51. 51. Loading strategy: It depends! • You know what you want – But it’s dynamic, depending on an execution path and its parameters
  52. 52. Loading strategy: It depends! • You know what you want – But it’s dynamic, depending on runtime parameters • That was the problem in JPA 2.0 – Fetch queries – Provider specific extensions – Different mappings for different cases • JPA 2.1 comes in handy
  53. 53. Entity Graphs in JPA 2.1 • „A template that captures the paths and boundaries for an operation or query” • Fetch plans for query or find operations • Defined by annotations • Created programmatically
  54. 54. Entity Graphs in JPA 2.1 • Defined by annotations – @NamedEntityGraph, @NamedEntitySubgraph, @NamedAttributeNode • Created programmatically – Interfaces EntityGraph, EntitySubgraph, AttributeNode
  55. 55. Entity Graphs in Query or Find • Default fetch graph – Transitive closure of all its attributes specified or defaulted as EAGER • javax.persistence.fetchgraph – Attributes specified by attribute nodes are EAGER, others are LAZY • javax.persistence.loadgraph – Attributes specified by by attribute nodes are EAGER, others as specified or defaulted
  56. 56. Entity Graphs in Query or Find • Default fetch graph – Transitive closure of all its attributes specified or defaulted as EAGER • javax.persistence.fetchgraph – Attributes specified by attribute nodes are EAGER, others are LAZY • javax.persistence.loadgraph – Attributes specified by by attribute nodes are EAGER, others as specified or defaulted
  57. 57. Entity Graphs in Query or Find • Default fetch graph – Transitive closure of all its attributes specified or defaulted as EAGER • javax.persistence.fetchgraph – Attributes specified by attribute nodes are EAGER, others are LAZY • javax.persistence.loadgraph – Attributes specified by by attribute nodes are EAGER, others as specified or defaulted
  58. 58. Entity Graphs Advantages • Better hints to JPA providers • Hibernate now generates smarter queries – 1 select with joins on 3 tables – 1 round-trip to a database instead of default N+1 • Dynamic modification of a fetch plan
  59. 59. There is that question...
  60. 60. JPA 2.1
  61. 61. Wrap-up •  Main use cases –  LisUng and reporUng •  JPA –  EnUty graphs (JPA 2.1) –  ProjecUons (JPA 2.0/2.1) •  Performance –  Don’t load if you don’t need –  Don’t execute many small queries if you can execute one big query –  Don’t calculate if a database can •  Tuning –  Tune in your concrete environment –  JPA Providers behave differently! –  Databases behave differently!
  62. 62. ConUnuous IntegraUon
  63. 63. ConUnuous Refactoring
  64. 64. ConUnuous Learning!
  65. 65. ConUnuous Learning Paradigm •  A fool with a tool is sUll a fool •  Let’s educate ourselves! J
  66. 66. Q&A patrycja@yonita.com @yonlabs

×