JPA支持

 JPA(Java Persistence API)是Java持久化的规范,但JPA本身并不是持久化的具体实现。这就好比规定了所有的手机都必须能打电话。针对以上这个比喻,JPA充当的角色只是手机能打电话这条约束,而不是电话本身。我们能说手机提供了通话功能,但是不能说通话功能就是一个手机,而且很显然手机提供了通话功能之外的其它很多附加的能力。同样的,JPA的实现者也可以根据自身的情况,提供基本功能之外的附加功能。

      目前JPA的实现主要有:Hibernate,OpenJPA, Toplink,JDO等。Play默认使用了Hibernate的实现,JPA作为一种规范,因此也可以使用其他任意的JPA实现来替代Hibernate。

      JPA的核心概念是通过注解或XML来描述对象与表的映射关系,并将运行期的实体对象持久化到数据库中。Play在JPA的基础上提供了一套非常实用的辅助类来简化对JPA实体的管理,使开发更加便捷。


提示:

开发过程中仍然可以通过原生JPA提供的API来管理实体。


10.2.1 启用JPA实体管理#

      Play会自动查找标记为javax.persistence.Entity注解的类,@javax.persistence.Entity注解的作用是通知Play对当前实体类进行管理,默认情况下实体类通过Hibernate的Entity Manager进行管理。当然以上所有的前提是必须正确配置JDBC数据源,否则Play将无法进行持久化操作。


10.2.2 获得JPA实体管理器#

      JPA实体管理器启动后,程序代码中就可以通过JPA辅助类来取得被管理的实体。例如:

public static void index(){
    Query query = JPA.em().createQuery("from Article");
    List<Article> articles = query.getResultList();
    render(articles);
}


10.2.3 事务管理#

      事务(Transaction)是访问并可能更新数据库中各种数据项的程序执行单元。Play会自动进行事务管理,在每次发送HTTP请求时自动开启事务,发送HTTP响应完毕后提交事务。如果程序在request/response过程中抛出了异常,事务会自动回滚。当然也可以显式调用JPA.setRollbackOnly()方法通知JPA不要提交当前事务,而在代码中对事务进行强制回滚。

      Play为事务管理控制提供了注解的支持。如果需要将Action声明为只读事务,可以在Action方法上标记@play.db.jpa.Transactional(readOnly=true)注解;如果执行当前方法时无需开启事务,可以在Action方法上标记@play.db.jpa.NoTransaction;如果当前控制器中所有方法都无需开启事务,可以直接在控制器上标明@play.db.jpa.NoTransaction。这样做的好处是,大大提升了应用的性能,因为添加@play.db.jpa.NoTransaction注解后Play不会从连接池中获取连接。


10.2.4 play.db.jpa.Model辅助类#

      play.db.jpa.Model是Play中主要的JPA辅助类,它提供了大量的辅助方法,简化程序对JPA的访问。需要做的就是使JPA实体类继承play.db.jpa.Model。如下是继承Model的Post实体类:

@Entity
public class Post extends Model {
    
    public String title;
    public String content;
    public Date postDate;
    
    @ManyToOne
    public Author author;
    
    @OneToMany
    public List<Comment> comments;
    
}

      play.db.jpa.Model类默认包含了一个自增长的长整型id字段,并将其作为主键。这通常是比较好的方案:让自增长的长整型id作为JPA实体的主键(技术主键),并指定另一字段作为实体的功能主键。


注意:

Play将实体的成员变量的访问权限定义为public,从而无需编写大量的setter/getter方法。


10.2.5 使用GenericModel自定义ID#

      Play并不强制要求实体类都继承play.db.jpa.Model,某些情况下应用不需要自增长型的id字段作为实体类的主键,可以选择将JPA实体类继承自play.db.jpa.GenericModel。

      下例是User实体的映射。其中实体的id属性是UUID(通用唯一识别码 Universally Unique Identifier),name和mail属性是必需的,并且使用Play验证器来验证简单的业务规则。

@Entity
public class User extends GenericModel {
    @Id
    @GeneratedValue(generator = "system-uuid")
    @GenericGenerator(name = "system-uuid", strategy = "uuid")
    public String id;

    @Required
    public String name;

    @Required
    @MaxSize(value=255, message = "email.maxsize")
    @play.data.validation.Email
    public String mail;
}


10.2.6 查找对象#

      play.db.jpa.Model 类提供多种方法用于数据查找。


通过ID查找

      findById()是最常用的查找对象方法,可以根据实体对象的Id进行查找:

Post aPost = Post.findById(5L);


查找所有对象

      findAll()用来检索所有对象:

List<Post> posts = Post.findAll();

      以下方法实现的效果与findAll()等价:

List<Post> posts = Post.all().fetch();

      采用以下写法可以对查询结果进行分页:

// 最多匹配100篇 post
List<Post> posts = Post.all().fetch(100); 

//从第50篇post开始查找,并且最多匹配到第100篇post
List<Post> posts = Post.all().from(50).fetch(100); 


使用精简语句查询

      Play提供了非常语义化的查询表达式来创建查询语句,但是只支持一些简单的条件查询。

Post.find("byTitle", "My first post").fetch();
Post.find("byTitleLike", "%hello%").fetch();
Post.find("byAuthorIsNull").fetch();
Post.find("byTitleLikeAndAuthor", "%hello%", connectedUser).fetch();

      简单的查询语句必须遵循固定语法规则:“[Property][Comparator]And?”。其中Comparator有以下几种形式:

  • LessThan – 小于给定的值
  • LessThanEquals – 小于等于给定的值
  • GreaterThan – 大于给定的值
  • GreaterThanEquals – 大于等于给定的值
  • Like – 与SQL语法中like相似,但属性总是会被转换成小写
  • Ilike – 与Like相似,但是大小写敏感,会将参数全部转换成小写
  • Elike – 与SQL语法中like等价,不会进行大小写转换
  • NotEqual – 不等于
  • Between – 在两个数值之间(需要两个参数)
  • IsNotNull – 不为空(不需要参数)
  • IsNull – 为空(不需要参数)


使用JPQL查询

      在Play中也可以书写完整的JPQL语句进行查询:

Post.find(
    "select p from Post p, Comment c " +
    "where c.post = p and c.subject like ?", "%hop%"
);

      也可以在JPQL语句中只写条件部分的查询:

Post.find("title", "My first post").fetch();
Post.find("title like ?", "%hello%").fetch();
Post.find("author is null").fetch();
Post.find("title like % and author is null", "%hello%").fetch();
Post.find("title like % and author is null order by postDate", 
"%hello%").fetch();

      甚至可以只写order by的部分,对查询结果进行升降排序:

Post.find("order by postDate desc").fetch();


10.2.7 统计对象#

      Play提供的count()方法具有统计查询对象结果的功能:
long postCount = Post.count();

      也可以在count()方法中指定条件来统计目标对象数量:

long userPostCount = Post.count("author = ?", connectedUser);


10.2.8 显式地持久化对象#

      Hibernate会将数据库中查询到的结果以对象缓存的形式维护起来。当实体管理器在进行查询匹配等操作的过程中,数据一直作为持久对象存在。这意味着如果事务没有提交,对象的任何改变都将自动持久化到数据库中。JPA的规范是这样定义的:对象的修改默认与事务过程一致,所以无需显式地调用任何方法就可以持久化修改后的数据。

      这种全自动的持久化管理,也有不足的地方,因为我们并不总是希望对象一旦修改就被持久化。所以与其通知实体管理器更新一个对象,还不如告诉它哪些对象不需要更新。refresh()方法可以回滚单个实体,在事务提交之前调用,对象将不被持久化。

      下例是提交表单后,处理持久化对象的常规方式:

public static void save(Long id) {
    User user = User.findById(id);
    user.edit("user", params.all());   //修改持久化的对象
    validation.valid(user);
    if(validation.hasErrors()) {
        // 需要显式抛弃user对象
        user.refresh();
        edit(id);
    }
    show(id);
}
      可能大部分开发者都会犯这样的错误:当不希望对象被持久化时,忘记通知实体管理器抛弃当前对象,以为只要没有显式地调用save()方法,对象就不会被持久化。

      Play总结了这个问题,并做了处理过程的改进。所有继承JPASupport/JPAModel的对象需要显式地调用save()方法才能实现实体的持久化操作。重写上例中的代码:

public static void save(Long id) {
    User user = User.findById(id);
    user.edit("user", params.all());
    validation.valid(user);
    if(validation.hasErrors()) {
        edit(id);
    }else{
        user.save();
        show(id);
    }
}

      显式地调用save()方法在对象级联的操作中显得非常繁琐,通过在注解中声明cascade=CascadeType.ALL属性可以很方便的解决这个问题。


提示:

注解中声明cascade=CascadeType.ALL后,save()方法会自动包含级联操作。


10.2.9 泛型问题#

      play.db.jpa.Model中定义了大量的泛型方法,这些方法通过泛型参数来指定方法的返回类型。调用这些方法时,会根据执行上下文返回具体的类型。

      例如,findAll()方法的定义如下:

<T> List<T> findAll();

      findAll()的使用方法如下:

List<Post> posts = Post.findAll();

      方法返回结果中指定了List<Post>,通知Java编译器确切的类型,泛型T才被解析为Post类型。但是我们无法直接在循环参数中指定泛型方法返回值类型。所以下面的代码无法通过编译,会提示“Type mismatch: cannot convert from element type Object to Post”类型转换错误:

for(Post p : Post.findAll()) {
    p.delete();
}

      针对这个问题,通常的解决方案是为其指定中间变量:

List<Post> posts = Post.findAll(); 
for(Post p : posts) {
    p.delete();
}

      下面介绍一种实用却较少用到的Java语言特性,代码简洁并更具可读性:

for(Post p : Post.<Post>findAll()) {
    p.delete();
}


注意:

开发者在使用JPA的时候需要注意,Play并不支持XA(采用两阶段提交方式来管理分布式事务)。如果在同一个请求中配置了多个不同的JPA,Play的做法是尽最大可能commit事务。如第一个数据库的commit成功了,但是第二个数据库的commit失败了,第一个commit并不会回滚。所以,开发者在同个请求中使用多个JPA配置的时候,需要格外的当心。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值