写点什么

ActiveJPA——针对 JPA 的活动记录模式

  • 2014-03-31
  • 本文字数:4422 字

    阅读完需:约 15 分钟

ActiveRecord 是 Ruby on Rails 的 ORM 层,大体上类似于 Java 中的 Hibernate。ActiveRecord 基于约定优于配置的原则,所以它使用起来比 Hibernate 更容易。在简化基本的数据操作方面,如创建、读取、更新和删除,它确实是非常棒的。

借助于 ActiveRecord,你的模型类也会作为数据访问对象(Data Access Object,DAO)来执行 CRUD 操作。在初步探究之后,我对 ActiveRecord 产生了浓厚的兴趣,因此开始寻找一种解决方案来简化基于 Java 持久化 API(Java Persistence API,JPA)的 ORM 框架的使用。

大多数 JPA 应用都会有某种类型的数据访问层(Data Access Layer,DAL)来与数据库进行交互。通常 DAL 会包含数据访问对象或符合 Repository 设计模式的类。

DAO 的实现与实体对象通常是一对一的关系,而 Repository 则是针对每个聚合根(aggregate root)实现一个。不管是哪种场景,应用最后都会创建多个类与数据库进行交互。尽管适当的抽象能够有效限制所创建类的数量,但是它终究还是会在应用中引入一个额外的层,这都是需要维护和测试的。

ActiveJPA 基于 JPA,提供了 Martin Fowler 所提出的活动记录模式(Active Record pattern)的 Java 实现。借助于 ActiveJPA,模型本身会作为 DAO 并与数据库交互,这样就不需要额外的代码作为数据访问层了。

ActiveJPA 使用到了 JPA 规范,因此所有 JPA 的 ORM 实现(Hibernate、EclipseLink、OpenJPA 等)都可以与 ActiveJPA 协同使用。

将已有的 JPA 模型转换为 ActiveJPA

要将已有的模型转换为 ActiveJPA,只需让你的模型实现扩展 org.activejpa.entity. Model 即可:

复制代码
@java.persistence.Entity
public class YourModel extends org.activejpa.entity.Model {
}

执行 CRUD 操作

你的模型将会从 ActiveJPA 的模型类中继承得到很多的 CRUD 功能。

复制代码
// 根据 id 获得订单
Order order = Order.findById(12345L);
// 根据 customer 获得其已发货的订单
List orders = Order.where("customerEmail", "dummyemail@dummy.com",
"status", "shipped");
// 得到匹配过滤条件的第一条订单记录
Long count = Order.first("customerEmail", "dummyemail@dummy.com",
"status", "shipped");
// 得到匹配过滤条件的唯一一条订单记录
Long count = Order.one("customerEmail", "dummyemail@dummy.com",
"status", "shipped");
// 得到所有的记录
List orders = Order.all();
// 检查指定标识符的订单是否存在
boolean exists = Order.exists(1234L);
// 保存订单
order.persist();
// 删除订单
order.delete();
// 刷新订单
order.refresh();
// 与持久化上下文中已有的订单进行合并
order.merge();

过滤与分页

对记录进行过滤时,你并不需要创建 JPQL 或 criteria 查询。ActiveJPA 提供了一个复杂的过滤器,用于在不同的操作间进行连接(conjunction):

复制代码
// 获取匹配指定 Email 地址且账单额大于 1000 的所有订单,并且要进行分页
Filter filter = new Filter();
filter.setPageNo(1);
filter.setPerPage(25);
filter.addCondition(new Condition("customerEmail", Operator.eq,
"dummyemail@dummy.com");
filter.addCondition(new Condition("billingAmount", Operator.gt,
1000.00);
List orders = Order.where(filter);
// 对满足过滤条件的订单进行计数
Long count = Order.count(filter);
// 删除匹配这个过滤器的订单
Long count = Order.deleteAll(filter);

嵌套查询

ActiveJPA 允许嵌套过滤参数。这样的话就能更容易地在运行时创建动态查询。例如,你获取的订单中,至少要有一个订单项是基于“book”类别的产品创建的。

复制代码
// 得到至少包含一个 book 项的所有订单
Filter filter = new Filter();
filter.setPageNo(1);
filter.setPerPage(25);
filter.addCondition(new Condition("orderItems.product.category",
Operator.eq, "books");
List orders = Order.where(filter);

使用集合

上面讨论的所有 CRUD 操作同时可以在集合级别执行。将查询范围设置到集合类的用法如下所示:

复制代码
// 在订单中,根据 id 查找订单项
order.collections("orderItems").findById(123L);
// 得到第一个已发货的订单项
order.collections(“orderItems”).first(“status”, “shipped”);
// 得到所有取消的订单项
order.collections(“orderItems”).where(“status”, “cancelled”);
// 得到集合中的所有项
order.collections(“orderItems”).all();
// 往集合中添加一项
order.collections(“orderItems”).add(orderItem);
// 移除集合中的一项
order.collections(“orderItems”).remove(orderItem);

过滤和分页也可以使用到集合之中

复制代码
// 对一个订单中的订单项基于过滤器进行查询并进行分页
Filter filter = new Filter();
filter.setPageNo(1);
filter.setPerPage(25);
filter.addCondition(new Condition(“status”, “shipped”);
order.collections("orderItems").where(filter);
// 在订单中,得到匹配过滤器的订单项的数量
order.collections(“orderItems”).count(filter);

动态更新

ActiveJPA 支持对模型的动态更新。在有些场景下这是很有用的,例如用户通过浏览器更新一个表单。你可以传递一个包含属性的 map 来进行更新,而不是调用每个属性的 setter 方法:

复制代码
// 更新属性 <br></br>
Map attributes = new HashMap();
attributes.put("billingAmount", 1000.0);
order.updateAttributes(attributes);

你也可以更新非原始 / 非包装类型的域,只需给这些对象传递 map 即可。以下的样例展现了更新一个订单的收获地址和账单金额。

复制代码
// 更新一个订单的收获地址和账单金额 <br></br>
Map attributes = new HashMap();<br></br>
Map address = new HashMap();<br></br>
address.put(“city”, “Bangalore”);<br></br>
address.put(“state”, “Karnataka”);<br></br>
attributes.put(“shippingAddress”, address);<br></br>
attributes.put("billingAmount", 1000.0);<br></br>
order.updateAttributes(attributes);

注意:目前尚不支持更新 list/set/array 域,未来的版本将会提供支持。

事务处理

默认情况下,如果没有事务,ActiveJPA 将会为所有的更新操作启动一个事务,不过你也可以将整个工作单元包装到一个事务之中:

复制代码
JPAContext context = JPA.instance.getDefaultConfig().getContext();
context.beginTxn();
boolean failed = true;
try {
// 你的工作单元置于此处
failed = false;
} finally {
// 提交或回滚事务
context.closeTxn(failed);
}

如果已经存在了外部的事务,那么 ActiveJPA 将会使用这个事务,但是不会进行提交或回滚。应该由应用来负责关闭该事务。

测试你的模型

ActiveJPA 为 TestNG 提供了一个基本的测试类,它会将 ActiveJPA 以挂钩(hook)的方式添加到测试运行时之中。只需让你的测试类扩展自 _org.activejpa.entity.testng.BaseModelTest_ 类即可。以下为一个样例代码:

复制代码
public class OrderTest extends BaseModelTest {
@Test
public void testCreateOrder() {
Order order = new Order();
order.setCustomerEmail("dummyemail@dummy.com");
...
...
order.persist();
Assert.assertEquals(Order.where("customerEmail",
"dummyemail@dummy.com").get(0), order);
}
}
}

使用起步

搭建 Maven

ActiveJPA 可以以 Maven artifact 的方式来获取,能够非常容易地集成到你的应用之中。只需在你的 pom.xml 文件中添加如下的 maven 依赖:

复制代码
<dependencies> <br></br><dependency> <br></br><groupId>org.activejpa</groupId> <br></br><artifactId>activejpa-core</artifactId> <br></br><version>0.1.5</version> <br></br></dependency> <br></br></dependencies>
<br></br><repositories> <br></br><repository> <br></br><id>activejpa-repo</id> <br></br><url>https://raw.github.com/ActiveJpa/activejpa/mvn-repo/releases</url> <br></br><snapshots> <br></br><enabled>true</enabled> <br></br><updatePolicy>always</updatePolicy> <br></br></snapshots> <br></br></repository> <br></br></repositories>

嵌入到你的应用之中

ActiveJPA 需要在实体类加载前就嵌入到你的应用之中。如果你使用 Tomcat 的话,那么 ServletContextListener 就是做这件事的一个很好的地方。你可以将以下的代码编写到上下文监听器的 contextInitialized() 方法之中。

复制代码
// 动态加载 Java 代理
ActiveJpaAgentLoader.instance().loadAgent();
// 添加定义在 persistence.xml 中的持久化单元,以“order”名进行标识。
persistence.xml 应该位于类路径下
JPA.addPersistenceUnit("order");
// 如果你已经创建了实体管理工厂的话,可以将其关联到 ActiveJpa 上
// JPA.addPersistenceUnit("order", entityManagerFactory);

与 Spring 框架集成

将 ActiveJPA 与 Spring 这样的框架进行集成是很容易的。大多数的应用都会使用 Spring 的注解来配置 JPA 和管理事务。ActiveJPA 能够以两种方式来进行配置——你可以让它来创建实体管理工厂也可以传入一个已存在的对象。在 Spring 配置 JPA 的情况下,我们可以使用 Spring 所创建的实体管理工厂。这样就能保证 ActiveJPA 使用 Spring 所创建的相同的连接和事务,从而提供无缝的集成。

以下的代码展现了如何将 ActiveJPA 集成到 Spring 应用之中,这个应用是部署在 servlet 容器里面的。它使用了一个自定义的上下文加载监听器,从而将 ActiveJPA 嵌入到应用之中。需要注意的是,这很类似与上面的 servlet 样例,区别在于这里使用了 Spring 框架的 ContextLoaderListener:

复制代码
public class CustomContextListener extends ContextLoaderListener {
@Override
public void contextInitialized(ServletContextEvent event) {
try {
// 在这里动态加载 Java 代理
ActiveJpaAgentLoader.instance().loadAgent();
} catch (Exception e) {
throw new RuntimeException(e);
}
super.contextInitialized(event);
JPA.instance.addPersistenceUnit("default",
getCurrentWebApplicationContext().getBean(EntityManagerFactory.
class), true);
}
}

样例应用

在 GitHub 的 ActiveJPA 工程页面上有一个示例应用,包含了很多更具体的样例,展现了 Spring-ActiveJPA 的集成。

关于作者

Ganesh Subramanian Hightail 的架构师,在构建高扩展、低延迟的跨域分布式应用架构方面有超过十年的工作经验。Ganesh 曾经参与 Flipkart (印度最大的电子商务厂商)的供应链管理平台的架构,他还是活跃的开源社区( ActiveJPA , Minnal , AutoPojo 等)贡献者以及技术博客的作者,在业余时间,他喜欢看电影(各种流派)以及陪伴家人。

原文英文链接: ActiveJPA – Active Record Pattern for JPA

2014-03-31 05:355322

评论

发布
暂无评论
发现更多内容

荟聚·沉淀——百度大脑AI开放平台的2020年

百度大脑

用 Selenium 搞定自动化测试项目,途牛搜机票。

梦想橡皮擦

28天写作 2月春节不断更

技术干货 | mPaaS 小程序高玩带你起飞:客户端预置小程序无视网络质量

蚂蚁集团移动开发平台 mPaaS

小程序 网络环境 mPaaS

中核携手北京天源迪科完成电子商城二期项目,开创集团数字化采购新局面

DT极客

多种多样的语音连麦方式

anyRTC开发者

flutter uni-app ios android WebRTC

LeetCode题解:120. 三角形最小路径和,动态规划(从上到下),JavaScript,详细注释

Lee Chen

算法 大前端 LeetCode

UI自动化测试框架_地图测试如何开展呢?

小小娃爱吃甜食

最佳实践 测试 测试落地

单例设计模式

桃子

正月十五吃汤圆CountDownLatch

叫练

AQS join 共享锁 CountDownLatch 多线程高并发

android开源!字节跳动Android岗经典面试真题,含泪整理面经

欢喜学安卓

android 程序员 面试 移动开发

程序员成长第十三篇:做好时间管理

石云升

程序员 时间管理 28天写作 2月春节不断更

双非本科生、非大厂,毕业一年,业余净收入20万

不脱发的程序猿

程序员 程序人生 28天写作 二月春节不断更 外包项目

架构设计篇之微服务实战笔记(七)

小诚信驿站

架构师 刘晓成 小诚信驿站 28天写作 架构师成长笔记

我决定输出一篇文章用于记录一个足足花了四小时才找到的BUG

LSJ

Java spring aop ProxyFactory proxyTargetClass

写给管理者的睡前故事

每天读本书

读书笔记 每天读本书

常见加密方式及Python实现

行者AI

加密

基于 Flink SQL 构建流批一体的 ETL 数据集成

Apache Flink

flink

MySQL COUNT(列名) 、COUNT(常量) 和 COUNT(*) 之间的区别

桃子

DBS

陈皓07

数字化转型提速,DataPipeline助力中石油产业大数据实时共享能力再提升

DataPipeline数见科技

数据融合

做好 IM/RTC 底层技术服务,帮助开发者打造自己的“Clubhouse”

融云 RongCloud

IM RTC clubhouse

超大规模会议技术优化策略 轻松实现 500 人线上流畅沟通

融云 RongCloud

音视频 视频会议

android开发视频!Android程序员最大的悲哀是什么?面试真题解析

欢喜学安卓

android 程序员 面试 移动开发

android开发视频教程!移动开发者升职加薪的8项技能,成功定级腾讯T3-2

欢喜学安卓

android 程序员 面试 移动开发

Aspose word 转换为pdf 排版错位和数据丢失

blues199

Java Apache POI

如何通过事务消息保障抢购业务的分布式一致性?

阿里巴巴中间件

阿里云 云原生 电商 消息队列 分布式柔性事务‘’

聊聊前端日志库在SaaS产品中的应用与设计

元三

大前端 SaaS

买基金赔掉一条裤衩之后,我又回来更文了

Java_若依框架教程

投资 理财 基金 茅台

智慧党建APP开发,智慧党建平台的功能

13530558032

MyCat集成MySQL完成数据库集群建设

831143

Java MySQL mycat

我看JAVA 之 String

awen

Java 源码 string

ActiveJPA——针对JPA的活动记录模式_Java_Ganesh Subramanian_InfoQ精选文章