基于 Java 的 Active Record 开源项目

Active Record 是什么?也许很多做 Java 的朋友并没有听说过这个概念,但它确实很早就已经出现了。

确切地说,应该是在 2003 年,由世界大师级人物 Martin Fowler(马丁 · 福勒)在他写的一本叫做《企业应用架构模式》书里就描述过这个模式。不可否认,马丁是软件架构的泰斗,他写的每本书,我都买过,虽然很多内容我还看不懂,但每次阅读都有新的认识,虽然这些文字已经很陈旧了。

如果您想了解关于 Active Record 的权威定义,可以点击下面的维基百科地址:

http://zh.wikipedia.org/wiki/Active_Record

当然,如果您想听到更通俗易懂的言语,我可以试着描述一下:

  1. 它是面向领域对象的设计模式
  2. 它为每个领域对象提供一组 CRUD 方法

以上提到的 领域对象 实际上就是我们经常说的 Entity(实体)。

Active Record 模式最早是在 Ruby on Rails(RoR)里取得了最佳实践,然后其它开发语言开始效仿,比如:PHP、Python 等,当然 Java 也不例外。

这几天我收集了几款基于 Java 的 Active Record 开源项目,这些项目都非常优秀,让我收获良多、受益匪浅!所以我忍不住想与大家分享一下我的学习心得与体会。

需要申明的是:本文仅代表个人看法,本人仅站在使用者的角度来体验这些产品,并非对技术架构与实现进行评价,若大家有不同见解,欢迎讨论!

下面的这些 Active Record 框架的排名不分先后,但我还是想给出一个“推荐指数”(满分是 5 颗星),当然这仅代表我个人的观点。

为了描述方便,以下将 Active Record 简称为 AR

JFinal(国产)

JFinal 是国内最流行的轻量级 Java Web 框架之一,作者深厚的功力,让我敬佩万分!今天也是我第一次评价 JFinal,当然仅作为一名用户,对该框架的 AR 使用方面发表一点个人观点。

我们不妨先看看在 JFinal 中是怎样使用 AR 的吧:

<!-- lang: java -->
// 创建name属性为James,age属性为25的User对象并添加到数据库
new User().set("name", "James").set("age", 25).save();

// 删除id值为25的User
User.dao.deleteById(25);

// 查询id值为25的User将其name属性改为James并更新到数据库
User.dao.findById(25).set("name", "James").update();

// 查询id值为25的user, 且仅仅取name与age两个字段的值
User user = User.dao.findById(25, "name, age");

// 获取user的name属性
String userName = user.getStr("name");

// 获取user的age属性
Integer userAge = user.getInt("age");

// 查询所有年龄大于18岁的user
List<User> users = User.dao.find("select * from user where age > 18");

// 分页查询sex为1并且年龄大于18的user,当前页号为1,每页10个user
Page<User> userPage = User.dao.paginate(1, 10, "select *", "from user where sex=? and age>?", 1, 18);

可见,代码还是非常精辟的,面向 User 实体,通过访问该实体的 dao 成员变量来调用相关 AR 方法,非常不错!

尤其是这种链式方法,简直太棒了!

<!-- lang: java -->
new User().set("name", "James").set("age", 25).save();

此外,该框架还提供了一个 Db + Record 的开发模式,无需编写任何的实体就能完成数据库操作。我很喜欢,很赞!

最新的 1.6 版本也支持了多数据源,也就是说,在 AR 中可以同时使用 MySQL 与 Oracle 数据库,这是两个不同的数据源。这个特性也非常好!相信一定很受欢迎。

但作为用户而言,我还是想提几点在编码过程中一点点太完美的地方,当然只是吹毛求疵了。

首先,以上代码中的 set 方法里的 key 是 User 的属性名,假如需要将 name 重命名,那么势必会修改很多相关的代码,否则如果有一个漏掉了,就容易出现 Bug,也就是说,当重构时会有风险。

然后,我们来看看以下这行代码:

<!-- lang: java -->
User.dao.findById(25)

我认为,如果能这样写会更加精简:

<!-- lang: java -->
User.find(25)

也就是说,把 dao 去掉,并且简化方法名,默认就是根据 id 来获取实体对象。

下面我大致总结一下:

亮点:

  • 链式方法调用,例如:new User().set("name", "James").set("age", 25).save()
  • 无需编写实体即可操作数据库,例如:提供 Record、Db 这些实用类
  • 支持多数据源,例如:在 find 时可指定 mysql 或 oracle

瑕疵:

  • 通过字符串来操作实体属性,当重构时会有风险,例如:user.set("name", "James")
  • API 不够简洁,例如:User.dao.findById(25) 方法,不妨可简化为:User.find(25)
  • 分页方法过于繁琐,例如:将 select 语句划分为两段,形成了两个参数

参考:

推荐指数:★ ★ ★

ar4j

这个框架是一个老外写的,我对它并不是太熟悉,只是从文档上大致学习了一下。

比较有特色的是,实体类无需扩展任何类,只需实现一个该框架提供的名为 ActiveRecord<T> 的泛型接口,我们需要将具体的 Entity 类型太填充这个泛型。就像这样:

<!-- lang: java -->
public abstract class Primary implements ActiveRecord<Primary> {
    ...
}

这里定义了一个名为 Primary 的实体类,实现了 ActiveRecord<Primary> 接口,此外,将该类设置为 abstract 的,因为无需在代码中 new 这个对象。

下面我们看看该框架是如何实现 find 操作的:

<!-- lang: java -->
IActiveRecordFactory factory = NamedSingletonActiveRecordFactory.getFactory();
Primary instance = factory.getActiveRecord(Primary.class);
instance.getContext().setDataSource(dataSource);

Map<String, Object> firstParameters = new HashMap<String, Object>();
firstParameters.put("code", "FIRST_PRIMARY");
Collection<Primary> firstResults = instance.find(firstParameters);

首先,我们得初始化一个 IActiveRecordFactory 工厂(接口),然后使用该工厂去获取相应的 AR 实例,也就是这里的 Primary 实体对象了。

然后,通过构造一个 Map,来填充查询条件,貌似这里的每个查询条件都是 and 的关系。

最后,携带那个 Map 参数来调用 Primary 实体对象的 find 方法,从而获取相应的集合对象。

这种方式感觉有些保守,从代码上来看,写得太多,不太优雅。说实话,我个人不太喜欢。

需要提醒大家的是,该框架在 2010 年就停止维护了,考虑使用该框架的同学需要想想了。

不管怎么说,还是要总结一下的:

亮点:

  • 实体无需扩展任何类,只需实现一个 ActiveRecord<T> 接口
  • 支持自定义数据类型转换
  • 方便与 Spring 集成

瑕疵:

  • 创建 Record 对象不太直接,需要借助 Factory 类
  • find 方法的参数过于繁琐,针对查询条件与分页参数
  • 不支持 OneToMany 等特性

参考:

推荐指数:★ ★

jActiveRecord(国产)

这个框架我是在 OSC Git 上发现的,当初是被它的 readme 文档所吸引,写得非常简洁,新手能在较短的时间内入门。

该框架为用户提供了三个实用类,分别是:DBTableRecord,可想而知,这三个类分别对应:数据库、表、记录,这样的定义方式让人非常容易接受,至少学过关系型数据库的同学们都知道这三个概念。

我们再来简单看看它的用法:

连接数据库:

<!-- lang: java -->
DB sqlite3 = DB.open("jdbc:sqlite::memory:");

添加:

<!-- lang: java -->
Table Zombie = sqlite3.active("zombies");
Zombie.create("name:", "Ash", "graveyard:", "Glen Haven Memorial Cemetery");
Zombie.create("name", "Bob", "graveyard", "Chapel Hill Cemetery");
Zombie.create("graveyard", "My Fathers Basement", "name", "Jim");

查询:

<!-- lang: java -->
Record jim = Zombie.find(3);
int id = jim.get("id");
String name = jim.get("name");
Timestamp createdAt = jim.get("created_at");

更新:

<!-- lang: java -->
Record jim = Zombie.find(3);
jim.set("graveyard", "Benny Hills Memorial").save();
jim.update("graveyard:", "Benny Hills Memorial");

删除:

<!-- lang: java -->
Zombie.find(1).destroy();
Zombie.delete(Zombie.find(1));

关联:

<!-- lang: java -->
Zombie.hasMany("tweets").by("zombie_id");
Tweet.belongsTo("zombie").by("zombie_id").in("zombies");

Record jim = Zombie.find(3);
Table jimTweets = jim.get("tweets");
for (Record tweet : jimTweets.all()) {
  ...
}

可见,代码可读性还是挺高的,而且也非常容易理解,这说明作者在 API 的设计上还是花了点功夫的,非常不错!

还支持多种实体之间的关联,绝对是有一定技术含量的,所以这一定是该框架的一大亮点。

该框架非常活跃,从 OSC Git 上观察到,最近作者提交过代码。

正所谓“人无完人”,对于框架也不例外,下面便是我对该框架的总结:

亮点:

  • 仅提供 DB、Table、Record 三个实用类,就能操作数据库
  • 支持多种数据库:MySQL、PostgreSQL、HyperSQL、SQLite
  • 支持一对多、多对一、多对多等关联

瑕疵:

  • 不支持 Oracle 数据库,需要自行扩展
  • 通过字符串来操作实体属性,当重构时会有风险
  • 需要通过 API 的手工方式来建立关联,不具备自动方式

参考:

推荐指数:★ ★ ★ ★

etmvc(国产)

其实我很早就关注过 etmvc 框架,因为它是一款轻量级 Java Web 开发框架。在该框架中,我学到了很多宝贵的经验,非常感谢作者的贡献与分享!

说起该框架的作者,也许大家非常熟悉,他就是著名的 jQuery Easy UI 的创始人,能把 Java 与 JS 玩得如此棒的人,我都非常地佩服,膜拜一下!

需要的是,对于 AR 而言,只是该框架中的一部分,但这部分绝对给它增加了不少分数。

还是先看看具体的使用方法吧:

首先,我们需要定义一个实体类:

<!-- lang: java -->
@Table(name="users")
public class User extends ActiveRecordBase{
    @Id private Integer id;
    @Column private String name;
    @Column private String addr;
    @Column private String email;
    @Column private String remark;
    //get,set...
}

需要注意的是,User 实体必须继承框架提供的 ActiveRecordBase 父类,这样才能拥有相关的 AR 方法。

然后,我们再来看看具体的 CRUD 操作:

插入:

<!-- lang: java -->
User user = new User();
user.setName("name1");
user.setAddr("addr1");
user.setEmail("name1@gmail.com");
user.save();

更新:

<!-- lang: java -->
User user = User.find(User.class, 3);
user.setRemark("user remark");
user.save();

删除:

<!-- lang: java -->
User user = User.find(User.class, 3);
user.destroy();

查询:

<!-- lang: java -->
List<User> users = User.findAll(User.class);
for(User user: users){
    System.out.println(user.getName());
}

多条件查询:

<!-- lang: java -->
List<User> users = User.findAll(User.class, "addr like ?", new Object[]{"%百花路%"});
for(User user: users){
    System.out.println(user.getName());
}

但貌似与 JFinal 存在类似的问题,那就是 find 方法用起来有些繁琐,比如:

<!-- lang: java -->
User user = User.find(User.class, 3);

如果能这样写就更加漂亮了:

<!-- lang: java -->
User user = User.find(3);

也就是说,find 方法里的第一个参数 User.class 是多余的,因为 find 方法已经是 User 类中的 static 方法了,是有办法获取 User.class 的。

与 JFinal 相同,多数据源在该框架中也是支持的。

此外,还提供了编程式事务,但框架自身没有提供声明式事务的支持。

有点特色的还有,可以在实体类上配置注解,来支持多种关联,相信用过 Hibernate 的同学一定不会感到陌生。

这个框架目前也不再维护了,最新版本的发布时间是 2009 年 12 月,虽然该框架真的非常优秀,但我还是要建议大家谨慎使用。

来对它总结一下吧:

亮点:

  • 支持多数据源
  • 使用类似注解来配置关联,支持一对多、一对一、多对一等
  • 支持回调方法

瑕疵:

  • find 方法不够简洁,例如:User.find(User.class, 3),不妨可简化为:User.find(3)
  • 不支持多对多关联
  • 仅支持编程式事务控制,不支持声明式或基于注解的配置

参考:

推荐指数:★ ★ ★ ★

ActiveJDBC

说起 ActiveJDBC,想必有些人听说过,因为该框架是最早开始用 Java 实现 AR 模式的,可以号称 Java 界里第一个吃 AR 这只螃蟹的人。

因为做得比较久了,项目得到了很好的沉淀,功能相当之多,建议大家可以看看它的官方文档。

有点特色的是,无需编写任何的实体属性,而只需继承一个框架提供的 Model 类即可。下面是一个实例类:

<!-- lang: java -->
public class Person extends Model {}

该框架可以自动扫描数据库的表结构,通过字节码增强的方式来修改实体类的 class 文件。

尤其是这样的链式方法,我个人是非常喜欢的:

<!-- lang: java -->
List<person> people = Person.where("name = ?", "John");
Person aJohn = people.get(0);
String johnsLastName = aJohn.get("last_name");
Paging through data
List<employee> people = Employee.where("department = ? and hire_date > ? ", "IT", hireDate)
    .offset(21)
    .limit(10)
    .orderBy("hire_date asc");

无需担心不同数据库的分页 SQL 语句的差异性了。

该框架的功能较多,特点也非常多,我还是简单总结一下吧:

亮点:

  • 无需编写任何的实体属性,只需继承一个 Model 父类,由框架来扫描数据库表结构,生成实体属性与关联
  • 提供链式的 find 方法调用,尤其是编写分页代码时非常优雅
  • 可使用注解定义缓存

瑕疵:

  • 通过字符串来操作实体属性,当重构时会有风险
  • 在实体上进行执行 SQL 查询,有些不太合适
  • 对于多对多关联,需要为中间表编写对应的实体类(Hibernate 不需要这样做)

参考:

推荐指数:★ ★ ★ ★

ActiveObjects

我是在 Google 里得知 ActiveObjects 这个开源项目的,貌似以前火了一段时间,至少在互联网上可以找到关于它的博文。

但让我非常理解的是,为何该框架官网也打不开?貌似作者不再维护了。更让我惊讶的是,我在 Atlassian 的开源项目中看到了该框架。可以推论出,该框架已经纳入 Atlassian 的体系架构了。

不知道算不算亮点,该框架竟然将实体定义为接口,而不是类。下面是一个实体接口:

<!-- lang: java -->
public interface Company extends Entity {
    // ...
}

这个接口需要继承框架提供的 Entity 接口。那么实体属性如何定义呢?

<!-- lang: java -->
public interface Company extends Entity {

    public String getName();
    public void setName(String name);
    
    public String getTickerSymbol();
    public void setTickerSymbol(String tickerSymbol);
}

看到以上这样的代码,我第一反应是,这些代码需要手写了,因为 IDE 几乎没办法自动生成这些 getter/setter 方法。

我们再来看看具体怎么用?

<!-- lang: java -->
EntityManager manager = new EntityManager("jdbc:mysql://localhost/test", "user", "password");
Company[] companies = manager.find(Company.class);

首先,我们需要创建一个 EntityManager 对象,然后,通过该对象去 find 出相应的实体数组。

这类风格好不好呢?反正我个人是不喜欢的。

说实话,我很少关注 Atlassian 的开源项目,他们的商业产品,比如:JIRA、Confluence 等,我还是非常喜爱的(尤其是破解版)。

亮点:

  • 将实体定义为接口,自身继承一个名为 Entity 的接口
  • 通过基于 Query 类的链式方法生成 SQL 语句
  • 支持自定义 ORM 映射规则

瑕疵:

  • 在实体接口中没有属性,只有属性的 getter/setter 方法,无法通过 IDE 自动生成
  • 查询操作均通过 EntityManager 来完成,与 ActiveRecord 的传统风格不太一致
  • 项目已归入 Atlassian 名下,成为 Atlassian Platform Common Components 的一部分

参考:

推荐指数:★ ★

Ebean

Ebean 是一位朋友推荐给我的,曾经听说过,但并没有关注过,一直认为它是一个小众框架。当我静下心来学习之后,才发现该框架真所谓“麻雀虽小,五脏俱全”啊!

有点类似于精简版的 Hibernate,它基于 JPA 接口,提供了根据实体自动生成 DDL 的功能,可以借助 Spring 强大的事务管理机制。总而言之,该框架我很喜欢!

要使用该框架,必须提供一个 ebean.properties 配置文件,做简单的配置即可使用,相应的配置请参考官方文档(见下面的参考部分)。

我们看看如何来插入一条记录?

<!-- lang: java -->
ESimple e = new ESimple();  
e.setName("test");  
e.setDescription("something");  
Ebean.save(e);  

通过 Ebean 这个实用类就能完成,这里不太像其他 AR 框架那样,直接在实体 ESimple 上调用 save 方法。不知道这算不算严格意义上的 AR 呢?

对于查询而言,同样也是面向 Ebean 类的:

<!-- lang: java -->
List<Order> list = Ebean.find(Order.class).findList(); 

以上代码貌似还可以再简洁一下,比如:

<!-- lang: java -->
List<Order> list = Ebean.findList(Order.class);

这样是不是更好呢?我认为没必要先调用 find 方法。

该项目最近(2014 年 4 月)才发布了 3.3.1 版本,可见还有是非常有生命力的。

稍微归纳一下吧:

亮点:

  • 基于 JPA 规范,依赖于 persistence.jar
  • 可以通过实体生成 DDL,类似于 Hibernate
  • 支持类似 Spring 的事务传播行为,并且能与 Spring 无缝集成

瑕疵:

  • find 方法不够简洁,例如:Ebean.find(Order.class).findList(),不妨可简化为:Ebean.findList(Order.class)
  • 链式风格的查询条件不太适合复杂情况,对于复杂的情况需要使用类似 SQL 的查询语言
  • 通过字符串硬编码的方式来连接查询,当重构时会有风险

参考:

推荐指数:★ ★ ★ ★

jOOQ

jOOQ 同样也是一位朋友推荐的,我花了一点时间学习了一下。发现学习成本不高,还是比较容易上手的。官网做得很漂亮,文档也非常丰富!

使用该框架,我们需要先定义数据库表结构,然后就是配置,最后该框架提供了一个代码生成器,我们只需通过 java 命令就能运行该代码生成器,最终为我们生成实体类 Java 源码。与 Hibernate 的使用过程正好相反,Hibernate 要求我们先定义实体类,然后通过实体类来生成数据库表结构。

对于用户而言,只需使用该框架提供的 API 即可完成底层的 SQL 操作,而无需编写具体的 SQL 代码。就像这样:

<!-- lang: java -->
DSLContext create = DSL.using(conn, SQLDialect.MYSQL);
Result<Record> result = create.select().from(AUTHOR).fetch();

我们必须提供一个数据库连接,也就是以上代码中的 conn 参数。第二行代码将执行一条 MySQL 的 SQL 语句:

<!-- lang: sql -->
select * from author;

需要补充说明的是,以上代码中的 AUTHOR 其实是来自于 test.generated.Tables.* 包,所以我们需要使用静态导入。此外,DSL 是来自于 org.jooq.impl.DSL.* 包的,同样需要静态导入。

<!-- lang: java -->
import static test.generated.Tables.*;
import static org.jooq.impl.DSL.*;

下面要做的就是遍历 result 对象了:

<!-- lang: java -->
for (Record r : result) {
    Integer id = r.getValue(AUTHOR.ID);
    String firstName = r.getValue(AUTHOR.FIRST_NAME);
    String lastName = r.getValue(AUTHOR.LAST_NAME);
    System.out.println("ID: " + id + " first name: " + firstName + " last name: " + lastName);
}

看起来貌似挺简单的,实际好不好用呢?那就需要在进一步的实践中慢慢体会了。

还有更复杂的 SQL 语句,都可以通过 Java API 的方式来表达,如下图(来自于官网):

jOOQ

需要指出的是,该框架仅提供基于 Java 代码的 SQL 操作,并没有提供事务管理框架,所以还是需要借助像 Spring 这样的框架来实现。

亮点:

  • 可读取配置扫描数据库,自动生成 Java 代码(与 Hibernate 正好相反)
  • 使用链式方法生成对应方言的 SQL 语句
  • 代码生成器可以定制

瑕疵:

  • 开发人员需要熟知表结构与之间的关系
  • 未提供事务控制,但可集成 Spring 的事务
  • 对于复杂 SQL 查询需要编写大量的 Java 代码

参考:

推荐指数:★ ★ ★ ★

其它

当然,基于 Java 的 AR 框架还有很多,下面补充几个,大家有空可以了解一下:

转载于:https://my.oschina.net/huangyong/blog/267968

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值