论坛首页 Java企业应用论坛

[讨论]Java中的ActiveRecord实现(我实现了一点儿)

浏览 5356 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2007-01-16  
感觉如果能开发出一部分功能,还是挺有用的。根据我自己的理解,下面做一个简单的分析:

Better Convention than Configuration

这个肯定可以做到的吧,而且我觉得Hibernate应该官方支持这个特性,也许就在不久的将来。

对象属性的自动创建

这个肯定做不到,语言没这个能力。看过一些框架用xx.getProperty("id")这样的方式实现,有点儿东施效颦的感觉。而且不自动创建属性也有它的好处:比如在IDE中使用代码提示,可以通过编译发现错误使用的属性(通常是拼写错误,在自动实时编译的IDE中基本不可能会出现)。可能有人会说这个好处很小,但那是从Ruby的哲学上看的;如果从Java上看,这还是挺好的。

CRUD方法的自动生成

这个东东要讨论的比较多。首先,最基本的CRUD肯定是可以生成的,但是find_by_id_and_name这样的肯定不行。但是我觉得后一种方法的用处也不是很大,最关键的是要解决好基本CRUD操作中参数传递的能力和方式。Java 5里面已经可以传入不定参数了,这个特性肯定可以好好利用。但是Java中没有"id"=>"001"这样方便的构造pair(hash)的方法,所以参数传递的方式肯定会比RoR麻烦很多。Query Object提出了用一个Object封装所有查询条件的模式,虽然确实比较灵活,但是使用起来很不方便。所以,如何传递查询条件是需要仔细讨论的一个话题。

我自己的一点儿实现

我自己按照上面的思想实现了一点点东东:自动map简单的对象(不包括一对一、一对多关系),自动创建基本的CURD。大致的使用过程如下:
  1. 所有的Model都继承一个Base类,这个Base类定义了static的CURD方法,在Base中,方法体是空的。
  2. 程序执行时,框架会找到所有继承Base的类,使用预定义的convention方式创建mapping信息并配置Hibernate。mapping信息不会出现在xml文件中,而是动态生成动态载入的。这样就没有配置了。
  3. 框架为上面找到的所有Model类,再byte code级别生成每个Model的CRUD方法,并写入相应的.class文件。
  4. 程序执行Model.find(...),这是执行的是先前生成的byte code,而不是通过反射,所以没有性能问题。
java 代码
 
  1. public class Base {  
  2.   
  3.     public static List find(Object... args) {  
  4.         return null;  
  5.     }  
  6.   
  7.     public static Object create() {  
  8.         return null;  
  9.     }  
  10.       
  11.     // other CRUD methods ...  
  12. }  
java 代码
 
  1. public class Model extends Base{  
  2.   
  3. }  
java 代码
 
  1. import java.util.List;  
  2.   
  3. public class Test {  
  4.   
  5.     public static void main(String[] args){  
  6.         List<Model> models = Model.find(args);  
  7.     }  
  8. }  
这样的话,CRUD操作就和RoR的感觉非常相似了。当然,前面说过的查询条件如何传入的问题还是没有解决。

目前的问题

目前在开发中遇到了一个问题,一直没有解决办法,说出来大家一起分析。自动生成CRUD部分我使用的是byte code generation。这样做是有原因的。
  1. 因为是static方法,所以除了AspectJ,其他的AOP实现都不能拦截。而我不想引入AspectJ(独立的编译器)。
  2. source code generation:主要有两个问题,如何merge自动生成的代码和用户编辑的代码,还有就是我不想引入源代码,真正的magic应该是没有源代码就能执行,而不是源代码生成。
  3. 反射:Java对static method的反射支持少的可怜,可以说,用反射根本就无法实现。
然而,byte code generation也有它的问题,抛开开发困难不说。现在最大的问题是,程序生成的byte code无法实时加载到jvm中。也就是说,jvm一旦加载了Model,我再修改它的byte code,在当次运行下是没用的,要下次运行才能反映我的修改。即jvm不能reload class文件。

有人可能会说ClassLoader可以reload或redefine,这个是当然的,但是reload或redefine后得到的是一个Class类型的对象,我要是用反射的写法才能执行其上的method,而不能简单的使用Model.find(...),这样就不是我们需要的CRUD了。


写这片文章有三个目的:
  1. 请各位看看我的思路,提些建议。
  2. 讨论一下查询条件如何传递的问题,最好是即方便又灵活。
  3. 运行时reload class文件的问题,如何可以马上反映出我对byte code的修改。
   发表时间:2007-01-16  
引用

因为是static方法,所以除了AspectJ,其他的AOP实现都不能拦截。而我不想引入AspectJ(独立的编译器)。

确实对开发工具的依赖越少越好.
引用

source code generation:主要有两个问题,如何merge自动生成的代码和用户编辑的代码,还有就是我不想引入源代码,真正的magic应该是没有源代码就能执行,而不是源代码生成。

JSR 269 系的设计思路主要是生成新的类和代码, 而不是去修改已有类代码, 我觉得这个对其它的代码生成机制也有借鉴意义.
引用

反射:Java对static method的反射支持少的可怜,可以说,用反射根本就无法实现。

Java是 静态类型 的 OO 语言, 这方面就不要想了.
你可以写成 Model.find(...); 但是编译器会把它翻译成 Base.find(...); 编译出来的代码里不会有 Model 或者其它子类的痕迹.
引用

请各位看看我的思路,提些建议。

可以参考 TOB 的一些思路, 目前只有apidoc是全的: http://tob.ableverse.org/apidoc/
引用

讨论一下查询条件如何传递的问题,最好是即方便又灵活。

TOB是这样:

<T extends TheObject> List<Persistent<? extends T>>
query(long start, long max, Class<T> klass, String clause, Object... args)
          Query on class klass and all its direct and indirect subclasses for persistent objects matching criteria specified by clause.

实际使用时可以这样写:
tob.querier.query(0, -1, Book.class, 
  BookQ.NAME + " LIKE %?% AND " + BookQ.PRICE + " < ? " +
  " AND " + BookQ.AUTHOR$NAME + " = ? "
  " ORDER BY " + BookQ.PRICE,
  searchName, maxPrice, authorName);

其中 BookQ 是编译时自动生成的查询条件类.

用类似命名参数的方式的话, 可以类似这样:
public class Simple
{
    /**
     * {name} was {age} {! age>1 ? "years" : "year"} old in year {year}.
     * 
     * @arg name : name of the person
     * @arg age = 26 : his/her age in that year
     * @arg year = 2002 : the year
     * @usage This is a demo message declaiming one person's age in a past year.
     */
    @Msg
    private static final String YEAR_AGE = "yr-age";

    public static void main(String[] args)
    {
        Messager msgr = Messager.get();

        System.out.println(msgr.format(YEAR_AGE, "name", "Peter", "year", 2002,
                "age", 26));

        msgr.info(YEAR_AGE, "age", 22, "year", 1998, "name", "Peter");
    }
}

Messager里用来解析变参的是这样一个类, 效率上应该基本优化到极限了:
/*
 * Copyright (c)2004-2006 Compl Yue Still, All Rights Reserved.
 */

package av.util;

import java.util.Arrays;

/**
 * This is a simple readonly map that takes an array of object in pairs. The
 * object at even index (odd position) is interpreted as the key, and the next
 * one at odd index (even position) is interpreted as the value, i.e.
 * <p>
 * <code>
 * new BinMap("name", "Peter", "year", 2006, "age", 26);
 * </code>
 * <p>
 * A binary index based on <code>key.hashCode()</code> will be constructed so
 * looking up gets optimized through binary search algorithm.
 * <p>
 * And this class benifits from operations on <code>long</code> type, so will
 * perform better in 64-bit platforms.
 * <p>
 * In fact, this class is providing fast lookups for
 * {@link av.msg.Messager#format(String, Object[])} and simular methods those
 * take a var arg list for key-value pairs.
 * 
 * @author Compl
 * 
 */
public class BinMap
{
    private Object[] content;

    private long[] index;

    public BinMap(Object... content)
    {
        assert content.length % 2 == 0 : "content must be in pairs";
        this.content = content;
        index = new long[content.length / 2];
        for (int i = 0; i < index.length; i++)
        {
            index[i] = (((long) content[i + i].hashCode()) << 32) + (i + i + 1);
        }
        Arrays.sort(index);
    }

    public Object get(Object key)
    {
        long k = ((long) key.hashCode()) << 32;
        long vk;
        int left = -1, right = index.length;
        for (int i = (left + right) / 2; (left < i) && (i < right);)
        {
            vk = (index[i] & 0xFFFFFFFF00000000L);
            if (k == vk)
            {
                int pos = (int) (index[i] & 0xFFFFFFFFL);
                if (content[pos - 1].equals(key)) return content[pos];
                for (int j = i + 1; j < right; j++)
                {
                    if (k != (index[j] & 0xFFFFFFFF00000000L)) break;
                    pos = (int) (index[j] & 0xFFFFFFFFL);
                    if (content[pos - 1].equals(key)) return content[pos];
                }
                for (int j = i - 1; j > left; j--)
                {
                    if (k != (index[j] & 0xFFFFFFFF00000000L)) break;
                    pos = (int) (index[j] & 0xFFFFFFFFL);
                    if (content[pos - 1].equals(key)) return content[pos];
                }
                return null;
            }
            else if (k > vk)
            {
                left = i;
            }
            else
            // (k < vk)
            {
                right = i;
            }
            i = (left + right) / 2;
        }
        return null;
    }

    /**
     * test this class.
     * 
     * @param args
     */
    public static void main(String[] args)
    {
        Object[] content = new Object[] { "Number 1", 1, "String 9", 9,
                "Number 5", 5, "Number 2", 2, "String 7", "7", "Number 3", 3, };
        BinMap bm = new BinMap(content);
        for (int i = 0; i < content.length; i += 2)
        {
            Object key = new StringBuffer(content[i].toString()).toString();
            Object val = bm.get(key);
            System.out.println(key + "=" + val);
            System.out.println(key + "_non" + "=" + bm.get(key + "_non"));
        }
    }
}


引用

运行时reload class文件的问题,如何可以马上反映出我对byte code的修改。

这个在JVM上实现可能还是比较脆弱的, 真想走下去, 可能应用方面免不了要作出牺牲性的改变, 或许 BeanShell 的代码会有些借鉴意义.
0 请登录后投票
   发表时间:2007-01-16  
以RoR的高标准作为起点我觉得是太高了。在RoR和流行的Java开源框架对比来说,我觉得在ORM这一块来说,Java做的还是相当不错的,从实际开发角度来说,主要还是Web层的开发过于烦琐,和RoR差距比较大,对比而言,ORM的烦琐程度不算太多。

Hibernate的annotation已经可以抛弃XML,还能带有编译期检查,我觉得不错,没有必要和RoR看齐;

查询语言方面HQL我用得也挺好(除了生成的SQL让人看了恶心之外);

动态查询条件参数传递可以用Hibernate DetechedCriteria,这个也不是问题;

ORM是比ActiveRecord烦琐,但我觉得可以接受,其实Hibernate从功能上来说要比AR强大的多。

IoC这一块不好弄,RoR不需要IoC,也不需要事务横切,也不需要管理数据库资源,所以业务逻辑可以直接写在Model里面,绝对的Rich Domain Model,简单说就是Java的PO,DAO,BO而为一体。但是Java的持久对象做不到,不能脱离IoC,不能脱离事务横切,也不能把所有的业务逻辑都放在Domain Model里面。

在Web这一块,差距也忒大了,rails1.2的REST都出来了,而Java Web框架还没有一个能够很好的实现Restful URL映射的,Struts2.0能把XML砍掉就谢天谢地了。
0 请登录后投票
   发表时间:2007-01-16  
不使用AspectJ、Refleaction和Source Code Generation,这个基本上通过了。

complystill 写道

引用

请各位看看我的思路,提些建议。

可以参考 TOB 的一些思路, 目前只有apidoc是全的: http://tob.ableverse.org/apidoc/
引用

讨论一下查询条件如何传递的问题,最好是即方便又灵活。


TOB的方法会不会太脆弱了?还有,这只是where子句,如果是order by,group by,paging,aggregation之类的东东该咋办...
complystill 写道

引用

运行时reload class文件的问题,如何可以马上反映出我对byte code的修改。

这个在JVM上实现可能还是比较脆弱的, 真想走下去, 可能应用方面免不了要作出牺牲性的改变, 或许 BeanShell 的代码会有些借鉴意义.

Java 5中有个Instrument机制,应该可以在main函数运行之前,先运行个premain函数,我可以在premain里面修改byte code。但是这需要使用额外的运行参数,这样一来,除了每次运行时要加参数,Junit做测试时也要加参数,启动tomcat也要加参数,所以不可取。

Java 6中增强了Instrument,提出可以不在运行时加参数,直接在main函数前执行一个类似premain的agentmain函数。但是,这个功能是可选的,Sun JDK没实现,它说每个vendor可以选择实现,我就faint了!
0 请登录后投票
   发表时间:2007-01-16  
robbin 写道
以RoR的高标准作为起点我觉得是太高了。

我觉得应该向高标准看齐,只是在具体实现的时候,实在由于无法实现,才应该降低标准。

robbin 写道
在RoR和流行的Java开源框架对比来说,我觉得在ORM这一块来说,Java做的还是相当不错的,从实际开发角度来说,主要还是Web层的开发过于烦琐,和RoR差距比较大,对比而言,ORM的烦琐程度不算太多。

web方面我还是期待struts2好了,2.02版本里面说是有RESTful API的支持了。至于ORM这里,Java领域从功能上看确实做得不错,而且比RoR要强,但是用起来方便我却不这么觉得。我想每个人(或组织)手里都应该有一个封装好的Hibernate CRUD操作实现库之类的东西吧,比如Manager.find(Model.class, ...)。

另外再说个其他问题,我用Spring 2.0里面的HibernateTemplate的save方法,本以为就可以save到数据库了,但是却不行。我要用execute,然后自己声明事务。如果说对于write操作,事务是必须的,那么提供一个save方法做什么?只是把对象加到session的cache中吗?那也太不直观了,我就不明白。
robbin 写道
Hibernate的annotation已经可以抛弃XML,还能带有编译期检查,我觉得不错,没有必要和RoR看齐;

是有了annotation,但是还是要配置一些(如果不是很多)东东啊。

robbin 写道
查询语言方面HQL我用得也挺好(除了生成的SQL让人看了恶心之外);

按照你的意思,我夸张点儿说,那我就写个execute方法,让调用者传HQL咯?

robbin 写道
动态查询条件参数传递可以用Hibernate DetechedCriteria,这个也不是问题;

这个我看了,大概形式是:session.xxx(param).xxx(param).xxx(param).xxx(param).xxx(param)....xxx(param);我还是觉得不爽。

robbin 写道
ORM是比ActiveRecord烦琐,但我觉得可以接受,其实Hibernate从功能上来说要比AR强大的多。

强大当然强大,但是大家不是都希望更简单嘛,我这里讨论的也是简单。
robbin 写道
IoC这一块不好弄,RoR不需要IoC,也不需要事务横切,也不需要管理数据库资源,所以业务逻辑可以直接写在Model里面,绝对的Rich Domain Model,简单说就是Java的PO,DAO,BO而为一体。但是Java的持久对象做不到,不能脱离IoC,不能脱离事务横切,也不能把所有的业务逻辑都放在Domain Model里面。

这里还没想好,只能边做边看。

robbin 写道
在Web这一块,差距也忒大了,rails1.2的REST都出来了,而Java Web框架还没有一个能够很好的实现Restful URL映射的,Struts2.0能把XML砍掉就谢天谢地了。

我要是牛我就写web去了,不牛才在ORM这边叨咕,哈哈。
0 请登录后投票
   发表时间:2007-01-16  
AllenYoung 写道

TOB的方法会不会太脆弱了?还有,这只是where子句,如果是order by,group by,paging,aggregation之类的东东该咋办...

开始一下子没写全, 刚才更新了一下加入了 ORDER BY 和 相关类字段的条件语法, 你再看一下.

另外TOB上的持久应用Java代码和传统数据库的存储过程性能是相当的, 基于SQL的查询通常只用作第一步的过滤, 更复杂的过滤/处理完全可以用Java代码来实现, 这时候就不要返回 List, 而是用这个:

<T extends TheObject> void query(PersistentObjectProcessor<T> processor, long max, Class<T> klass, String clause, Object... args)
          Query on class klass and all its direct and indirect subclasses for persistent objects matching criteria specified by clause.

在实现 PersistentObjectProcessor<T> 的类代码里过滤和处理.

AllenYoung 写道

Java 5中有个Instrument机制,应该可以在main函数运行之前,先运行个premain函数,我可以在premain里面修改byte code。但是这需要使用额外的运行参数,这样一来,除了每次运行时要加参数,Junit做测试时也要加参数,启动tomcat也要加参数,所以不可取。

Java 6中增强了Instrument,提出可以不在运行时加参数,直接在main函数前执行一个类似premain的agentmain函数。但是,这个功能是可选的,Sun JDK没实现,它说每个vendor可以选择实现,我就faint了!

类似的机制基本都是为了 Profiling 设计的, 主要目的可能还是为了第三方调试程序以及分析瓶颈, 热点等. 不是为了生产执行用的, 如果生产环境也必需启用它们的话, 对性能的影响应该是无法避免的. 而且挂上去的扩展程序不像 Annotation Processors 那样可以多个并存, 同时生效, 排他性很强, 会造成跟第三方优化分析工具的不兼容.
0 请登录后投票
   发表时间:2007-01-16  
唉...我现在就是被block在这个地方了,深感Java的不灵活啊!
0 请登录后投票
论坛首页 Java企业应用版

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