jpa 查询 列表
JPA:NamedQuery,使用日期查询,有关getSingleResult方法的警告
为了避免重复查询代码,提高性能并简化维护查询,我们可以使用NamedQueries。 NamedQuery使用JPQL作为语法,并在实体类中声明。 类代码更新后,更容易编辑查询。
如果要使用日期作为参数进行查询,则只能发送日期对象,也可以传递描述日期类型的枚举(推荐)。
在下面,您将看到如何创建和使用@NamedQuery以及如何使用日期查询:
package com.model;
import java.util.Date;
import javax.persistence.*;
@Entity
@NamedQuery(name='Dog.FindByDateOfBirth', query='select d from Dog d where d.dateOfBirth = :dateOfBirth')
public class Dog {
public static final String FIND_BY_DATE_OF_BIRTH = 'Dog.FindByDateOfBirth';
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
// get and set
}
package com.model;
import java.util.*;
import javax.persistence.*;
@Entity
@NamedQueries({
@NamedQuery(name='Person.findByName', query='select p from Person p where p.name = :name'),
@NamedQuery(name='Person.findByAge', query='select p from Person p where p.age = :age')})
})
public class Person {
public static final String FIND_BY_NAME = 'Person.findByName';
public static final String FIND_BY_AGE = 'Person.findByAge';
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
// get and set
}
package com.main;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.TemporalType;
import com.model.Dog;
import com.model.Person;
public class Page07 {
public static void main(String[] args) {
CodeGenerator.startConnection();
CodeGenerator.generateData();
EntityManager em = CodeGenerator.getEntityManager();
int age = 70;
List<Person> personByAge = getPersonByAge(em, 70);
System.out.println('Found ' + personByAge.size() + ' person(s) with the age of: ' + age);
SimpleDateFormat formatter = new SimpleDateFormat('dd/MM/yyyy');
Date dateOfBirth = null;
try {
dateOfBirth = formatter.parse('10/1/1995');
} catch (ParseException e) {
e.printStackTrace();
}
List<Dog> dogsByDayOfBirth = getDogsByDayOfBirth(em, dateOfBirth);
System.out.println('Found ' + dogsByDayOfBirth.size() + ' dog with birth date of ' + formatter.format(dateOfBirth));
/*
* This queries will raise Runtime Exceptions
*
* em.createQuery('select p from Person p').getSingleResult(); // NonUniqueResultException
*
* em.createQuery('select p from Person p where p.name = 'JJJJ'').getSingleResult(); //NoResultException
*/
CodeGenerator.closeConnection();
}
@SuppressWarnings('unchecked')
private static List<Dog> getDogsByDayOfBirth(EntityManager em, Date dateOfBirth) {
Query query = em.createNamedQuery(Dog.FIND_BY_DATE_OF_BIRTH);
query.setParameter('dateOfBirth', dateOfBirth, TemporalType.DATE);
return query.getResultList();
}
@SuppressWarnings('unchecked')
private static List<Person> getPersonByAge(EntityManager em, int age) {
Query query = em.createNamedQuery(Person.FIND_BY_AGE);
query.setParameter('age', age);
return query.getResultList();
}
}
关于上面的代码:
- 如果只有一个查询,则可以使用@NamedQuery注释; 如果您有多个查询,则可以使用@NamedQueries批注。
- 使用日期对象进行查询时,还可以使用TemporalType枚举来详细说明日期的类型。 对于日期查询,可以使用“ java.util.Date ”或“ java.util.GregorianCalendar ”。
getSingleResult()
使用此方法时要小心。 它有一种特殊的方式来处理容易发生的两种行为,并且两种行为都会引发异常:
- 从查询结果中查找多个对象: NonUniqueResultException
- 找不到结果: NoResultException
您始终需要使用try / catch以避免在生产环境中引发这些异常。
如果您想实时查看此异常,则在上面的代码中可以找到两个带注释的查询。 它将引发以下异常:
Exception in thread 'main' <span style='text-decoration: underline;'>javax.persistence.NonUniqueResultException</span>: result returns more than one elements
at org.hibernate.ejb.QueryImpl.getSingleResult(<span style='text-decoration: underline;'>QueryImpl.java:287</span>)
Exception in thread 'main' <span style='text-decoration: underline;'>javax.persistence.NoResultException</span>: No entity found for query
at org.hibernate.ejb.QueryImpl.getSingleResult(<span style='text-decoration: underline;'>QueryImpl.java:280</span>)
JPA:NativeQuery,名为NativeQuery
JPA使用的JPQL没有任何特定于数据库的功能。 当JPQL仅提供数据库之间的公用功能时,如何进行查询以调用特定的数据库功能?
“ 从人员p中选择p,其中p.name〜*:name ”该查询语法是对Postgres数据库的有效查询; 如果尝试使用NamedQuery(使用JPQL)运行此查询,则会看到以下异常:
<strong><em>ERROR SessionFactoryImpl:422 - Error in named query: Person.FindByName</em></strong>
<strong><em>org.hibernate.QueryException: unexpected char: '~' [select p from com.model.Person p where p.name ~* :name]</em></strong>
<strong><em>at org.hibernate.hql.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:229)</em></strong>
<strong><em>at org.hibernate.hql.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:136)</em></strong>
<strong><em>at org.hibernate.engine.query.HQLQueryPlan.<init>(HQLQueryPlan.java:101)</em></strong>
<strong><em>at org.hibernate.engine.query.HQLQueryPlan.<init>(HQLQueryPlan.java:80)</em></strong>
<strong><em>at org.hibernate.engine.query.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:124)</em></strong>
<strong><em>at org.hibernate.impl.SessionFactoryImpl.checkNamedQueries(SessionFactoryImpl.java:547)</em></strong>
<strong><em>at org.hibernate.impl.SessionFactoryImpl.<init>(SessionFactoryImpl.java:411)</em></strong>
<strong><em>at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1842)</em></strong>
<strong><em>at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:902)</em></strong>
<strong><em>at org.hibernate.ejb.HibernatePersistence.createEntityManagerFactory(HibernatePersistence.java:57)</em></strong>
<strong><em>at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:48)</em></strong>
<strong><em>at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:32)</em></strong>
<strong><em>at com.main.CodeGenerator.startConnection(CodeGenerator.java:27)</em></strong>
<strong><em>at com.main.Page05.main(Page05.java:12)</em></strong>
<strong><em>Exception in thread “main” javax.persistence.PersistenceException: [PersistenceUnit: JpaQuery] Unable to build EntityManagerFactory</em></strong>
<strong><em>at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:911)</em></strong>
<strong><em>at org.hibernate.ejb.HibernatePersistence.createEntityManagerFactory(HibernatePersistence.java:57)</em></strong>
<strong><em>at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:48)</em></strong>
<strong><em>at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:32)</em></strong>
<strong><em>at com.main.CodeGenerator.startConnection(CodeGenerator.java:27)</em></strong>
<strong><em>at com.main.Page05.main(Page05.java:12)</em></strong>
<strong><em>Caused by: org.hibernate.HibernateException: Errors in named queries: Person.FindByName</em></strong>
<strong><em>at org.hibernate.impl.SessionFactoryImpl.<init>(SessionFactoryImpl.java:424)</em></strong>
<strong><em>at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1842)</em></strong><strong><em> </em></strong>
<strong><em>at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:902)</em></strong>
<strong><em> ... 5 more</em></strong>
解决这种情况的方法是使用不使用JPQL作为其语法的查询。 使用NativeQuery,您将能够使用数据库语法进行查询。
您可以在下面的代码中看到如何使用NativeQueries:
package com.main;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import com.model.Dog;
public class Page08 {
public static void main(String[] args) {
CodeGenerator.startConnection();
CodeGenerator.generateData();
EntityManager em = CodeGenerator.getEntityManager();
String nameOfFirstPerson = getFirstPersonName(em);
System.out.println(nameOfFirstPerson);
Dog dog = getTopDogDescending(em);
System.out.println(dog.getName());
CodeGenerator.closeConnection();
}
/**
* Returns the name of the first person using a native sql
*/
private static String getFirstPersonName(EntityManager em) {
Query query = em.createNativeQuery('select top 1 name from person');
return (String) query.getSingleResult();
}
/**
* Return an object using a native sql
*/
private static Dog getTopDogDescending(EntityManager em) {
Query query = em.createNativeQuery('select top 1 id, name, weight from dog order by id desc', Dog.class);
return (Dog) query.getSingleResult();
}
}
关于上面的代码:
- 注意,我们使用本机查询代替JPQL。 我们可以有一个实体作为本地查询的结果。 方法“ getTopDogDescending ”在本机查询调用之后返回Dog对象。
您还可以将本地查询创建为@NamedNativeQuery。 NamedNativeQuery和NativeQuery之间的区别在于NamedNativeQuery是在其实体类上定义的,并且您只能使用该名称类。
使用@NamedNativeQuery的优点是:
- 易于维护的代码:每个查询都在该类上,如果一个类更新属性,则更新查询将变得更加容易。
- 帮助提高性能:一旦声明了查询,JPA将对其进行映射并将其语法保留在内存中。 JPA不需要在项目每次使用时都“解析”您的查询。
- 提升代码重用性:声明@NamedNativeQuery后,必须为“ name”参数提供一个值,并且该名称对于Persistence Unit范围必须是唯一的(您在“ persistence.xml”中设置此值,并由EntityManager)。
您将在下面看到如何声明@NamedNativeQuery:
package com.model;
import java.util.*;
import javax.persistence.*;
@Entity
@NamedQueries({
@NamedQuery(name='Person.findByName', query='select p from Person p where p.name = :name'),
@NamedQuery(name='Person.findByAge', query='select p from Person p where p.age = :age')
})
@NamedNativeQuery(name='Person.findByNameNative', query='select id, name, age from person where name = :name')
public class Person {
public static final String FIND_BY_NAME = 'Person.findByName';
public static final String FIND_BY_AGE = 'Person.findByAge';
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
// get and set
}
您将可以使用@NamedNativeQuery,就像@NativeQuery一样: em.createNamedQuery(“ Person.findByNameNative”); ”。
这里有一些坏消息。 不幸的是,Hibernate尚未实现@NamedNativeQuery。 如果您尝试使用此批注运行代码,则会看到以下异常:
<em>Caused by: <span style='text-decoration: underline;'>org.hibernate.cfg.NotYetImplementedException</span>: Pure native scalar queries are not yet supported</em>
<em>at org.hibernate.cfg.annotations.QueryBinder.bindNativeQuery(<span style='text-decoration: underline;'>QueryBinder.java:140</span>)</em>
<em>at org.hibernate.cfg.AnnotationBinder.bindQueries(<span style='text-decoration: underline;'>AnnotationBinder.java:339</span>)</em>
<em>at org.hibernate.cfg.AnnotationBinder.bindClass(<span style='text-decoration: underline;'>AnnotationBinder.java:548</span>)</em>
<em>at org.hibernate.cfg.Configuration$MetadataSourceQueue.processAnnotatedClassesQueue(<span style='text-decoration: underline;'>Configuration.java:3977</span>)</em>
<em>at org.hibernate.cfg.Configuration$MetadataSourceQueue.processMetadata(<span style='text-decoration: underline;'>Configuration.java:3931</span>)</em>
<em>at org.hibernate.cfg.Configuration.secondPassCompile(<span style='text-decoration: underline;'>Configuration.java:1368</span>)</em>
<em>at org.hibernate.cfg.Configuration.buildMappings(<span style='text-decoration: underline;'>Configuration.java:1345</span>)</em>
<em>at org.hibernate.ejb.Ejb3Configuration.buildMappings(<span style='text-decoration: underline;'>Ejb3Configuration.java:1477</span>)</em>
<em>at org.hibernate.ejb.EventListenerConfigurator.configure(<span style='text-decoration: underline;'>EventListenerConfigurator.java:193</span>)</em>
<em>at org.hibernate.ejb.Ejb3Configuration.configure(<span style='text-decoration: underline;'>Ejb3Configuration.java:1096</span>)</em>
<em>at org.hibernate.ejb.Ejb3Configuration.configure(<span style='text-decoration: underline;'>Ejb3Configuration.java:278</span>)</em>
<em>at org.hibernate.ejb.Ejb3Configuration.configure(<span style='text-decoration: underline;'>Ejb3Configuration.java:362</span>)</em>
JPA:复杂的本地查询
您将能够创建映射到NativeQuery的复合体。 此映射将返回多个类或值。
您可以在下面看到我们的类如何映射此复杂结果以及如何使用它执行查询:
package com.model;
import java.util.*;
import javax.persistence.*;
@Entity
@NamedQueries({
@NamedQuery(name='Person.findByName', query='select p from Person p where p.name = :name'),
@NamedQuery(name='Person.findByAge', query='select p from Person p where p.age = :age')})
})
@SqlResultSetMappings({
@SqlResultSetMapping(name='personAndAdress',
entities={
@EntityResult(entityClass=Person.class),
@EntityResult(entityClass=Address.class,
fields={
@FieldResult(name='id', column='ADDRESS_ID')
}
)
}),
@SqlResultSetMapping(name='personWithDogAmount',
entities={@EntityResult(entityClass=Person.class)},
columns={@ColumnResult(name='dogAmount')}
)
})
public class Person {
public static final String FIND_BY_NAME = 'Person.findByName';
public static final String FIND_BY_AGE = 'Person.findByAge';
public static final String MAPPING_PERSON_AND_ADDRESS = 'personAndAdress';
public static final String MAPPING_DOG_AMOUNT = 'personWithDogAmount';
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
// get and set
}
package com.main;
import java.math.BigInteger;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import com.model.Address;
import com.model.Person;
public class Page09 {
public static void main(String[] args) {
CodeGenerator.startConnection();
CodeGenerator.generateData();
EntityManager em = CodeGenerator.getEntityManager();
Query query = em.createNativeQuery('select id, name, age, a.id as ADDRESS_ID, houseNumber, streetName ' +
'from person p join address a on a.id = p.address_id where p.id = 1',
Person.MAPPING_PERSON_AND_ADDRESS);
Object[] result = (Object[]) query.getSingleResult();
Person personWithAdress = (Person) result[0];
Address address = (Address) result[1];
System.out.println(personWithAdress.getName() + ' lives at ' + address.getStreetName());
query = em.createNativeQuery('select p.id, p.name, count(0) as dogAmount ' +
'from person p join dog d on p.id = d.person_id where name = 'Mark' ' +
'group by p.id, p.name',
Person.MAPPING_DOG_AMOUNT);
result = (Object[]) query.getSingleResult();
Person person = (Person) result[0];
BigInteger total = (BigInteger) result[1];
System.out.println(person.getName() + ' has ' + total + ' dogs');
CodeGenerator.closeConnection();
}
}
关于上面的代码:
- 使用“ @SqlResultSetMapping ”,您将通知JPA我们想要哪个实体作为结果。 注意,在映射“ personAndAdress ”中,我们编写了将返回的类。 我们还使用了一个名为“ @FieldResult ”的属性。 此属性将映射具有相同名称的查询字段,在我们的查询中,我们获得了人员ID和地址ID。 这就是为什么我们使用“ @FieldResult ”来通知JPA将列ADDRESS_ID映射到Address类的ID属性的原因。
- 在“ dogAmount ”映射中,我们设置“ @ColumnResult”属性,该属性通知JPA我们将在查询结果中包含一个“ 额外的列 ”,并且此“ 额外的列 ”不属于任何类。
JPA:使用EJB优化查询
每次在事务范围内执行查询时,持久性上下文将使结果保持“ 附加 ”状态。 持久性上下文将“ 监视 ”该对象,以防万一该“ 附加 ”对象中的任何对象收到任何更新。 所有“ 附加的 ”对象更新都将保留在数据库中。
@PersistenceContext(unitName = 'myPU')
private EntityManager em;
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void editPersonName(Integer personId, String personNewName){
Person person = em.find(Person.class, personId);
person.setName(personNewName);
}
您可以在上面的代码中看到我们不需要调用“ em.merge() ”来更新数据库中的人名。
当我们从数据库中带出一个集合(通常要显示在数据表或报表上)时,所有这些对象都将附加到持久性上下文中。 制作此对象的过程将触发多个过程,数据验证和同步。 当对象数越高时,分配给查询结果的内存就越高,而使所有这些对象保持“ 连接 ”状态的持久性上下文的工作就越高。
将所有这些对象的最终目的地发送到视图时,将所有这些对象“附加”起来有什么意义? 当对象离开EJB项目并转到视图项目时,它们将被视为“分离的”。 在这种情况下,我们无需进行不必要的工作即可从数据库中获取所有数据,进行make(而不是“附加”)并将其发送到视图以使其“分离”。
有一种简单的方法可以使这些对象来自已经分离的数据库。 这种方法的优势在于,持久性上下文将永远不会浪费时间,并且容器处理器会尝试同步查询结果。
您可以在下面的代码中看到此解决方案的工作方式。
package com.main;
import java.util.List;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import com.model.Person;
@Stateless
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class PersonDAO {
@PersistenceContext(unitName = 'myPU')
private EntityManager em;
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void editPersonName(Integer personId, String personNewName){
Person person = em.find(Person.class, personId);
person.setName(personNewName);
}
@SuppressWarnings('unchecked')
public List<Person> listAll(){
Query query = em.createQuery('select p from Person p');
return query.getResultList();
}
@SuppressWarnings('unchecked')
public List<Person> listAllWithoutDogs(){
Query query = em.createQuery('select p from Person p where p.dogs is empty');
return query.getResultList();
}
}
在上面的代码中,我们获得了一个DAO类,即EJB。 默认情况下,我们的EJB缺少事务(“ @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) ”),使用这种事务,持久性上下文将不会“ 附加 ”查询结果。 查询返回的对象将被视为“ 分离 ”。
请注意,方法“ editPersonName ”的注释仍相同:“ @TransactionAttribute(TransactionAttributeType.REQUIRED) ”。 这种事务向EJB指示如果还没有启动任何新事务,则应该启动一个新事务。 您可以为该类设置事务属性,但是方法可以覆盖此属性,就像我们在“ editPersonName ”中所做的一样 。 方法事务定义将优先于类事务定义。
JPA:分页
如果要进行JPA分页,请像下面的方法一样:
package com.main;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import com.model.Dog;
public class Page11 {
@SuppressWarnings('unchecked')
public static void main(String[] args) {
CodeGenerator.startConnection();
CodeGenerator.generateData();
EntityManager em = CodeGenerator.getEntityManager();
Query query = em.createQuery('select d from Dog d');
List<Dog> dogs = query.getResultList();
System.out.println('Total of dogs found: ' + dogs.size());
query.setMaxResults(5);
query.setFirstResult(0);
List<Dog> fiveFirstDogs = query.getResultList();
System.out.print('Total of dogs found: ' + fiveFirstDogs.size() + ' ');
for (Dog dog : fiveFirstDogs) {
System.out.print(dog.getName() + ' ');
}System.out.println();
query.setMaxResults(5);
query.setFirstResult(5);
List<Dog> fiveSecondDogs = query.getResultList();
System.out.print('Total of dogs found: ' + fiveSecondDogs.size() + ' ');
for (Dog dog : fiveSecondDogs) {
System.out.print(dog.getName() + ' ');
}
CodeGenerator.closeConnection();
}
}
关于上面的代码:
- 方法“ setMaxResults ”将设置查询将返回的结果量。
- 方法“ setFirstResult ”将设置将要带来的第一行。
在第一个查询中,我们搜索了数据库中的所有数据。
在第二个查询中,我们从位置0开始获得了五个结果。
在上一个查询中,我们再次获得了五个结果,但是从位置5开始。
请记住,第一个位置始终为零而不是一个。
JPA:数据库提示
数据库供应商向我们提供了名为“提示”的特定功能。 这些提示非常有用,因为它们可以优化查询并帮助我们完成其他任务。 每个数据库都有自己的提示,并且这些值不可移植。
在下面,您可以看到一些提示:
- SQLServer:OPTION(OPTIMIZE FOR(@name ='Mark',@age UNKNOWN));
- Oracle:选择/ * + first_rows(100)* /名称
- MySQL:从人忽略索引中选择*(col3_index)
每个数据库供应商都将规则设置为其提示,诸如语法和执行命令之类的规则。
有两种定义提示的方法:
package com.main;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import com.model.Dog;
public class Page12 {
@SuppressWarnings('unchecked')
public static void main(String[] args) {
CodeGenerator.startConnection();
CodeGenerator.generateData();
EntityManager em = CodeGenerator.getEntityManager();
Query query = em.createQuery('select d from Dog d');
query.setHint('org.hibernate.timeout', 1000);
List<Dog> dogs = query.getResultList();
System.out.println('Found ' + dogs.size() + ' dogs');
CodeGenerator.closeConnection();
}
}
package com.model;
import java.util.*;
import javax.persistence.*;
@Entity
@NamedQueries({
@NamedQuery(name='Person.findByName', query='select p from Person p where p.name = :name'),
@NamedQuery(name='Person.findByAge', query='select p from Person p where p.age = :age', hints={@QueryHint(name='org.hibernate.timeout', value='1000')})
})
@SqlResultSetMappings({
@SqlResultSetMapping(name='personAndAdress',
entities={
@EntityResult(entityClass=Person.class),
@EntityResult(entityClass=Address.class,
fields={
@FieldResult(name='id', column='ADDRESS_ID')
}
)
}),
@SqlResultSetMapping(name='personWithDogAmount',
entities={@EntityResult(entityClass=Person.class)},
columns={@ColumnResult(name='dogAmount')}
)
})
public class Person {
public static final String FIND_BY_NAME = 'Person.findByName';
public static final String FIND_BY_AGE = 'Person.findByAge';
public static final String MAPPING_PERSON_AND_ADDRESS = 'personAndAdress';
public static final String MAPPING_DOG_AMOUNT = 'personWithDogAmount';
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
// get and set
}
您可以在@NamedQuery和直接在查询中设置提示。
您必须始终记住提示的缺点 。 在NamedQuery上设置提示后,您的代码将无法移植到其他数据库供应商。 一种解决方案是仅使用查询而不是NamedQuery。 在将提示添加到查询之前,您可以检查当前数据库是否支持该提示。 您可以使用属性文件来定义应用程序的当前数据库。
继续本系列的第三部分
翻译自: https://www.javacodegeeks.com/2012/07/ultimate-jpa-queries-and-tips-list-part_09.html
jpa 查询 列表