楼主: Sky-Tiger

JPA implementation patterns

[复制链接]
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
31#
 楼主| 发表于 2009-7-16 22:38 | 只看该作者
Mo associations, mo problems

All this is pretty straight forward, but it gets interesting when we make this association bidirectional. Let's add an orderLines field to our Order object and include a naïve implementation of the getter/setter pair:

        @OneToMany(mappedBy = "order")
        private Set<OrderLine> orderLines = new HashSet<OrderLine>();

        public Set<OrderLine> getOrderLines() { return orderLines; }
        public void setOrderLines(Set<OrderLine> orderLines) { this.orderLines = orderLines; }

The mappedBy field on the @OneToMany annotation tells JPA that this is the reverse side of an association and, instead of mapping this field directly to a database column, it can look at order field of a OrderLine object to know with which Order object it goes.

So without changing the underlying database we can now retrieve the orderlines for an order like this:

        Collection<OrderLine> lines = o.getOrderLines();

No more need to access the OrderLineDao. :-)

But there is a catch! While container managed relationships (CMR) as defined by EJB 2.x made sure that adding an OrderLine object to the orderLines property of an Order also sets the order property on that OrderLine (and vice versa), JPA (being a POJO framework) performs no such magic. This is actually a good thing because it makes our domain objects usable outside of a JPA container, which means you can test them more easily and use them when they have not been persisted (yet). But it can also be confusing for people that were used to EJB 2.x CMR behaviour.

If you run the examples above in separate transactions, you will find that they run correctly. But if you run them within one transaction like the code below does, you will find that the item list while be empty:

        Order o = new Order();
        o.setCustomerName("Mary Jackson");
        o.setDate(new Date());

        OrderLine line = new OrderLine();
        line.setDescription("Java Persistence with Hibernate");
        line.setPrice(5999);
        line.setOrder(o);

        System.out.println("Items ordered by " + o.getCustomerName() + ": ");
        Collection<OrderLine> lines = o.getOrderLines();
        for (OrderLine each : lines) {
                System.out.println(each.getId() + ": " + each.getDescription()
                                + " at $" + each.getPrice());
        }

This can be fixed by adding the following line before the first System.out.println statement:

        o.getOrderLines().add(line);

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
32#
 楼主| 发表于 2009-7-16 22:38 | 只看该作者
Fixing and fixing...

It works, but it's not very pretty. It breaks the abstraction and it's brittle as it depends on the user of our domain objects to correctly invoke these setters and adders. We can fix this by moving that invocation into the definition of OrderLine.setOrder(Order):

        public void setOrder(Order order) {
                this.order = order;
                order.getOrderLines().add(this);
        }

When can do even better by encapsulating the orderLines property of the Order object in a better manner:

        public Set<OrderLine> getOrderLines() { return orderLines; }
        public void addOrderLine(OrderLine line) { orderLines.add(line); }


And then we can redefine OrderLine.setOrder(Order) as follows:

        public void setOrder(Order order) {
                this.order = order;
                order.addOrderLine(this);
        }

Still with me? I hope so, but if you're not, please try it out and see for yourself.

Now another problem pops up. What if someone directly invokes the Order.addOrderLine(OrderLine) method? The OrderLine will be added to the orderLines collection, but its order property will not point to the order it belongs. Modifying Order.addOrderLine(OrderLine) like below will not work because it will cause an infinite loop with addOrderLine invoking setOrder invoking addOrderLine invoking setOrder etc.:

        public void addOrderLine(OrderLine line) {
                orderLines.add(line);
                line.setOrder(this);
        }

This problem can be solved by introducing an Order.internalAddOrderLine(OrderLine) method that only adds the line to the collection, but does not invoke line.setOrder(this). This method will then be invoked from OrderLine.setOrder(Order) and not cause an infinite loop. Users of the Order class should invoke Order.addOrderLine(OrderLine).

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
33#
 楼主| 发表于 2009-7-16 22:38 | 只看该作者
The pattern

Taking this idea to its logical conclusion we end up with these methods for the OrderLine class:

        public Order getOrder() { return order; }

        public void setOrder(Order order) {
                if (this.order != null) { this.order.internalRemoveOrderLine(this); }
                this.order = order;
                if (order != null) { order.internalAddOrderLine(this); }
        }

And these methods for the Order class:

        public Set<OrderLine> getOrderLines() { return Collections.unmodifiableSet(orderLines); }

        public void addOrderLine(OrderLine line) { line.setOrder(this); }
        public void removeOrderLine(OrderLine line) { line.setOrder(null); }

        public void internalAddOrderLine(OrderLine line) { orderLines.add(line); }
        public void internalRemoveOrderLine(OrderLine line) { orderLines.remove(line); }

These methods provide a POJO-based implementation of the CMR logic that was built into EJB 2.x. With the typical POJOish advantages of being easier to understand, test, and maintain.

Of course there are a number of variations on this theme:

    * If Order and OrderLine are in the same package, you can give the internal... methods package scope to prevent them from being invoked by accident. (This is where C++'s friend class concept would come in handy. Then again, let's not go there. ;-) ).
    * You can do away with the removeOrderLine and internalRemoveOrderLine methods if order lines will never be removed from an order.
    * You can move the responsibility for managing the bidirectional association from the OrderLine.setOrder(Order) method to the Order class, basically flipping the idea around. But that would mean spreading the logic over the addOrderLine and removeOrderLine methods.
    * Instead of, or in addition to, using Collections.singletonSet to make the orderLine set read-only at run-time, you can also use generic types to make it read-only at compile-time:

      public Set<? extends OrderLine> getOrderLines() { return Collections.unmodifiableSet(orderLines); }

      But this makes it harder to mock these objects with a mocking framework such as EasyMock.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
34#
 楼主| 发表于 2009-7-16 22:39 | 只看该作者
The pattern

Taking this idea to its logical conclusion we end up with these methods for the OrderLine class:

        public Order getOrder() { return order; }

        public void setOrder(Order order) {
                if (this.order != null) { this.order.internalRemoveOrderLine(this); }
                this.order = order;
                if (order != null) { order.internalAddOrderLine(this); }
        }

And these methods for the Order class:

        public Set<OrderLine> getOrderLines() { return Collections.unmodifiableSet(orderLines); }

        public void addOrderLine(OrderLine line) { line.setOrder(this); }
        public void removeOrderLine(OrderLine line) { line.setOrder(null); }

        public void internalAddOrderLine(OrderLine line) { orderLines.add(line); }
        public void internalRemoveOrderLine(OrderLine line) { orderLines.remove(line); }

These methods provide a POJO-based implementation of the CMR logic that was built into EJB 2.x. With the typical POJOish advantages of being easier to understand, test, and maintain.

Of course there are a number of variations on this theme:

    * If Order and OrderLine are in the same package, you can give the internal... methods package scope to prevent them from being invoked by accident. (This is where C++'s friend class concept would come in handy. Then again, let's not go there. ;-) ).
    * You can do away with the removeOrderLine and internalRemoveOrderLine methods if order lines will never be removed from an order.
    * You can move the responsibility for managing the bidirectional association from the OrderLine.setOrder(Order) method to the Order class, basically flipping the idea around. But that would mean spreading the logic over the addOrderLine and removeOrderLine methods.
    * Instead of, or in addition to, using Collections.singletonSet to make the orderLine set read-only at run-time, you can also use generic types to make it read-only at compile-time:

      public Set<? extends OrderLine> getOrderLines() { return Collections.unmodifiableSet(orderLines); }

      But this makes it harder to mock these objects with a mocking framework such as EasyMock.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
35#
 楼主| 发表于 2009-7-16 22:39 | 只看该作者
When lazy loading can occur

All @Basic, @OneToMany, @ManyToOne, @OneToOne, and @ManyToMany annotations have an optional parameter called fetch. If this parameter is set to FetchType.LAZY it is interpreted as a hint to the JPA provider that the loading of that field may be delayed until it is accessed for the first time:

    * the property value in case of a @Basic annotation,
    * the reference in case of a @ManyToOne or a @OneToOne annotation, or
    * the collection in case of a OneToMany or a @ManyToMany annotation.

The default is to load property values eagerly and to load collections lazily. Contrary to what you might expect if you have used plain Hibernate before, references are loaded eagerly by default.

Before discussing how to use lazy loading, let's have a look into how lazy loading may be implemented by your JPA provider.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
36#
 楼主| 发表于 2009-7-16 22:39 | 只看该作者
Build-time bytecode instrumentation, run-time bytecode instrumentation and run-time proxies

To make lazy loading work, the JPA provider has to do some magic to make the objects that are not there yet appear as if they are there. There are a number of different ways JPA providers can accomplish this. The most popular methods are:

    * Build-time bytecode instrumentation - The entity classes are instrumented just after they have been compiled and before they are packaged to be run. The advantage of this approach is that bytecode instrumentation can provide the best performance and the least leaky abstraction. A disadvantage is that it requires you to change your build procedure and is (therefore) not always compatible with IDE's. the instrumented classes may be binary incompatible with their uninstrumented versions which could cause Java serialization problems and the like, but this is not something I have heard anybody mention as a problem yet.
    * Run-time bytecode instrumentation - Instead of instrumenting the entity classes at build-time, they can also be instrumented at run-time. This requires installing a Java agent using the -javaagent option from JDK 1.5 and upwards, using class retransformation when running under JDK 1.6 or a later version, or some proprietary method if you are using an older JDK. So while this method does not require you to modify your build procedure, it is very specific to the JDK you are using.
    * Run-time proxies - In this case the classes are not instrumented but the objects returned by the JPA provider are proxies to the actual entities. These proxies can be dynamic proxy classes, proxies that have been created by CGLIB, or proxy collections classes. While requiring the least setup, this method is the least transparent of the ones available to JPA implementors and therefore requires you to know most about them.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
37#
 楼主| 发表于 2009-7-16 22:39 | 只看该作者
Run-time proxy based lazy loading with Hibernate

While Hibernate supports build-time bytecode instrumentation to enable lazy loading of individual properties, most users of Hibernate will be using run-time proxies; it is the default and works well for most cases. So let's explore Hibernate's run-time proxies.

Two kind of proxies are created by Hibernate:

   1. When lazily loading an entity through a lazy many-to-one or one-to-one association or by invoking EntityManager.getReference, Hibernate uses CGLIB to create a subclass of the entity class that acts a proxy to the real entity. The first time any method on that proxy is invoked, the entity is loaded from the database and the method call is passed on to the loaded entity. My colleague Maarten Winkels has blogged about the pitfalls of these Hibernate proxies last year.
   2. When lazily loading a collection of entities though a one-to-many or a many-to-many association, Hibernate returns an instance of a class that implements the PersistentCollection interface such as PersistentSet or PersistentMap. The first time that collection is accessed, its members are loaded. The member entities are loaded as regular classes, so the Hibernate proxy pitfalls mentioned above don't apply here.

To get a feel for what happens here, you might want to step through some simple JPA code in the debugger and see the objects that Hibernate creates. It will increase your understanding if the mechanism a lot. :-)

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
38#
 楼主| 发表于 2009-7-16 22:39 | 只看该作者
Run-time bytecode instrumentation with OpenJPA

OpenJPA offers a number of enhancement methods, as the documentation calls it, of which I found run-time bytecode instrumentation the easiest to set up.

Stepping through the debugger you can see that OpenJPA does not create proxies. Instead a few extra fields have appeared in each entity class, with names like pcStateManager or pcDetachedState. More importantly you can see that a lazily loaded entity has all its fields set to 0 or null and that its state is only loaded when a method is invoked on it. More precisely, a property that is configured to be loaded lazily is only loaded when its getter is invoked.

It is very important to know that direct access to the fields of a lazily loaded entity (or the field behind a lazily loaded property) does not trigger loading of that entity (or field). Also, when the session is no longer available OpenJPA does not throw an exception as Hibernate does but just leaves the values in their uninitialized state, later causing the NullPointerExceptions I mentioned above.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
39#
 楼主| 发表于 2009-7-16 22:39 | 只看该作者
OpenJPA vs. Hibernate

The first difference between these two approach you might notice is the objects that get proxied/instrumented:

    * OpenJPA instruments all entities which means it can detect when you access a lazy reference or collection from the referring entity and it will then return an actual entity or a collection of actual entities. Only when you lazily load an entity using EntityManager.getReference or when you have configured a property to be lazily loaded, will you get a (partially) empty entity.
    * In the case of a lazy reference (or an entity that has been lazily loaded with EntityManager.getReference) Hibernate proxies the lazy object itself using CGLIB, which causes the proxy pitfalls mentioned before. When using a lazy collection Hibernate is just as transparent as OpenJPA. Finally, Hibernate does not support lazily loaded properties using proxies.

If you compare OpenJPA's instrumentation to the run-time proxies created by Hibernate you can see that the approach taken by OpenJPA is more transparent. Unfortunately it is let down somewhat by OpenJPA's less than robust error handling.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
40#
 楼主| 发表于 2009-7-16 22:39 | 只看该作者
The pattern

So now that we know how to configure lazy loading and how it works, how can we use it properly?

Step 1 would be to examine all your associations and see which should be lazily loaded and which should be eagerly loaded. As a rule of thumb I start out leaving all *-to-one associations eager (the default). They usually don't add up to a large number of queries anyway and if they do, I can change them. Then I examine all *-to-many associations. If any of them are to entities that are always accessed and therefore always loaded, I configure them to be loaded eagerly. And sometimes I use the Hibernate specific @CollectionOfElements annotation to map such "value type" entities.

Step 2 is the most important. To prevent any LazyInitializationExceptions or NullPointerExceptions you need to make sure that all access to your domain objects occurs within one transaction. When domain objects are accessed after a transaction has finished, the persistence context can no longer be accessed to load the lazy objects, and that causes these problems. There are two ways to solve this:

   1. The most pure way is to place a Service Facade (or Remote Facade if you will) in front of your services and only communicate with clients of your service facade through Transfer Objects (a.k.a. Data Transfer Objects a.k.a. DTO's). The facade's responsibility is to copy all appropriate values from your domain objects to the data transfer objects, including making deep copies of references and collections. The transaction scope of your application should include the service facade for this pattern to work, i.e. set your facade to be @Transactional or give it a proper @TransactionAttribute.
   2. If you are writing a Model 2 web application with an MVC framework, a widely used alternative is to use the open EntityManager in view pattern. In Spring you can configure a Servlet filter or a Web MVC interceptor that will open the entity manager when a request comes in and will keep it open until the request has been handled. The means the same transaction is active in your controller and in your view (JSP or otherwise). While purists may argue that this makes your presentation layer depends on your domain objects, it is a compelling approach for simple web applications.

Step 3 is to enable SQL logging of your JPA provider and exercise some of the use cases of your applications. It is enlightening to see what queries are performed when entities are accessed. The SQL log can also provide you with input for performance optimizations so you can revisit the decisions you made in step 1 and tune your database. In the end lazy loading is all about performance, so don't forget this step!

I hope this blog has given you some insight into how lazy loading works and how you can use it in your application. In the next blog I will delve deeper into the topic of the DTO and Service Facade patterns. But before I leave you I would like to thank everybody that came to my J-Spring 2009 talk on this subject. I had a lot of fun! It really seems a lot of people are wondering how to effectively use JPA because I got a lot of questions. Unfortunately the questions made me run out of time. Next time I will pay more attention to the girl with the time card. And bring my own water. ;-) Thanks again for being there!

P.S. Does anybody know what happened to hibernate.org? For more than a week the site has been showing a message saying they are down for maintenance.

使用道具 举报

回复

您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

TOP技术积分榜 社区积分榜 徽章 团队 统计 知识索引树 积分竞拍 文本模式 帮助
  ITPUB首页 | ITPUB论坛 | 数据库技术 | 企业信息化 | 开发技术 | 微软技术 | 软件工程与项目管理 | IBM技术园地 | 行业纵向讨论 | IT招聘 | IT文档
  ChinaUnix | ChinaUnix博客 | ChinaUnix论坛
CopyRight 1999-2011 itpub.net All Right Reserved. 北京盛拓优讯信息技术有限公司版权所有 联系我们 未成年人举报专区 
京ICP备16024965号-8  北京市公安局海淀分局网监中心备案编号:11010802021510 广播电视节目制作经营许可证:编号(京)字第1149号
  
快速回复 返回顶部 返回列表