Friday, April 10, 2009

Java persistent with hibernate

在采用hibernate作为JPA实现的过程中,由于本身对JPA和hibernate都不是很熟悉,碰到了一些疑问。总之,抱定了见神杀神,见佛杀佛的宗旨,不怕碰到问题,碰到了就搞定它。
昨天一直迷惑于hibernate对FetchTYpe.LAZY的支持。这里有三个类,Transaction, Ticket, GameDraw,在Ticket表中通过Transaction_ID和GameDraw_ID来关联另外两个表。 首先构造了一个Ticket实例来准备插入数据库:
Ticket ticket = new Ticket();
Transaction trans = new Transaction();
trans.setId("trans-id");
ticket.setTrans(trans);
GameDraw draw = new GameDraw();
draw.setId("draw-id");
ticket.setDraw(draw);
ticket.setId("ticket.id");

然后在执行数据库插入的时候,我发现hibernate会首先查询Transaction和GameDraw:
- [AbstractEntityPersister] Getting current persistent state for: [com.mpos.lottery.te.trans.domain.Transaction#TRANS-ID]
- [AbstractEntityPersister] Getting current persistent state for: [com.mpos.lottery.te.draw.domain.GameDraw#DRAW-ID]
看起来,应该是JPA在插入Ticket的时候,这个ticket实例会从new转化到managed的状态,而由于它关联的Transaction和GameDraw也是@Entity,并且已经被assign了identifier,所以JPA需要确定Transaction和GameDraw是什么状态(到底是new还是detached,如果存在,那么应该是detached,否则应该是new)。
看起来,如果要避免这两个额外的查询,就是采用下面的方法:
Transaction trans = entityManager.getReferece(Transaction.class, "TRANS-ID");
GameDraw draw = entityManager.getReferece(GameDraw .class, "DRAW-ID");
Ticket ticket = new Ticket();
ticket.setTransaction(trans);
ticket.setGameDraw(draw);
ticket.setId("Ticket-ID");
entityManager.persist(ticket);
本来,保存Ticket实例只需要Transaction.id和GameDraw.id就够了,并不需要其他的信息,通过Proxy来避免JPA访问数据库。

再所说JPA的lazy loading。EntityManager的find(),getReference()分别对应到hibernate native的get()和load(),即前一个方法总是查询数据库来获得实例,而后一个方法不会访问数据库,而是返回一个本地的代理,只有在真正访问某个getter方法的时候才会去查询数据库。
对于JPA、hibernate,调用getID()的时候不会访问数据库,而对于ibatis,这没有这么细粒度,访问任何getter方法都会查询数据库。实际上,hibernate通过proxy(runtime generation)和interception(需要在编译后,对class进行bytecode enhancemant)。Proxies and collection wrappers can only be used to lazy load entity associations and collections. 就是说Proxies是对整个关联实例进行代理,只能lazy load一个实例,无法lazy load实例的某个属性。 Interception可以实现lazy load某个属性(对属性@Basic(fetch=FetchType.Lazy)),但是一般不需要采用interception这种方式。 此外,正如前面提到过,在"Java Persistence with Hibernate"的“13.1.6"中也说过,采用proxy这种方式的话,ticket.getTransaction().getId()应该不会出发proxy的initialization(访问数据库),从我个人的理解来说,我也认为这样的行为是合理的,因为Ticket表中已经保存了Transtion_id,而且fetch=FetchType.LAZY,所以ticket.getTransaction()和entityManager.getReference(Transaction.class, "TRANS-ID")应该得到同样的proxy,所以ticket.getTransaction().getId()不会出发proxy initialization。不过实际的结果是,在测试过程中我就发现ticket.getTransaction().getId()的时候,hibernate会根据trans_ID去查询Transaction. . .????
需要弄清楚的是,hibernate默认是返回proxy,而JPA的ManyToOne和OneToOne默认是访问数据库(fetch=FetchType.EAGER),所以在处理关联对象的时候,如果需要lazy loading,那么需要显示指定fetch=FetchType.EAGER. 这里需要特别考虑的是ManyToOne和OneToOne两种关联,如果optinal是true,即One的一方可以为null,那么JPA总会访问数据库,因为不访问数据库,JAP无法确定到底是应该返回Proxy还是返回null. 如果One的一方为null,而JPA直接返回Proxy,那么很明显会得到一个nullpointerexception。
所有对Ticket的查询都会应用上面的layz规则,而不是说只有通过find()方法才能激活lazy。

No comments: