thinking beyond orm in jpa

Post on 10-Apr-2017

1.828 Views

Category:

Software

1 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Thinking Beyond ORM in JPA

Patrycja WegrzynowiczJavaOne 2015

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

Outline• Motivation• Why?– App-centric vs. data-centric

• What?– Use cases and performance

• How?– JPA (2.1)

• Conclusion

Database

DatabaseThe Mordor of Java Devs

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

Use CasesLegacy systemsReportingComplex queriesBulk operations

Performance

SpeedLatencyThroughput

Use Cases and Performance

Spee

dLa

tenc

yTh

roug

hput

Legacy systemsReportingComplex queriesBulk operations

Legacy Systems

Several Apps -> One Source

Database-Level Abstraction• Views• Stored procedures• Triggers

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

ery–@NamedStoredProcedureQuery

Example: Stored ProcedureResult Set

-- MY SQLCREATE PROCEDURE GET_EMPLOYEES()BEGIN

SELECT *FROM EMPLOYEES;

END

Example: Stored ProcedureEntityManager API

// create and setup a stored procedure queryStoredProcedureQuery q = em.createStoredProcedureQuery(

"GET_EMPLOYEES", Employee.class);

// gather the results (an implicit call to execute)List<Employee> list = (List<Employee>) q.getResultList();

Example: Stored ProcedureOUT Parameter

-- MY SQLCREATE PROCEDURE SUM_SALARIES(

OUT TOTAL INT)BEGIN

SELECT SUM(SALARY) INTO TOTALFROM EMPLOYEES;

END

Example: Stored ProcedureEntityManager API

// create and setup a stored procedure queryStoredProcedureQuery q = em.createStoredProcedureQuery(

"SUM_SALARIES");q.registerStoredProcedureParameter(

"TOTAL", Integer.class, ParameterMode.OUT);

// execute the query...q.execute();

// ...to obtain the output valueInteger total = (Integer) q.getOutputParameterValue("TOTAL");

Example: Stored ProcedureAll in One

-- MY SQLCREATE PROCEDURE GET_EMPLOYEES(

IN GIVEN_COUNTRY VARCHAR(255), OUT TOTAL INT

)BEGIN

SELECT SUM(SALARY) INTO TOTALFROM EMPLOYEESWHERE COUNTRY = GIVEN_COUNTRY; SELECT *FROM EMPLOYEESWHERE COUNTRY = GIVEN_COUNTRY;

END

Example: Stored ProcedureEntityManager API

// create and setup a stored procedure queryStoredProcedureQuery q = em.createStoredProcedureQuery(

"GET_EMPLOYEES", Employee.class);q.registerStoredProcedureParameter(

"COUNTRY", String.class, ParameterMode.IN);q.registerStoredProcedureParameter(

"TOTAL", Integer.class, ParameterMode.OUT);

// setup the parametersq.setParameter("COUNTRY", "Poland");

// obtain the employees...List<Employee> list = (List<Employee>) q.getResultList();

// ...and the output valueInteger total = (Integer) q.getOutputParameterValue("TOTAL");

Example: Stored ProcedureEntityManager API

// create and setup a stored procedure queryStoredProcedureQuery q = em.createStoredProcedureQuery(

"GET_EMPLOYEES", Employee.class);q.registerStoredProcedureParameter(

"COUNTRY", String.class, ParameterMode.IN);q.registerStoredProcedureParameter(

"TOTAL", Integer.class, ParameterMode.OUT);

// setup the parametersq.setParameter("COUNTRY", "Poland");

// obtain the employeesList<Employee> list = (List<Employee>) q.getResultList();

// do we need execute here?Integer total = (Integer) q.getOutputParameterValue("TOTAL");

Example: Stored ProcedureEntityManager API

// create and setup a stored procedure queryStoredProcedureQuery q = em.createStoredProcedureQuery(

"GET_EMPLOYEES", Employee.class);q.registerStoredProcedureParameter(

"COUNTRY", String.class, ParameterMode.IN);q.registerStoredProcedureParameter(

"TOTAL", Integer.class, ParameterMode.OUT);

// setup the parametersq.setParameter("COUNTRY", "Poland");

// first, an implicit call to executeList<Employee> list = (List<Employee>) q.getResultList();

// ...then, we can safely obtain the output value Integer total = (Integer) q.getOutputParameterValue("TOTAL");

Example: Stored ProcedureEntityManager API

// create and setup a stored procedure queryStoredProcedureQuery q = em.createStoredProcedureQuery(

"GET_EMPLOYEES", Employee.class);q.registerStoredProcedureParameter(

"COUNTRY", String.class, ParameterMode.IN);q.registerStoredProcedureParameter(

"TOTAL", Integer.class, ParameterMode.OUT);

// setup the parametersq.setParameter("COUNTRY", "Poland");

// what if we reorder the lines?Integer total = (Integer) q.getOutputParameterValue("TOTAL");

// an implicit call to executeList<Employee> list = (List<Employee>) q.getResultList();

Example: Stored ProcedureEntityManager API

// create and setup a stored procedure queryStoredProcedureQuery q = em.createStoredProcedureQuery(

"GET_EMPLOYEES", Employee.class);q.registerStoredProcedureParameter(

"COUNTRY", String.class, ParameterMode.IN);q.registerStoredProcedureParameter(

"TOTAL", Integer.class, ParameterMode.OUT);

// setup the parametersq.setParameter("COUNTRY", "Poland");

// the other way around it doesn’t work!Integer total = (Integer) q.getOutputParameterValue("TOTAL");

// an implicit call to executeList<Employee> list = (List<Employee>) q.getResultList();

Example: Stored ProcedureEntityManager API

// create and setup a stored procedure queryStoredProcedureQuery q = em.createStoredProcedureQuery(

"GET_EMPLOYEES", Employee.class);q.registerStoredProcedureParameter(

"COUNTRY", String.class, ParameterMode.IN);q.registerStoredProcedureParameter(

"TOTAL", Integer.class, ParameterMode.OUT);

// setup the parametersq.setParameter("COUNTRY", "Poland");

// execute must be called before getOutputParameterValue// explicitely or implicitelyq.execute();Integer total = (Integer) q.getOutputParameterValue("TOTAL");

// an implicit call to execute List<Employee> list = (List<Employee>) q.getResultList();

Example: Stored ProcedureEntityManager API

// create and setup a stored procedure queryStoredProcedureQuery q = em.createStoredProcedureQuery(

"GET_EMPLOYEES", Employee.class);q.registerStoredProcedureParameter(

"COUNTRY", String.class, ParameterMode.IN);q.registerStoredProcedureParameter(

"TOTAL", Integer.class, ParameterMode.OUT);

// setup the parametersq.setParameter("COUNTRY", "Poland");

// execute must be called before getOutputParameterValue// explicitely or implicitelyq.execute();Integer total = (Integer) q.getOutputParameterValue("TOTAL");

// deos it call execute once more time?List<Employee> list = (List<Employee>) q.getResultList();

Example: Stored ProcedureEntityManager API

// create and setup a stored procedure queryStoredProcedureQuery q = em.createStoredProcedureQuery(

"GET_EMPLOYEES", Employee.class);q.registerStoredProcedureParameter(

"COUNTRY", String.class, ParameterMode.IN);q.registerStoredProcedureParameter(

"TOTAL", Integer.class, ParameterMode.OUT);

// setup the parametersq.setParameter("COUNTRY", "Poland");

// execute must be called before getOutputParameterValue// explicitely or implicitelyq.execute();Integer total = (Integer) q.getOutputParameterValue("TOTAL");

// an implicit call to execute only if not executed yet!List<Employee> list = (List<Employee>) q.getResultList();

Example: Stored ProcedureEntityManager API

// create and setup a stored procedure queryStoredProcedureQuery q = em.createStoredProcedureQuery(

"GET_EMPLOYEES", Employee.class);q.registerStoredProcedureParameter(

"COUNTRY", String.class, ParameterMode.IN);q.registerStoredProcedureParameter(

"TOTAL", Integer.class, ParameterMode.OUT);

// setup the parametersq.setParameter("COUNTRY", "Poland");

// execute must be called before getOutputParameterValue// explicitely or implicitelyq.execute();Integer total = (Integer) q.getOutputParameterValue("TOTAL");

// an implicit call to execute only if not executed yet!List<Employee> list = (List<Employee>) q.getResultList();

Example: Stored ProcedureEntityManager API

// create and setup a stored procedure queryStoredProcedureQuery 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 parametersq.setParameter("COUNTRY", "Poland");

// execute must be called before getOutputParameterValue// explicitely or implicitelyq.execute();Integer total = (Integer) q.getOutputParameterValue("TOTAL");

// an implicit call to execute only if not executed yet!List<Employee> list = (List<Employee>) q.getResultList();

Example: Stored ProcedureEntityManager API

// create and setup a stored procedure queryStoredProcedureQuery 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 parametersq.setParameter("COUNTRY", "Poland");

// execute must be called before getOutputParameterValue// explicitely or implicitelyq.execute();Integer total = (Integer) q.getOutputParameterValue("TOTAL");

// an implicit call to execute only if not executed yet!List<Employee> list = (List<Employee>) q.getResultList();

Example: Stored ProcedureEntityManager API

// create and setup a stored procedure queryStoredProcedureQuery 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 parametersq.setParameter("COUNTRY", "Poland");

// execute must be called before getOutputParameterValue// explicitely or implicitelyq.execute();Integer total = (Integer) q.getOutputParameterValue("TOTAL");

// an implicit call to execute only if not executed yet!List<Employee> list = (List<Employee>) q.getResultList();

Example: Stored ProcedureEntityManager API

// create and setup a stored procedure queryStoredProcedureQuery 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 parametersq.setParameter(1, "Poland");

// execute must be called before getOutputParameterValue// explicitely or implicitelyq.execute();Integer total = (Integer) q.getOutputParameterValue(2);

// an implicit call to execute only if not executed yet!List<Employee> list = (List<Employee>) q.getResultList();

Example: Stored ProcedureEntityManager API

// create and setup a stored procedure queryStoredProcedureQuery 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 parametersq.setParameter(1, "Poland");

// execute must be called before getOutputParameterValue// explicitely or implicitelyq.execute();Integer total = (Integer) q.getOutputParameterValue("TOTAL");

// an implicit call to execute only if not executed yet!List<Employee> list = (List<Employee>) q.getResultList();

Example: Stored ProcedureAnnotation

@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), }}@Entitypublic class Employee {...}

Example: Stored ProcedureEntityManager API

// create and setup a stored procedure queryStoredProcedureQuery q = em.createNamedStoredProcedureQuery(

”getEmployees");

// setup the parametersq.setParameter(”COUNTRY", "Poland");

// first, an implicit call to executeList<Employee> list = (List<Employee>) q.getResultList();

// ...then, we can safely obtain the output value Integer total = (Integer) q.getOutputParameterValue("TOTAL");

Example: Stored Procedure-- PostgreSQLCREATE 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 EMPLOYEEWHERE COUNTRY = GIVEN_COUNTRY;RETURN EMPS;

END;$BODY$LANGUAGE plpgsql

Example: Stored ProcedureEntityManager API

// create and setup a stored procedure queryStoredProcedureQuery q = em.createStoredProcedureQuery(

"GET_EMPLOYEES", Employee.class);q.registerStoredProcedureParameter(

1, void.class, ParameterMode.REF_CURSOR);q.registerStoredProcedureParameter(

"COUNTRY", String.class, ParameterMode.IN);

// setup the parametersq.setParameter("COUNTRY", "Poland");

// first, an implicit call to execute...List<Employee> list = (List<Employee>) q.getResultList();

Example: Stored ProcedureAnnotation

@NamedStoredProcedureQuery{ name = "getEmployees", procedureName = "GET_EMPLOYEES", resultClasses = Employee.class, parameters = { @StoredProcedureParameter(mode = ParameterMode.REFCUR, type = void.class), @StoredProcedureParameter(mode = ParameterMode.IN,

type = String.class)}@Entitypublic class Employee {...}

Example: Stored ProcedureEntityManager API

// create and setup a stored procedure queryStoredProcedureQuery q = em.createNamedStoredProcedureQuery(

”getEmployees”);

// setup the parametersq.setParameter(2, "Poland");

// obtain the resultList<Employee> list = (List<Employee>) q.getResultList();

Stored Procedures in JPA 2.1Wrap-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

Reporting

Reporting Anti-Patterns• Direct usage of an object-oriented

domain model• Too much data loaded• Heavy processing on the Java side

Reporting Anti-PatternsExample

Reporting Anti-PatternsEmployee Entity

@Entitypublic 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; …}

Sum of Salaries By CountrySelect 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->sumMap<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);}

Sum of Salaries by CountrySelect 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->sumMap<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);}

Reporting Anti-PatternsProjection (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->sumMap<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);}

Reporting Anti-PatternsAggregation 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!

Reporting Anti-PatternsAggregation 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!

Comparison 1-4100 000 employees, EclipseLink

MySQL PostgreSQLSelect all (1+N) (1) 25704ms 18120msSelect join fetch (2) 6211ms 3954msProjection (3) 533ms 569msAggregation JPQL (4) 410ms 380msAggregation SQL (5) 380ms 409ms

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

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

ProjectionSQL -> Value Object

@SqlResultSetMapping( name = "countryStatVO", classes = { @ConstructorResult( targetClass = CountryStatVO.class, columns = { @ColumnResult( name = "ssum", type = BigDecimal.class), @ColumnResult( name = "country", type = String.class) }) })

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

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

AggregationWrap-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!

Complex Queries

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”

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

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}*)

Helper Functions JPA 2.1• Invocation of predefined or user-

defined functions: – FUNCTION(function_name {,

function_args}*)– SELECT c

FROM Customer cWHERE FUNCTION(‘hasGoodCredit’, c.balance

c.creditLimit)

JPA 2.1

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

Continuous Learning Paradigm• A fool with a tool is still a fool• Let’s educate ourselves!

Q&A

patrycja@yonita.com@yonlabs

top related