准备学习一下,翻译目的在于通读学习,水平有限。
Using GWT with Hibernate
Sumit Chandel, Google Developer Relations
July 2009
(with thanks to Bruno Marchesson for his contributions to this article)
许多开发者常常问道:怎样在 GWT 中使用 Hibernate . 尽管你能在论坛 GWT Developer Forum 中找到各式各样的讨论, 我们还是认为将最流行的方式归纳一起会更有帮助, 然后强调一下GWT 应用开发的优缺点.
在我们进入正题之前,先来熟悉下基础知识.
The Basics 基础
对于本文,我们假设读者已经熟悉 Hibernate 在服务端(server-side)的配置和使用, 加入你想更多了解Hibernate的相关知识,强力推荐访问 getting started tutorial .
要运行持久层的 Hibernate 对象, 我们需要使用 HSQLDB 提供一个 in-memory Hibernate SQL database. 你可以从 这里 下载. 这里不会过多讨论 HSQLDB, 只要能覆盖我们将要讨论的例子就够了.
我们开始一个简单的例子. 假设我们要开发一个在线音乐唱片商店. 显然对于这样一个应用,Record(唱片)对象是需要定义为在server端的持久对象并且要显示在client端. 我们也需要用户能创建账户并且在线为他们的profile增加音乐唱片. 这样我们也需要创建一个 持久对象Account
(账户). 这里创建两个类.
Record.java
Account.java
然后我们需要为这些持久类创建相应的 Hibernate 映射文件, 如下:
Record.hbm.xml
Account.hbm.xml
现在我们创建了持久化类, 然后创建界面来输入新账号和唱片, 同样创建 GWT RPC 服务来保存它们到服务端. 开始看 RPC 服务.
这里我们不考虑每个 RPC 组件是何种角色, 加入你对 GWT RPC 系统和机制不熟悉, 请查看 GWT RPC docs 增加了解.
首先, 创建客户端服务接口. 如果你不想写如下大量的接口方法,可以考虑使用 Command pattern, 请看 这里 的描述:
MusicStoreService.java
MusicStoreServiceAsync.java
最后, 我们在服务端创建服务实现类.
MusicStoreServiceImpl.java
在上面的代码中你或许注意到 HibernateUtil
被 the MusicStoreServiceImpl
里的方法实现中调用. 这是一个自定义的类用来获得功能和使用 Hibernate session factory, 在 Hibernate tutorial 有提及. 为了方便大家,下面提供了 HibernateUtil
的代码共参考. 如果你想了解关于 HibernateUtil
类功能的更多细节, 强烈建议你去看 Hibernate tutorial 教程。
最后服务端 GWT RPC 服务已经可以对Hibernate对象进行增删改查了 CRUD (事实上,我们没有包含删除功能). 现在我们只需要一个接口来响应 RPC. 我已经写好了一个带界面的示例应用来创建唱片,创建账户,添加唱片到账户,当然还可以查看所有已存在的账户及改账户对应的唱片. The sample code is not representative of best practices, just a quick and dirty implementation to get us up and running. 这个示例包含服务端 RPC 和上面提及的 Hibernate 代码. 点击 这里 下载.
在示例代码中, 你会在根目录下找到 build.xml和
build.properties
两个文件. 在你机器上配置好 gwt.home
和 gwt.dev.jar
后, 你就可以使用 Ant 来构建你的项目, 然后从嵌套的Jetty Server服务中启动 hosted mode 显示界面和 Hibernate 实例. 只要咬运行下面的命令行:
Before doing that, though, we'll need to have our in-memory HSQLDB up and running so we can persist our Hibernate objects. In the example project you downloaded, you should find 'data
' folder under the project root directory. You'll also find the hsqldb.jar
in the lib
folder. All we need to do to start up the in-memory HSQLDB is invoke the org.hsqldb.Server
class contained in the hsqldb.jar
file, while in the 'data
' directory to host the HSQLDB properties and log output. You can do this by running the following from command line (while in the 'data
' directory):
Now that we have our persistence layer ready, let's compile and run our application in hosted mode using the ant command above. Once both build
and hosted
ant tasks have completed, you should see the hosted mode browser startup, with the "Add Accounts / Records" tab displayed. Finally, we can start persisting our records (from the GWT client-side to the Hibernate database, using our Hibernate objects!). Go ahead and try adding an account and a a record to our in-memory Hibernate to get our data set started.
Next, try selecting the "Add Records To Account" panel to add our newly created record to the also newly created account. Chances are, you'll get an error message along the lines of the screenshot below.

Why Hibernate objects can't be understood when they reach the browser world 为何Hibernate对象在浏览器中不能被识别
哪里错了? 查看hosted mode的控制台, 你会看到"Exception while dispatching incoming RPC call" 这条信息出现. 选中这条警告信息, 下面面板会显示更详细的stack trace.
这部分注意:
Caused by: com.google.gwt.user.client.rpc.SerializationException: Type 'org.hibernate.collection.PersistentSet' was not included in the set of types which can be serialized by this SerializationPolicy or its Class object could not be loaded. For security purposes, this type will not be serialized. at com.google.gwt.user.server.rpc.impl.StandardSerializationPolicy.validateSerialize(StandardSerializationPolicy.java:83) at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serialize(ServerSerializationStreamWriter.java:591)
The key here is the SerializationException
that was thrown when we tried to load up and retrieve accounts.
So what exactly went wrong? Well, as you may have read in the GWT RPC docs , a SerializationException
is thrown whenever a type transferred over RPC is not "serializable". The definition of serializable here means that the GWT RPC mechanism knows how to serialize and deserialize the type from bytecode to JSON and vice-versa. To declare a type as serializable to the GWT compiler, you can either make the type to be transferred over RPC implement the IsSerializable
interface, especially created for this purpose, or implement the standard java.io.Serializable
interface, provided that its members and methods consist of types that are also serializable.
In the case of the Account
and Record
Hibernate objects, we are implementing the Serializable
interface, so these should work, shouldn't they?. As it turns out, the devil is in the details.
When you take an object and turn it into a Hibernate object, the object is now enhanced to be persistent. That persistence does not come without some type of instrumentation of the object. In the case of Hibernate, the Javassist library actually replaces and rewrites the bytecode for these objects by persistent entities to make the Hibernate magic work. What this means for GWT RPC is that by the time the object is ready to be transferred over the wire, it actually isn't the same object that the compiler thought was going to be transferred, so when trying to deserialize, the GWT RPC mechanism no longer knows what the type is and refuses to deserialize it.
In fact, if you were to look deeper to the earlier call to loadAccounts()
, and step into the RPC.invokeAndEncodeResponse()
method, you would see that the object we're trying to deserialize has now become an ArrayList
of Account
types with their java.util.Set
of records replaced by the org.hibernate.collection.PersistentSet
type.
Similar problems arise with other persistence frameworks, such as JDO or JPA, used on Google App Engine .
A potential solution would be to replace the types once more in the opposite direction before returning through the server-side RPC call. This is doable, and would solve the problem we encountered here, but we wouldn't be out of harm's way just yet. Another great benefit of using Hibernate is the fact that we can lazily load associated objects when needed. For example, on the server-side, I could load an account, change it around, and only load its associated records when a call to account.getRecords()
and some action on those records was taken. The special Hibernate instrumentation will take care of actually fetching the records when I make the call, making them available only when really needed.
As you may imagine, this will translate to strange behaviour in the GWT RPC world where these Hibernate objects traveled from the Java server-side to browser land. If a GWT RPC service tries to access associations lazily, you might see something like a LazyInitializationException
being thrown.
Integration Strategies
Fortunately, there are a number of workarounds that sidestep these issues, as well as provide other benefits inherent to these approaches.
Using Data Transfer Objects 使用数据传输对象
One of the easiest ways to deal with this issue is to introduce a light object to go between the heavy Hibernate object and its data representation that we care about on the client-side. This go-between is typically referred to as a Data Transfer Object (DTO) .
The DTO is a simple POJO only containing simple data fields that we can access on the client-side to display on the application page. The Hibernate objects can then be constructed from the data in our data transfer objects. The DTOs themselves will only contain the data we want to persist, but none of the lazy loading or persistence logic added by the Hibernate Javassist to their Hibernate counterparts.
Applying this to our example, we get the following two DTOs:
AccountDTO.java
RecordDTO.java
下一步,增加构造函数这些Hibernate对象相应的DTO作为参数传递:
Account.java
Record.java
最后, 我们需要修改已存在的GWT RPC组件让对应的DTO作为参数传递:
MusicStoreService.java
MusicStoreServiceAsync.java
现在修改类 MusicStoreServiceImpl.java
.
MusicStoreServiceImpl.java
And lastly, we need to update the RPC service interface calls from the MusicStore
entry point class to use the new DTO parametrized method signatures.
We now have the domain package containing the Account
and Record
classes where it belongs, isolated to the server-side. We can remove the <source>
tag referencing the domain package from the base application module XML file now:
MusicStore.gwt.xml
<!-- Remove the line below --> <source path="domain"/>
Notice that not much changes here. The only thing we need to do after retrieving the Hibernate objects from the database is copy them into their DTO equivalents, add those DTOs to a list and return them back in the client-side callback. However, there is one thing that we'll need to watch out for, and that's the process by which we copy these objects to our DTOs.
We created the createAccountDTO(Account account)
method, which contains the logic that we want to transform the Account Hibernate objects into the data-only DTOs that we're going to return.
createAccountDTO(Account account)
You'll also notice that we're making a call to another copy method called createRecordDTO(Record record)
. As you might imagine, much like we needed to transform Account objects into their DTO equivalents, we need to do the same directional transformation for the Record object.
createRecordDTO(Record record)
With the DTO solution implemented, try running: ant clean build hosted
from command line once more to see the solution in action (all while making sure that the HSQL in-memory DB is still running).
You can download a version of the first sample application with the DTO solution fully implemented here .
When should you use the DTO approach
You can see where this is going. The more Hibernate objects we need to transform into DTOs, the more special-cased copy methods we'll need to create to get them transferred over the wire. What's more, because the DTOs that we transfer over the wire won't have the full object graphs loaded as a Hibernate object would, in some situations we need to carefully consider how we're going to copy the Hibernate object to the DTO and how full we want it and its associated objects to be when we send them over the wire, and when we want to leave that to another RPC call. With the DTO approach, all of this has to be handled in code.
There is some good to this approach, though. For one, we now have lightweight data transfer objects that we can send over the wire to the client, leading to leaner payloads. Also, by forcing us to think about copy strategies as we're taking full Hibernate object graphs from the server and optimizing them for what the client needs to see at a given point in time, we reduce the risk of having the browser blowup on an account with a set of five thousand records in it and also make the user experience faster.
The DTOs we created might also double or even triple in use depending on the server-side architecture of our application. For example, something like Java Message Service won't necessarily know how to deal with a Hibernate object passed in as the message. The DTOs can now be used instead to pass in for something much easier to work with in the related JMS components.
All that said, if you have many Hibernate objects that need to be translated, the DTO / copy method creation process can be quite a hassle. Thankfully, there are other strategies that can help with that situation.
Using Dozer for Hibernate integration
Dozer is an open source library that can automatically generate DTOs for us by reading and parsing XML files, thereby lightening the load on the developer who would no longer have to create these manually. We can use Dozer to clone our Hibernate entities for us.
First, a little bit of background about how Dozer works. Dozer is based on the Java Bean norm, and uses this to copy data from persistent entities to a new POJO instance.
As mentioned before, Dozer permits us to use XML mappings to tell it which properties to copy to a new DTO instance, as well as which properties to exclude. When Dozer reads these mapping files and copies objects to DTOs, it does so implicitly, meaning that you can expect that any property that hasn't been specifically excluded in the mapping file will be included. This helps keep the Dozer mapping file short and to the point:
Now that we know how Dozer mappings work, let's see how they apply to Hibernate objects. The idea is to simply copy all the properties over to our DTOs while removing any properties marked with lazy="true"
. Dozer will take care of replacing these originally lazily loaded persistent collections by plain collections, which can be transferred and serialized through RPC.
One of the nice things about Dozer is that it automatically takes care of copying data between two classes for properties that exist in both classes that have the same field type and name. Since the Account
/ Record
objects and their DTO equivalents both use the same property names, we're already done with our Dozer mappings as configured above. Save this mapping file to dozerBeanMapping.xml
, and place it on the project classpath. Now all we need to have our previous DTO solution use Dozer is remove the copy logic we added as it is no longer needed, and use the Dozer mappings to copy our Hibernate data to our DTOs, and send them over the wire.
The method signatures for all three GWT RPC MusicStore
service components remain the same. What changes is simply the copy logic from Hibernate object to DTO in the MusicStoreServiceImpl
method implementations. Anywhere we would have had createAccountDTO()
or createRecordDTO()
calls, we will now have:
DozerBeanMapperSingletonWrapper.getInstance().map(account, AccountDTO.class)); // or DozerBeanMapperSingletonWrapper.getInstance().map(record, RecordDTO.class));
And similarly the other way around when we need to create a Hibernate object from an incoming DTO.
You'll notice this approach forces us to make separate calls to load an Account object's records since we're no longer writing and using our own copy logic. This transitive logic is not necessarily so bad, since the reasons for which we wanted the property to be lazy on the server will probably still be true on the client. When we do want to copy properties with lazy="true"
without running into LazyInitializationExceptions
, Dozer does allow us to create our own custom converters - classes which dictate how one type is copied to the next. This becomes similar to the DTO approach, except now all our copy logic would be neatly refactored into a purpose-built converter class.
Try the sample application with the Dozer solution by once again running: ant clean build hosted
from command line. You can download a version of the first sample application with the Dozer solution fully implemented here .
When you should use the Dozer approach
This approach suffers from some of the same drawbacks as the DTO approach. You will need to create a DTO or some other type of RPC transferrable class that the Dozer mapper can copy Hibernate object data into. You will also need to create a <mapping>
entry for every Hibernate object that needs to be copied to a DTO when transferring data through RPC.
Another downside to using Dozer is that for types with data deeply nested in its properties, the DTO lookup and generation could take a while, especially if we're in the process of mapping many of those entities. Custom converters can help here, but they can become cumbersome as the number of objects continues to grow.
However, the Dozer approach adds some nice automation to the generation of DTOs, and saves some time over manually generating them by hand as we saw earlier. We have saved on a lot of copy logic that would have made our code a lot heftier and less cohesive. It also gives us configurable control over which properties get copied, which helps make sure we aren't sending objects bloated with extraneous data back to the client.
Therefore, if your project counts many Hibernate objects that need to be transferred over RPC, and also contains copy logic that is straightforward enough to be captured in Dozer XML mappings, this might be a good approach for you. On the other hand, when the fact that lazily loaded properties force changes in your load strategies all the way up to the client layer - breaking the benefit of layering in the first place, and when the number of objects starts growing drastically, perhaps the Gilead approach, described below, would be the best choice.
Using Gilead for Hibernate Integration
Gilead (formerly known as Hibernate4Gwt) is an opensource library that proposes another, more transparent solution to exchange objects between Hibernate and GWT.
How it works
The first principle to Gilead is the automatic replacement of uninitialized proxies by null and persistent collections by basic collections present in the emulated JRE. These replacements are both done without requiring any specific mappings to be defined. Gilead also stores the information necessary to recreate the Hibernate proxy or persistent collection in either the server-side object or the cloned object, making it possible to recreate these objects without needing to make calls to the database.

Gilead also provides a dedicated adapter for Hibernate and GWT to make their integration painless.
In the simplest case, integration between GWT and Hibernate can be realized by following these steps:
- Make your persistent classes inherit the
LightEntity
class (class used for stateless mode integration in the Gilead library). - Making your remote RPC services extend
PersistentRemoteService
instead ofRemoteServiceServlet
. - Configuring your beanManager for your GWT RPC service as shown in the code snippet below (see Gilead documentation for more on configuring bean managers):
一旦配置写好之后, Hibernate实体将被自动转换为能通过RPC被客户端GWT代码使用的类型, 而不需要任何其他的编码或者映射.
基于MusicStore应用需要实现下面三个改动:
1) 让持久化类继承 LightEntity
Account.java
Record.java
2) 确保远程RPC服务继承PersistentRemoteService
MusicStoreServiceImpl.java
There is a change to note here, aside from the new constructor that sets up the bean manager. We're now using net.sf.gilead.core.hibernate.HibernateUtil
in addition to the HibernateUtil
class we defined in the util package. This is required to setup Gilead appropriately.
And that's all there is to it. We're ready to go with the original calls we made from our GWT RPC service interfaces on the client-side referring the Account
and Record
objects. Try executing the command below to compile the application with the Gilead approach and see it running in hosted mode:
ant clean build hosted
Once more, you can download a version of the sample application we've been building with the Gilead solution fully implemented here .
Transport annotations
In order to avoid sending sensitive or heavy data over the network, Gilead also provides an @ServerOnly
annotation that will exclude the property annotated in the cloned object. Also, if you don't want values that are changed in the clone on the GWT client-side to be reflected and persisted in the persistent entity, you can add a @ReadOnly
annotation to properties as well.
When you should use Gilead
The main advantage to Gilead is transparency and developer productivity: once the configuration is in place, everything is taken care of to transfer and use your Hibernate entities as you would expect in the client-side (what we tried to do in our initial attempt to get Account
and Record
transferred over the RPC wire).
There is also a lot of support available on the Gilead forum , mainly from the actual author of the library, Bruno Marchesson. Having first been announcement two years ago, Gilead has matured and is renowned for its effectiveness for GWT and Hibernate integration.
However, Gilead does come with some disadvantages:
- The initial project configuration is a crucial initial step that is sometimes difficult to get right for newcomers to the library. Much of the forum posts on the Gilead project are in fact related to issues with configuration and setup.
- The Gilead library works like a black box, much like Hibernate and GWT.
- The '
dynamic proxy
' feature is still in beta mode.
Conclusion 结论
如果你正在服务端使用Hibernate, hopefully the integration strategies discussed above will help get your GWT client-side talking to your Hibernate backend. Each of these have their pluses and minuses, which vary especially with respect to the burden on the developer implementing the interoperation. However, the overarching concern across each of these strategies, as well as other facets of web application development, should always be performance for your users. A number of these approaches can sometimes incur considerable runtime overhead. For example, the Dozer and Gilead approach may become taxing to the user experience when there are larger sets of data to serialize, whereas the DTO solution can be designed to be as concise and effective as needed to improve performance.
There are other aspects of Hibernate and GWT integration that might not have been covered in this article. For any further discussions, I strongly encourage you to come visit us on the GWT Developer Forum .
原文链接:http://code.google.com/intl/zh-CN/webtoolkit/articles/using_gwt_with_hibernate.html