openjpa/openjpa-project/src/doc/manual/jpa_overview_sqlquery.xml

339 lines
14 KiB
XML
Raw Normal View History

<chapter id="jpa_overview_sqlquery">
<title>SQL Queries</title>
<indexterm zone="jpa_overview_sqlquery">
<primary>SQL queries</primary>
<seealso>Query</seealso>
</indexterm>
<indexterm>
<primary>Query</primary>
<secondary>SQL</secondary>
<see>SQL queries</see>
</indexterm>
<indexterm>
<primary>SQL</primary>
<secondary>queries</secondary>
<see>SQL queries</see>
</indexterm>
<indexterm>
<primary>Native</primary>
<secondary>queries</secondary>
<see>SQL queries</see>
</indexterm>
<para>
JPQL is a powerful query language, but there are times when it is
not enough. Maybe you're migrating a JDBC application to JPA
on a strict deadline, and you don't have time to translate your existing
SQL selects to JPQL. Or maybe a certain query requires
database-specific SQL your JPA implementation doesn't support.
Or maybe your DBA has spent hours crafting the perfect select statement
for a query in your application's critical path. Whatever the reason, SQL
queries can remain an essential part of an application.
</para>
<para>
You are probably familiar with executing SQL queries by obtaining a
<classname>java.sql.Connection</classname>, using the JDBC APIs to create
a <classname>Statement</classname>, and executing that <classname>Statement
</classname> to obtain a <classname>ResultSet</classname>. And of course,
you are free to continue using this low-level approach to SQL execution in
your JPA applications. However, JPA also supports executing SQL queries
through the <classname>javax.persistence.Query</classname>
interface introduced in <xref linkend="jpa_overview_query"/>.
Using a JPA SQL query, you can retrieve either persistent objects
or projections of column values. The following sections detail each use.
</para>
<section id="jpa_overview_sqlquery_create">
<title>Creating SQL Queries</title>
<indexterm zone="jpa_overview_sqlquery_create">
<primary>SQL queries</primary>
<secondary>creating</secondary>
</indexterm>
<para>
The <classname>EntityManager</classname> has two factory methods
suitable for creating SQL queries:
</para>
<programlisting format="linespecific">
public Query createNativeQuery (String sqlString, Class resultClass);
public Query createNativeQuery (String sqlString, String resultSetMapping);
</programlisting>
<para>
The first method is used to create a new <classname>Query</classname>
instance that will return instances of the specified class.
</para>
<para>
The second method uses a <literal>SqlResultSetMapping</literal>
to determine the type of object or objects to return.
The example below shows these methods in action.
</para>
<example id="jpa_overview_sqlquery_createex">
<title>Creating a SQL Query</title>
<programlisting format="linespecific">
EntityManager em = ...;
Query query = em.createNativeQuery ("SELECT * FROM MAG", Magazine.class);
processMagazines (query.getResultList ());
</programlisting>
</example>
<note>
<para><indexterm><primary>SQL queries</primary><secondary>stored procedures</secondary></indexterm><indexterm><primary>stored procedures</primary><secondary>as queries</secondary><seealso>Query</seealso></indexterm>
In addition to SELECT statements, OpenJPA supports stored procedure
invocations as SQL queries. OpenJPA will assume any SQL that does
not begin with the <literal>SELECT</literal> keyword (ignoring
case) is a stored procedure call, and invoke it as such at the
JDBC level.
</para>
</note>
</section>
<section id="jpa_overview_sqlquery_obj">
<title>Retrieving Persistent Objects with SQL</title>
<indexterm zone="jpa_overview_sqlquery_obj">
<primary>SQL queries</primary>
<secondary>retrieving persistent objects</secondary>
</indexterm>
<indexterm zone="jpa_overview_sqlquery_obj">
<primary>persistent objects</primary>
<secondary>retrieving with SQL</secondary>
<seealso>SQL queries</seealso>
</indexterm>
<para>
When you give a SQL <classname>Query</classname> a candidate class, it
will return persistent instances of that class. At a minimum, your
SQL must select the
class' primary key columns, discriminator column (if mapped), and
version column (also if mapped). The JPA runtime uses the values
of the primary key columns to construct each result object's identity,
and possibly to match it with a persistent object already in the
<classname>EntityManager</classname>'s cache. When an object is
not already cached, the
implementation creates a new object to represent the current result
row. It might use the discriminator column value to make sure it
constructs an object of the correct subclass. Finally, the query
records available version column data for use in optimistic concurrency
checking, should you later change the result object and flush it back
to the database.
</para>
<para>
Aside from the primary key, discriminator, and version columns, any
columns you select are used to populate the persistent fields of each
result object. JPA implementations will compete on how effectively
they map your selected data to your persistent instance fields.
</para>
<para>
Let's make the discussion above concrete with an example. It uses
the following simple mapping between a class and the database:
</para>
<mediaobject>
<imageobject>
<!-- PNG image data, 320 x 149 (see README) -->
<imagedata fileref="img/sqlquery-model.png" width="213px"/>
</imageobject>
</mediaobject>
<example id="jpa_overview_sqlquery_objex">
<title>Retrieving Persistent Objects</title>
<programlisting format="linespecific">
Query query = em.createNativeQuery ("SELECT ISBN, TITLE, PRICE, "
+ "VERS FROM MAG WHERE PRICE &gt; 5 AND PRICE &lt; 10", Magazine.class);
List&lt;Magazine&gt; results = query.getResultList ();
for (Magazine mag : results)
processMagazine (mag);
</programlisting>
</example>
<para>
The query above works as advertised, but isn't very flexible. Let's
update it to take in parameters for the minimum and maximum price,
so we can reuse it to find magazines in any price range:
</para>
<example id="jpa_overview_sqlquery_obj_paramex">
<title>SQL Query Parameters</title>
<programlisting format="linespecific">
Query query = em.createNativeQuery ("SELECT ISBN, TITLE, PRICE, "
+ "VERS FROM MAG WHERE PRICE &gt; ?1 AND PRICE &lt; ?2", Magazine.class);
query.setParameter (1, 5d);
query.setParameter (2, 10d);
List&lt;Magazine&gt; results = query.getResultList ();
for (Magazine mag : results)
processMagazine (mag);
</programlisting>
</example>
<para><indexterm><primary>SQL queries</primary><secondary>parameters</secondary></indexterm><indexterm><primary>parameters</primary><secondary>in SQL queries</secondary><seealso>SQL queries</seealso></indexterm>
Like JDBC prepared statements, SQL queries represent parameters with
question marks, but are followed by an integer to represent its
index.
</para>
</section>
<!--
<section id="jpa_overview_sqlquery_proj">
<title>SQL Projections</title>
<indexterm zone="jpa_overview_sqlquery_proj">
<primary>SQL queries</primary>
<secondary>projections</secondary>
</indexterm>
<indexterm zone="jpa_overview_sqlquery_proj">
<primary>projections</primary>
<secondary>of column data</secondary>
<seealso>SQL queries</seealso>
</indexterm>
<para>
SQL queries without a candidate class are treated as projections of
column data. If you select a single column, the query returns
a list of <classname>Object</classname>s. If you select multiple
columns, it returns a list of <classname>Object[]</classname>s.
In either case, each column value is obtained using the
<methodname>java.sql.ResultSet.getObject</methodname> method. The
following example demonstrates a query for the values of the
<literal>ISBN</literal> and <literal>VERS</literal> columns of all
<literal>MAG</literal> table records, using the data model we
defined in <xref linkend="jpa_overview_sqlquery_obj"/>.
</para>
<example id="jpa_overview_sqlquery_projex">
<title>Column Projection</title>
<programlisting>
Query query = em.newQuery ("javax.persistence.query.SQL",
"SELECT ISBN, VERS FROM MAG");
List results = query.getResultList ();
for (Iterator itr = results.iterator (); itr.hasNext ();)
{
Object[] data = (Object[]) results.next ();
processISBNAndVersion (data[0], data[1]);
}
</programlisting>
<para>
Notice that in the code above, we did not set a candidate class.
Therefore, the query is treated as a projection.
</para>
</example>
<para>
<indexterm>
<primary>SQL queries</primary>
<secondary>result class</secondary>
</indexterm>
Our discussion of JPQL query result classes in
<xref linkend="jpa_overview_query_resultcls"/> also
applies to SQL queries. As with JPQL queries, SQL queries can
automatically pack their results into objects of a specified type.
JPA uses the <methodname>java.sql.ResultSetMetaData.getColumnLabel
</methodname> method to match each column alias to the result class'
public fields and JavaBean setter methods. Here is a modification of
our example above that packs the selected column values into JavaBean
instances.
</para>
<example id="jpa_overview_sqlquery_proj_labelex">
<title>Result Class</title>
<programlisting>
public class Identity
{
private String id;
private int versionNumber;
public void setId (String id)
{
this.id = id;
}
public String getId ()
{
return id;
}
public void setVersionNumber (int versionNumber)
{
this.versionNumber = versionNumber;
}
public int getVersionNumber ()
{
return versionNumber;
}
}
Query query = em.createNativeQuery ("javax.persistence.query.SQL",
"SELECT ISBN AS id, VERS AS versionNumber FROM MAG", Identity.class);
List results = query.getResultList ();
for (Iterator itr = results.iterator (); itr.hasNext ();)
processIdentity ((Identity) itr.next ());
</programlisting>
</example>
</section>
<section id="jpa_overview_sqlquery_named">
<title>Named SQL Queries</title>
<indexterm zone="jpa_overview_sqlquery_named">
<primary>SQL queries</primary>
<secondary>named</secondary>
<see>named queries</see>
</indexterm>
<indexterm zone="jpa_overview_sqlquery_named">
<primary>named queries</primary>
<secondary>SQL</secondary>
</indexterm>
<para>
We discussed how to write named JPQL queries in
<xref linkend="jpa_overview_query_named"/>. Named queries, however,
are not limited to JPQL. By setting the <literal>query</literal>
element's <literal>language</literal> attribute to <literal>
javax.persistence.query.SQL</literal>, you can define a named SQL query. A
named SQL query within a <literal>class</literal> element queries for
instances of that class; a named SQL query outside of a <literal>class
</literal> element acts as a column data projection.
</para>
<example id="jpa_overview_sqlquery_namedex">
<title>Named SQL Queries</title>
<programlisting>
<![CDATA[<?xml version="1.0"?>
<jdoquery>
<query name="salesReport" language="javax.persistence.query.SQL">
SELECT TITLE, PRICE * COPIES FROM MAG
</query>
<package name="org.mag">
<class name="Magazine">
<query name="findByTitle" language="javax.persistence.query.SQL">
SELECT * FROM MAG WHERE TITLE = ?
</query>
</class>
</package>
</jdoquery>]]>
</programlisting>
<para>
The <literal>salesReport</literal> query above returns the title
and revenue generated for each <classname>Magazine</classname>.
Because it is a projection, it does not have a candidate class, and
so we specify it at the root level.
</para>
<para>
The <literal>findByTitle</literal> query returns the <classname>
Magazine</classname> with the title given on execution. The code
below executes both queries.
</para>
<programlisting>
EntityManager em = ...;
Query query = em.newNamedQuery (null, "salesReport");
List sales = query.getResultList ();
for (Iterator itr = sales.iterator (); itr.hasNext ();)
{
Object[] salesData = (Object[]) itr.next ();
processSalesData ((String) salesData[0], (Number) salesData[1]);
}
query = em.newNamedQuery (Magazine.class, "findByTitle");
query.setUnique (true);
Magazine jdj = (Magazine) query.execute ("JDJ");
</programlisting>
</example>
</section>
<section id="jpa_overview_sqlquery_conclusion">
<title>Conclusion</title>
<para>
If you've used relational databases extensively, you might be tempted
to perform all your JPA queries with SQL. Try to resist this
temptation. SQL queries tie your application to the particulars of
your current table model and database vendor. If you stick with JPQL,
on the other hand, you can port your application to other schemas and
database vendors without any changes to your code. Additionally,
most JPA implementations already produce highly optimized SQL from
your JPQL filters, and many are able to cache JPQL query results
for added performance.
</para>
</section>
-->
</chapter>