论坛首页 Java企业应用论坛

在Java 实现真正的富领域Model层

浏览 20913 次
精华帖 (10) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2012-08-07   最后修改:2012-08-15

Java 限于本身语法,是不敏捷的。是比较难缩减代码的。所以在其上会有很多语法改良型的语言,比如JRuby,Groovy,Scala等。这些语言是很容易实现类似Rails ActiveRecord的 ORM框架。

但是这意味这大量的学习成本。毕竟相当大一部分人还是靠Java吃饭。而且公司的环境也不一定会让你轻易去使用这些新型的语言。

但是Java真的做不到类似Rails ActiveRecord一样ORM模型吗,也就是我们前些年一直讨论的充血模型吗?非也。

(昨天该系统已经开源,https://github.com/allwefantasy/ServiceFramework  ^_^ )

假设我们有个标签系统。采用ORM的方式,你需要定义一些模型类。定义就定义吧。

public class Tag extends Model {
    @Validate
    private final static Map $name = map(
    presence, map("message", "{}字段不能为空"),
    uniqueness, map("message", "{}字段不能重复")
    );

    @OneToMany
    private List<BlogTag> blog_tags = list();

    @ManyToMany
    private List<TagGroup> tag_groups = list();
}
  
public class BlogTag extends Model {

    @ManyToOne
    private Tag tag;
}
public class TagGroup extends Model {
    @ManyToMany
    private List<Tag> tags = list();
}
public class TagSynonym extends Model {
    @OneToMany
    private List<Tag> tags = list();
}

初看模型,你可能会惊讶于代码之少,关联配置之简单。是的,上面就是我们对模型类所有的配置了。甚至,连属性都没有,更没有get/set方法,但是就是这些代码已经可以满足你80%的需求了。

模型关系介绍:

  1. TagGroup 和 Tag是多对多关系
  2. Tag和BlogTag是一对多关系。
  3. TagSynonym 和Tag 是多对一关系

(篇幅限制,回帖继续...)


   发表时间:2012-08-07   最后修改:2012-08-15

关联关系

框架支持标准的三种种关系。

  • OneToOne
  • OneToMany
  • ManyToMany

 

通常的ORM框架,比如Hibernate,关联关系配置相当不友好。比如

 

 

@Entity
@Table(name="EMPLOYEE")
public class Employee {
   @ManyToMany(cascade = {CascadeType.ALL})
   @JoinTable(name="EMPLOYEE_MEETING", 
    joinColumns={@JoinColumn(name="EMPLOYEE_ID")}, 
    inverseJoinColumns={@JoinColumn(name="MEETING_ID")})
  private Set<Meeting> meetings = new HashSet<Meeting>();
   } 
// Meeting Entity    

@Entity
@Table(name="MEETING")
public class Meeting {
  @ManyToMany(mappedBy="meetings")
   private Set<Employee> employees = new HashSet<Employee>();
}

 

 非常的复杂,你要配置主控端(通过mappedBy),你还要配置关联表,其实这一切都是不必要的

 

 

public class Tag extends Model {    
    @ManyToMany
    private List<TagGroup> tag_groups = list();
}


public class TagGroup extends Model {
    @ManyToMany
    private List<Tag> tags = list();
}
 

传统的进行关系操作是比较复杂的。比如多对多关系,如果你添加一个关系,你需要 这样做:

 

tagGroup.getTags().add(tag);
tag.getTagGroups.add(tagGroup);
tag.save();
 

另外你可能还需要小心主控端。因为某些情况只有对主控端持久化,才会将关联关系(外键)设置好。

但其实有更优雅的做法,

 

 tagGroup.associate("tags").add(tag);

 这个时候tag与tagGroup的关系就已经建立在中间表了。相应的

 

 

 tagGroup.associate("tags").remove(tag);
 

会删除中间表相关的记录。 你也可以用 将tagGroup添加到tag的tagGroups中。效果是一样的。这说明你不需要区分主控端。

 

 

表单填充模型类

一行代码就能解决:

 

Tag tag = Tag.create(params());

 params() 会返回一个表单的map对象。表单通常无非键值对。

对于那种表单字段名和模型类字段名不一致的情况,也是非常容易,

 

Tag tag = Tag.create(selectMapWithAliasName(params(),"tag_name","name"));

 

 selectMapWithAliasName会将tag_name 替换成name.其他不变。

代码提示

我们可以看到 “tags“ 就是我们定义在TagSynonym 中的一个属性。为了获得IDE提示的好处, 你可以把上面那段代码写进你的模型类中。框架会去实现里面具体的细节。

 

public class TagSynonym extends Model {
    @OneToMany
    private List<Tag> tags = list();
    public Association tags(){throw new AutoGeneration();}
}
 

现在假设我们要获取一个同义词组所有的d>10的tag,我们可以这么做

 

List<Tag> tags = tagSynonym.tags().where("id>10").fetch(); 
 

当然,你依然可以写成

 

List<Tag> tags = tagSynonym.associate("tags").where("id>10").fetch(); 
 

结果是一样的。对于这种只是为了代码提示而创建的方法,我们推荐方法内部 填充 'throw new AutoGeneration()'来标记它会被框架自动实现。虽然,即使它不存在,系统也会创建它。

经过上面的例子可以看出模型的关联关系可以给我们带来很多便利。

 

 


0 请登录后投票
   发表时间:2012-08-07   最后修改:2012-08-08

 

查询接口

 

为了高效,规范化的操作数据库, 框架提供了众多的查询方法. 每个查询方法允许你传递参数执行特定的查询而不需要你写令人烦躁的sql语句。

方法列表:

  • where
  • select
  • group
  • order
  • limit
  • offset
  • joins
  • from

以后我们会继续完善。添加更多方法,譬如 lock,having等。

从这些关键字可以看出,这些方法基本是以Sql关键字为基础的。所有这些方法最终返回的是JPQL对象(ServiceFramework内部组装sql语句的一个类)。

1.1 根据ID获得对象

 

Tag.findById(10)
//或者
Tag.find(10)
 

1.2 根据多个ID获取

 

Tag.find(list(1,2,,4,5))
 

1.3 条件查询

 

Tag.where("id=:id",map("id",7)).fetch();
 

map 是一个创建Map的一个便利方法。

你也可以使用一个更复杂的例子:

 

Tag.where("tag_synonym=:tag_synonym",map("tag_synonym",tag_synonym));
 

还记得之前提到的,对象关联关系的建立,可以方便框架进行一些对象化的操作。在Tag中tag_synonym是一个对象属性,你可以直接在where中使用该属性。 他会转为为类似:

 

select * from Tag where tag_synonym_id=? 
 

因为对象关联模型告诉了系统那个是外键。这不会带来任何性能方面的损耗。

1.4 order

 

Tag.order("id desc")
 

或者

 

Tag.order("id desc,name asc")
 

1.5 joins

joins 语法也是对象化的,这也得益于我们之前简单的模型关系声明。你所操作的就是相应的模型属性。不管简单属性还是对象属性。

 

Tag.joins("tag_synonym").fetch();
 

那么 tag对象的tag_synonym 属性会自动得到填充。这不会有n+1问题。因为一条SQL语句就搞定了

你也可以join多个属性

 

Tag.joins("tag_synonym left join  tag_groups left join blog_tags").fetch();
 

当然,对于互联网应用,这么多join毫无疑问会拖垮你的数据库。我们只是举个例子,你不应该这么做。

1.6 offset,limit

 

Tag.offset(10).limit(15);
//这相当于
select * from Tag limit 10,15;
 

1.7 select

这通常用于你不想获取所有的字段的场合

 

 List<Object[]> results =Tag.select("name").fetch();
 

这通常返回是一个数组。当然,如果你想让它填充进一个模型也是可以的。

 

 List<Tag> results =Tag.select("new Tag(name)").fetch();
 

需要注意的是,你需要在Tag填充一个相应的构造方法。希望不久就能去掉这个限制。嗯,应该尽力去掉。

1.8 group 说实话,真不应该提供这个,性能杀手。不过还是提供了….

 

Tag.where("id>10").group("name").fetch();
 

Name_Scope

假设tag需要审核。只有审核通过的才应该被查询出来。如果每次查询的时候都要加这个条件岂不是 太麻烦?我们可以定义一个方法:

 

@Entity
public class Tag extends Model {
    public static JPQL active(){
      return where("status=1");
    }
}
 

之后你就可以这么用了

 

Tag.active().where("id>10").join("tag_groups").offset(0).limit(15).fetch();
 

模型方法

一旦你定义了模型类,那么该模型类会自动拥有众多的方法。一些静态方法:

    Tag.create(map)
    Tag.deleteAll()
    Tag.count()
    Tag.count(String query, Object... params)
    Tag.findAll()
    Tag.findById(Object id)
    Tag.all() 
    Tag.delete(String query, Object... params)

    Tag.where(String whereCondition, Object... params)
    Tag.join(String join)
    Tag.order(String order)
    Tag.offset(int offset)
    Tag.limit(int limit)
    Tag.select(String select)

一些实例方法

    tag.save()
    tag.valid()
    tag.update()
    tag.refresh()
    tag.delete()

框架还会为你生成很多你看不见的"模型实例方法"。你需要特定语法去调用他。这里使用"m" 方法。 这主要针对关联关系。 对于类似这种申明:

 

@ManyToMany
private List<Tag> tags = new ArrayList<Tag>();
 

那么你能获得tags方法。

 

tagGroup.m("tags",Tag.create(map("name","jack")));
 

这段代码的含义是,调用tags方法,该方法接受tag实例作为参数。实际上tags方法等价于下面的方法(只是你看不到这个方法,但是能通过"m”调用他)

 

public TagGroup tags(Tag tag){
       this.tags.add(tag);
       tag.getTag_groups().add(this);
       return this;
  }
 

配置了关联关系的字段都会自动生成一个同名的方法,通过调用他们,会自动将对象之间的关联关系设置好,从而可以直接使用包括级联保存等ORM特性。

 

Validator

框架应该提供一个声明式的validator语法。

 

@Validate
    private final static Map $name = map(
         presence, map("message", "{}字段不能为空"),
         uniqueness, map("message", "{}字段不能重复")
    );
 

验证器:

  • presence 值不能为null或者空
  • uniqueness 值具有唯一性
  • numericality 是数字,且可以设置范围
  • format 正则
  • associated 关联对象验证
  • length 长度校验

 

    你可以显式调用一个模型的valid()方法。你也可以直接调用save()方法。该方法返回boolean.false代表没有通过验证。 验证结果你可以通过直接使用模型的validateResults属性获取。

     

    if(!tag.save()){
       render(HTTP_400,tag.validateResults);
     }
    
     //或者
    
     if(tag.valid()){
       tag.save();
     }
     

    对于save方法,你也可以跳过验证

    tag.save(false)
    

    参数 false 表示不需要验证就进行保存。

    1.1 prensence

     

    @Validate
    private final static Map $name = map(presence, map("message", "{}字段不能为空"));
     

    1.2 uniqueness

     

    @Validate
    private final static Map $name = map(uniqueness, map("message", ""));
     

    1.3 numericality

     

    @Validate
    private final static Map $id = map(numericality, map("greater_than",10,"message":""));
     

    拥有的选项为:

    • greater_than
    • greater_than_or_equal_to
    • equal_to
    • less_than
    • less_than_or_equal_to
    • odd
    • even

    1.4 length

     

    @Validate
    private final static Map $name = map(length, map("minimum",10));
     

    拥有的选项:

    • minimum
    • maximum
    • too_short
    • too_long

    0 请登录后投票
       发表时间:2012-08-07   最后修改:2012-08-15
    回调其实非常有用,你可以使用标准的JPA回调注解。但是我们依然希望你使用我们替你增强过的回调

    @BeforeSave
    @AfterSave
    @BeforeUpdate
    @AfterUpdate
    @BeforeDestory
    @AfterLoad

    
    public class Tag extends Model {
        @AfterUpdate
        public void afterUpdate() {
            findService(RedisClient.class).expire(this.id().toString());
        }
    

    需要注意的是,接受注解的方法必须没有参数。 ServiceFramework 任何一个模型类都能通过findService 方法获得有用的Service,Util服务。例子中 当更新一个对象的时候,我们就让redis缓存中的对象过期。

    在回调中你依然可调用模型类进行持久化操作。但是需要注意的是

    不能对本身进行相关的持久化,更新操作。但是可以进行查询动作。
    回调函数被包装在一个事务中,执行完后会被立即提交
    
    public class Tag extends Model {
        @AfterUpdate
        public void afterUpdate() {
            BlogTag.create(map("object_id",10)).save();
        }
    


    其实,从上面的介绍可以看出,ServiceFramework的Model层是真正富领域模型。关于数据库大部分逻辑操作都应该定义在model层。 当然,Service层依然是需要的。DAO层则被完全摒弃了。通常我们建议,对model调用 可以直接在controller中。而Service则提供其他服务,譬如远程调用,复杂的逻辑判断。当然, 我们完全赞同在Service里调用model。这样对事物也具有较好的控制。
    0 请登录后投票
       发表时间:2012-08-07  
    其实就是用dao.save(obj);还是obj.save() 对象自己保存自己的问题...
    0 请登录后投票
       发表时间:2012-08-07  
    这是是什么框架?
    0 请登录后投票
       发表时间:2012-08-07  
    没看明白,到底想说明啥。。
    0 请登录后投票
       发表时间:2012-08-07  
    没看出来哪地方是富领域model层了,说的某一个的orm框架吧或对象sql工具,跟领域扯不上边,
    领域是基于具体业务的,不是简单的CRUD放在model就完了
    0 请登录后投票
       发表时间:2012-08-07  
    就是一个ORM而已啊,跟领域模型没啥关系
    0 请登录后投票
       发表时间:2012-08-08  
    olivechinese 写道
    这是是什么框架?

    呵呵 就是想问问 如果某个框架的模型层按上面的设计,你会考虑用这个框架吗?
    0 请登录后投票
    论坛首页 Java企业应用版

    跳转论坛:
    Global site tag (gtag.js) - Google Analytics