JPA提供了几种查询数据的方法。 可以根据各种标准(例如,所使用的语言(SQL与JPQL)或查询是静态的(编译时间)还是动态的(执行时间))对此类替代方案进行分类。
静态查询是使用@Entity类定义本身中的注释@NamedQuery ( javax.persistence.NamedQuery )和@NamedQueries ( javax.persistence.NamedQueries )定义的:
@NamedQuery(name="findAllCustomersWithName",query="SELECT c FROM Customer c WHERE c.name LIKE :custName")
另一方面, EntityManager提供的方法createQuery(…)和createNativeQuery(…)分别采用JPQL或SQL查询。
因此,可以在编译或执行时定义查询。
( 注意 :建议始终使用Query中的 setParameter(…)方法使用参数化查询,以避免SQL注入漏洞。)
标准API
但是,JPA提供了另一种查询对象的方法: Criteria API 。 确实,切换到JPA的动机之一是处理对象而不是SQL方言,不是吗?
让我们看一个示例代码。
实体定义:
@Entity
public class User {@Idprivate Integer userId;@Basic@Column(length=15, nullable=false)private String name;@Basic@Column(length=64, nullable=false)private String userDigestedPasswd;@Basic@Column(length=50, nullable=true)private String email;@Basic@Column(nullable=false)public Integer privilegeLevel;@Basic@Column(nullable=false)private Boolean active;
}
让我们查询数据库并检查结果(使用JUnit):
public class UserTest {@Testpublic void testUserCriteria(){
EntityManagerFactory emf = null;
EntityManager em = null;
try {emf = Persistence.createEntityManagerFactory("criteria");em = emf.createEntityManager();final CriteriaBuilder cb = em.getCriteriaBuilder();final CriteriaQuery<User> q = cb.createQuery(User.class);final Root<User> users = q.from(User.class);final Predicate condition = cb.equal(users.get("privilegeLevel"), 5);q.select(users).where(condition).orderBy(cb.asc(users.get("userId")));em.getTransaction().begin();List<User> result = em.createQuery(q).getResultList();em.getTransaction().commit();assertNotNull(result);assertEquals(2, result.size());assertEquals(1, (int)result.get(0).getUserId());assertEquals("Pepe", result.get(0).getName());assertEquals(3, (int)result.get(1).getUserId());assertEquals("Dolores", result.get(1).getName());} catch (Exception e) {fail("Unexpected Exception " + e.getMessage());
} finally {if (em != null)em.close();if (emf != null)emf.close();
}
}
}
以下几行显示查询的创建:
final CriteriaBuilder cb = em.getCriteriaBuilder();final CriteriaQuery<User> q = cb.createQuery(User.class);final Root<User> users = q.from(User.class);final Predicate condition = cb.equal(users.get("privilegeLevel);q.select(users).where(condition).orderBy(cb.asc(users.get("userId
首先,从EntityManager获得CriteriaBuilder 。 然后,获取一个CriteriaQuery实例,将该类设置为保存结果。 在我们的例子中, User.class :
final CriteriaBuilder cb = em.getCriteriaBuilder();
final CriteriaQuery<User> q = cb.createQuery(User.class);
接下来,必须设置要对其运行查询的实体:
final Root<User> users = q.from(User.class);
现在是时候设置查询匹配条件了。 在示例代码中,条件只是属性privilegeLevel等于5:
final Predicate condition = cb.equal(users.get("privilegeLevel"), 5);
最后,构建查询以在Root上添加条件。 也可以设置分组和排序选项(即,对userId设置升序排序):
q.select(users).where(condition).orderBy(cb.asc(users.get(“userId”)));
请查看CriteriaBuilder中的不同选项。 可以在CriteriaQuery中找到分组和排序选项。
使用元模型进行编译时检查
请注意,我们刚刚构建的查询需要跟踪对象属性名称。 例如,要构建查询,请使用属性privilegeLevel的名称。 但是,如果稍后更改属性名称,则代码将编译并且仅在运行时失败:
final CriteriaQuery<User> q = cb.createQuery(User.class);final Root<User> users = q.from(User.class);final Predicate condition = cb.equal(users.get("privilegeLevel"), 5);q.select(users).where(condition).orderBy(cb.asc(users.get("userId")));
那不好
幸运的是,使用元模型,我们将能够构建编译时检查的查询。 可以在The Java EE6 Tutorial中找到简短的介绍。
使用元模型,代码将引用对象的SingularAttribute,而不是使用包含对象属性名称的String。 因此,如果稍后更改对象属性,则编译器将为我们标记该属性。
首先,必须创建对应的元模型类( EntityType )。 尽管可以通过多种方法实现,但对于openJPA实现,最简单的方法可能是添加openJPA构建标志 : -Aopenjpa.metamodel = true 。
因此,我们创建了User_类,它是User的对应元模型类:
* Generated by OpenJPA MetaModel Generator Tool. **/
package com.wordpress.tododev.criteria.entities;
import javax.persistence.metamodel.SingularAttribute;
@javax.persistence.metamodel.StaticMetamodel
(value=com.wordpress.tododev.criteria.entities.User.class)
@javax.annotation.Generated
(value="org.apache.openjpa.persistence.meta.AnnotationProcessor6",date="Mon Mar 04 16:47:46 CET 2013")
public class User_ {public static volatile SingularAttribute<User,Boolean> active;public static volatile SingularAttribute<User,String> email;public static volatile SingularAttribute<User,String> name;public static volatile SingularAttribute<User,Integer> privilegeLevel;public static volatile SingularAttribute<User,String> userDigestedPasswd;public static volatile SingularAttribute<User,Integer> userId;
}
如果将此类添加到代码库中,则以后对User类的任何更改都不会引起注意。 而且,将自动生成的项目添加到代码版本控制系统中不是一个好主意。
使用ant , maven或类似工具,可以添加目标以创建元模型类。 在更改JPA实体后,应执行该目标。
也可以使用IDE。 例如,对于使用Eclipse的,只需要已经提到编译标志添加属性- > Java的反编译>注解处理器和的lib(JAR)包含所选择的JPA实现第厂路内注释处理器的注释处理器(可能导致自动模式下的编译问题,前提是必须在使用它的代码之前编译元模型类)。
让我们向套件添加另一个测试。 这个不会提供包含属性名称的String,而是使用metamodel类:
@Testpublic void testUserCriteriaMetaModel(){EntityManagerFactory emf = null;EntityManager em = null;try {emf = Persistence.createEntityManagerFactory("criteria");em = emf.createEntityManager();final CriteriaBuilder cb = em.getCriteriaBuilder();final CriteriaQuery<User> q = cb.createQuery(User.class);final Metamodel m = em.getMetamodel();final Root<User> user = q.from(m.entity(User.class));final Predicate condition = cb.equal(user.get(User_.privilegeLevel), 5);q.select(user).where(condition).orderBy(cb.asc(user.get(User_.userId)));em.getTransaction().begin();List<User> result = em.createQuery(q).getResultList();em.getTransaction().commit();assertNotNull(result);assertEquals(2, result.size());assertEquals(1, (int)result.get(0).getUserId());assertEquals("Pepe", result.get(0).getName());assertEquals(3, (int)result.get(1).getUserId());assertEquals("Dolores", result.get(1).getName());
} catch (Exception e) {fail("Unexpected Exception " + e.getMessage());} finally {if (em != null)em.close();if (emf != null)emf.close();}}
更相关的更改是user.get(User_.privilegeLevel)而不是users.get(“ privilegeLevel”)和 user.get(User_.userId)而不是 users.get(“ userId”)。
- 从GitHub下载源代码。
翻译自: https://www.javacodegeeks.com/2014/08/compile-time-checking-jpa-queries.html