Pro JPA2 第七章(使用查询)
7.1 Java持久化查询语言
7.1.1 入门
从Employee实体中查询所有字段SELECT e FROM Employee e
7.1.2 筛选结果
SELECT e FROM Employee e WHERE e.department.name = 'NA42' AND e.address.state IN ('NY','CA')
7.1.3 投影结果
SELECT e.name, e.salary FROM Employee e
7.1.4 实体之间的联接
写法一:SELECT p.number FROM Employee e,Phone p WHERE e = p.employee AND e.department.name='NA42' AND p.type='Cell'
写法二:
SELECT p.number FROM Employee e JOIN e.phones p WHERE e.department.name = 'NA42' AND p.type='Cell'
7.1.5 聚合查询
SELECT d,COUNT(e),MAX(e.salary),AVG(e.salary) FROM Department d JOIN d.employees e GROUP BY d HAVING COUNT(e) >= 5
7.1.6 查询参数
位置参数表示法:SELECT e FROM Employee e WHERE e.department = ?1 AND e.salary > ?2
命名参数表示法:
SELECT e FROM Employee e WHERE e.department = :dept AND e.salary > :base
7.2 定义查询
7.2.1 动态定义查询
public class QueryServiceImpl implements QueryService { @PersistenceContext(unitName="DynamicQueries") EntityManager em; public long queryEmpSalary(String deptName,String empName) { String query = "SELECT e.salary " + "FROM Employee e "+ "WHERE e.department.name = '" + deptName + "' AND " + " e.name = '" + empName + "'"; return em.createQuery(query,Long.class).getSingleResult(); } }
7.2.2 命名查询定义
可以把@NamedQuery注解定义在任何实体的类定义之上.@NamedQuery(name="findSalaryForNameAndDepartment", query="SELECT e.salary " + "FROM Employee e " + "WHERE e.department.name=:deptName AND " + " e.name=:empName")
执行命名查询
public class EmployeeServiceImpl implements EmployeeService { @PersistenceContext(unitName="EmployeeService") EntityManager em; public Employee findEmployeeByName(String name) { return em.createNamedQuery("Employee.findByName",Employee.class).setParameter("name",name).getSingleResult(); } }
7.3 参数类型
对于命名参数和位置参数,存在三种类型的参数可以设置:- 参数的名称或编号
- 绑定到命名参数的对象
Date和Calendar
绑定日期参数:
public class EmployeeServiceImpl implements EmployeeService { @PersistenceContext(unitName="EmployeeService") EntityManager em; public List<Employee> findEmployeesHiredDuringPeriod(Date start,Date end) { return em.createQuery("SELECT e " + "FROM Employee e " + "WHERE e.startDate BETWEEN ?1 AND ?2",Employee.class) .setParameter(1,start,TemporalType.DATE) .setParameter(2,end,TemporalType.DATE) .getResultList(); } }
7.4 执行查询
遍历排序的结果public void displayProjectEmployees(String projectName){ List<Employee> result = em.createQuery("SELECT e " + "FROM Project p JOIN p.employees e " + "WHERE p.name = ?1 " + "ORDER BY e.name",Employee.class) .setParameter(1,projectName) .getResultList(); int count = 0; for(Employee e : result) { System.out.println(++count + " :" + e.getName() + "," + e.getDepartment().getName()); } }
7.4.1 使用查询结果
JP QL查询的结果类型:基本类型, 实体类型,对象数组,从一个构造方法表达式创建的用户自定义类型- 非类型化结果
我们可以通过绑定到预期类型的TypedQuery实例来获得限定结果的类型,但是在结果类型为Object的情况下,或者当JP QL查询选择多个对象时还可以使用非类型化查询创建方法的版本.结果类型将会生成一个Query实例,而不是一个TypedQuery实例. 优化只读查询
当不会修改查询结果时,若结果类型是一个实体,则在事务外部使用事务范围的实体管理器的查询将会比在事务内执行的查询更加有效.当在事务中准备查询结果时,持久化提供程序必须采取多个步骤以把结果转换成托管实体.为了获得用于事务提交时比较的一个基准,通常需要为每个实体获得数据的快照.如果从不需要修改托管实体,那么将会浪费把结果转换为托管实体的努力.
支持非事务性查询的最简单的方法是对会话bean的方法使用NOT_SUPPORTED事务特性.public class QueryServiceImpl implements QueryService { @PersistenceContext(unitName="EmployeeService") EntityManager em; @TranscationAttribute(TranscationAttributeType.NOT_SUPPORTED) public List<Department> findAllDepartmentsDetached(){ } }
特殊结果类型
每当查询在SELECT字句中涉及一个以上的表达式时,该查询的结果将是一个Object数组的List.public void displayProjectEmployees(String projectName) { List result = em.createQuery( "SELECT e.name,e.department.name " + "FROM Project p JOIN p.employees e " + "WHERE p.name = ?1 " + "ORDER BY e.name") .setParameter(1,projectName) .getResultList(); int count = 0; for(Iterator i = result.iterator();i.hasNext();) { Object [] values = (Object[]) i.next(); System.out.println(++count + ": " + values[0] + ", " +values[1]); } }
构造函数表达式(constructor expression),用于把Object数组结果类型映射为自定义的对象.这通常用于把结果转换到JavaBean样式的表.
在JP QL中,使用SELECT子句的NEW操作符来定义一个构造函数表达式.NEW操作符的参数是完全限定的类名,将会对它进行实例化以保存每个数据行返回的结果.对这个类的唯一要求就是它要有一个带参数的构造函数,这些参数将匹配在查询中指定的确切类型和顺序.public class EmpMenu { private String employeeName; private String departmentName; public EmpMenu(String employeeName,String departmentName) { this.employeeName = employeeName; this.departmentName = departmentName; } public String getEmployeeName() { return employeeName; } public String getDepartmentName() { return departmentName; } } public void displayProjectEmployees(String projectName) { List<EmpMenu> result = em.createQuery( "SELECT NEW example.EmpMenu(" + "e.name,e.department.name") " + "FROM Project p JOIN p.employees e " + "WHERE p.name = ?1 " + "ORDER BY e.name",EmpMenu.class) .setParameter(1,projectName) .getResultList(); int count = 0; for (EmpMenu menu : result) { System.out.println(++count + ": " + menu.getEmployeeName() + ", " + menu.getDepartmentName()); } }
- 非类型化结果
7.4.2 查询分页
通过使用setFirstResult()和setMaxResults(),Query和TypedQuery接口提供了对分页的支持.也可以通过getFirstResult()和getMaxResults()方法来检索默认值.public class ResultPagerBean implements ResultPager { @PersistenceContext(unitName = "QueryPaging") private EntityManager em; private String reportQueryName; private long currentPage; private long maxResults; private long pageSize; public long getPageSize() { return pageSize; } public long getMaxPages() { return maxResuls/pageSize; } public void init(long pageSize,String countQueryName,String reportQueryName) { this.pageSize = pageSize; this.reportQueryName = reportQueryName; maxResults = em.createNamedQuery(countQueryName,Long.class).getSingleResult(); currentPage = 0; } public List getCurrentResults() { return em.createNamedQuery(reportQueryName).setFirstResult(currentPage * pageSize) .setMaxResults(pageSize) .getResultList(); } public void next(){ currentPage++; } public void previous(){ currentPage --; if(currentPage < 0) { currentPage = 0; } } public long getCurrentPage() { return currentPage; } public void setCurrentPage(long currentPage) { this.currentPage = currentPage; } }
- 7.4.3 查询与未提交的更改
略 7.4.4 查询超时
一般而言,当查询执行时,它会一直阻塞直到数据库查询返回,如果查询正在参与一个事务,并且在JTA事务或在数据库中设置了超时,这样也是有问题的.在事务或数据库中超时可能导致查询提前终止,但是它也将导致事务回滚,防止在相同的事务中处理任何进一步的工作.
如果一个应用程序需要设置查询相应时间的限制,而无需使用事务或引起事务回滚,那么就可以在查询中设置javax.persistence.query.timeout属性或把它作为持久化单元的一部分.public Date getLastUserAcitivity() { TypedQuery<Date> lastActive = em.createNamedQuery("findLastUserActivity",Date.class); lastActive.setHint("javax.persistence.query.timeout",5000); try { return lastActive.getSingleResult(); } catch (QueryTimeoutException e) { return null; } }
但是并不是所有的数据库平台都支持它.如果想使用,有三种场景:第一是安静地忽略该属性,从而不产生任何效果.第二是启用该属性,任何选择,更新或者删除操作若运行超过指定的超时值,则会终止,并抛出一个QueryTimeoutException.这种异常可能会被处理,而且不会导致标记任何活动事务为回滚.第三个是启用该属性,但是当超时发生时,数据库会强迫事务回滚,在这种情况下将会抛出一个PersistenceException异常,并标记事务为回滚.
7.5批量更新和删除
7.5.1 使用批量更新和删除
public class EmployeeServiceImpl implements EmployeeService { @PersistenceContext(unitName="BulkQueries") EntityManager em; @TranscationAttribute(TransactionAttributeType.REQUIRES_NEW) public void assignManager(Department dept,Employee manager) { em.createQuery("UPDATE Employee e " + "SET e.manager = ?1 " + "WHERE e.department = ?2") .setParameter(1,manager) .setParameter(2,dept) .executeUpdate(); } }
批量操作所带来的危险以及它们必须在事务中首先发生的原因在于,由持久化上下文管理的任何活动实体将保持原样,忽略在数据库级别上发生的实际变化/活动的持久化上下文是独立的,区别在于提供程序用于优化而使用的任何数据缓存.考虑下列操作序列:
(1)启动一个新的事务
(2)通过调用persist()创建实体A以使它变成托管的
(3)通过find()操作检索实体B并修改它
(4)批量删除实体A
(5)批量更新实体B的属性,与第(3)步中所修改的属性相同
(6)提交事务7.5.2 批量删除和关系
强调:关系维护永远是开发人员的责任.级联删除发生的唯一时间是为关系设置REMOVE级联选项.即使这样,持久化提供程序不会自动更新任何引用已删除实体的托管实体的状态.
例如,假设公司希望改组它的部门结构.想要删除一些部门,然后给员工分配新的部门,第一部是删除旧的部门:DELETE FROM Department d WHERE d.name IN ('CA13','CA19','NY30')
当执行这个查询时,会抛出一个PersistenceException异常,报告违反了一个外键完整性约束.因为它跟另一张表有外键关联关系.所以我们首先要更新有问题的Employee实体,以确保它们没有指向我们正尝试删除的部门:
UPDATE Employee e SET e.department = null WHERE e.department.name IN ('CA13','CA19','NY30')
7.6 查询提示
每个查询都可以关联任何数量的提示,通过在持久性单元的元数据中设置为@NamedQuery注解的一部分,或者在Query或TypedQuery接口上使用setHint()方法.
为了简化供应商之间的可移植性,持久化提供程序需要忽略它们不理解的提示.public Employee findEmployeeNoCache(int empId) { TypedQuery<Employee> q = em.createQuery("SELECT e FROM Employee e WHERE e.id=:empId",Employee.class); q.setHint("eclipselink.cache-usage","DoNotCheckCache"); q.setParameter("empId",empId); try{ return q.getSingleResult(); } catch (NoResultException e) { return null; } }
如果频繁执行此查询,那么一个命名查询将会更有效:
@NamedQuery(name="findEmployeeNoCache", query="SELECT e FROM Employee e WHERE e.id=:empId", hints={@QueryHint(name="eclipselink.cache-usage",value="DoNotCheckCache")})
- 7.7 查询的最佳实践
- 7.7.1 命名查询
我们应该尽量使用命名查询,这避免了连续解析JP QL和生成SQL的系统开销.即使采用一个用于转换查询的缓存,动态查询定义将永远比使用命名查询的效率更低.
命名查询还强制使用查询参数的最贱实践 - 7.7.2 报告查询
如果执行查询以返回用于报告的实体,并且没有打算修改结果,那么考虑使用一个事务范围的实体管理器,但是在事务外部来执行查询. - 7.7.3 供应商提示
查询提示的理想位置是在一个XML映射文件中,或者至少是一个命名查询的一部分.提示常常是高度依赖目标平台的,而且很可能随着时间而改变.如果可能的话,那么尽量保持提示与您的代码解耦合. - 7.7.4 无状态会话bean
使用无状态会话bean有若干好处:
- 客户端可以通过调用适当命名的业务方法来执行查询,而不是依赖于一个神秘的查询名称或多个相同查询字符串的副本.
- 无状态会话bean方法可以优化事务的使用,这取决于结果是否需要托管或分离.
- 使用一个事务范围的持久化上下文,将确保大量的实体实例不会再需要它们很久之后保持托管
- 对已有的EJB实体bean应用程序而言,无状态会话bean是迁移查找工具查询以远离实体bean主接口的理想工具
- 7.7.5 批量更新和删除
如果必须使用批量更新和删除操作,那么确保它们只在一个孤立的事务中执行,其中没有任何其他的更改.
- 7.7.1 命名查询