`
icewubin
  • 浏览: 34865 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Hibernate的POJO到JSON序列化过程的讨论

    博客分类:
  • Java
阅读更多
    一年前的代码,最近写了点文档share大家,有点乱,这个乱主要指夹杂了些我们公司开发框架的一些东西。
    如果看到错误或者莫名其妙的地方还请指出,包括错别字。
    此文纯属个人观点,欢迎讨论。

论坛关联的地址:
http://www.iteye.com/topic/296467

我认为的“智能”的JSON序列化方案主要包含如下特点:
1.要像JSON-lib那样能够利用反射和递归,自动遍历对象图,同时并序列化,这个要求JSON-lib已经满足。
2.默认情况下,所有的简单对象(非Hibernate代理对象)都能够自动序列化,利用JSON-lib就能实现。
3.所有的Hibernate代理对象自动屏蔽。
4.提供足够灵活和足够简单的参数方式,有选择的序列化指定的Hibernate代理对象。
5.对Hibernate多对一的代理对象提供足够的支持,能够直接抓取外键ID而不触发多余的sql,这个多对一的性能问题可以参考自我的另外一帖的分析:
【Hibernate 3.2中annotation注释的位置对性能的巨大影响】
http://www.iteye.com/topic/212236

    有兴趣的人参看附件中的源代码。

    先介绍JSON的基本概念,至于序列化成JSON的目的后问会详细叙述。JSON:JavaScript Object Notation,是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScript(Standard ECMA-262 3rd Edition - December 1999)的一个子集。
    来看一个NodeUser的部分属性的最简单的例子:
{"nusId":"402892891b209674011b209681530009","nusCreateTime":"2008-05-06 20:21:34"}

    可以理解为一个map结构的数据结构,一个key(属性名),一个value(属性值),只不过都是文本类型。
看一个数组或者Collection转换成JSON后的例子:
[
    {"nusEmail":"john@gmail.com","nusStatus":{"code":"1040-1010","name":"身份证"}},
    {"nusEmail":"david@hotmail.com","nusStatus":{"code":"1040-1020","name":"军人证"}}
]

    就是用中括号来表示多个对象的集合。在这例子中还出现了对象的嵌套,nusStatus是一个枚举对象,之前已经介绍过,只有code和name两个属性,从数据库读取数据以后,只有code有值,name的值一开始是空的,随意JSON序列化的过程除了把Java对象转换成JSON文本以外,还要把所有的枚举类中的name的值填上,最后才把序列化好的JSON字符串传递到客户端浏览器,JSON通讯策略后文会分析。

    分析一个性能问题:JSON序列化和Hibernate放在一起使用其实就是一个数据抓取的过程,因为JSON序列化的时候遍历的的是Hibernate生成的实体类的每一个属性,表面上看访问的是属性,但是实际上是Hibernate生成的代理对象,调用属性的get方法的过程某种意义上来说就是一个数据抓取的过程。Hibernate的性能问题绝大部分都是出现在数据抓取的粒度和深度问题。所以JSON-lib的工具类net.sf.json.JSONObject和net.sf.json.JSONArray是不能满足本框架的需求的,或者说是不能直接和Hibernate结合使用的。
    一般大多数的JSON序列化的做法是在每一个实体类中实现一个toJSON()的方法来直接提供序列化的支持,我认为这种做法是非常的有问题的,实体类的结构是要充分利用的,也就意味着一个实体类的数据抓取粒度和深度在不同场景下是完全不一样的,不可能用一个toJSON()方法就能完全搞定。再有就是JSON序列化属于和Ajax客户端通讯是才使用的机制,把Ajax通讯相关的代码写在实体类中,从软件分层角度上来说也是非常不合适的。

    举一个抓取粒度和深度不同的例子:先参考前文描述的NodeEducation和NodeUser的多对一的关系。第一个场景是,需要列出所有教育背景的基本信息。第二个场景是,列出所有教育背景的基本信息,外加所属用户的姓名,这个需求下,数据抓取一定会牵涉到NodeUser所对应的表,这两个场景的数据抓取策略肯定是不一样的。

    介绍JSONConvert类中的三个方法:
    modelCollect2JSONArray方法,把Collection转换为JSONArray对象,实现方式就是遍历需要转换的Collection,针对每一个model再次调用另一个专门转换model方法,最后返回JSONArray对象。

    model2JSON是最重要的方法,把model转换为JSON对象,凡是实现JSONNotAware的不序列化,按照get方法得到属性信息,默认对所有可能引起的Hibernate查询数据的sql的操作都会跳过,目的是减少不必要的延迟加载,如果一定要要抓取的话,则把需要序列化的名字通过jsonAwareArray传入。

    getAwareSubList方法是用来处理每一级的递归需要的字符串处理,从原始的jsonAwareList中查找前缀为name的,去除前缀后,放到新的List中返回。

    结合一个具体的例子详细分析如下,先不考虑分页:

    场景一:需要列出所有教育背景的基本信息。

    NodeEducationService中代码如下,因为利用的BaseManager,所以NodeEducationManager针对这个查询中没有特别的代码:
    public List findList() {
        return nodeEducationManager.findList();
    }

    Ctrl类最终会间接的调用modelCollect2JSONArray(nodeEducationList),Ctrl的代码规范和运行过程后文会详细介绍,这里的list就是List,modelCollect2JSONArray遍历每一个元素,调用model2JSON(nodeEducation),这个方法会根据反射的结果遍历所有的nodeEducation的所有method。为什么是遍历方法,而不是遍历属性是有重要原因的,例如一个文件类,只保存了完整的文件名的这一个属性,但是提供了很多get方法,有获得文件名前缀、后缀的等等,虽然页面上编程也能达到同样的效果,但是实体类中既然已经提供了这样便利的方法,就应该充分利用,就应该根据get方法造两个属性“文件名前缀”、“文件名后缀”出来,这样能充分减少页面上的编程工作量。

    继续分析序列化单实例的过程,在遍历所有的方法时,会判断当前方法是否符合getMethod的特征,符合的才序列化这个getMethod对应的虚拟属性,因为这个属性未必真实存在。此时还会过滤一些明显不需要序列化的get方法,例如getClass方法、或者是getMethod返回类型是Document.class(XML Document对象)、byte[].class(二进制文件对象)、Logger.class(系统日志对象)、LazyInitializer.class(Hibernate代理对象中的延迟加载属性),这些都是明显不需要序列化的对象。

    然后继续判断,如果getMethod返回格式是Calendar和Date格式,则调用相应的转换方法,转换成统一的字符串格式,如果是枚举类格式,则调用dictionaryFactory中的setName方法,根据枚举类的code找到对应的name,并赋值到给这个枚举类的name。、

    如果getMethod返回格式是JSONNotAware,说明遇到了多对一的属性,如果继续访问这个这个返回对象的其他值就有可能要触发sql,所以跳过。

    如果getMethod返回格式是org.hibernate.collection.AbstractPersistentCollection,说明遇到了一对多的属性,如果继续访问这个这个返回对象的其他值就有可能要触发sql,必须跳过。

    对于场景一序列化大致就是这个过程,但是对于多对一的跳过的说法其实是不完全的,先分析一下,假设页面上有这样的需求,每一个教育背景信息占一行,每一行有一个链接“查看所属用户”,指向一个新页面,那这个url里的get参数肯定是要带上所属用户id的,到了显示用户的页面再根据次id查询相关用户信息,也就是说序列化的内容中需要多对一属性nodeUser的主键nusId,在表node_education中就是普通的外键,这项信息本来就是应该存在的,不需要再次触发什么sql来查询node_user表。而且Hibernate也确实提供了这样的机制来获取这个外键id,而不会触发新的查询sql,一旦触发就是著名的N+1查询问题,有10个教育背景可能就产生11条sql语句。

    现在详细说明获取这个外键id的过程,首先判断这个返回对象是不是HibernateProxy的类型,不是的话说明不是Hibernate多对一的代理对象。然后利用ModelUtils.getIdFieldName((Modelable) getObj)获取idName,利用(((HibernateProxy) getObj).getHibernateLazyInitializer()).getIdentifier()获取id的值,构造一个只有id的JSONObject对象进行序列化。也就是说,序列化的时候,默认总是会序列化多对一的那个对象的主键,也就是主表的外键,因为这些数据应经抓取到了,不会触发额外的sql影响性能。最后得到的序列化的结果示例如下:
{
    "nedId":"4028928d1c744a03011c744a03770000",
    "nedCreateTime":"2008-10-06 20:12:21",
    "nodeUser":{"nusId":"4028928d1c744a03011c744a03770000"}
}

  省略了部分属性,注意这里的nodeUser嵌套对象只有nusId这一个属性。

  场景二,如果需求变化,在这个教育背景列表中的每一行都要显示所属用户的姓名,从数据库的角度来分析,这是一定要查询第二张表才能获取到数据的。NodeUserManager中新增一个方法:
    public List findListWithNodeUser() {
        String hql = "from NodeEducation ned join ned.nodeUser";
        return getQuery(hql).list();
    }

  虽然这是一个跨表的查询,但是不是一个跨表的业务操作,所以可以放在Manager中。NodeUserService也增加一个同名方法:
    public List findListWithNodeUser() {
        return nodeEducationManager.findListWithNodeUser();
    }

    之后和场景一类似,不同之处在于Ctrl类最终会的调用代码类似下面的例子:
List jsonAwareCollect = new LinkedList();
jsonAwareCollect.add("nodeUser");
modelCollect2JSONArray(nodeEducationList, jsonAwareCollect);

    jsonAwareCollect是第二个参数,其中指定的属性名字会强制序列化,在判断多对一和一对多的场景前,会先判断jsonAwareCollect参数中是否含有当前属性的名称,有的话会强制序列化,所以最终的序列化结果示例如下:
{
    "nedId":"4028928d1c744a03011c744a03770000",
    "nedCreateTime":"2008-10-06 20:12:21",
    "nodeUser":{"nusId":"4028928d1c744a03011c744a03770000","nusEmail":"john@live.com"}
}

    序列化过程会经由一个递归过程完整遍历nodeUser对象,getAwareSubList方法就是每次递归之前处理下一次递归所需的jsonAwareCollect用的,例如,如果jsonAwareCollect的代码如下,假设nodeUser中还有个多对一对象group:
jsonAwareCollect.add("nodeUser");
jsonAwareCollect.add("nodeUser.group");

    表示继续序列化实体类group,实际上上面的例子只需第二行就行了,因为第二行肯定会序列化nodeUser对象了,第一行就不需要了。至此说明了jsonAwareCollect就是可定制序列化的核心,通过简单明了的参数传递就能够很方便的进行序列化的定制,达到既能只获取必须数据,又不会过多的抓取数据的效果。在场景二的例子中,因为Manager中的实现已经做了两表的关联查询,相关的nodeUser数据早已抓取完毕,所以序列化nodeUser的时候不会触发新的sql,也就是说通过配套的查询方法,能够和JSON序列化工具很好的配合,最终只产生一句sql,就能抓取并序列化所有需要数据。

    但是这种配合在一对多下是不成立,本质上因为,一对多很难用一句sql搞定所有数据,此时要考虑UI交互问题,而不是强行的一次性抓取数据。例,场景三,罗列当前数据库中所有NodeUser用户的基本信息,同时还要显示每个用户的所有教育背景资料,初看是一个必须要抓取每个nodeUser的一对多属性nodeEducationList的例子,但是实际上这个需求本身就不合理,这么多信息在一个页面本来就难以显示,实际一点的做法往往是,给一个名为“查看所有教育背景”的链接,点击以后,传递一个所在行nodeUser的id到新的页面,服务端重新根据传递的id来查询次用户下的所有教育背景资料,第二次查询用Hibernate来做也是很简单的,花不了多少工作量,但是逻辑非常清楚。
分享到:
评论
12 楼 sigmad 2009-06-03  
终于找到了  多谢
11 楼 icewubin 2009-02-25  
kimmking 写道
1、vo最好工具生成
2、vo<->dto也是工具类或是框架来做


如果不怎么复杂、也没有hibernate和ajax/ws的话,vo一般都不怎么需要


程序员的双手还是解放出来好。

焦点其实是我要复用Hibernate运行时产生的字节码增强过的代理POJO。

另:Hibernate对VO的支持也是很强的,如果不是有很复杂的内存操作处理数据,hql + VO是很方便的组合,也没有触发多余sql的隐患。
10 楼 kimmking 2009-02-25  
icewubin 写道
kimmking 写道
hibernate是ajax和webservice杀手

---------------------

一般情况还是建议:自定义VO <-> dto


---------------------
偶也做了一个xml和json序列化的东西

不过更进一步,我实现了js java .net的类似webservice的远程调用
其实和andot的phprpc一致~


http://code.google.com/p/rpcfx/downloads/list

要看什么项目什么需求的,我现在碰到的项目,95%的功能点都不需要专门的VO。
我写这个工具类就是为了解决这个问题的,动不动一个页面就要搞一个VO,太浪费时间。

从本质上来讲,给VO设值的过程,就是我的工具类遍历对象图的过程。

我建VO的目的并不是避开Hibernate的性能问题,因为我的工具类已经解决这个问题了,我建VO的主要目的还是因为,某些页面需要的一些特有属性,现成的POJO中不存在,我又不高兴重用(继承)某个POJO(代码可读性很差),才会建VO。

还有一大类是页面偏复杂的报表统计类(复杂SQL或复杂内存操作),那多半都是要建VO的。

1、vo最好工具生成
2、vo<->dto也是工具类或是框架来做


如果不怎么复杂、也没有hibernate和ajax/ws的话,vo一般都不怎么需要


程序员的双手还是解放出来好。
9 楼 icewubin 2009-02-25  
kimmking 写道
hibernate是ajax和webservice杀手

---------------------

一般情况还是建议:自定义VO <-> dto


---------------------
偶也做了一个xml和json序列化的东西

不过更进一步,我实现了js java .net的类似webservice的远程调用
其实和andot的phprpc一致~


http://code.google.com/p/rpcfx/downloads/list

要看什么项目什么需求的,我现在碰到的项目,95%的功能点都不需要专门的VO。
我写这个工具类就是为了解决这个问题的,动不动一个页面就要搞一个VO,太浪费时间。

从本质上来讲,给VO设值的过程,就是我的工具类遍历对象图的过程。

我建VO的目的并不是避开Hibernate的性能问题,因为我的工具类已经解决这个问题了,我建VO的主要目的还是因为,某些页面需要的一些特有属性,现成的POJO中不存在,我又不高兴重用(继承)某个POJO(代码可读性很差),才会建VO。

还有一大类是页面偏复杂的报表统计类(复杂SQL或复杂内存操作),那多半都是要建VO的。
8 楼 kimmking 2009-02-25  
hibernate是ajax和webservice杀手

---------------------

一般情况还是建议:自定义VO <-> dto


---------------------
偶也做了一个xml和json序列化的东西

不过更进一步,我实现了js java .net的类似webservice的远程调用
其实和andot的phprpc一致~


http://code.google.com/p/rpcfx/downloads/list
7 楼 icewubin 2009-02-25  
chris45 写道
这几天用了下,觉得还不错,发现一个问题,现在我在类里面有个get方法里面调用了lazy=true的对象,但是我不想序列化这个方法,因为你JSONConvert这个类是通过判断方法名来序列化,这种情况怎么办呢?

1.你的这个对象是Hibernate对象吧,是多对一的么,如果是多对一的话,我的策略是只序列化这个对象的ID,其他属性不会序列化。规则是默认绝不序列化可能触发sql的操作,只序列化多对一的ID不会触发新的sql。

2.当然这里有一个隐含的前提,就是利用这个工具类,利用现成的领域对象结构,来序列化JSON,所以只考虑通用(解决80%的问题),尽可能少的减少复杂的参数和配置,现有的参数主要也是防止多触发sql的,在不多触发sql的前提下,是尽可能多序列化属性的,一般来说增加一些不必要的属性是会增加网络传输量,但是一般认为不会带来太大的影响,如果开GZIP的情况下传输JSON的话,更不用担心会增加太多的流量。

如果真的要去除不需要的属性,三种思路:
1)改造这个工具类,再增加一套可以完全定制属性的参数。

2)改造这个工具类或者是再多一个工具类,默认不序列化任何属性,完全依赖于传入的参数,定制序列化。

3)我认为你说的需求不属于先前提到的80%的范围了,建议自定义VO(value object或叫DTO DateTrasferObject)对象,自己组装这个VO的数据,然后直接用JSONObject.fromObject(vo),直接序列化。
6 楼 chris45 2009-02-25  
这几天用了下,觉得还不错,发现一个问题,现在我在类里面有个get方法里面调用了lazy=true的对象,但是我不想序列化这个方法,因为你JSONConvert这个类是通过判断方法名来序列化,这种情况怎么办呢?
5 楼 icewubin 2009-02-19  
chris45 写道
里面有好几个类没给出如net.gbicc.x27.util.hibernate.ModelUtils,没办法调试啊,能给出相关类源码吗?十分感谢!!!

已上传,还有什么不明白的可直接问。

但是不太可能上传所有的,我没有提炼,连带的类很多的。

我的目的不是让大家调试,是让大家有思路,ModelUtils中其实也没有什么特别的代码,重要的是思想,JSONConvert中完全可以根据自己的需要,把一些明显不相干的部分或注释、或修改,成为自己的东西。
4 楼 chris45 2009-02-19  
里面有好几个类没给出如net.gbicc.x27.util.hibernate.ModelUtils,没办法调试啊,能给出相关类源码吗?十分感谢!!!
3 楼 icewubin 2008-12-18  
fins 写道
循环引用的问题 也要注意 不知道你这个解决没

没,我碰到的场景,循环引用都是Hibernate对象引起的,所以碰不到这个问题。

我把标题名字改改好。其实是针对Hibernate的POJO的数据抓取策略的处理。
2 楼 fins 2008-12-18  
循环引用的问题 也要注意 不知道你这个解决没
1 楼 talangniao 2008-12-18  
第一个,顶!
下载学习!

相关推荐

    Jackson 框架,轻易转换JSON

    - **注解驱动**:通过在Java类和字段上添加注解,如`@JsonProperty`、`@JsonInclude`等,可以定制JSON序列化和反序列化的规则。 - **XML与JSON互转**:Jackson还支持XML与JSON之间的转换,通过`jackson-dataformat-...

    JSON jar包

    2. **net.sf.json**: 这是由OpenSymphony Group开发的另一个JSON库,提供了丰富的API,支持XML到JSON的转换,JSON到Java对象的映射,以及JSON的序列化和反序列化。它的主要类有JSONArray、JSONObject、...

    jackson-core-asl-1.9.13+jackson-mapper-asl-1.9.13札包

    总的来说,Jackson库是Java开发中处理JSON不可或缺的工具,它的高效性能和丰富的功能使得JSON序列化和反序列化变得简单而强大。对于SSH整合或其他任何需要JSON数据处理的项目,这两个jar文件都是必不可少的依赖。

    jackson-2.4.2

    Jackson与Spring、Hibernate等其他Java框架有良好的集成,可以无缝地在这些框架中使用JSON序列化和反序列化功能。 总结来说,Jackson 2.4.2是一个强大的JSON处理工具,适用于各种Java项目。它的灵活性、性能和易用...

    ezmorph-1.0.6.jar和json-lib-2.2.2-jdk15.rar

    - **json-lib标签**:代表JSON操作,如序列化和反序列化Java对象到JSON格式。 - **jdk15标签**:提示这两个库的兼容性,它们能在Java 1.5及以上的环境中运行。 5. **文件名列表**:压缩包内包含的"ezmorph-1.0.6....

    基于json的异构数据库整合研究--大学毕业论文.doc

    转化过程涉及到POJO的序列化和反序列化,使得数据在Java对象和JSON之间自由流动,便于在数据库与应用程序间传输。 3.4 JSON融入ORM(Object-Relational Mapping) ORM框架如Hibernate、MyBatis等,可以结合JSON,将...

    jackson-2.8.5

    3. **jackson-databind-2.8.5.jar**:数据绑定模块是Jackson最强大的部分,它允许直接将JSON数据映射到Java对象(POJO)上,反之亦然。`ObjectMapper`是该模块的核心类,它提供了丰富的API来实现JSON与Java对象之间...

    jackjson类型转换各种方法

    3. JSON到POJO集合 处理JSON数组时,可以转换为Java的List或Array: ```java String jsonArray = "[{\"name\":\"Alice\", \"age\":25}, {\"name\":\"Bob\", \"age\":35}]"; List&lt;Person&gt; people = new ObjectMapper...

    pojoghost:基于反射的 POJO 转换器

    5. **应用场景**:这个工具可能用于JSON序列化库(如Jackson或Gson)、ORM框架(如Hibernate或MyBatis),或者任何需要将Java对象转换为其他格式(如数据库记录、网络请求参数等)的场景。 6. **安全性与性能考虑**...

    jackson的jar包.zip_Java编程_Java_

    这个模块包含了一些注解,可以用来定制JSON序列化和反序列化的规则。例如,`@JsonProperty`用于标记哪些字段应该被包括在JSON中,`@JsonIgnore`排除某个属性,`@JsonInclude`和`@JsonExclude`控制序列化时的空值...

    weather-webservice

    【weather-webservice】是一个关于Web服务的项目,主要涉及了Web服务的基本...通过研究这个项目,开发者可以掌握Web服务的开发技术,包括Spring框架的应用、数据库操作、JSON序列化和反序列化、HTTP通信等关键技能。

    spring-MVC整合jar包

    10. **其他依赖**: 包括 Servlet API、JSTL、Commons Logging、Jackson/JSON-P/JSON-B 等,用于完成 HTTP 交互、日志记录、JSON 序列化等任务。 在"spring-MVC整合jar包"中,包含了以上所述的关键组件和它们的依赖...

    excel生成sql语句和实体类代码生成器

    - **序列化支持**:自动添加Jackson或Gson的序列化注解,便于JSON数据的转换。 - **校验框架集成**:可选地,可以集成Hibernate Validator或Apache Commons Lang的校验注解,确保输入数据的合法性。 4. **其他...

    基于java的物流信息网的设计与实现.zip

    Java的HTTP客户端库(如HttpURLConnection或OkHttp)可用于发送网络请求,JSON库(如Jackson或Gson)处理数据序列化和反序列化,便于与RESTful API进行数据交换。 七、安全性与性能优化 项目中应考虑安全性问题,如...

    PinjamAmbulan

    为了实现“PinjamAmbulan”的功能,开发者可能利用了Java的并发处理、网络编程、JSON序列化(如Jackson或Gson)以及可能的数据库连接池(如HikariCP或Apache DBCP)等技术。如果项目使用了Spring框架,那么还有可能...

    springmvc:学习springmvc

    Spring MVC 自带了多种消息转换器,如 MappingJackson2HttpMessageConverter,用于处理 JSON 和 XML 数据的序列化和反序列化。 13. **国际化与本地化** Spring MVC 支持多语言环境,可以通过 LocaleResolver 和 ...

Global site tag (gtag.js) - Google Analytics