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

Thinking Beyond ORM in JPA

2,029 views

Published on

JavaOne 2015, JPA, stored procedures, projections, aggregation, JPQL vs. SQL

Published in: Software
  • Be the first to comment

Thinking Beyond ORM in JPA

  1. 1. Thinking Beyond ORM in JPA Patrycja Wegrzynowicz JavaOne 2015
  2. 2. About Me • 15+ professional experience – Software engineer, architect, head of software 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 detection and refactoring of software defects – Security, performance, concurrency, databases • @yonlabs
  3. 3. Outline • Motivation • Why? – App-centric vs. data-centric • What? – Use cases and performance • How? – JPA (2.1) • Conclusion
  4. 4. Database
  5. 5. Database The Mordor of Java Devs
  6. 6. App-Centric vs. Data-Centric • App-centric – Java code drives database design – One app accesses data – CRUD more often than complex queries • Data-centric – Database design drives Java code – Several apps access data – CRUD as often as complex queries
  7. 7. Use Cases
  8. 8. Performance
  9. 9. Use Cases and Performance
  10. 10. Legacy Systems
  11. 11. Several Apps -> One Source
  12. 12. Database-Level Abstraction • Views • Stored procedures • Triggers
  13. 13. Stored Procedures in JPA • 2.0 and before – Native queries to call stored procedures – No OUT/INOUT parameters – Database dependent CALL syntax • 2.1 – EntityManager.createStoredProcedureQuery – @NamedStoredProcedureQuery
  14. 14. Example: Stored Procedure Result Set -- MY SQL CREATE PROCEDURE GET_EMPLOYEES() BEGIN SELECT * FROM EMPLOYEES; END
  15. 15. Example: Stored Procedure EntityManager API // create and setup a stored procedure query StoredProcedureQuery q = em.createStoredProcedureQuery( "GET_EMPLOYEES", Employee.class); // gather the results (an implicit call to execute) List<Employee> list = (List<Employee>) q.getResultList();
  16. 16. Example: Stored Procedure OUT Parameter -- MY SQL CREATE PROCEDURE SUM_SALARIES( OUT TOTAL INT ) BEGIN SELECT SUM(SALARY) INTO TOTAL FROM EMPLOYEES; END
  17. 17. Example: Stored Procedure EntityManager API // create and setup a stored procedure query StoredProcedureQuery q = em.createStoredProcedureQuery( "SUM_SALARIES"); q.registerStoredProcedureParameter( "TOTAL", Integer.class, ParameterMode.OUT); // execute the query... q.execute(); // ...to obtain the output value Integer total = (Integer) q.getOutputParameterValue("TOTAL");
  18. 18. Example: Stored Procedure All in One -- MY SQL CREATE PROCEDURE GET_EMPLOYEES( IN GIVEN_COUNTRY VARCHAR(255), OUT TOTAL INT ) BEGIN SELECT SUM(SALARY) INTO TOTAL FROM EMPLOYEES WHERE COUNTRY = GIVEN_COUNTRY; SELECT * FROM EMPLOYEES WHERE COUNTRY = GIVEN_COUNTRY; END
  19. 19. Example: Stored Procedure EntityManager API // create and setup a stored procedure query StoredProcedureQuery q = em.createStoredProcedureQuery( "GET_EMPLOYEES", Employee.class); q.registerStoredProcedureParameter( "COUNTRY", String.class, ParameterMode.IN); q.registerStoredProcedureParameter( "TOTAL", Integer.class, ParameterMode.OUT); // setup the parameters q.setParameter("COUNTRY", "Poland"); // obtain the employees... List<Employee> list = (List<Employee>) q.getResultList(); // ...and the output value Integer total = (Integer) q.getOutputParameterValue("TOTAL");
  20. 20. Example: Stored Procedure EntityManager API // create and setup a stored procedure query StoredProcedureQuery q = em.createStoredProcedureQuery( "GET_EMPLOYEES", Employee.class); q.registerStoredProcedureParameter( "COUNTRY", String.class, ParameterMode.IN); q.registerStoredProcedureParameter( "TOTAL", Integer.class, ParameterMode.OUT); // setup the parameters q.setParameter("COUNTRY", "Poland"); // obtain the employees List<Employee> list = (List<Employee>) q.getResultList(); // do we need execute here? Integer total = (Integer) q.getOutputParameterValue("TOTAL");
  21. 21. Example: Stored Procedure EntityManager API // create and setup a stored procedure query StoredProcedureQuery q = em.createStoredProcedureQuery( "GET_EMPLOYEES", Employee.class); q.registerStoredProcedureParameter( "COUNTRY", String.class, ParameterMode.IN); q.registerStoredProcedureParameter( "TOTAL", Integer.class, ParameterMode.OUT); // setup the parameters q.setParameter("COUNTRY", "Poland"); // first, an implicit call to execute List<Employee> list = (List<Employee>) q.getResultList(); // ...then, we can safely obtain the output value  Integer total = (Integer) q.getOutputParameterValue("TOTAL");
  22. 22. Example: Stored Procedure EntityManager API // create and setup a stored procedure query StoredProcedureQuery q = em.createStoredProcedureQuery( "GET_EMPLOYEES", Employee.class); q.registerStoredProcedureParameter( "COUNTRY", String.class, ParameterMode.IN); q.registerStoredProcedureParameter( "TOTAL", Integer.class, ParameterMode.OUT); // setup the parameters q.setParameter("COUNTRY", "Poland"); // what if we reorder the lines? Integer total = (Integer) q.getOutputParameterValue("TOTAL"); // an implicit call to execute List<Employee> list = (List<Employee>) q.getResultList();
  23. 23. Example: Stored Procedure EntityManager API // create and setup a stored procedure query StoredProcedureQuery q = em.createStoredProcedureQuery( "GET_EMPLOYEES", Employee.class); q.registerStoredProcedureParameter( "COUNTRY", String.class, ParameterMode.IN); q.registerStoredProcedureParameter( "TOTAL", Integer.class, ParameterMode.OUT); // setup the parameters q.setParameter("COUNTRY", "Poland"); // the other way around it doesn’t work! Integer total = (Integer) q.getOutputParameterValue("TOTAL"); // an implicit call to execute List<Employee> list = (List<Employee>) q.getResultList();
  24. 24. Example: Stored Procedure EntityManager API // create and setup a stored procedure query StoredProcedureQuery q = em.createStoredProcedureQuery( "GET_EMPLOYEES", Employee.class); q.registerStoredProcedureParameter( "COUNTRY", String.class, ParameterMode.IN); q.registerStoredProcedureParameter( "TOTAL", Integer.class, ParameterMode.OUT); // setup the parameters q.setParameter("COUNTRY", "Poland"); // execute must be called before getOutputParameterValue // explicitely or implicitely q.execute(); Integer total = (Integer) q.getOutputParameterValue("TOTAL"); // an implicit call to execute List<Employee> list = (List<Employee>) q.getResultList();
  25. 25. Example: Stored Procedure EntityManager API // create and setup a stored procedure query StoredProcedureQuery q = em.createStoredProcedureQuery( "GET_EMPLOYEES", Employee.class); q.registerStoredProcedureParameter( "COUNTRY", String.class, ParameterMode.IN); q.registerStoredProcedureParameter( "TOTAL", Integer.class, ParameterMode.OUT); // setup the parameters q.setParameter("COUNTRY", "Poland"); // execute must be called before getOutputParameterValue // explicitely or implicitely q.execute(); Integer total = (Integer) q.getOutputParameterValue("TOTAL"); // deos it call execute once more time? List<Employee> list = (List<Employee>) q.getResultList();
  26. 26. Example: Stored Procedure EntityManager API // create and setup a stored procedure query StoredProcedureQuery q = em.createStoredProcedureQuery( "GET_EMPLOYEES", Employee.class); q.registerStoredProcedureParameter( "COUNTRY", String.class, ParameterMode.IN); q.registerStoredProcedureParameter( "TOTAL", Integer.class, ParameterMode.OUT); // setup the parameters q.setParameter("COUNTRY", "Poland"); // execute must be called before getOutputParameterValue // explicitely or implicitely q.execute(); Integer total = (Integer) q.getOutputParameterValue("TOTAL"); // an implicit call to execute only if not executed yet! List<Employee> list = (List<Employee>) q.getResultList();
  27. 27. Example: Stored Procedure EntityManager API // create and setup a stored procedure query StoredProcedureQuery q = em.createStoredProcedureQuery( "GET_EMPLOYEES", Employee.class); q.registerStoredProcedureParameter( "COUNTRY", String.class, ParameterMode.IN); q.registerStoredProcedureParameter( "TOTAL", Integer.class, ParameterMode.OUT); // setup the parameters q.setParameter("COUNTRY", "Poland"); // execute must be called before getOutputParameterValue // explicitely or implicitely q.execute(); Integer total = (Integer) q.getOutputParameterValue("TOTAL"); // an implicit call to execute only if not executed yet! List<Employee> list = (List<Employee>) q.getResultList();
  28. 28. Example: Stored Procedure EntityManager API // create and setup a stored procedure query StoredProcedureQuery q = em.createStoredProcedureQuery( "GET_EMPLOYEES", Employee.class); // what about the order here? q.registerStoredProcedureParameter( "COUNTRY", String.class, ParameterMode.IN); q.registerStoredProcedureParameter( "TOTAL", Integer.class, ParameterMode.OUT); // setup the parameters q.setParameter("COUNTRY", "Poland"); // execute must be called before getOutputParameterValue // explicitely or implicitely q.execute(); Integer total = (Integer) q.getOutputParameterValue("TOTAL"); // an implicit call to execute only if not executed yet! List<Employee> list = (List<Employee>) q.getResultList();
  29. 29. Example: Stored Procedure EntityManager API // create and setup a stored procedure query StoredProcedureQuery q = em.createStoredProcedureQuery( "GET_EMPLOYEES", Employee.class); // what about the order here? q.registerStoredProcedureParameter( "TOTAL", Integer.class, ParameterMode.OUT); q.registerStoredProcedureParameter( "COUNTRY", String.class, ParameterMode.IN); // setup the parameters q.setParameter("COUNTRY", "Poland"); // execute must be called before getOutputParameterValue // explicitely or implicitely q.execute(); Integer total = (Integer) q.getOutputParameterValue("TOTAL"); // an implicit call to execute only if not executed yet! List<Employee> list = (List<Employee>) q.getResultList();
  30. 30. Example: Stored Procedure EntityManager API // create and setup a stored procedure query StoredProcedureQuery q = em.createStoredProcedureQuery( "GET_EMPLOYEES", Employee.class); // what about the order here? q.registerStoredProcedureParameter( "TOTAL", Integer.class, ParameterMode.OUT); q.registerStoredProcedureParameter( "COUNTRY", Integer.class, ParameterMode.IN); // setup the parameters q.setParameter("COUNTRY", "Poland"); // execute must be called before getOutputParameterValue // explicitely or implicitely q.execute(); Integer total = (Integer) q.getOutputParameterValue("TOTAL"); // an implicit call to execute only if not executed yet! List<Employee> list = (List<Employee>) q.getResultList();
  31. 31. Example: Stored Procedure EntityManager API // create and setup a stored procedure query StoredProcedureQuery q = em.createStoredProcedureQuery( "GET_EMPLOYEES", Employee.class); // what about the order of the positional parameters? q.registerStoredProcedureParameter( 2, Integer.class, ParameterMode.OUT); q.registerStoredProcedureParameter( 1, String.class, ParameterMode.IN); // setup the parameters q.setParameter(1, "Poland"); // execute must be called before getOutputParameterValue // explicitely or implicitely q.execute(); Integer total = (Integer) q.getOutputParameterValue(2); // an implicit call to execute only if not executed yet! List<Employee> list = (List<Employee>) q.getResultList();
  32. 32. Example: Stored Procedure EntityManager API // create and setup a stored procedure query StoredProcedureQuery q = em.createStoredProcedureQuery( "GET_EMPLOYEES", Employee.class); // what about the order of the positional parameters? q.registerStoredProcedureParameter( 2, Integer.class, ParameterMode.OUT); q.registerStoredProcedureParameter( 1, String.class, ParameterMode.IN); // setup the parameters q.setParameter(1, "Poland"); // execute must be called before getOutputParameterValue // explicitely or implicitely q.execute(); Integer total = (Integer) q.getOutputParameterValue("TOTAL"); // an implicit call to execute only if not executed yet! List<Employee> list = (List<Employee>) q.getResultList();
  33. 33. Example: Stored Procedure Annotation @NamedStoredProcedureQuery{ name = "getEmployees", procedureName = "GET_EMPLOYEES", resultClasses = Employee.class, parameters = { @StoredProcedureParameter(name = "COUNTRY", mode = ParameterMode.IN, type = String.class), @StoredProcedureParameter(name = "TOTAL", mode = ParameterMode.OUT, type = Integer.class), } } @Entity public class Employee { ... }
  34. 34. Example: Stored Procedure EntityManager API // create and setup a stored procedure query StoredProcedureQuery q = em.createNamedStoredProcedureQuery( ”getEmployees"); // setup the parameters q.setParameter(”COUNTRY", "Poland"); // first, an implicit call to execute List<Employee> list = (List<Employee>) q.getResultList(); // ...then, we can safely obtain the output value  Integer total = (Integer) q.getOutputParameterValue("TOTAL");
  35. 35. Example: Stored Procedure -- PostgreSQL CREATE OR REPLACE FUNCTION GET_EMPLOYEES( IN GIVEN_COUNTRY VARCHAR(255), OUT TOTAL INT ) RETURNS REFCURSOR AS $BODY$ DECLARE EMPS REFCURSOR; BEGIN OPEN EMPS FOR SELECT * FROM EMPLOYEE WHERE COUNTRY = GIVEN_COUNTRY; RETURN EMPS; END; $BODY$ LANGUAGE plpgsql
  36. 36. Example: Stored Procedure EntityManager API // create and setup a stored procedure query StoredProcedureQuery q = em.createStoredProcedureQuery( "GET_EMPLOYEES", Employee.class); q.registerStoredProcedureParameter( 1, void.class, ParameterMode.REF_CURSOR); q.registerStoredProcedureParameter( "COUNTRY", String.class, ParameterMode.IN); // setup the parameters q.setParameter("COUNTRY", "Poland"); // first, an implicit call to execute... List<Employee> list = (List<Employee>) q.getResultList();
  37. 37. Example: Stored Procedure Annotation @NamedStoredProcedureQuery{ name = "getEmployees", procedureName = "GET_EMPLOYEES", resultClasses = Employee.class, parameters = { @StoredProcedureParameter(mode = ParameterMode.REFCUR, type = void.class), @StoredProcedureParameter(mode = ParameterMode.IN, type = String.class) } @Entity public class Employee { ... }
  38. 38. Example: Stored Procedure EntityManager API // create and setup a stored procedure query StoredProcedureQuery q = em.createNamedStoredProcedureQuery( ”getEmployees”); // setup the parameters q.setParameter(2, "Poland"); // obtain the result List<Employee> list = (List<Employee>) q.getResultList();
  39. 39. Stored Procedures in JPA 2.1 Wrap-up • Annotation – @NamedStoredProcedureQuery • EntityManager API – createStoredProcedureQuery – registerStoredProcedureParameter • Use cases – Existing database – Abstraction on database level (e.g., for several applications) • Still differences between databases! – Much smaller though
  40. 40. Reporting
  41. 41. Reporting Anti-Patterns • Direct usage of an object-oriented domain model • Too much data loaded • Heavy processing on the Java side
  42. 42. Reporting Anti-Patterns Example
  43. 43. Reporting Anti-Patterns Employee Entity @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; … }
  44. 44. 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); }
  45. 45. 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); }
  46. 46. Reporting Anti-Patterns Projection (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); }
  47. 47. Reporting Anti-Patterns Aggregation 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!
  48. 48. Reporting Anti-Patterns Aggregation 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!
  49. 49. Comparison 1-4 100 000 employees, EclipseLink MySQL PostgreSQL Select all (1+N) (1) 25704ms 18120ms Select join fetch (2) 6211ms 3954ms Projection (3) 533ms 569ms Aggregation JPQL (4) 410ms 380ms Aggregation SQL (5) 380ms 409ms
  50. 50. Projection 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();
  51. 51. Projection 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();
  52. 52. Projection SQL -> Value Object @SqlResultSetMapping( name = "countryStatVO", classes = { @ConstructorResult( targetClass = CountryStatVO.class, columns = { @ColumnResult( name = "ssum", type = BigDecimal.class), @ColumnResult( name = "country", type = String.class) }) })
  53. 53. Projection 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();
  54. 54. Projection Wrap-up • JPA 2.0 – Only JPQL query to directly produce a value object! • JPA 2.1 – JPQL and native queries to directly produce a value object! • Managed object – Sync with database – L1/L2 cache • Use cases for Direct Value Object – Reporting, statistics, history – Read-only data, GUI data – Performance: • No need for managed objects • Rich (or fat) managed objects • Subset of attributes required • Gain speed • Offload an app server
  55. 55. Aggregation Wrap-up • JPA 2.0 – Selected aggregation functions: COUNT, SUM, AVG, MIN, MAX • JPA 2.1 – All function as supported by a database – Call any database function with new FUNCTION keyword • Database-specific aggregate functions – MS SQL: STDEV, STDEVP, VAR, VARP,… – MySQL: BIT_AND, BIT_OR, BIT_XOR,… – Oracle: MEDIAN, PERCENTILE,… – More… • Use cases – Reporting, statistics – Performance • Gain speed • Offload an app server to a database!
  56. 56. Complex Queries
  57. 57. JPQL vs. SQL • Column/table visibility – JPA 2.0/2.1: Only mapped columns and tables • Operations on sets – UNION, INTERSECT, EXCEPT – JPA 2.0/2.1: No support • Nested fetch joins – JPA 2.0: No support – JPA 2.1: entity graphs for different fetching strategies • ON – JPA 2.0: No support – JPA 2.1: Only on “connected entities”
  58. 58. JPQL vs. SQL • Database functions – Aggregate functions – Conversion functions – Extraction functions – Manipulation functions – Functions, functions, functions… – JPA 2.0: Selected functions – JPA 2.1: FUNCTION keyword
  59. 59. Helper Functions JPA 2.1 • String functions: – CONCAT, SUBSTRING, TRIM, LOWER, UPPER, LENGTH, LOCATE • Arithmetic functions: – ABS, SQRT, MOD, SIZE, INDEX • Datatime functions: – CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP • Aggregate functions: – COUNT, SUM, MIN, MAX, AVG • Invocation of predefined or user-defined functions: – FUNCTION(function_name {, function_args}*)
  60. 60. Helper Functions JPA 2.1 • Invocation of predefined or user-defined functions: – FUNCTION(function_name {, function_args}*) – SELECT c FROM Customer c WHERE FUNCTION(‘hasGoodCredit’, c.balance c.creditLimit)
  61. 61. JPA 2.1
  62. 62. Wrap-up • JPA 2.1 – Stored procedures support – Projections • Direct value object for native queries – Richer JPQL • 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
  63. 63. Continuous Learning Paradigm • A fool with a tool is still a fool • Let’s educate ourselves! 
  64. 64. Q&A patrycja@yonita.com @yonlabs

×