编译时检查JPA查询

JPA提供了几种查询数据的方法。 可以根据各种标准(例如,所使用的语言(SQL与JPQL)或查询是静态的(编译时间)还是动态的(执行时间))对此类替代方案进行分类。

静态查询是使用@Entity类定义本身中的注释@NamedQueryjavax.persistence.NamedQuery )和@NamedQueriesjavax.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 {

 @Id
 private 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 {
 @Test
 public 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类的任何更改都不会引起注意。 而且,将自动生成的项目添加到代码版本控制系统中不是一个好主意。

使用antmaven或类似工具,可以添加目标以创建元模型类。 在更改JPA实体后,应执行该目标。

也可以使用IDE。 例如,对于使用Eclipse的,只需要已经提到编译标志添加属性- > Java的反编译>注解处理器和的lib(JAR)包含所选择的JPA实现第厂路注释处理器的注释处理器(可能导致自动模式下的编译问题,前提是必须在使用它的代码之前编译元模型类)。

让我们向套件添加另一个测试。 这个不会提供包含属性名称的String,而是使用metamodel类:

@Test
 public 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”)。

翻译自: https://www.javacodegeeks.com/2014/08/compile-time-checking-jpa-queries.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值