如今,Hibernate正在迅速成为非常流行的(如果不是最流行的)J2EE O/R映射程序/数据集成框架。它为开发人员提供了处理企业中的关系数据库的整洁、简明且强大的工具。但如果外部需要访问这些已被包装在J2EE Web应用程序中的实体又该怎么办?是开发独立但相同的实体来访问数据,还是另外编写Web组件来管理内部访问的数据?
在某种程度上,这些问题是一定会发生的,对于我来说,当我的公司要向数据库中加载来自多个供应商的多种文件格式的记录时,就出现了这些问题。我考虑过以前常用的方法:用shell和SQL脚本(甚至存储过程)来加载数据。但由于数据模型过于复杂,我决定尽量利用现有的实体、Spring DAO以及Web应用程序之外的服务,并开发定制的J2SE命令行数据加载程序。
重要问题:我们是否应该这样做?
目前与Hibernate有关的大部分文档和例子都基于在容器中使用Hibernate。无论Hibernate是用于Web应用程序还是内部的“胖应用程序”,总是涉及到一个容器。这样做是有原因:容器支持各种特性,比如事务处理、线程和安全性。现在,要开发中型和企业应用程序,有一些工具是必需的。但当我们需要在容器外部访问实体对象时要怎么做?是使用现有的基础架构和代码呢,还是从另一种角度甚至还可能使用另一种语言去解决问题?当然,这个问题没有正确答案,在本文余下的部分中我将解释我所使用的方法,即,在Spring容器外重用现有的实体/POJO。
脚本语言(如:Perl、Python、Ruby,甚至是Tcl)乍一看都有一些优点。很多时候,脚本语言可以快速开发,并易于获得初始结果,它还可以绕过Hibernate底层的复杂性。有可能在短短数行内就连接到数据库、选择一些结果并将其打印到屏幕或某个日志文件中。但受数据模型的影响,事情可能(通常情况下都会)变得非常复杂。假设有一张person表,其中有一个到address表的外键,在插入数据时,address没有被正确插入,这会导致person也不能被插入:这是典型的事务问题。有人可能会辩解说在脚本语言中这个问题并不难解决,就像在主应用程序中所做的那样。但还是有问题存在:为什么要这样做?如果逻辑已经存在于应用程序中,为什么还要再次进行编码?而且这并不是唯一的问题,我们将需要复制工作和逻辑,还可能由此产生许多错误。
有些人可能认为这些都不是大问题,并用自认为是最合适的工具来解决这些问题。也许您已经由于编码之外的原因使用了某种独立的基础架构。也许您事先将数据上传到独立的数据库中并进行充分测试,然后再将数据迁移到生产数据库中。又或者您的数据库维护工作已经外包出去,您只需要将文件发送给合作伙伴公司,由他们来解决这些问题。最后,可能还有许多其他原因造成您并没有使用现有的Hibernate数据层——不管这些原因正确与否。但如果您可以并打算在应用程序之外使用现有的代码库,请继续往下读。我将介绍一些技巧,并解决一些令人头疼的问题。
配置
一旦决定在容器之外使用现有的Hibernate对象,那么首先就必须自己管理所有的配置。下文介绍的方法是使用一个独立的Java命令行应用程序。既然已经设置了Hibernate XML配置文件,那么您应该知道哪些参数是必需的,比如JNDI DataSource名称、实体映射文件以及用于记录SQL的各种属性。如果您决定使用命令行应用程序,那就一定要解决如何分析XML文件并把它添加到新配置中的问题。分析XML文档不是不可能的,但是这有时会带来一些其他的小任务。因此我建议使用常规的属性文件。属性文件的加载非常简单,而且从其中取值也很容易。下面的例子示范了配置Hibernate所需的最小属性集(没有任何实体映射)。
hibernate.dialect=net.sf.hibernate.dialect.PostgreSQLDialect
hibernate.connection.driver_class=org.postgresql.Driver
hibernate.connection.url=jdbc:postgresql://devserver/devdb
hibernate.connection.username=dbuser
hibernate.connection.password=dbpassword
hibernate.query.substitutions yes 'Y'
正如您所看到的,上面的属性指定了数据库的非标准语言,JDBC驱动类、数据库服务器名称、用户名、密码以及是否使用查询替换。一旦定义这些属性并保存到hibernate.properties文件(应该在类路径下)中,就很容易加载它们并传递给Hibernate Configuration对象。
Properties props = new Properties();
try {
props.load(props.getClass().getResourceAsStream("hibernate.properties"));
}catch(Exception e){
System.out.println("Error loading hibernate "+"properties.");
e.printStackTrace();
System.exit(0);
}
String driver = props.getProperty("hibernate.connection." + "driver_class");
String connUrl = props.getProperty("hibernate.connection.url");
String username = props.getProperty("hibernate.connection." + "username");
String password = props.getProperty("hibernate.connection.password");
// In my examples, I use Postgres, but Hibernate
// supports virtually every popular dbms out there.
Class.forName("org.postgresql.Driver");
Connection conn = DriverManager.getConnection(connUrl, username, password);
Configuration cfg = new Configuration();
cfg.setProperties( props );
SessionFactory sessions = cfg.buildSessionFactory();
Session session = sessions.openSession(conn);
段代码描述了如何从hello包加载Message的实体定义。但这种方式只适用于某些情况,对大部分实体来说这样做是乏味且容易出错的,这些代码必须人工维护,每次增加新的实体都要更新加载程序代码 。真令人厌烦!有一种更容易的发现并加载这些映射的方法,可以使这些映射与.jar一样经常保持最新。 首先,正如在web应用程序或企业应用程序中一样,映射文件必须保存在类路径中,这样Hibernate才能正常工作。这是一件好事,因为只需使用同样的.jar文件并找到这些映射文件名。如果在类路径中有多个.jar文件,则需要指定哪个文件包含映射。下面的代码是寻找映射的方法之一。