Recently in HIBERNATE Category

对于大多数开发者来说,在系统中为每一个DAO编写几乎一样的代码已经成为了一种习惯。同时大家也都认可这种重复就是“代码的味道”,我们中的大多 数已经习惯如此。当然也有另外的办法。你可以使用很多ORM工具来避免代码的重复编写。举个例子,用Hibernate,你可以简单的使用session 操作直接控制你的持久化领域对象。这种方式的负面影响就是丢失了类型安全。

为什么你的数据访问代码需要一个类型安全的接口?我认为它减少了编程错误,提高了生产率,尤其是在使用现代高级IDE的时候。首先,一个类型安全的 接口清晰的制定了哪些领域对象具有持久化功能。其次,它消除了类型转换带来的潜在问题。最后,它平衡了IDE的自动完成功能。使用自动完成功能是最快的方 式来记住对于适当的领域类哪些查询是可用的。

在这篇文章里,我将展示给大家如何避免一次次地重复编写DAO代码,但同时还收益于类型安全的接口。事实上,所有内需要编写的是为新的DAO编写一个Hibernate映射文件,一个POJO的Java接口,并且10行Spring配置文件。

DAO实现

DAO模式对于任何Java开发人员来说都是耳熟能详的。这个模式的实现相当多,所以让我们仔细推敲一下我这篇文章里面对于DAO实现的一些假设:

  • 所有系统中的数据库访问都是通过DAO来完成封装
  • 每一个DAO实例对一个主要的领域对象或者实体负责。如果一个领域对象具有独立的生命周期,那么它需要具有自己的DAO。
  • DAO具有CRUD操作
  • DAO可以允许基于criteria方式的查询而不仅仅是通过主键查询。我将这些成为finder方法或者finders。这个finder的返回值通常是DAO所负责的领域对象的集合。

范型DAO接口

范型DAO的基础就是CRUD操作。下面的接口定义了范型DAO的方法:

public interface GenericDao <T, PK extends Serializable> {
 
/** Persist the newInstance object into database */
PK create(T newInstance);
 
/** Retrieve an object that was previously persisted to the database using
* the indicated id as primary key
*/

T read(PK id);
 
/** Save changes made to a persistent object. */
void update(T transientObject);
 
/** Remove an object from persistent storage in the database */
void delete(T persistentObject);
}

实现这个接口

使用Hibernate实现上面的接口是非常简单的。也就是调用一下Hibernate的方法和增加一些类型转换。Spring负责session和transaction管理。

public class GenericDaoHibernateImpl <T, PK extends Serializable>
implements GenericDao<T, PK>, FinderExecutor {
private Class<T> type;
 
public GenericDaoHibernateImpl(Class<T> type) {
this.type = type;
}
 
public PK create(T o) {
return (PK) getSession().save(o);
}
 
public T read(PK id) {
return (T) getSession().get(type, id);
}
 
public void update(T o) {
getSession().update(o);
}
 
public void delete(T o) {
getSession().delete(o);
}
 
// Not showing implementations of getSession() and setSessionFactory()
}

Spring 配置

最后,Spring配置,我创建了一个GenericDaoHibernateImpl的实例。GenericDaoHibernateImpl的 构造器必须被告知领域对象的类型,这样DAO实例才能为之负责。这个同样需要Hibernate运行时知道这个对象的类型。下面的代码中,我将领域类 Person传递给构造器并且将Hibernate的session工厂作为一个参数用来实例化DAO:

<bean id="personDao" class="genericdao.impl.GenericDaoHibernateImpl">
<constructor-arg>
<value>genericdaotest.domain.Person</value>
</constructor-arg>
<property name="sessionFactory">
<ref bean="sessionFactory"/>
</property>
</bean>

可用的范型DAO

我还没有全部完成,但我现在已经有了一个可供作的代码。下面的代码展示了范型DAO如何使用:

public void someMethodCreatingAPerson() {
...
GenericDao dao = (GenericDao)
beanFactory.getBean("personDao"); // This should normally be injected
 
Person p = new Person("Per", 90);
dao.create(p);
}

这时候,我有一个范型DAO有能力进行类型安全的CRUD操作。同时也有理由编写GenericDaoHibernateImpl的子类来为每个领 域对象增加查询功能。但是这篇文章的主旨在于展示如何完成这项功能而不是为每个查询编写明确的代码,然而,我将会使用多个工具来介绍DAO的查询,这就是 Spring AOP和Hibernate命名查询。

Spring AOP介绍

你可以使用Spring AOP提供的introduction功能将一个现存的对象包装到一个代理里面来增加新的功能,定义它需要实现的新接口,并且将之前所有不支持的方法委派 到一个处理机。在我的DAO实现里面,我用introduction将一定数量的finder方法增加到现存的范型DAO类里面。因为finder方法针 对特定的领域对象,所以它们被应用到表明接口的范型DAO中。

<bean id="finderIntroductionAdvisor" class="genericdao.impl.FinderIntroductionAdvisor"/>
 
<bean id="abstractDaoTarget"
class="genericdao.impl.GenericDaoHibernateImpl" abstract="true">

<property name="sessionFactory">
<ref bean="sessionFactory"/>
</property>
</bean>
 
<bean id="abstractDao"
class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true">

<property name="interceptorNames">
<list>
<value>finderIntroductionAdvisor</value>
</list>
</property>
</bean>

在上面的配置中,我定义了三个Spring bean,第一个bean,FinderIntroductionAdvisor,处理那些introduce到DAO中但是不属于 GenericDaoHibernateImpl类的方法。一会我再介绍Advisor bean的详细情况。

第二个bean定义为“abstract”。在Spring中,这个bean可以被其他bean重用但是它自己不会被实例化。不同于抽象属性, bean的定义简单的指出了我需要一个GenericDaoHibernateImpl的实例同时需要一个SessionFactory的引用。注意 GenericDaoHibernateImpl类只定义了一个构造器接受领域类作为参数。因为这个bean是抽象的,我可以无限次的重用并且设定合适的 领域类。

最后,第三个,也是最有意思的是bean将GenericDaoHibernateImpl的实例包装进了一个代理,给予了它执行finder方法的能力。这个bean定义同样是抽象的并且没有指定任何接口。这个接口不同于任何具体的实例。

扩展通用DAO

每个DAO的接口,都是基于GenericDAO接口的。我需要将为特定的领域类适配接口并且将其扩展包含我的finder方法。

public interface PersonDao extends GenericDao<Person, Long> {
List<Person> findByName(String name);
}

上面的代码清晰的展示了通过用户名查找Person对象列表。所需的Java实现类不需要包含任何的更新操作,因为这些已经包含在了通用DAO里。

配置PersonDao

因为Spring配置依赖之前的那些抽象bean,所以它变得很紧凑。我需要指定DAO负责的领域类,并且我需要告诉Spring我这个DAO需要实现的接口。

<bean id="personDao" parent="abstractDao">
<property name="proxyInterfaces">
<value>genericdaotest.dao.PersonDao</value>
</property>
<property name="target">
<bean parent="abstractDaoTarget">
<constructor-arg>
<value>genericdaotest.domain.Person</value>
</constructor-arg>
</bean>
</property>
</bean>

你可以这样使用:

public void someMethodCreatingAPerson() {
...
PersonDao dao = (PersonDao)
beanFactory.getBean("personDao"); // This should normally be injected
 
Person p = new Person("Per", 90);
dao.create(p);
 
List<Person> result = dao.findByName("Per"); // Runtime exception
}

上面的代码是使用类型安全接口PersonDao的一种正确途径,但是DAO的实现并没有完成。当调用findByName()的时候导致了一个运 行时异常。这个问题是我还没有findByName()。剩下的工作就是指定查询语句。要完成这个,我使用Hibernate命名查询。

Hibernate命名查询

使用Hibernate,你可以定义任何HQL查询在映射文件里,并且给它一个名字。你可以在之后的代码里面方便的通过名字引用这个查询。这么做的 一个优点就是能够在部署的时候调节查询而不需要改变代码。正如你一会将看到的,另一个好处就是实现一个“完整”的DAO而不需要编写任何Java实现代 码。

<hibernate-mapping package="genericdaotest.domain">
<class name="Person">
<id name="id">
<generator class="native"/>
</id>
<property name="name" />
<property name="weight" />
</class>
 
<query name="Person.findByName">
<![CDATA[select p from Person p where p.name = ? ]]>
</query>
</hibernate-mapping>

上面的代码定义了领域类Person的Hibernate映射文件,有两个属性:name和weight。Person是一个具有上面属性的简单的 POJO。这个文件同时包含了一个查询,通过提供的name属性从数据库查找Person实例。Hibernate为命名查询提供了不真实的命名空间功 能。为了便于讨论,我将所有的查询名字的前缀变成领域类的的名称。在现实场景中,使用完整的类名,包含包名,是一个更好的主意。

总览

你已经看到了为任何领域对象创建并配置DAO的所需步骤了。这三个简单的步骤就是:

  1. 定义一个接口继承GenericDao并且包含任何所需的finder方法
  2. 在映射文件中为每个领域类的finder方法增加一个命名查询。
  3. 为DAO增加10行Spring配置

可重用的DAO类

Spring advisor和interceptor的功能比较琐碎,事实上他们的工作都引用回了GenericDaoHibernateImpl类。所有带有“find”开头的方法都被传递给DAO的单一方法executeFinder()。

public class FinderIntroductionAdvisor extends DefaultIntroductionAdvisor {
public FinderIntroductionAdvisor() {
super(new FinderIntroductionInterceptor());
}
}
 
public class FinderIntroductionInterceptor implements IntroductionInterceptor {
 
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
 
FinderExecutor genericDao = (FinderExecutor) methodInvocation.getThis();
 
String methodName = methodInvocation.getMethod().getName();
if (methodName.startsWith("find")) {
Object[] arguments = methodInvocation.getArguments();
return genericDao.executeFinder(methodInvocation.getMethod(), arguments);
} else {
return methodInvocation.proceed();
}
}
 
public boolean implementsInterface(Class intf) {
return intf.isInterface() && FinderExecutor.class.isAssignableFrom(intf);
}
}

executeFinder() 方法

上面的代码唯一缺的就是executeFinder的实现。这个代码观察被调用的类的名字和方法,并且将他们与Hibernate的查询名相匹配。 你可以使用一个FinderNamingStrategy来激活其他方式的命名查询。默认的实现查找一个名为 “ClassName.methodName”的查询,ClassName是除包名之外的类名。

public List<T> executeFinder(Method method, final Object[] queryArgs) {
final String queryName = queryNameFromMethod(method);
final Query namedQuery = getSession().getNamedQuery(queryName);
String[] namedParameters = namedQuery.getNamedParameters();
for(int i = 0; i < queryArgs.length; i++) {
Object arg = queryArgs[i];
Type argType = namedQuery.setParameter(i, arg);
}
return (List<T>) namedQuery.list();
}
 
public String queryNameFromMethod(Method finderMethod) {
return type.getSimpleName() + "." + finderMethod.getName();
}

总结

在Java 5之前,Java语言并不支持代码同时具有类型安全和范性的特性;你不得不二者选一。在这篇文章里,你可以看到使用Java 5范型支持并且结合Spring和Hibernate(和AOP)一起来提高生产力。一个范型类型安全的DAO类非常容易编写,所有你需要做的就是一个接 口,一些命名查询,并且10行Spring配置,并且可以极大的减少错误,同时节省时间。

代码下载: j-genericdao.zip

hibernate与应用缓存方案总结

| No Comments | No TrackBacks

XXXX项目是目前在实际工作中正在做的事情,该项目是一个大型系统的内容管理内核,负责最核心的meta data的集中管理,性能有较高的要求,设计初期就要求能够支持cluster。项目使用hibernate 3.2,针对开发过程中对于各种缓存的不同看法,撰写了本文。重点在于澄清一些hibernate的缓存细节,纠正一些错误的缓存用法。

一、hibernate的二级缓存
如果开启了二级缓存,hibernate在执行任何一次查询的之后,都会把得到的结果集放到缓存中,缓存结构可以看作是一个hash table,key是数据库记录的id,value是id对应的pojo对象。当用户根据id查询对象的时候(load、iterator方法),会首先 在缓存中查找,如果没有找到再发起数据库查询。但是如果使用hql发起查询(find, query方法)则不会利用二级缓存,而是直接从数据库获得数据,但是它会把得到的数据放到二级缓存备用。也就是说,基于hql的查询,对二级缓存是只写 不读的。

针对二级缓存的工作原理,采用iterator取代 list来提高二级缓存命中率的想法是不可行的。Iterator的工作方式是根据检索条件从数据库中选取所有目标数据的id,然后用这些id一个一个的 到二级缓存里面做检索,如果找到就直接加载,找不到就向数据库做查询。因此假如iterator检索100条数据的话,最好情况是100%全部命中,最坏 情况是0%命中,执行101条sql把所有数据选出来。而list虽然不利用缓存,但是它只会发起1条sql取得所有数据。在合理利用分页查询的情况下, list整体效率高于iterator。

二级缓存的失效机制由hibernate控制,当某条数据被修改之后,hibernate会根据它的id去做缓存失效操作。基于此机制,如果数据表不是被hibernate独占(比如同时使用jdbc或者ado等),那么二级缓存无法得到有效控制。

由于hibernate的缓存接口很灵活,cache provider可以方便的切换,因此支持cluster环境不是大问题,通过使用swarmcache、jboss cache等支持分布式的缓存方案,可以实现。但是问题在于:
1、 分布式缓存本身成本偏高(比如使用同步复制模式的jboss cache)
2、 分布式环境通常对事务控制有较高要求,而目前的开源缓存方案对事务缓存(transaction cache)支持得不够好。当jta事务发生会滚,缓存的最后更新结果很难预料。这一点会带来很大的部署成本,甚至得不偿失。

结论:XXXX不应把hibernate二级缓存作为优化的主要手段,一般情况下建议不要使用。

原因如下:
1、 XXXX 的DAO类大部分是从1.0升级过来,由于1.0采用的是hibernate 2.1,所以在批量删除数据的时候采用了native sql的方式。虽然XXXX2.0已经完全升级到hibernate 3.2,支持hibernate原生的批量删改,但是由于hibernate批量操作的性能不如sql,而且为了兼容1.0的dao类,所以很多地方保留 了sql操作。哪些数据表是单纯被hibernate独占无法统计,而且随着将来业务的发展可能会有很大变数。因此不宜采用二级缓存。
2、 针对系统业务来说,基于id检索的二级缓存命中率极为有限,hql被大量采用,二级缓存对性能的提升很有限。
3、 hibernate 3.0在做批量修改、批量更新的时候,是不会同步更新二级缓存的,该问题在hibernate 3.2中是否仍然存在尚不确定。

二、hibernate的查询缓存

查询缓存的实现机制与二级缓存基本一致,最大的差异在于放入缓存中的key是查询的语句,value是查询之后得到的结果集的id列表。表面看来这 样的方案似乎能解决hql利用缓存的问题,但是需要注意的是,构成key的是:hql生成的sql、sql的参数、排序、分页信息等。也就是说如果你的 hql有小小的差异,比如第一条hql取1-50条数据,第二条hql取20-60条数据,那么hibernate会认为这是两个完全不同的key,无法 重复利用缓存。因此利用率也不高。

另外一个需要注意的问题是,查询缓存和二级缓存是有关联关系的,他们不是完全独立的两套东西。假如一个查询条件hql_1,第一次被执行的时候,它 会从数据库取得数据,然后把查询条件作为 key,把返回数据的所有id列表作为value(请注意仅仅是id)放到查询缓存中,同时整个结果集放到class缓存(也就是二级缓存),key是 id,value是pojo对象。当你再次执行hql_1,它会从缓存中得到id列表,然后根据这些列表一个一个的到class缓存里面去找pojo对 象,如果找不到就向数据库发起查询。也就是说,如果二级缓存配置了超时时间(或者发呆时间),就有可能出现查询缓存命中了,获得了id列表,但是 class里面相应的pojo已经因为超时(或发呆)被失效,hibernate就会根据id清单,一个一个的去向数据库查询,有多少个id,就执行多少 个sql。该情况将导致性能下降严重。

查询缓存的失效机制也由 hibernate控制,数据进入缓存时会有一个timestamp,它和数据表的timestamp对应。当hibernate环境内发生save、 update等操作时,会更新被操作数据表的timestamp。用户在获取缓存的时候,一旦命中就会检查它的timestamp是否和数据表的 timestamp匹配,如果不,缓存会被失效。因此查询缓存的失效控制是以数据表为粒度的,只要数据表中任何一条记录发生一点修改,整个表相关的所有查 询缓存就都无效了。因此查询缓存的命中率可能会很低。

结论:XXXX不应把hibernate二级缓存作为优化的主要手段,一般情况下建议不要使用。

原因如下:
1、 XXXX的上层业务中检索条件都比较复杂,尤其是涉及多表操作的地方。很少出现重复执行一个排序、分页、参数一致的查询,因此命中率很难提高。
2、 查询缓存必须配合二级缓存一起使用,否则极易出现1+N的情况,否则性能不升反降
3、 使用查询缓存必须在执行查询之前显示调用Query.setCacheable(true)才能激活缓存,这势必会对已有的hibernate封装类带来问题。

总结
详细分析hibernate的二级缓存和查询缓存之后,针对XXXX项目的具体情况做出结论,在底层使用通用缓存方案的想法基本上是不可取的。比较好的做 法是在高层次中(业务逻辑层面),针对具体的业务逻辑状况手动使用数据缓存,不仅可以完全控制缓存的生命周期,还可以针对业务具体调整缓存方案提交命中 率。 Cluster中的缓存同步可以完全交给缓存本身的同步机制来完成。比如开源缓存swarmcache采用invalidate的机制,可以根据用户指定 的策略,在需要的时候向网络中的其他swarmcache节点发送失效消息,这一机制和XXXX1.0中已经采用的MappingCache的同步方案基 本一致。建议采用。

Hibernate性能调优

| No Comments | No TrackBacks

一、inverse = ?

          inverse=false(default)
                      用于单向one-to-many关联
                      parent.getChildren().add(child) // insert child
                      parent.getChildren().delete(child) // delete child
           inverse=true
                      用于双向one-to-many关联
                      child.setParent(parent); session.save(child) // insert child
                       session.delete(child)
            在分层结构的体系中
             parentDao, childDao对于CRUD的封装导致往往直接通过session接口持久化对象,而很少通过关联对象可达性 

二、one-to-many关系

                单向关系还是双向关系?
                     parent.getChildren().add(child)对集合的触及操作会导致lazy的集合初始化,在没有对集合配置二级缓存的情况下,应避免此类操作
                   select * from child where parent_id = xxx;
          性能口诀:
                  1.  一般情况下避免使用单向关联,尽量使用双向关联
                  2.  使用双向关联,inverse=“true”
                  3.  在分层结构中通过DAO接口用session直接持久化对象,避免通过关联关系进行可达性持久化

 

三、many-to-one关系

         单向many-to-one表达了外键存储方
         灵活运用many-to-one可以避免一些不必要的性能问题
         many-to-one表达的含义是:0..n : 1,many可以是0,可以是1,也可以是n,也就是说many-to-one可以表达一对多,一对一,多对一关系
          因此可以配置双向many-to-one关系,例如:
                1.   一桌四人打麻将,麻将席位和打麻将的人是什么关系?是双向many-to-one的关系

四、one-to-one

            通过主键进行关联
            相当于把大表拆分为多个小表
            例如把大字段单独拆分出来,以提高数据库操作的性能
            Hibernate的one-to-one似乎无法lazy,必须通过bytecode enhancement

五、集合List/Bag/Set 

            one-to-many
               1.    List需要维护index column,不能被用于双向关联,必须inverse=“false”,被谨慎的使用在某些稀有的场合

               2.      Bag/Set语义上没有区别
               3.       我个人比较喜欢使用Bag
           many-to-many
               1.      Bag和Set语义有区别
               2。   建议使用Set

六、集合的过滤

             1.  children = session.createFilter(parent.getChildren(), “where this.age > 5 and   this.age < 10”).list()
         针对一对多关联当中的集合元素非常庞大的情况,特别适合于庞大集合的分页:
                   session.createFilter(parent.getChildren(),“”).setFirstResult(0).setMaxResults(10).list();

七、继承关系当中的隐式多态

           HQL: from Object
             1.     把所有数据库表全部查询出来
              2.     polymorphism=“implicit”(default)将当前对象,和对象所有继承子类全部一次性取出
              3.      polymorphism=“explicit”,只取出当前查询对象

八、Hibernate二级缓存

              著名的n+1问题:from Child,然后在页面上面显示每个子类的父类信息,就会导致n条对parent表的查询:
                   select * from parent where id = ?
                   .......................
                   select * from parent where id = ?
              解决方案
                        1.      eager fetch
                         2.      二级缓存

九、inverse和二级缓存的关系

            当使用集合缓存的情况下:
                 1.     inverse=“false”,通过parent.getChildren()来操作,Hibernate维护集合缓存
                  2.    inverse=“true”,直接对child进行操作,未能维护集合缓存!导致缓存脏数据
                  3.    双向关联,inverse=“true”的情况下应避免使用集合缓存

十、Hibernate二级缓存是提升web应用性能的法宝

              OLTP类型的web应用,由于应用服务器端可以进行群集水平扩展,最终的系统瓶颈总是逃不开数据库访问;

           哪个框架能够最大限度减少数据库访问,降低数据库访问压力, 哪个框架提供的性能就更高;针对数据库的缓存策略:
                    1.        对象缓存:细颗粒度,针对表的记录级别,透明化访问,在不改变程序代码的情况下可以极大提升web应用的性能。对象缓存是ORM的制胜法宝。
                    2.       对象缓存的优劣取决于框架实现的水平,Hibernate是目前已知对象缓存最强大的开源ORM
                    3.        查询缓存:粗颗粒度,针对查询结果集,应用于数据实时化要求不高的场合

十一、应用场合决定了系统架构

一、是否需要ORM
Hibernate or iBATIS?
二、采用ORM决定了数据库设计

            Hibernate:
                    倾向于细颗粒度的设计,面向对象,将大表拆分为多个关联关系的小表,消除冗余column,通过二级缓存提升性能(DBA比较忌讳关联关系的出现,但是 ORM的缓存将突破关联关系的性能瓶颈);Hibernate的性能瓶颈不在于关联关系,而在于大表的操作
            iBATIS:
                    倾向于粗颗粒度设计,面向关系,尽量把表合并,通过表column冗余,消除关联关系。无有效缓存手段。iBATIS的性能瓶颈不在于大表操作,而在于关联关系。

总结:

     性能口诀
               1、使用双向一对多关联,不使用单向一对多
               2、灵活使用单向多对一关联
               3、不用一对一,用多对一取代
               4、配置对象缓存,不使用集合缓存
               5、一对多集合使用Bag,多对多集合使用Set
               6、继承类使用显式多态
               7、表字段要少,表关联不要怕多,有二级缓存撑腰

Hibernate 3中的formula

| No Comments | No TrackBacks

  HibernateSpring是两个杰出的开源框架,它们在越来越多的J2EE应用中得到采用。尽管它们致力于解决的问题有很大区别,它们却都有一个重要特性:依赖注入。Spring有助于在将对象返回给客户端之前整理出对象之间的依赖关系,从而大大减少客户端的编码。而Hibernate则擅长于在将整个对象模型返回给客户端之前整理出数据模型所表现的依赖关系。当直接使用JDBC将数据模型映射为对象模型时,我们通常需要编写大量的代码以构建对象模型。而Hibernate消除了其中的大部分编码工作。

  Hibernate 2.x提供了基本的表格到对象的映射、常见的关联映射(包括一对一、一对多和多对多关联)、多态映射等等。Hibernate 3.x则通过使用formula、filter、subselect等提高映射灵活性,提供细粒度的解释特性,从而将其推进到一个新的级别。

  在本文中,我们将展示有助于模型转换的各种formula特性。在Hibernate 3.x之前,formula属性只能够出现在property元素中。现在仍然可以这样做,但是Hibernate 3.x提供了一个formula属性或元素(两者在formula的用法方面实质上是等效的),可以在许多元素中使用,包括discriminator、 many-to-one、one-to-one、element、many-to-many、map-key、map-key-many-to-many 和property。这样就大大提高了对象关系映射的灵活性,从而支持对复杂数据模型的更为细粒度的解释。

  基本上,有两种情况必须使用formula:

  • 需要formula的计算结果时。与元素discriminator、element、map-key、map-key-many-to-many和property一起使用formula属于这类情况。
  • 为了连接的目的需要使用formula时。与元素many-to-one、one-to-one和many-to-many一起使用formula属于这类情况。
第一类:从formula获取计算结果 Discriminator(识别器)
在现实数据模式中,经常出现使用一个表来描述另一个表的情况。在对象关系映射中,Formula有助于提供灵活的多态性。

  在图1展示的例子中,有两个表:Product和ProductRelease。每个产品记录有一个ProductReleaseID来引用它对应的产品版本记录,包括产品版本名称、类型、版本日期等。

Product and Product Release Data Model
图1. 产品和产品版本数据模型

  在ProductRelease表中有一个值得注意的属性是SubProductAllowable,它的值可以是0或1。值为1意味着允许该 产品版本中的任何产品有子产品,而值为0则意味着不允许有子产品。例如,有些产品由多个子产品组成,而有些产品只有该产品本身。

  图2展示了一个对该数据模型解释而成的对象模型。Nested接口定义了getSubProducts和setSubProducts方法。 NestedProduct类扩展了基类Product并实现了Nested接口。一个产品数据记录应该是Product还是NestedProduct 取决于相应产品版本记录的SubProductAllowable值。

Product and Product Release Object Domain Model
图2. 产品和产品版本对象的域模型

  为了完成这个模型转换,我们使用了Hibernate 3.x映射,如下:

<hibernate-mapping>
<class name="Product"
discriminator-value="0" lazy="false">
<id name="id" type="long"/>
<discriminator
formula="(select pr.SubProductAllowable
from ProductRelease pr
where pr.productReleaseID=
productReleaseID)"
type="integer" />
<subclass name="NestedProduct"
discriminator-value="1"/>
</class>
</hibernate-mapping>

  如果formula表达式计算结果为0,也就是不支持子产品,则对象将属于Product类。如果结果是1,对象将是一个 NestedProduct。在表1和表2中,对Product表中的第一个记录(ProductID=10000001)来说,初始化的类将是 NestedProduct,因为它引用一个SubProductAllowable=1的ProductRelease记录。对Product表中的第 二个记录(ProductID=20000001)来说,初始化的类将是Product,因为它引用一个SubProductAllowable=0的 ProductRelease记录。

S/N ProductReleaseID SubProductAllowable ...
1 11 1 i
2 601 0 i
. ProductRelease表中的记录

S/N ProductID ProductReleaseID ...
1 10000001 11 i
2 20000001 601 ...
表 2. Product表中的记录
Property
Property元素中的formula允许对象属性包含导出值,比如sum、average、max等的结果。如:

<property name="averagePrice" formula="(select avg(pc.price) from PriceCatalogue pc, SelectedItems si where si.priceRefID=pc.priceID)"/>

  此外,formula还可以基于当前记录的特定属性值从另一个表检索值。例如:

<property name="currencyName" formula="(select cur.name from currency cur where cur.id= currencyID)"/>

  它从currency表检索货币名称。如您所见,这些直接的映射可以消除大量的转换编码。

map-key
formula允许map-key取任何可能的值。在下面的例子中(图3),我们希望Role_roleID成为对象模型的map-key(图4)。

User Role Data Schema
图3. 用户角色数据模式

User Role Object Model
图4. 用户角色对象模型

  在上面的数据模式中,User和Role被通过一个称为User_has_Role的多对多的关系表连接起来。为了获取一个User以及分配给它的所有角色,我们使用下面的映射:

<hibernate-mapping>
<class name="User">
<id name="userID"/>
<map name="roles"
table="UserRole"/>
<key column="User_userID"/>
<map-key
formula="Role_RoleID"
type="string"/>
<many-to-many
column="Role_RoleID"
class="Role"/>
</map>
</class>
<class name="Role">
<id name="roleID"/>
</class>
</hibernate-mapping>
Role_RoleID用作many-to-many元素的连接列值。然而,Hibernate不允许map-key和many-to-many的 column属性同时使用Role_RoleID。但是使用一个formula,Role_RoleID还是可以用于map-key。

  Formula和map-key-many-to-many的用法与map-key类似。然而,map-key-many-to-many通常用于三重关联,其中map键是被引用的对象自身,而不是一个被引用的属性。

  然而,有些地方不支持formula。有些数据库(如Oracle 7)不支持嵌入的select语句(即,嵌入一个SQL语句的select部分中的selectSQL),也不支持用于计算结果的formula。因此,需要首先检查是否支持嵌入式的selectSQL语句。

  因为由Hibernate映射生成的SQL将formula表达式作为其select目标的一部分,所以对所使用的数据库的非标准语言将有助于充分使用formula,尽管这可能会降低代码的可移植性。

第二类:将formula用于连接 many-to-one
现实世界数据模型中的另一个常见场景是私有关系映射,它是指除基本的一对一、一对多和多对多关系之 外的映射。formula是针对这种私有关系管理所提供的元素之一。图5展示了一个例子,其中一个公司可以有多个联系人,但是他们之中只能有一个是默认的 联系人。一个公司有多个联系人是典型的一对多关系。但是,为了标识默认联系人,ContactPerson表使用了一个defaultFlag属性(1为 是,0为否)。


图5. 用户角色数据模式


图6. 用户角色对象模型

  为了将默认联系人关系解释为对象模型(图6),我们使用下面的映射:

<hibernate-mapping>
<class name="Company" table="Company">
<id name="id" />
<many-to-one
name="defaultContactPerson"
property-ref="defaultContactPerson">
<column name="id"/>
<formula>1</formula>
</many-to-one>
</class>
<class name="Person" >
<id name="id" />
<properties name="defaultContactPerson">
<property name="companyID" />
<property name="defaultFlag" />
</properties>
</class>
</hibernate-mapping>

  我们将companyID和defaultFlag聚合到一个名为defaultContactPerson的properties元素中,形 成Person表的一个独有的键。将Company类中的many-to-one元素与Person类中的 defaultContactPersonproperties元素连接。产成的SQL将类似于:

select c.id, p.id from Company c, Person p where p.companyID=c.id and p.defaultFlag=1

one-to-one
在Hibernate中,one-to-one主要用于两个表共用相同的主键。而对于外键关联,通常使用many-to-one。但是,使用formula,one-to-one可以通过外键连接多个表。上面的many-to-one例子可以使用one-to-one映射为:

<hibernate-mapping>
<class name="Company" table="Company" >
<id name="id" />
<one-to-one name="defaultContactPerson"
property-ref="defaultContactPerson" >
<formula>id</formula>
<formula>1</formula>
</many-to-one>
</class>
<class name="Person" >
<id name="id" />
<properties name="defaultContactPerson">
<property name="companyID" />
<property name="defaultFlag" />
</properties>
</class>
</hibernate-mapping>

其他:many-to-many
formula可以与many-to-many元素一起用于从关系表到实体表的特殊连接,尽管通常不需要这样做。

结束语
  本文中的例子展示了大部分的formula使用场景。当需要formula的计算值时,formula表达式将出现在产生的SQL语句的select部分。而当formula用于连接时,它出现在产生的SQL语句的where部分。此外,formula表达式可以使用任意的SQL非标准语言,只要目标数据库支持。因此,formula有助于无需编码地实现从数据模型到对象模型的细粒度映射。

参考资料

原文出处:Hibernate 3 Formulas http://www.onjava.com/pub/a/onjava/2005/08/03/hibernate.html
 作者简介
  Dai Yifan是一家领先的银行解决方案提供商的技术顾问。

Hibernate Tool 使用说明

| No Comments | No TrackBacks

使用环境:

Eclipse 3.2M3 (http://www.eclipse.org)

HibernateTools-3.1.0.beta2(http://www.hibernate.org/255.html)

插件的安装就不用多说了吧。

1、  创建cfg文件。

Ctrl + N 带出如下窗体。
New.gif

选中Hibernate Configuration File(cfg.xml)项。并Next下去。

选择配置文件的路径。

Hibernate代码生成工具 设计全攻略

| No Comments | No TrackBacks
Sybase 公司PowerDesigner上海研发中心 汪晟杰

1.简述

Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了轻量级的对象封装,提供HQL查询语言,使得Java程序员可以随心所欲的使 用对象编程思维来操纵数据库。使用Hibernate,必须为配置映射文件—ClassMapping File和Configuration File,现在市场上提供了诸多Hibernate代码生成工具,比如:XDoclet,以及Hibernate官方自带的sechmaExport工 具。然而它们都有如下的缺点:仅提供一些基本的输入模版,用户仍需要时间进行配置和修改;没有提供自动的持久类以及InvokeBean的代码生成;不支 持图形界面;不支持对HibernateTestCase的代码生成。

hibernate 的自动生成工具

| No Comments | No TrackBacks
1. Middlegen

是用来从DB中已存在的表,生成相应的mapping file. 可以下载一个老外的middlegen的例子。

http://sourceforge.net/project/showfiles.php?group_id=40712

调用Middlegen很简单,例子中的middlegen自动生成ant指令如下

<middlegen
         appname="${name}"
         prefsdir="${src.dir}"
         gui="${gui}"
         databaseurl="${database.url}"
         initialContextFactory="${java.naming.factory.initial}"
         providerURL="${java.naming.provider.url}"
         datasourceJNDIName="${datasource.jndi.name}"
         driver="${database.driver}"
         username="${database.userid}"
         password="${database.password}"
         
      >
     <hibernate
            destination="${build.gen-src.dir}"
            package="${name}.hibernate"
      />
  </middlegen>

然后会有一个GUI,给我们专门设计各种表与表之间的关系(一对一,一对多以及单向双向关系)。需要说明的是,middlegen生成的代码没有直接写mapping file灵活性好,所以生成的mapping file有时还需要我们去修改。

Buffalo处理Ajax有多牛,嘿嘿 我还真没有正儿八经的整过。惭愧啊,争取在最近好好研究下吧。
Buffalo支持和Spring整合。嘿嘿 这也是一个亮点。亮的有些不自在。为啥?
假如你用Spring+Struts+Hibernate来构建的轻量级J2EE框架,Spring和Struts整合有好几种方式,有一种方式不要要论论了。

Spring+Hibernate+Struts分页程序核心代码

| 1 Comment | No TrackBacks

这是我的DAO的核心代码
import org.springframework.orm.hibernate.support.HibernateDaoSupport;
// 用Spring支持的Hibernate方法,使Hibernate对数据库的操作继续瘦身
public List getOfficeBySearchCriteria(final String hsql,final int pageNo,final int page_size) throws DataAccessException // hsql 是如:"select office1 from Office as office1 order by office1.officename";pageNo 是第几页;page_size是每页记录数
{
String sql;
int total_count=0;
List offices=new ArrayList();
//offices= getHibernateTemplate().find("from Office office1 where office1.officename like ?", "%"+officeName+"%");
offices= getHibernateTemplate().find(hsql); //为了得到总记录数
total_count=offices.size();
crossPageInfo= crossPageBean.getCrossPageInfo(total_count,pageNo,page_size);

sql=hsql+ " limit " + (pageNo-1)*page_size + "," +page_size;
offices= getHibernateTemplate().find(sql); //为了得到页记录信息 System.out.println("The list offices size: "+offices.size());
return offices;
}

//其中crossPageBean.getCrossPageInfo只是得到页面的如:总页数、供多少页的信息等一般的翻页信息;

我在Action中是这样调用的
public ActionForward execute(
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception
{
CrossPageInfo crossPageInfo=new CrossPageInfo();
String hsql="select office1 from Office office1 order by office1.officename";
String pageNo=request.getParameter("pageNo");
int pageNoi=1;
if(pageNo==null)
pageNo="1";
pageNoi=Integer.parseInt(pageNo);
int pageSize=5;
//List offices=getOfficeService().getAllOffice();
List offices=getOfficeService().getOfficeBySearchCriteria(hsql,pageNoi,pageSize);
crossPageInfo=getOfficeService().getCrossPageInfo();
System.out.println("The CorssPgaeInfo :"+crossPageInfo.getPageNo());
System.out.println(crossPageInfo.getPageSize());

request.setAttribute("offices",offices);
request.setAttribute("pageInfo",crossPageInfo);
return mapping.findForward("success");
//throw new UnsupportedOperationException("Generated method 'execute(...)' not implemented.");
}

//其中getOfficeService()只是提供接口服务的方法。


我的表现页面是这样的

<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<%@ taglib uri="/WEB-INF/struts-template.tld" prefix="template" %>
<%@ page import="com.uplus.util.CrossPageInfo"%>

<html>
<head>
<title>
mySearchCList
</title>

</head>
<body bgcolor="#ffffff">
<form name="form1" action="officesearch.do" method="post">
<table >
<tr>
<td>OfficeName:<input name="officeName" type="text"></td><td><input type="submit" name="sb" value="Search"></td>
</tr>
</table>
</form>
<br><a href="/jsp/office/officeadd.jsp">Add</a>

<table bgcolor="#DBE9F1" align="center" class="InputFrameMain" style="MARGIN: 0px" cellSpacing="1" cellPadding="0" BGALIGN="CENTER" BGVALIGn="middle" width="100%" VALIGN="middle" >
<tr><td align="center">OfficeName</td><td align="center">OfficePhone</td></tr>
<logic:iterate id="office" name="offices" >
<tr bgcolor="#ffffff">
<td align="center"><a href="officesee.do?id=<bean:write name='office' property='id'/>" target="_blank"><bean:write name="office" property="officename"/></a></td>
<td align="center"><bean:write name="office" property="officephone"/></td>
<td align="center"><a href="officeedit.do?id=<bean:write name='office' property='id'/>" >Update </a>
<td align="center"><a href="officedel.do?id=<bean:write name='office' property='id'/>" onclick="return confirm('Would You Detele It? ')" >Delete </a>
</tr>
</logic:iterate>
</table>
<%CrossPageInfo cpInfo=(CrossPageInfo)request.getAttribute("pageInfo");%>

<table width="100%" align="center" class="InputFrameMain" style="MARGIN: 0px" cellPadding="0" cellSpacing="0">
<tr ><form action="officelist.do" method="post" onsubmit='return checkform2(this)'>
<td width=70%>Total <font color="blue"><%=cpInfo.getTotalRow()%></font>&items found,Total&<font color="blue"><%=cpInfo.getTotalPage()%></font> Pages,Current No <font color="blue"><%=cpInfo.getPageNo()%> </font>Page.
Go to <input name="pageNo" type="text" size="5" class="input">Page
<input name="sb2" type="submit" class="button" value="Go">
</td></form>
<td width=30% align='left'>
<%if(cpInfo.getPageNo()>1){%>
&<a href="officelist.do?pageNo=1">
<%}%>First</a>
<%if(cpInfo.getPageNo()>1){ %>
&<a href="officelist.do?pageNo=<%=cpInfo.getPageNo()-1%>">
<%}%>Previous</a>
<%if(cpInfo.getPageNo()<cpInfo.getTotalPage()){ %>
&<a href="officelist.do?pageNo=<%=cpInfo.getPageNo()+1%>">
<%}%>Next</a>
<%if(cpInfo.getTotalPage()>cpInfo.getPageNo()){%>
&<a href="officelist.do?pageNo=<%=cpInfo.getTotalPage()%>">
<%}%>Last</a></td>
</tr>

</table>
</body>
</html>


大家可以看一下我的处理过程,其中在DAO里为了得到总计录数执行了一次次数据表查询HSQL;得到数据又执行了一次HSQL,我觉得这样好像有些不太好,大家觉得怎样?大家提出宝贵的意见吧!

Struts与Hibernate的完美结合

| No Comments | No TrackBacks

将Hibernate和Struts进行配合, 以节省开发时间和成本. 经过再三考虑,发现通过JavaScript生成XML发送到后台Servlet 利用Hibernate再写入数据库的方法并不可取,此方案只能用于简单操作.当数据库的结构发生变更的时候,则对网站代码需要进行五次修改:

1.修改Hibernate映射;

2.修改Servlet中对XML的解析;

3.修改JavaScript中生成的XML结构;

4.修改HTML表单验证;

5.修改HTML表单.而通过Struts直接将Form表单递交给Hibernate这种方法则灵活性很强.