目前人们很容易发现Hibernate正迅速的成为流行的JEE的O/R映射工具和数据集成框架(如果不是最流行的)Hibernate为企业应用开发者操作关系数据库的提供了清晰明了而又强大的工具然而如果你需要在外部访问那些包装在JEE web应用里的实体情况又怎样呢?你的开发跟应用独立分开却又相同的实体以访问你的数据吗?又或者你得编写附加的web组件来管理对数据的内部访问吗?
在很多情况下这些问题都会出现我的情况是我的公司需要将来自多个供应商有着多种文件格式的记录导入到数据库里我想起我以前经常使用的方法那就是编写Shell和SQL教本(甚至是存储过程)来导入数据但是由于我们的数据模型太过复杂我决定在web应用之外尽可能的利用现有的实体Spring DAO以及服务并且开发一个自定义的JSE命令行数据加载工具
大问题你该怎样呢?
现在很多Hibernate的文档和范例都是绑定在容器上不管是web应用还是内部的大型应用总会使用到容器的人们有很好的理由去使用它容器是设计来提供对各种特性的支持例如事务处理线程以及安全现今这些特性都是开发中等规模和企业应用所必需的工具然而当你需要在容器之外访问实体时你该怎样呢?你是使用现有的架构和代码呢还是会从一个不同的角度来解决问题比如说完全采用另一种开发语言?当然我们没有正确答案在本文的余下部分我将说明我的方法就是在Spring容器之外重用现有的实体/POJO
起初脚本语言例如PerlPythonRuby甚至Tcl(是的我以前也做过这个)看起来有很多优势它们能省下很多时间可以轻易得到初始结果还能规避许多Hibernate潜在的复杂度人们完全可能只用几行代码就可以连接数据库查询结果已经打印输出到终端屏幕或者日志文件然而取决于你的数据模型事情也(总是)会变得相当复杂譬如说你有一个表 person 其中有一个外键属于表 address当我们添加数据的时候表address没有正确的插入数据就会导致表person 也不能插入了这是个很典型的事务处理方面的问题也许有人会说在脚本语言中这个问题不难解决就像在你的主程序里一样可是问题仍然存在为什么要这样做呢?业务逻辑不是已经在你的应用里面了吗?为什么要在写一遍代码呢?而且这并不是唯一的情况你必须重复你的工作和业务逻辑这样就会带来出错的可能
然而有些人会觉得这样也行他们使用自己觉得最适合的工具也许你已经因为程序之外的原因而有了某种独立的架构也许你会在独立的数据库里加载和测试数据然后在通过各种测试后再迁移到产品的数据库里又也许你把数据库维护外包出去你只需要把相关文件发给合作伙伴让他们去处理那些问题总之总会有很多理由不使用现有的Hibernate数据层没有谁对谁错只是如果你可以也愿意在你的应用之外使用现有的代码请往下看我会告诉你一些方法这能解决你不少的烦恼噢
配置
如果你觉得可以在容器之外使用现有的Hibernate对象的话那你首先要做的事就是得自己手工管理所有的配置项在本文余下部分我所采用的方法是使用一个基于命令行的JAVA程序既然你已经配置了Hibernate XML配置文件你应该知道需要提供的参数例如JNDI DataSource名实体映射文件还有其他一些处理SQL日志的属性如果你想使用命令行程序的话你就得解决如何解析XML文件和把它添加到配置项中的这些问题虽然解析XML文件也不难但这本身并不是我们的重点因此我建议使用propetries文件properties文件比较直观而且容易加载并从中读取数据下面是配置Hibernate所需要的最小属性集(不包括任何实体映射)
清单
hibernatedialect=netsfhibernatedialectPostgreSQLDialect
nnectiondriver_class=orgpostgresqlDriver
nnectionurl=jdbc:postgresql://devserver/devdb
nnectionusername=dbuser
nnectionpassword=dbpassword
hibernatequerysubstitutions yes Y
正如你所看到的上面的属性值指定了数据库方言JDBC驱动数据库url用户名用户密码以及是否使用查找替换只要定义以上几项数值并保存在文件hibernateproperties里(要放置在你的类路径里面哦)就能很轻松的加载填充到Hibernate Configuation类里面
清单
Properties props = new Properties();
try {
propsload(propsgetClass()getResourceAsStream(hibernateproperties));
}catch(Exception e){
Systemoutprintln(Error loading hibernate properties);
eprintStackTrace();
Systemexit();
}
String driver = propsgetProperty(nnectiondriver_class);
String connUrl = propsgetProperty(nnectionurl);
String username = propsgetProperty(nnectionusername);
String password = propsgetProperty(nnectionpassword);
// In my examples I use Postgres but Hibernate
// supports virtually every popular dbms out there
ClassforName(orgpostgresqlDriver);
Connection conn = DriverManagergetConnection(connUrl username password);
Configuration cfg = new Configuration();
cfgsetProperties( props );
SessionFactory sessions = cfgbuildSessionFactory();
Session session = sessionsopenSession(conn);
这样我们就得到了Hibernate Session类了但我们也有必要解决如何利用现有的实体映射这个问题在《Hibernate in Action》一书中提到怎样从实体映射XML文件中加载如下所示
清单
Configuration cfg = new Configuration();
cfgaddResource(hello/Messagehbmxml);
cfgsetProperties( SystemgetProperties() );
SessionFactory sessions = cfgbuildSessionFactory();
这段代码清晰的说明了从hello包里加载Message实体定义的过程对于这个例子来说还好但对那些有多个实体的应用来说就很单一而且容易出错不仅映射关系是硬编码还得手工管理每次添加一个新的实体就要更新实体加载的代码其实有跟简单的方法去查找和加载映射关系以使其与最新的jar文件保持一致
首先在你的web服务器或者企业服务器里映射文件需要放置在类路径里这样Hibernate才能正常的运行这样做是很有好处的因为你所需要做的就是使用同样的jar包和查找相应的映射文件的名字因为你可能会有多个jar文件在你的类路径里你需要指定哪个jar包包含了映射文件以下就是一种查找映射关系的方法
清单
String cp = SystemgetProperty(javaclasspath);
String jarFile = null;
List hbmList = null;
String[] cparr = cpsplit(\\:);
for(int j=;j<cparrlength;j++){
// The following assumes our entities
// are wrapped up in a jar file
// called dbobjsjar
if(cparr[j]indexOf(dbobjsjar) != )
jarFile=(cparr[j]);
}
if(jarFile != null){
JarFile jar = new JarFile(new File(jarFile));
Enumeration e = jarentries();
if(ehasMoreElements()){
hbmList = new ArrayList();
while(ehasMoreElements()){
// Object comes back as JarFile$JarFileEntry
JarEntry entry = (JarEntry)enextElement();
if(entrygetName()indexOf(hbmxml) != ){
hbmListadd(entrygetName());
}
}
}else {
Systemoutprintln(Error: The entity jar dbobjsjar was not found in +
classpath: + cp);
}
}
上面的代码主要完成了以下几件事情获取Java虚拟机初始化的classpath系统属性查找含有实体映射文件的jar包解析映射文件的名字然后添加到一个ArrayList对象中去当我们的ArrayList对象装满了实体映射的名字后就可以将其传递到Hibernate Configuration 对象如下所示
清单
Configuration cfg = new Configuration();
Iterator iterator = erator();
while(iteratorhasNext()){
cfgaddResource((String)iteratornext());
}
只要我们在Hibernate Session 对象里配置好正确的映射关系我们就可以将实体拿来使用了
使用Session
关于这一点你可以参考关于Hibernate和持久层的文章或者指南也可以查询各种对象和实例来了解怎么使用事务所以我不打算详细说这些内容相反我会更多考虑使用实体后我们需要做什么?这会对Hibernate Session 对象有怎样的影响?是否可以使用现有的业务对象甚至是数据访问对象?当我建立数据层的时候我使用了Spring及其提供的一些类来管理数据库连接事务和会话这些对象都在XML配置文件里面定义了并与很多规则和关系紧密集成在Spring里首先通过Spring的依赖注射DAO对象会被引入到应用服务中(关于依赖注射参见Bruce Tate的《Five Things I Love About Spring》一书)然后配置应用服务以捕获DAO异常(通过XML配置文件)让Spring去处理可是因为我觉得把Spring集成到数据加载应用里会带来相当大的工作量我对DAO对象做了轻微的修改使得可以在web应用之外使用他们
例如说在PersonDAO类里面有一个保存person对象的方法如果Hibernate Session 是由容器建立的那这个方法就不能在容器外使用因为这需要一个配置好的Session对象以下是一个典型的PersonDAO它由Spring 容器提供了对Session的支持
清单
import orgspringframeworkormhibernateHibernateTemplate;
import testpojosPerson;
public class PersonDAO extends HibernateTemplate {
public PersonDAO(){}
public Person save(Person aPerson) {
if(aPerson != null) supersave(person);
return person;
}
}
上面的类继承的是Spring HibernateTemplate 类它提供了各种不错的使用Hibernate的基类方法而且由于HibernateTemplate类维护了大多数的常见操作你就只需要专注特定的持久层需求当然也应该有相应的异常处理但在本次范例当中只要以上的就够了
现在要在容器外加上对Session的支持我们只需要做一些小小的改动
清单
import orgspringframeworkormhibernateHibernateTemplate;
import netsfhibernateSession;
import testpojosPerson;
public class PersonDAO extends HibernateTemplate {
public PersonDAO(){}
public void setExternalSessionFactory(Session aSession){
setSessionFactory(sessiongetSessionFactory());
}
public Person save(Person aPerson) {
if(aPerson != null) supersave(person);
return person;
}
}
因为HibernateTemplate类继承于HibernateAccessor类我们就可以从任何Session对象中建立SessionFactory这是Spring小组的一个高灵活性的设计使得重用现有代码更加容易
也许你并没有使用Spring而是采用完全不同的方法如果你不喜欢Spring的一来注射你也可以通过JNDI查找Session对象
清单
import netsfhibernateSession;
public class PersonDAO {
// This example assumes that there is a Hibernate
// Session object at the following JNDI location
// on a Tomcat server:
// java:/comp/env/obj/hibernateSession
private Session session;
public PersonDAO(){
try {
Context initCtx = new InitialContext();
Context envCtx = (Context) initCtxlookup(java:comp/env);
session = (Session)envCtxlookup(obj/hibernateSession);
}catch(Exception e) {
eprintStackTrace();
}
}
public Person save(Person aPerson) {
if(aPerson != null) sessionsave(person);
return person;
}
}
以上的例子依赖于应用服务器来使得Hibernate Session对象可用在容器之外使用的最简单方法就是添加一个带Session参数的构造函数如下所示
清单
import netsfhibernateSession;
public class PersonDAO {
// This example assumes that there is a Hibernate
// Session object at the following JNDI location
// on a Tomcat server:
// java:/comp/env/obj/hibernateSession
private Session session;
public PersonDAO(){
try {
Context initCtx = new InitialContext();
Context envCtx = (Context) initCtxlookup(java:comp/env);
session = (Session)envCtx lookup(obj/hibernateSession);
}catch(Exception e) {
eprintStackTrace();
}
}
public PersonDAO(Session aSession){
session = aSession;
}
public Person save(Person aPerson) {
if(aPerson != null) sessionsave(person);
return person;
}
}
当然我们并没有处理太多的异常事务问题甚至我们在多个方法内共用一个Session对象这会导致一些并发事务问题(取决于容器或框架如何处理对象实例)不过我想很显然以上的例子演示了如何重用大量的现有数据层代码只需要一点点有创造力的想法要弄清楚你是否想在应用服务器之外使用现有的实体和DAO接下来就不断尝试把
结论
正如你所见要在web容器外使用Hibernate 实体和DAO是需要技巧的但这肯定能做到的最大的困难在于如何查找实体映射关系和如何重设置(或者说修改)现有的数据访问对象(即DAO)处理后者时要小心处理事务问题因为没有应用服务可以依赖了不过最后我们还是可以访问所有的实体和进行持久化的对象这样能省下大量的重新开发代码的时间祝您好运!!