posts - 495,  comments - 11,  trackbacks - 0
 

4.9 Struts与Hibernate的整合策略

前面介绍了Hibernate的一些相关知识点,距离Hibernate进入实际开发还有一段路要走。Hibernate作为持久层解决方案,必须与其他表现层技术组合在一起才可形成一个J2EE开发框架。经常看到网上一些朋友给出的Hibernate入门示例,居然在JSP页面中访问Hibernate Configuratioin对象。甚至看到某些所谓的精通J2EE书籍,也居然在JSP页面中访问Hibernate的Configuration对象——这种现状非常让人担忧,Hibernate并不是万金油,并不是说项目中使用Hibernate就怎么了不起了,而是通过使用Hibernate,可以让J2EE应用架构更科学,可以让开发者以更好的面向对象的方式进行项目开发。

反过来说,即使不使用Hibernate,而使用普通的JDBC持久化解决方案,也不应该在JSP(表现层)访问到JDBC API(持久层API)。下面介绍如何让Hibernate和Struts进行整合,整合Spring部分将在后面章节介绍。

4.9.1 工厂模式介绍

工厂模式是指当应用程序中A组件需要B组件协助时,并不是直接创建B组件的实例,而是通过B组件的工厂——该工厂可以生成某一个类型组件的实例。在这种模式下,A组件无须与B组件以硬编码方式耦合在一起,而只需要与B组件的工厂耦合。

对于A组件而言,它只关心工厂生产的实例是否满足某种规范,即实现了某个接口(满足接口规范,即可供自己正常调用)。这种模式提供了对象之间清晰的角色划分,降低了程序的耦合。

接口产生的全部实例通常实现相同接口,接口里定义全部实例共同拥有的方法,这些方法在不同的实现类中实现方式不同。程序调用者无须关心方法的具体实现,从而降低了系统异构的代价。

下面是工厂模式的示例代码:

//Person接口定义

public interface Person

{  

    /**

    * @param name 对name打招呼

    * @return 打招呼的字符串

    */

    public String sayHello(String name);

    /**

    * @param name 对name告别

    * @return 告别的字符串

    */

    public String sayGoodBye(String name);

}

该接口定义Person的规范,该接口必须拥有两个方法:能打招呼、能告别。规范要求实现该接口的类必须具有这两个方法:

//American类实现Person接口

public class American implements Person

{

    /**

    * @param name 对name打招呼

    * @return 打招呼的字符串

    */

    public String sayHello(String name)

    {

        return name + ",Hello";

    }

    /**

    * @param name 对name告别

    * @return 告别的字符串

    */

    public String sayGoodBye(String name)

    {

        return name + ",Good Bye";

    }

}

下面是实现Person接口的另一个实现类Chinese

public class Chinese implements Person

{

    /**

    * @param name 对name打招呼

    * @return 打招呼的字符串

    */

    public String sayHello(String name)

    {

        return name + ",您好";

    }

    /**

    * @param name 对name告别

    * @return 告别的字符串

    */

    public String sayGoodBye(String name)

    {

        return name + ",下次再见";

    }

}

然后看Person工厂的代码:

public class PersonFactory

{

    /**

    * 获得Person实例的工厂方法

    * @ param ethnic 调用该实例工厂方法传入的参数

    * @ return返回Person实例

    */

    public Person getPerson(String ethnic)

    {

        //根据参数返回Person接口的实例

        if (ethnic.equalsIgnoreCase("chin"))

        {

            return new Chinese();

        }

        else

        {

            return new American();

        }

    }

}

最简单的工厂模式的框架基本如上所示。

主程序部分仅仅需要与工厂耦合,而无须与具体的实现类耦合在一起。下面是主程序部分:

public class FactroyTest

{

    public static void main(String[] args)

    {

        //创建PersonFactory的实例,获得工厂实例

        PersonFactory pf = new PersonFactory();

        //定义接口Person的实例,面向接口编程

        Person p = null;

        //使用工厂获得Person的实例

        p = pf.getPerson("chin");

        //下面调用Person接口的方法

        System.out.println(p.sayHello("wawa"));

        System.out.println(p.sayGoodBye("wawa"));

        //使用工厂获得Person的另一个实例

        p = pf.getPerson("ame");

        //再次调用Person接口的方法

        System.out.println(p.sayHello("wawa"));

        System.out.println(p.sayGoodBye("wawa"));

    }

}

主程序从Person接口的具体类中解耦出来,而且程序调用者无须关心Person的实例化过程,角色划分清晰。主程序仅仅与工厂服务定位结合在一起,获得工厂的引用,程序将可获得所有工厂产生的实例。具体类的变化,重要接口不发生任何改变,调用者程序代码部分几乎无须发生任何改动。

4.9.2 使用DAO模式

第1章介绍了J2EE应用的架构,最上面的表现层,表现层与MVC框架的控制器交互,控制器负责调用业务逻辑组件的业务逻辑方法来处理用户请求,而业务逻辑组件则依赖于DAO组件提供的数据库原子操作,这种模式也被称为DAO模式。

由上面关于J2EE应用架构的介绍可见,控制器总是依赖于业务逻辑组件,而业务逻辑组件总是依赖于DAO组件。也就是说,控制器需要调用业务逻辑组件的方法,而业务逻辑组件需要调用DAO组件的方法。

DAO模式的分层非常清晰,持久层访问被封装在DAO层下,而决不会扩散到业务逻辑层,更不会在JSP页面(表现层)中进行持久层访问。

注意:即使在早期的Model 1(使用JSP + JavaBean创建应用的模式,没有使用MVC设计模式)模式下,持久层访问也被封装在JavaBean中完成,而不是直接在JSP页面中进行数据库访问。对于直接在JSP中访问持久层API的做法,可以说根本不了解J2EE开发。

那么控制器采用怎样的方式访问业务逻辑组件呢?应该采用工厂模式,让控制器与业务逻辑组件的实现类分离,仅与业务逻辑工厂耦合;同样,业务逻辑组件也应该采用工厂模式访问DAO模式,而不是直接与DAO实现类耦合。

后面的案例部分会介绍更实际的整合策略,此处仅仅介绍DAO模式下两个工厂模式策略。

4.9.3 DAO组件的工厂模式

在J2EE应用开发中,可扩展性是一个随时需要关注的问题。而DAO组件是经常需要增加的项目组件,如果每次需要增加一个DAO组件都需要修改代码是相当让人沮丧的事情。为了避免这种情况,采用XML配置文件来管理所有的DAO组件,这种DAO组件配置文件的代码如下:

<?xml version="1.0" encoding="GBK"?>

<daoContext>

    <!-- 配置应用需要的sonDao组件 -->

    <dao id="sonDao" class="org.yeeku.dao.impl.SonDaoImpl"/>

    <!-- 配置应用需要的personDao组件 -->

    <dao id="personDao" class="org.yeeku.dao.impl.PersonDaoImpl"/>

</daoContext>

查看上面的配置文件可以看出,应用中有配置了两个DAO组件,因为每个DAO组件在J2EE应用中仅需要一个实例就足够了,因此DAO工厂类提供了一个缓存池来缓存每个DAO实例,并负责在应用启动时创建所有的DAO。

下面是DAO工厂类的代码:

public class DaoFactory

{

    //用于缓存DAO实例的Map对象

    private Map<String, Dao> daoMap = new HashMap<String , Dao>();

    //将DAO工厂写成单态模式

    private static DaoFactory df;

    //DAO工厂的构造器

    private DaoFactory()throws Exception

    {

        //使用SAXReader来负责解析daoContext.xml配置文档

        Document doc = new SAXReader().read(new File(ConstantsUtil.realPath

        + "\\daoContext.xml"));

        //获取文档的根文档

        Element root = doc.getRootElement();

        //获取daoContext根元素的所有子元素

        List el = root.elements();

        for (Iterator it = el.iterator();it.hasNext() ; )

        {

            //每个子元素对应一个DAO组件

            Element em = (Element)it.next();

            String id = em.attributeValue("id");

            //获取实现类

            String impl = em.attributeValue("class");

            //通过反射,根据类名创建DAO组件的实例

            Class implClazz = Class.forName(impl);

            Dao d = (Dao)implClazz.newInstance();

            //将创建的DAO组件放入缓存池中

            daoMap.put(id, d);

        }

    }

    //单态模式必须提供一个入口方法来创建DAO工厂的方法

    public static DaoFactory instance()throws Exception

    {

        //如果DAO工厂还未创建

        if (df == null)

        {

            df = new DaoFactory();

        }

        return df;

    }

    //下面的方法用于根据DAO组件ID获取DAO组件

    public Dao getDao(String id)

    {

        return daoMap.get(id);

    }

}

通过上面的工厂类代码可以看出,DAO工厂负责初始化所有的DAO组件。系统每增加一个DAO组件,无须再修改任何代码,仅仅需要在daoContext.xml配置文件中增加配置即可。

注意:这种整合策略非常优秀。可扩展性很好,如果应用需要增加一个DAO组件,只需要修改配置文件,并提供相应的DAO组件实现即可。而且,如果有一天需要重构DAO组件,只须提供修改过的DAO组件实现类,而业务逻辑组件无须任何改变。

业务逻辑组件代码无须与DAO实现类耦合,业务逻辑组件的代码面向DAO组件的接口编程,将业务逻辑组件和DAO组件的耦合降低到接口层次。

4.9.4 业务逻辑组件的工厂模式

与此类似的是,业务逻辑组件完全可以采用这种编程模式,业务逻辑组件的配置文件代码如下:

<?xml version="1.0" encoding="GBK"?>

<appContext>

    <!-- 配置应用需要的业务逻辑组件,每个业务逻辑组件对应一个app元素 -->

    <app id="wawa" class="org.yeeku.service.impl.WawaServiceImpl"/>

</appContext>

业务逻辑组件工厂同样可根据该配置文件来初始化所有业务逻辑组件,并将业务逻辑组件放入缓存池中,让控制器与业务逻辑组件的耦合降低到接口层次。业务逻辑组件的工厂类代码如下:

public class AppFactory

{

    private Map<String , Object> appMap = new HashMap<String , Object>();

    //业务逻辑组件工厂采用单态模式

    private static AppFactory df;

    //业务逻辑组件工厂的私有构造器

    private AppFactory()throws Exception

    {

        //使用SAXReader来负责解析appContext.xml配置文档

        Document doc = new SAXReader().read(new File(ConstantsUtil.realPath

        + "\\appContext.xml"));

        //获取文档的根文档

        Element root = doc.getRootElement();

        //获取appContext根元素的所有子元素

        List el = root.elements();

        for (Iterator it = el.iterator();it.hasNext() ; )

        {

            //每个app元素对应一个业务逻辑组件

            Element em = (Element)it.next();

            String id = em.attributeValue("id");

            //根据配置文件指定的业务逻辑组件实现类来创建业务逻辑组件实例

            String impl = em.attributeValue("class");

            Class implClazz = Class.forName(impl);

            Object d = implClazz.newInstance();

            //将业务逻辑组件放入缓存池中

            appMap.put(id , d);

        }

    }

    //单态模式必须提供入口方法,用于创建业务逻辑组件工厂

    public static AppFactory instance()throws Exception

    {

        //如果业务逻辑组件工厂为空

        if (df == null)

        {

            df = new AppFactory();

        }

        return df;

    }

    //根据业务逻辑组件的id属性获取业务逻辑组件

    public Object getApp(String id)

    {

        //直接从缓存池中取出业务逻辑组件,并返回

        return appMap.get(id);

    }

}

从某种程度上来讲,这种方式与后来Spring的控制反转(Inversion of Control,IoC)容器有异曲同工之妙,但Spring的IoC容器则提供了更多的功能。

上面的两个类中都用到了一个ConstantsUtil,它仅用于保存一个全局变量,有一个public static的realPath属性,该属性用于保存应用在服务器中的路径。

posted @ 2009-07-19 10:08 jadmin 阅读(66) | 评论 (0)编辑 收藏

4.8 事 件 机 制

通常,Hibernate执行持久化过程中,应用程序无法参与其中。所有的数据持久化操作,对用户都是透明的,用户无法插入自己的动作。

通过事件框架,Hibernate允许应用程序能响应特定的内部事件,从而允许实现某些通用的功能,或对Hibernate功能进行扩展。

Hibernate的事件框架由两个部分组成:

   ● 拦截器机制,对于特定动作拦截,回调应用中的特定动作。

   ● 事件系统,重写Hibernate的事件监听器。

4.8.1 拦截器

通过Interceptor接口,可以从Session中回调应用程序的特定方法,这种回调机制可让应用程序在持久化对象被保存、更新、删除或加载之前,检查并修改其属性。

通过Interceptor接口,可以在数据进入数据库之间,对数据进行最后的检查,如果数据不符合要求,可以修改数据,从而避免非法数据进入数据库。当然,通常无须这样做,只是在某些特殊的场合下,才考虑使用拦截器完成检查功能。

使用拦截器可按如下步骤进行:

(1)定义实现Interceptor接口的拦截器类;

(2)通过Session启用拦截器,或者通过Configuration启用全局拦截器。

下面是一个拦截器的示例代码,该拦截器没有进行任何实际的操作,仅仅打印出标志代码:

public class MyInterceptor extends EmptyInterceptor

{

    //更新的次数

    private int updates;

    //插入的次数

    private int creates;

    //删除数据时,将调用onDelete方法

    public void onDelete(Object entity,Serializable id,Object[]

    state,String[] propertyNames, Type[] types)

    {

        //do nothing

    }

    //同步Session和数据库中的数据

    public boolean onFlushDirty(Object entity, Serializable id, Object[]

    currentState, Object[] previousState, String[] propertyNames, Type[]

                            types)

    {

        //每同步一次,修改的累加器加1

        updates++;

        for ( int i=0; i < propertyNames.length; i++ )

        {

            if ( "lastUpdateTimestamp".equals( propertyNames[i] ) )

            {

                currentState[i] = new Date();

                return true;

            }

        }

        return false;

        }

    //加载持久化实例时,调用该方法

    public boolean onLoad(Object entity,Serializable id,Object[]

    state,String[] propertyNames,Type[] types)

    {

        System.out.println("========================");

        for ( int i=0; i < propertyNames.length; i++ )

        {

            if ( "name".equals( propertyNames[i] ) )

            {

                System.out.println(state[i]);

                state[i] = "aaa";

                return true;

            }

        }

        return false;

    }

    //保存持久化实例时,调用该方法

    public boolean onSave(Object entity,Serializable id,Object[]

    state,String[] propertyNames,Type[] types)

    {

        creates++;

        for ( int i=0; i<propertyNames.length; i++ )

        {

            if ( "createTimestamp".equals( propertyNames[i] ) )

            {

                state[i] = new Date();

                return true;

            }

        }

        return false;

    }

    //提交刷新

    public void postFlush(Iterator entities)

    {

        System.out.println("创建的次数: " + creates + ", 更新的次数: " +

    updates);

    }

    public void preFlush(Iterator entities)

    {

        updates=0;

        creates=0;

    }

    //事务提交前,触发该方法

    public void beforeTransactionCompletion(Transaction tx)

    {

        System.out.println("事务即将结束");

    }

    //事务提交后,触发该方法

    public void afterTransactionCompletion(Transaction tx)

    {

        System.out.println("事务已经结束");

    }

}

在上面的拦截器实现类中,实现了很多方法,这些方法都是在Hibernate执行特定动作时自动调用。

完成了拦截器的定义,下面是关于拦截器的使用。拦截器的使用有两种方法:

   ● 通过SessionFactory的openSession(Interceptor in)方法打开一个带局部拦截器的Session。

   ● 通过Configuration的setInterceptor(Interceptor in)方法设置全局拦截器。

下面是使用局部拦截器的示例代码:

public class HibernateUtil

{

    //静态类属性 SessionFactory

    public static final SessionFactory sessionFactory;

    //静态初始化块,完成静态属性的初始化

    static

    {

        try

        {

            //采用默认的hibernate.cfg.xml来启动一个Configuration的实例

            Configuration configuration=new Configuration().configure();

            //由Configuration的实例来创建一个SessionFactory实例

            sessionFactory = configuration.buildSessionFactory();

        }

        catch (Throwable ex)

        {

            System.err.println("初始化sessionFactory失败." + ex);

            throw new ExceptionInInitializerError(ex);

        }

    }

    //ThreadLocal是隔离多个线程的数据共享,不存在多个线程之间共享资源,因此不再需要

    对线程同步   

    public static final ThreadLocal session = new ThreadLocal();

    //不加拦截器的打开Session方法

    public static Session currentSession() throws HibernateException

    {

        Session s = (Session) session.get();

        //如果该线程还没有Session,则创建一个新的Session

        if (s == null)

        {

            s = sessionFactory.openSession();

            //将获得的Session变量存储在ThreadLocal变量的Session里

            session.set(s);

        }

        return s;

    }

    //加拦截器的打开Session方法

    public static Session currentSession(Interceptor it) throws

    HibernateException

    {

        Session s = (Session) session.get();

        //如果该线程还没有Session,则创建一个新的Session

        if (s == null)

        {

            //以拦截器创建Session对象

            s = sessionFactory.openSession(it);

            //将获得的Session变量存储在ThreadLocal变量的Session里

            session.set(s);

            }

        return s;

    }

    //关闭Session对象

    public static void closeSession() throws HibernateException

    {

        Session s = (Session) session.get();

        if (s != null)

            s.close();

        session.set(null);

    }

}

上面的Hibernate工具类提供了两个currentSession方法,分别用于不使用拦截器获取Session对象和使用拦截器获取Session对象。

下面是主程序使用拦截器的代码片段:

private void testUser()

{

    //以拦截器开始Session

    Session session = HibernateUtil.currentSession(new MyInterceptor());

    //开始事务

    Transaction tx = session.beginTransaction();

    //执行下面的代码时,可以看到系统回调onSave等方法

    /*

    User u = new User();

    u.setName("Yeeku Lee");

    u.setAge(28);

    u.setNationality("中国");

    session.persist(u);

    u.setAge(29);

    u.setAge(30);

    session.persist(u);

    */

    //执行下面的代码时,可以看到系统回调onLoad等方法

    Object o = session.load(User.class , new Integer(1));

    System.out.println(o);

    User u = (User)o;

    System.out.println(u.getName());

    //提交事务时,可以看到系统回调事务相关方法

    tx.commit();

    HibernateUtil.closeSession();

}

4.8.2 事件系统

Hibernate 3的事件系统是功能更强大的事件框架,事件系统可以替代拦截器,也可以作为拦截器的补充来使用。

基本上,Session接口的每个方法都有对应的事件。如LoadEvent和FlushEvent等。当Session调用某个方法时,Hibernate Session会生成对应的事件,并激活对应的事件监听器。

系统默认监听器实现的处理过程,完成了所有的数据持久化操作,包括插入和修改等操作。如果用户定义了自己的监听器,则意味着用户必须完成对象的持久化操作。

例如,可以在系统中实现并注册LoadEventListener监听器,该监听器负责处理所有调用Session的load()方法的请求。

监听器是单态模式对象,即所有同类型的事件处理共享同一个监听器实例,因此监听器不应该保存任何状态,即不应该使用成员变量。

使用事件系统可按如下步骤进行:

(1)实现自己的事件监听器类;

(2)注册自定义事件监听器,代替系统默认的事件监听器。

实现用户的自定义监听器有如下3个方法:

   ● 实现对应的监听器接口,这是不可思议的,实现接口必须实现接口内的所有方法,关键是必须实现Hibernate对应的持久化操作,即数据库访问,这意味着程序员完全取代了Hibernate的底层操作。

   ● 继承事件适配器,可以选择性地实现需要关注的方法,但依然试图取代Hibernate完成数据库的访问,这也不太现实。

   ● 继承系统默认的事件监听器,扩展特定方法。

实际上,前两种方法很少使用。因为Hibernate的持久化操作也是通过这些监听器实现的,如果用户取代了这些监听器,则应该自己实现所有的持久化操作,这意味着用户放弃了Hibernate的持久化操作,而改为自己完成Hibernate的核心操作。

通常推荐采用第三种方法实现自己的事件监听器。Hibernate默认的事件监听器都被声明成non-final,从而方便用户继承。

下面是用户自定义监听器的示例:

//自定义LoadListener,继承默认的DefaultLoadEventListener实现类

public class MyLoadListener extends DefaultLoadEventListener

{

    //在LoadEventListener接口仅仅定义了这个方法

    public Object onLoad(LoadEvent event, LoadEventListener.LoadType

    loadType)throws HibernateException

    {

        //先调用父类的onLoad方法,从而完成默认的持久化操作

        Object o = super.onLoad(event, loadType);

        //加入用户的自定义处理

        System.out.println("自定义的load事件");

        System.out.println(event.getEntityClassName() + "==========" +

        event.getEntityId());

        return o;

    }

}

下面还有一个MySaveListener,用于监听SaveEvent事件:

//自定义SavaListener,继承默认的DefaultSaveEventListener实现类

public class MySaveListener extends DefaultSaveEventListener

{

    //该方法完成实际的数据插入动作

    protected Serializable performSaveOrUpdate(SaveOrUpdateEvent event)

    {

        //先执行用户自定义的操作

        System.out.println(event.getObject());

        //调用父类的默认持久化操作

        return super.performSaveOrUpdate(event);

    }

}

注意:扩展用户自定义监听器时,别忘了在方法中调用父类的对应方法。

注册用户自定义监听器也有两种方法:

   ● 编程式,通过使用Configuration对象编程注册。

   ● 声明式,在Hibernate的XML格式配置文件中进行声明,使用Properties格式的配置文件将无法配置自定义监听器。

下面的示例代码,通过编程方式使用自定义监听器:

public class HibernateUtil2

{

    //静态类属性 SessionFactory

    public static final SessionFactory sessionFactory;

    //静态初始化块,完成静态属性的初始化

    static

    {

        try

        {

            Configuration cfg = new Configuration();

            //注册loadEventListener监听器

            cfg.getSessionEventListenerConfig().setLoadEventListener

            ( new MyLoadListener() );

            //注册saveListener监听器

            cfg.getSessionEventListenerConfig().setSaveEventListener

            (new MySaveListener() );

            //由Configuration实例来创建一个SessionFactory实例

            sessionFactory = cfg.configure().buildSessionFactory();

        }

        catch (Throwable ex)

        {

            System.err.println("初始化sessionFactory失败." + ex);

            throw new ExceptionInInitializerError(ex);

        }

    }

    //ThreadLocal是隔离多个线程的数据共享,不存在多个线程之间共享资源,因此不再需要

    对线程同步

    public static final ThreadLocal session = new ThreadLocal();

    //不加拦截器的打开Session方法

    public static Session currentSession() throws HibernateException

    {

        Session s = (Session) session.get();

        //如果该线程还没有Session,则创建一个新的Session

        if (s == null)

        {

            s = sessionFactory.openSession();

            //将获得的Session变量存储在ThreadLocal变量的Session里

            session.set(s);

        }

        return s;

    }

    //关闭Session对象

    public static void closeSession() throws HibernateException

    {

        Session s = (Session) session.get();

        if (s != null)

            s.close();

        session.set(null);

    }

}

如果不想修改代码,也可以在配置文件中使用事件监听器,注册事件监听器的Hibernate配置文件代码如下:

<?xml version='1.0' encoding='GBK'?>

<!-- Hibernate配置文件的文件头,包含DTD等信息 -->

<!DOCTYPE hibernate-configuration PUBLIC

        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"

        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.

        dtd">

<!-- Hibernate配置文件的根元素 -->

<hibernate-configuration>

    <session-factory>

        <!—设置数据库驱动 -->

        <property name="connection.driver_class">com.mysql.jdbc.Driver

        </property>

        <!-- 数据库服务的url -->

        <property name="connection.url">jdbc:mysql://localhost/hibernate

        </property>

        <!-- 数据库服务的用户名 -->

        <property name="connection.username">root</property>

        <!-- 数据库服务的密码 -->

        <property name="connection.password">32147</property>

        <!-- JDBC connection pool (use the built-in) -->

        <property name="connection.pool_size">5</property>

        <!-- 设置数据库方言 -->

        <property name="dialect">org.hibernate.dialect.MySQLDialect

        </property>

        <!-- 显示Hibernate生成的SQL语句 -->

        <property name="show_sql">true</property>

        <!-- 配置应用启动时,是否启动自动建表 -->

        <property name="hbm2ddl.auto">update</property>

        <!-- 列出所有的持久化映射文件 -->

        <mapping resource="User.hbm.xml"/>

        <!-- 注册事件监听器 -->

        <listener type="load" class="lee.MyLoadListener"/>

        <listener type="save" class="lee.MySaveListener"/>

    </session-factory>

</hibernate-configuration>

使用配置文件注册事件监听器虽然方便,但也有不利之处,通过配置文件注册的监听器不能共享实例。如果多个<listener/>元素中使用了相同的类,则每一个引用都将产生一个新的拦截器实例。如果需要在多个事件之间共享监听器的实例,则必须使用编程方式注册事件监听器。

注意:虽然监听器类实现了特定监听器的接口,在注册的时候还要明确指出注册的事件。这是因为一个类可能实现多个监听器的接口,注册时明确指定要监听的事件,可以使得启用或者禁用某个事件监听的配置工作更简单。

posted @ 2009-07-19 09:42 jadmin 阅读(85) | 评论 (0)编辑 收藏

4.7 事 务控 制

每个业务逻辑方法都是由一系列的数据库访问完成,这一系列的数据访问可能会修改多条数据记录,这系列的修改应该是一个整体,绝不能仅修改其中的几条。也就是说,多个数据库原子访问应该绑定成一个整体——这就是事务。事务是一个最小的逻辑执行单元,整个事务不能分开执行,要么同时执行,要么同时放弃执行。

4.7.1 事务的概念

事务是一步或几步基本操作组成的逻辑执行单元,这些基本操作作为一个整体执行单元,它们要么全部执行,要么全部取消,绝不能仅仅执行部分。一般而言,每次用户请求,对应一个业务逻辑方法,一个业务逻辑方法往往具有逻辑上的原子性,应该使用事务。例如,一个转账操作,对应修改两个账户的余额,这两个账户的修改要么同时生效,要么同时取消——同时生效是转账成功,同时取消是转账失败;但不可只修改其中一个账户,那将破坏数据库的完整性。

通常来讲,事务具备如下4个特性:原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持续性(durability)。这4个特性也简称为ACID性。

   ● 原子性:事务是应用中最小执行单位,就如原子是自然界最小颗粒,具有不可再分的特征一样。事务是应用中不可再分的最小逻辑执行体。

   ● 一致性:事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态。当数据库只包含事务成功提交的结果时,数据库处于一致性状态。如果系统运行发生中断,某个事务尚未完成而被迫中断,而该未完成的事务对数据库所做的修改已被写入数据库,此时,数据库就处于一种不正确的状态。比如银行在两个账户之间转账,从A账户向B账户转入1000元。系统先减少A账户的1000元,然后再为B账户增加1000元。如果全部执行成功,数据库处于一致性状态。如果仅执行完A账户金额的修改,而没有增加B账户的金额,则数据库就处于不一致性状态。因此,一致性是通过原子性来保证的。

   ● 隔离性:各个事务的执行互不干扰,任意一个事务的内部操作对其他并发的事务,都具有隔离性。也即并发执行的事务之间不能互相影响。

   ● 持续性:持续性也称为持久性(persistence),指事务一旦提交,对数据所做的任何改变,都要记录到永久存储器中,通常保存进物理数据库。

4.7.2 Hibernate的事务

Hibernate直接使用JDBC连接和JTA资源,不添加任何附加锁定行为。Hibernate只添加自动版本管理,而不会锁定内存中的对象,也不会改变数据库事务的隔离级别。基本上,使用 Hibernate就好像直接使用JDBC(或者JTA/CMT)进行数据库访问。

Hibernate中SessionFactory对象的创建代价很高,它是线程安全的对象,被设计成可以为所有的应用程序线程所共享。通常,SessionFactory会在应用程序启动时创建,一旦创建了SessionFactory将不会轻易关闭,只有当应用关闭时,SessionFactory才会关闭。

而Session的对象是轻量级的,它也是线程不安全的。对于单个业务进程单个工作单元而言,Session只被使用一次。创建Session时,并不会立即打开与数据库之间的连接,Session只在需要进行数据库操作时,才会获取JDBC连接。因此,打开和关闭Session,并不会对性能造成很大的影响。甚至即使无法确定一个请求是否需要数据访问,也可以打开Session对象,因为如果不进行数据库访问,Session不会获取JDBC连接。

相反,数据库事务应该尽可能的短。从而,降低数据库锁定造成的资源争用。数据库长事务会导致应用程序无法承载高并发的负荷。

由上面的介绍可知,Hiberante的Session和事务是紧密相关的,因为事务是通过Session来打开的。那么事务的范围是多大?单个Session可以跨越多个数据库事务吗?事务和Session的对应关系又如何呢?下面将介绍Hibernate Session和事务的关系。

4.7.3 事务和Session

数据库操作必须在Hibernate的Session管理下进行,但不推荐因为一次简单的数据库原子调用,就打开和关闭一次Session,数据库事务也是如此。因为,对于一次原子操作打开的事务没有任何意义——事务应该是将多个操作步骤组合成一个逻辑整体。

事务是按顺序发送并组成一个逻辑整体的原子操作单元。

注意:也就是说单个的SQL语句发送之后,自动事务提交模式失效了。这种自动提交模式仅为SQL控制台设计,在实际项目没有太大的实用价值。Hibernate禁止事务立即自动提交模式,或者让应用服务器禁止事务自动提交。

通常,建议每个请求对应一个Session。在这种模式下,来自客户端的请求被发送到服务器端,此处可能对应一个业务逻辑方法。在这个业务逻辑方法内,一个新的Hibernate Session被打开,然后开始事务,在事务内执行这个操作单元中所有的数据库操作。一旦操作完成,需要发送给客户端的响应也准备就绪。此时,提交事务,然后关闭Session。在这种模式下,Session和用户请求是一对一的关系,这是一种理想的Session管理模式。

为了达到这种效果,推荐使用一个ThreadLocal变量,把Session绑定到处理客户端请求的线程上去。这种方式可以让运行在该线程上的所有程序代码轻松地访问Session。也可以在一个ThreadLocal变量中保持事务上下文环境,不过这依赖于所选择的数据库事务划分机制。这种实现模式被称之为ThreadLocal Session和Open Session in View。

下面是一个HibernateUtil类,该类将Hibernate Session存放在一个ThreadLocal变量中,对于同一个线程的请求,将可以轻松访问该Session。

public class HibernateUtil

{

    public static final SessionFactory sessionFactory;

    //静态初始化块,使用该类时使用该代码块

    static

    {

        try

        {

            //采用默认的hibernate.cfg.xml来启动一个Configuration的实例

            Configuration configuration=new Configuration().configure();

            //由Configuration的实例来创建一个SessionFactory实例

            sessionFactory = configuration.buildSessionFactory();

        }

        catch (Throwable ex)

        {

            System.err.println("初始化sessionFactory失败." + ex);

            throw new ExceptionInInitializerError(ex);

        }

    }

    //ThreadLocal是隔离多个线程的数据共享,不存在多个线程之间共享资源,因此不再需要

    对线程同步

    public static final ThreadLocal session = new ThreadLocal();

    //该方法用于获取当前线程的Session对象

    public static Session currentSession() throws HibernateException

    {

        Session s = (Session) session.get();

        //如果该线程还没有Session,则创建一个新的Session

        if (s == null)

        {

            s = sessionFactory.openSession();

            //将获得的Session变量存储在ThreadLocal变量的Session里

            session.set(s);

        }

        return s;

    }

    //该方法用于关闭当前线程里的Session

    public static void closeSession() throws HibernateException

    {

        Session s = (Session) session.get();

        if (s != null)

            s.close();

        session.set(null);

    }

}

在上面的代码中,Hibernate Session被绑定到当前线程。当调用currentSession方法时,如果当前线程中的Session已经创建出来,那么将返回这个已经存在的Session实例。

每次请求对应一个Session的模式不仅可以用于设计操作单元,甚至很多业务处理流程都需要组合一系列的用户操作,即用户对数据库的交叉访问。

但是,对于企业应用,跨用户交互的数据库事务是无法接受的。例如,在第一个页面,用户打开对话框,打开一个特定Session装入的数据,可以随意修改对话框中的数据,修改完成后,将修改结果存入数据库。

从用户的角度来看,这个操作单元被称为应用程序长事务。在一个J2EE应用实现中,可以有很多方法来实现这种应用程序长事务。

一个比较差的做法是,当用户思考时,应用程序保持Session和数据库事务是打开的,并保持数据库锁定,以阻止并发修改,从而保证数据库事务隔离级别和原子操作。这种数据库锁定会导致应用程序无法扩展并发用户的数目。

因此,不要使用每个应用对应一次Hibernate Session的模式,也不要使用每次Http Session对应一次Hibernate Session的模式。

注意:几乎所有情况下,都不要使用每个应用对应一次Hibernate Session的模式,也不要使用每次Http Session对应一次Hibernate Session的模式。

对于这种情况,Hibernate主要有如下两种模式来解决这个问题:

   ● 脱管对象,如果采用每次用户请求对应一次Session的模式。那么,前面载入的实例在用户思考的过程中,始终与Session脱离,处于脱管状态。都处于与Session脱离的状态。Hibernate允许把脱管对象重新关联到Session上,并且对修改进行持久化。在这种模式下,自动版本化被用来隔离并发修改。这种模式也被称为使用脱管对象的每个请求对应一个Hibernate Session。

   ● 长生命周期Session,Session可以在数据库事务提交之后,断开和底层的JDBC连接。当新的客户端请求到来时,它又重新连接上底层的JDBC连接。这种模式被称为每个应用程序事务对应一个Session,因为应用程序事务相当长(跨越多个用户请求),所以也被称为每次应用事务对应一个Hibernate Session。

posted @ 2009-07-19 09:11 jadmin 阅读(84) | 评论 (0)编辑 收藏

数据过滤并不是一种常规的数据查询方法,而是一种整体的筛选方法。数据过滤也可对数据进行筛选,因此,将其放在Hibernate的数据查询框架中介绍。

如果一旦启用了数据过滤器,则不管数据查询,还是数据加载,该过滤器将自动作用于所有数据,只有满足过滤条件的记录才会被选出来。

过滤器与定义在类和集合映射文件上的“where”属性非常相似。它们的区别是过滤器可以带参数,应用程序可以在运行时决定是否启用指定的过滤器,以及使用什么样的参数值。而映射文件上的“where”属性将一直生效,且无法动态传入参数。

过滤器的用法很像数据库视图,区别是视图在数据库中已经定义完成,而过滤器则还需在应用程序中确定参数值。

过滤器的使用分成三步:

(1)定义过滤器。使用filter-def元素定义过滤器;

(2)使用过滤器。使用filter元素使用过滤器;

(3)在代码中启用过滤器。

前两个步骤都是在Hibernate的映射文件中完成的,其中filter-def是hibernate-mapping元素的子元素,而filter元素是class集合元素的子元素。

filter-def元素用于定义一个过滤器,filter则将指定的过滤器应用到指定的持久化类。

一个持久化类或集合可以使用多个过滤器,而一个过滤器也可以作用于多个持久化类或集合。

看下面的映射文件示例:

<?xml version="1.0"?>

<!-- Hibernate配置文件的文件头,包含DTD等信息 -->

<!DOCTYPE hibernate-mapping

    PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<!-- Hibernate 配置文件的根元素 -->

<hibernate-mapping >

    <!-- 每个class元素定义一个持久化类 -->

    <class name="Category" table="category">

        <!-- 定义标识属性 -->

        <id name="id" column="category_id" >

            <!-- 指定主键生成器策略 -->

            <generator class="native"/>

        </id>

        <!-- 映射name属性 -->

        <property name="name" type="string"/>

        <!-- 映射effectiveStartDate属性 -->

        <property name="effectiveStartDate" column="eff_start_date"

        type="java.util.Date"/>

        <!-- 映射effectiveEndDate属性 -->

        <property name="effectiveEndDate" column="eff_end_date"

        type="java.util.Date"/>

        <!-- 映射N-N关联属性 -->

        <set cascade="none" inverse="true" name="products"

        table="product_category">

            <!-- 定义关联属性的key,对应连接表中的外键列 -->

            <key column="category_id"/>

            <!-- 定义关联属性 -->

            <many-to-many column="product_id" class="Product"/>

        </set>

        <!-- 使用过滤器,并设置过滤器条件 -->

        <filter name="effectiveDate" condition=":asOfDate BETWEEN

        eff_start_date and eff_end_date"/>

    </class>

    <!-- 定义第二个持久化类 -->

    <class name="Product" table="product">

        <!-- 定义标识属性 -->

        <id name="id" column="product_id" >

            <!-- 指定主键生成器策略 -->

            <generator class="native"/>

        </id>

        <!-- 映射name属性 -->

        <property name="name" type="string"/>

        <!-- 映射stockNumber属性 -->

        <property name="stockNumber" column="stock_number" type="int"/>

        <!-- 映射effectiveStartDate属性 -->

        <property name="effectiveStartDate" column="eff_start_date"

        type="java.util.Date"/>

        <!-- 映射effectiveEndDate属性 -->

        <property name="effectiveEndDate" column="eff_end_date"

        type="java.util.Date"/>

        <!-- 映射N-N关联属性 -->

        <set cascade="all" name="categories" fetch="join"

        table="product_category" >

            <!-- 定义关联属性的key,对应连接表中的外键列 -->

            <key column="product_id"/>

            <!-- 定义关联属性 -->

            <many-to-many column="category_id"

                        class="Category" fetch="join">

                <!-- 对关联属性使用第一个过滤器 -->

                <filter name="effectiveDate"

                    condition=":asOfDate BETWEEN eff_start_date and

                    eff_end_date"/>

                <!-- 对关联属性使用第二个过滤器 -->

                <filter name="category" condition="category_id = :catId"/>

            </many-to-many>

        </set>

        <filter name="effectiveDate" condition=":asOfDate BETWEEN

        eff_start_date AND eff_end_date"/>

    </class>

    <!-- 定义第一个过滤器,该过滤器包含一个date类型的参数 -->

    <filter-def name="effectiveDate">

        <filter-param name="asOfDate" type="date"/>

    </filter-def>

    <!-- 定义第二个过滤器,该过滤器包含一个long类型的参数 -->

    <filter-def name="category">

        <filter-param name="catId" type="long"/>

    </filter-def>

</hibernate-mapping>

在上面的配置文件中,定义了两个过滤器,过滤器的定义通过filter-def元素完成。定义过滤器时,只需要指定过滤器的名字,以及过滤器的参数即可。如Java里的一个方法声明,只有方法名和参数列表,具体的方法实现是没有的。

过滤器的过滤条件是使用过滤器时才确定的,使用过滤器通过filter元素确定,filter的condition属性用于确定过滤条件,满足该条件的记录才会被抓取到。

系统默认不启用过滤器,必须显式通过enableFilter(String filterName)才可以启用过滤器,该方法返回一个Filter实例,Filter包含setParameter方法用于为过滤器参数赋值。

一旦启用了过滤器,过滤器在整个Session内有效,所有的数据加载将自动应用该过滤条件,直到调用disableFilter方法。

看下面的使用过滤器的示例代码:

private void test() throws Exception

{

    //获取Hibernate Session对象

    Session session = HibernateUtil.currentSession();

    //开始事务

    Transaction tx = session.beginTransaction();

    //启用第一个过滤器

    session.enableFilter("effectiveDate")

            //为过滤器设置参数

            .setParameter("asOfDate", new Date());

    //启动第二个过滤器

    session.enableFilter("category")

            //为过滤器设置参数

            .setParameter("catId", new Long(2));

    //执行查询,该查询没有任何的查询条件

    Iterator results = session.createQuery("from Product as p")

                          .iterate();

    //遍历结果集

    while (results.hasNext())

    {

        Product p = (Product)results.next();

        System.out.println(p.getName());

        //此处获取Product关联的种类,过滤器也将自动应用过滤

        Iterator it = p.getCategories().iterator();

        System.out.println(p.getCategories().size());

        while (it.hasNext())

        {

            Category c = (Category)it.next();

            System.out.println(c.getName());

        }

    }

    tx.commit();

    HibernateUtil.closeSession();

}

通过使用过滤器定义常用的数据筛选规则,如果是临时的数据筛选,还是使用常规查询比较好。对于从前使用行列表达式视图的地方,此处可以考虑使用过滤器。

posted @ 2009-07-19 09:08 jadmin 阅读(116) | 评论 (0)编辑 收藏

4.5 SQL查询

Hibernate还支持使用SQL查询,使用SQL查询可以利用某些数据库的特性,或者用于将原有的JDBC应用迁移到Hibernate应用上。使用命名的SQL查询还可以将SQL语句放在配置文件中配置,从而提高程序的解耦,命名SQL查询还可以用于调用存储过程。

如果是一个新的应用,通常不要使用SQL查询。

SQL查询是通过SQLQuery接口来表示的,SQLQuery接口是Query接口的子接口,因此完全可以调用Query接口的方法:

   ● setFirstResult(),设置返回结果集的起始点。

   ● setMaxResults(),设置查询获取的最大记录数。

   ● list(),返回查询到的结果集。

但SQLQuery比Query多了两个重载的方法:

   ● addEntity,将查询到的记录与特定的实体关联。

   ● addScalar,将查询的记录关联成标量值。

执行SQL查询的步骤如下:

(1)获取Hibernate Session对象;

(2)编写SQL语句;

(3)以SQL语句作为参数,调用Session的createSQLQuery方法创建查询对象;

(4)如果SQL语句包含参数,调用Query的setXxx方法为参数赋值;

(5)调用SQLQuery对象的addEntity或addScalar方法将选出的结果与实体或标量值关联;

(6)调用Query的list方法返回查询的结果集。

看下面的SQL查询示例:

private void test()

{

    //获取Hibernate Session对象

    Session session = HibernateUtil.currentSession();

    //开始事务

    Transaction tx = session.beginTransaction();

    //编写SQL语句

    String sqlString = "select {s.*} from student s where s.name like '马军'";

    //以SQL语句创建SQLQuery对象

    List l = session.createSQLQuery(sqlString)

                    //将查询到的记录与特定实体关联起来

                    .addEntity("s",Student.class)

                    //返回全部的记录集

                    .list();

    //遍历结果集

    Iterator it = l.iterator();

    while (it.hasNext())

    {

        //因为将查询结果与Student类关联,因此返回的是Student集合

        Student s = (Student)it.next();

        Set enrolments = s.getEnrolments();

        Iterator iter = enrolments.iterator();

        while(iter.hasNext())

        {

            Enrolment e = (Enrolment)iter.next();

            System.out.println(e.getCourse().getName());

        }

    }

    //提交事务

    tx.commit();

    //关闭Session

    HibernateUtil.closeSession();

}

上面的示例显示了将查询记录关联成一个实体的示例。事实上,SQL查询也支持将查询结果转换成标量值,转换成标量值可以使用addScalar方法,如:

Double max = (Double) session.createSQLQuery("select max(cat.weight) as maxWeight from cats cat")

        .addScalar("maxWeight", Hibernate.DOUBLE);

        .uniqueResult();

使用SQL查询,如果需要将查询到的结果转换成特定实体,就要求为选出的字段命名别名。这别名不是随意命名的,而是以“/”实例名.属性名“/”的格式命名,例如:

//依次将多个选出的字段命名别名,命名别名时都以ss作为前缀,ss是关联实体的别名

String sqlStr = "select stu.studentId as {ss.studentNumber},"

        + "stu.name as {ss.name} from "

        + "student as stu where stu.name like '杨海华'";

List l = session.createSQLQuery(sqlStr)

            //将查询出的ss实例,关联到Student类

            .addEntity("ss",Student.class)

            .list();

在第一个示例中,以{s.*}代表该表的全部字段,且关联实例的别名也被指定为s。

注意:如果不使用{s.*}的形式,就可让实体别名和表别名互不相同。关联实体的类型时,被关联的类必须有对应的setter方法。

4.5.1 命名SQL查询

可以将SQL语句不放在程序中,而放在配置文件中,这种方式以松耦合的方式配置SQL语句,可以提高程序解耦。

在Hibernate的映射文件中定义查询名,然后确定查询所用的SQL语句,然后就可以直接调用该命名SQL查询。在这种情况下,不需要调用addEntity()方法,因为在配置命名SQL查询时,已经完成了查询结果与实体的关联。

下面是命名SQL查询的配置片段:

<!-- 每个sql-query元素定义一个命名SQL查询 -->

<sql-query name="mySqlQuery">

    <!-- 关联返回的结果与实体类 -->

    <return alias="s" class="Student"/>

        <!-- 定义命名SQL查询的SQL语句 -->

         SELECT {s.*}

        from student s WHERE s.name like'杨海华'

</sql-query>

sql-query元素是hibernate-mapping元素的子元素。因此,sql-query定义的名可以直接通过Session访问,上面定义的mySqlQuery查询可以直接访问,下面是使用该命名SQL查询的示例代码:

private void testNamedSQl()

{

    //获取Hibernate Session对象

    Session session = HibernateUtil.currentSession();

    //开始事务

    Transaction tx = session.beginTransaction();

    //调用命名查询,直接返回结果

    List l = session.getNamedQuery("mySqlQuery")

                         .list();

    //遍历结果集

    Iterator it = l.iterator();

    while (it.hasNext())

    {

        //在定义SQL查询时,已经将结果集与Student类关联起来

        //因此,集合里的每个元素都是Student实例

        Student s = (Student)it.next();

        Set enrolments = s.getEnrolments();

        Iterator iter = enrolments.iterator();

        while(iter.hasNext())

        {

            Enrolment e = (Enrolment)iter.next();

            System.out.println("=====================================");

            System.out.println(e.getCourse().getName());

            System.out.println("=====================================");

        }

    }

    tx.commit();

    HibernateUtil.closeSession();

}

4.5.2 调用存储过程

Hibernate 3增加了存储过程的支持,该存储过程只能返回一个结果集。

下面是Oracle 9i的存储过程示例:

CREATE OR REPLACE FUNCTION selectAllEmployments

    RETURN SYS_REFCURSOR

AS

    st_cursor SYS_REFCURSOR;

BEGIN

    OPEN st_cursor FOR

SELECT EMPLOYEE, EMPLOYER,

STARTDATE, ENDDATE,

REGIONCODE, EID, VALUE, CURRENCY

FROM EMPLOYMENT;

      RETURN st_cursor;

END;

如果需要使用该存储过程,可以先将其定义成命名SQL查询,例如:

<!-- 定义命名SQL查询,name属性指定命名SQL查询名 -->

<sql-query name="selectAllEmployees_SP" callable="true">

    <!-- 定义返回列与关联实体类属性之间的映射 -->

    <return alias="emp" class="Employment">

        <!-- 依次定义每列与实体类属性的对应 -->

        <return-property name="employee" column="EMPLOYEE"/>

        <return-property name="employer" column="EMPLOYER"/>

        <return-property name="startDate" column="STARTDATE"/>

        <return-property name="endDate" column="ENDDATE"/>

        <return-property name="regionCode" column="REGIONCODE"/>

        <return-property name="id" column="EID"/>

        <!-- 将两列值映射到一个关联类的组件属性 -->

        <return-property name="salary">

            <!-- 映射列与组件属性之间的关联 -->

            <return-column name="VALUE"/>

            <return-column name="CURRENCY"/>

        </return-property>

    </return>

    { ? = call selectAllEmployments() }

</sql-query>

调用存储过程还有如下需要注意的地方:

   ● 因为存储过程本身完成了查询的全部操作,所以调用存储过程进行的查询无法使用setFirstResult()/setMaxResults()进行分页。

   ● 存储过程只能返回一个结果集,如果存储过程返回多个结果集,Hibernate将仅处理第一个结果集,其他将被丢弃。

   ● 如果在存储过程里设定SET NOCOUNT ON,将有更好的性能表现。当然也可以没有该设定。

posted @ 2009-07-19 09:02 jadmin 阅读(1157) | 评论 (0)编辑 收藏

4.4 条 件 查 询

条件查询是更具面向对象特色的数据查询方式。条件查询可通过如下3个类完成:

   ● Criteria,代表一次查询。

   ● Criterion,代表一个查询条件。

   ● Restrictions,产生查询条件的工具类。

执行条件查询的步骤如下:

(1)获得Hibernate的Session对象。

(2)以Session对象创建Criteria对象。

(3)增加Criterion查询条件。

(4)执行Criteria的list等方法返回结果集。

看下面的条件查询示例:

private void test()

{

    //获取Hibernate Session对象

    Session session = HibernateUtil.currentSession();

    //开始事务

    Transaction tx = session.beginTransaction();

    //创建Criteria和添加查询条件同步完成

    //最后调用list方法,返回查询到的结果集

    List l = session.createCriteria(Student.class)

        //此处增加限制条件必须是Student已经存在的属性

        .add( Restrictions.gt("studentNumber" , new Long(20050231) ) )

        //如果要增加对Student的关联类的属性的限制则必须重新createCriteria()

        /如果此关联属性是集合,则只要集合里任意一个对象的属性满足下面条件

        .createCriteria("enrolments")即可

        .add( Restrictions.gt("semester" , new Short("2") ) )

        .list();

        Iterator it = l.iterator();

    //遍历查询到的记录

    while (it.hasNext())

    {

        Student s = (Student)it.next();

        System.out.println(s.getName());

        Set enrolments = s.getEnrolments();

        Iterator iter = enrolments.iterator();

        while(iter.hasNext())

        {

            Enrolment e = (Enrolment)iter.next();

            System.out.println(e.getCourse().getName());

       }

    }

    tx.commit();

    ibernateUtil.closeSession();

}

在条件查询中,Criteria接口代表一次查询,该查询本身不具备任何的数据筛选功能,Session调用createCriteria(Class clazz)方法对某个持久化类创建条件查询实例。

Criteria包含如下两个方法:

   ● Criteria setFirstResult(int firstResult),设置查询返回的第一行记录。

   ● Criteria setMaxResults(int maxResults),设置查询返回的记录数。

这两个方法与Query的这两个方法用法相似,都用于完成查询分页。

而Criteria还包含如下常用方法:

   ● Criteria add(Criterion criterion),增加查询条件。

   ● Criteria addOrder(Order order),增加排序规则。

   ● List list(),返回结果集。

Criterion接口代表一个查询条件,该查询条件由Restrictions负责产生,Restrictions是专门用于产生查询条件的工具类,它的方法大部分都是静态方法,常用的方法如下:

   ● static Criterion allEq(Map propertyNameValues),判断指定属性(由Map参数的key指定)和指定值(由Map参数的value指定)是否完全相等。

   ● static Criterion between(String propertyName,Object lo, Object hi),判断属性值在某个值范围之内。

   ● static Criterion ilike(String propertyName, Object value),判断属性值匹配某个字符串。

   ● static Criterion ilike(String propertyName, String value,MatchMode matchMode),判断属性值匹配某个字符串,并确定匹配模式。

   ● static Criterion in(String propertyName,Collection values),判断属性值在某个集合内。

   ● static Criterion in(String propertyName,Object[] values),判断属性值是数组元素的其中之一。

   ● static Criterion isEmpty(String propertyName),判断属性值是否为空。

   ● static Criterion isNotEmpty(String propertyName),判断属性值是否不为空。

   ● static Criterion isNotNull(String propertyName),判断属性值是否为空。

   ● static Criterion isNull(String propertyName),判断属性值是否不为空。

   ● static Criterion not(Criterion expression),对Criterion求否。

   ● static Criterion sizeEq(String propertyName, int size),判断某个属性的元素个数是否与size相等。

   ● static Criterion sqlRestriction(String sql),直接使用SQL语句作为筛选条件。

   ● static Criterion sqlRestriction(String sql, Object[] values, Type[] types),直接使用带参数占位符的SQL语句作为条件,并指定多个参数值。

   ● static Criterion sqlRestriction(String sql, Object value, Type type),直接使用带参数占位符的SQL语句作为条件,并指定参数值。

Order实例代表一个排序标准,Order有如下构造器:

Order(String propertyName, boolean ascending),根据propertyName排序,是否采用升序,如果后一个参数为true,采用升序排序,否则采用降序排序。

如果需要使用关联类的属性来增加查询条件,则应该对属性再次使用createCriteria方法。看如下示例:

session.createCriteria(Person.class)

    .add(Restrictions.like("name" , "dd%"))

    .createCriteria("addresses")

    .add(Restrictions.like("addressdetail" , "上海%"))

    .list();

上面的代码表示建立Person类的条件查询,第一个查询条件是直接过滤Person的属性,即选出name属性以dd开始的Person实例,第二个查询条件则过滤Person关联实例的属性,其中addresses是Person类的关联持久化类Address,而addressdetail则是Address类的属性。值得注意的是,查询并不是查询Address持久化类,而是查询Person持久化类。

注意:使用关联类的条件查询,依然是查询原有持久化类的实例,而不是查询被关联类的实例。

posted @ 2009-07-19 08:59 jadmin 阅读(145) | 评论 (0)编辑 收藏

4.3 使用HQL查询

Hibernate提供了异常强大的查询体系,使用Hibernate有多种查询方式。可以选择使用Hibernate的HQL查询,或者使用条件查询,甚至可以使用原生的SQL查询语句,此外还提供了一种数据过滤功能,这些都可用于筛选目标数据。

下面分别介绍Hibernate的4种数据筛选方法:

4.3.1 HQL查询

HQL是Hibernate Query Language的缩写,HQL的语法很像SQL的语法,但HQL是一种面向对象的查询语言。因此,SQL的操作对象是数据表和列等数据对象,而HQL的操作对象是类、实例、属性等。

HQL是完全面向对象的查询语言,因此可以支持继承和多态等特征。

HQL查询依赖于Query类,每个Query实例对应一个查询对象。使用HQL查询可按如下步骤进行:

(1)获取Hibernate Session对象;

(2)编写HQL语句;

(3)以HQL语句作为参数,调用Session的createQuery方法创建查询对象;

(4)如果HQL语句包含参数,调用Query的setXxx方法为参数赋值;

(5)调用Query对象的list等方法遍历查询结果。

看下面的查询示例:

public class HqlQuery

{

    public static void main(String[] args)throws Exception

    {

        HqlQuery mgr = new HqlQuery();

        //调用查询方法

        mgr.findPersons();

        //调用第二个查询方法

        mgr.findPersonsByHappenDate();

        HibernateUtil.sessionFactory.close();

    }

    //第一个查询方法

    private void findPersons()

    {

        //获得Hibernate Session

        Session sess = HibernateUtil.currentSession();

        //开始事务

        Transaction tx = sess.beginTransaction();

        //以HQL语句创建Query对象.

        //执行setString方法为HQL语句的参数赋值

        //Query调用list方法访问查询的全部实例

        List pl = sess.createQuery("from Person p where p.myEvents.title

        = :eventTitle")

                        .setString("eventTitle","很普通事情")

                        .list();

        //遍历查询的全部结果

        for (Iterator pit = pl.iterator() ; pit.hasNext(); )

        {

            Person p = ( Person )pit.next();

            System.out.println(p.getName());

        }

        //提交事务

        tx.commit();

        HibernateUtil.closeSession();

    }

    //第二个查询方法

    private void findPersonsByHappenDate()throws Exception

    {

        //获得Hibernate Session对象

        Session sess = HibernateUtil.currentSession();

        Transaction tx = sess.beginTransaction();

        //解析出Date对象

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

        Date start = sdf.parse("2005-01-01");

        System.out.println("系统开始通过日期查找人" + start);

        //通过Session的createQuery方法创建Query对象

        //设置参数

        //返回结果集

        List pl = sess.createQuery(

            "from Person p where p.myEvents.happenDate between :firstDate

            and :endDate")

                        .setDate("firstDate",start)

                        .setDate("endDate",new Date())

                        .list();

        //遍历结果集

        for (Iterator pit = pl.iterator() ; pit.hasNext(); )

        {

            Person p = ( Person )pit.next();

            System.out.println(p.getName());

        }

        tx.commit();

        HibernateUtil.closeSession();

    }

}

通过上面的示例程序,可看出查询步骤基本相似。Query对象可以连续多次设置参数,这得益于Hibernate Query的设计。

通常,setXxx方法的返回值都是void,但Hibernate Query的setXxx方法返回值是Query本身。因此,程序通过Session创建Query后,直接多次调用setXxx方法为HQL语句的参数赋值,再直接调用list方法返回查询到的全部结果即可。

Query还包含两个方法:

   ● setFirstResult(int firstResult),设置返回的结果集从第几条记录开始。

   ● setMaxResults(int maxResults),设置本次查询返回的结果数。

这两个方法用于实现Hibernate分页。

下面简单介绍HQL语句的语法。

HQL语句本身是不区分大小写的。也就是说,HQL语句的关键字和函数都是不区分大小写的。但HQL语句中所使用的包名、类名、实例名和属性名都区分大小写。

4.3.2 HQL查询的from子句

from子句是最简单的HQL语句,也是最基本的HQL语句。from关键字后紧跟持久化类的类名。例如:

from Person

表明从Person持久化类中选出全部的实例。

大部分时候,推荐为该Person的每个实例起别名。例如:

from Person as p

在上面的HQL语句中,Person持久化类中的实例的别名为p,既然 p是实例名,因此也应该遵守Java的命名规则:第一个单词的首字母小写,后面每个单词的首字母大写。

命名别名时,as关键字是可选的,但为了增加可读性,建议保留。

from后还可同时出现多个持久化类,此时将产生一个笛卡儿积或跨表的连接。

4.3.3 HQL查询的select子句

select子句用于确定选择出的属性,当然select选择的属性必须是from后持久化类包含的属性。例如:

select p.name from Person as p

select可以选择任意属性,不仅可以选择持久化类的直接属性,还可以选择组件属性包含的属性,例如:

select p.name.firstName from Person as p

select也支持将选择出的属性存入一个List对象中,例如:

select new list(p.name , p.address) from Person as p

甚至可以将选择出的属性直接封装成对象,例如:

select new ClassTest(p.name , p.address) from Person as p

前提是ClassTest支持p.name和p.address的构造器,假如p.name的数据类型是           String,p.address的数据类型是String,则ClassTest必须有如下的构造器:

ClassTest(String s1, String s2)

select还支持给选中的表达式命名别名,例如:

select p.name as personName from Person as p

这种用法与new map结合使用更普遍。如:

select new map(p.name as personName) from Person as p

在这种情形下,选择出的是Map结构,以personName为key,实际选出的值作为value。

4.3.4 HQL查询的聚集函数

HQL也支持在选出的属性上,使用聚集函数。HQL支持的聚集函数与SQL完全相同,有如下5个:

   ● avg,计算属性平均值。

   ● count,统计选择对象的数量。

   ● max,统计属性值的最大值

   ● min,统计属性值的最小值。

   ● sum,计算属性值的总和。

例如:

select count(*) from Person

select max(p.age) from Person as p

select子句还支持字符串连接符、算术运算符以及SQL函数。如:

select p.name || "" || p.address from Person as p

select子句也支持使用distinct和all关键字,此时的效果与SQL中的效果完全相同。

4.3.5 多态查询

HQL语句被设计成能理解多态查询,from后跟的持久化类名,不仅会查询出该持久化类的全部实例,还会查询出该类的子类的全部实例。

如下面的查询语句:

from Person as p

该查询语句不仅会查询出Person的全部实例,还会查询出Person的子类,如Teacher的全部实例,前提是Person和Teacher完成了正确的继承映射。

HQL支持在from子句中指定任何Java类或接口,查询会返回继承了该类的持久化子类的实例或返回实现该接口的持久化类的实例。下面的查询语句返回所有被持久化的对象:

from java.lang.Object o

如果Named接口有多个持久化类,下面的语句将返回这些持久化类的全部实例:

from Named as n

注意:后面的两个查询将需要多个SQL SELECT语句,因此无法使用order by子句对结果集进行排序,从而,不允许对这些查询结果使用Query.scroll()方法。

4.3.6 HQL查询的where子句

where子句用于筛选选中的结果,缩小选择的范围。如果没有为持久化实例命名别名,可以直接使用属性名引用属性。

如下面的HQL语句:

from Person where name like 'tom%'

上面HQL语句与下面的语句效果相同:

from Person as p where p.name like "tom%"

在后面的HQL语句中,如果为持久化实例命名了别名,则应该使用完整的属性名。两个HQL语句都可返回name属性以tom开头的实例。

复合属性表达式加强了where子句的功能,例如如下HQL语句:

from Cat cat where cat.mate.name like "kit%"

该查询将被翻译成为一个含有内连接的SQL查询,翻译后的SQL语句如下:

select * from cat_table as table1 cat_table as table2 where table1.mate =

table2.id and table1.name like "kit%"

再看下面的HQL查询语句:

from Foo foo where foo.bar.baz.customer.address.city like"guangzhou%"

翻译成SQL查询语句,将变成一个四表连接的查询。

=运算符不仅可以被用来比较属性的值,也可以用来比较实例:

from Cat cat, Cat rival where cat.mate = rival.mate

select cat, mate

from Cat cat, Cat mate

where cat.mate = mate

特殊属性(小写)id可以用来表示一个对象的标识符。(也可以使用该对象的属性名。)

from Cat as cat where cat.id = 123

from Cat as cat where cat.mate.id = 69

第二个查询是一个内连接查询,但在HQL查询语句下,无须体会多表连接,而完全使用面向对象方式的查询。

id也可代表引用标识符。例如,Person类有一个引用标识符,它由country属性 与medicareNumber两个属性组成。

下面的HQL语句有效:

from Person as person

where person.id.country = 'AU'

    and person.id.medicareNumber = 123456

from Account as account

where account.owner.id.country = 'AU'

    and account.owner.id.medicareNumber = 123456

第二个查询跨越两个表Person和Account。是一个多表连接查询,但此处感受不到多表连接查询的效果。

在进行多态持久化的情况下,class关键字用来存取一个实例的鉴别值(discriminator value)。嵌入where子句中的Java类名,将被作为该类的鉴别值。例如:

from Cat cat where cat.class = DomesticCat

where子句中的属性表达式必须以基本类型或java.lang.String结尾,不要使用组件类型属性结尾,例如Account有Person属性,而Person有Name属性,Name有firstName属性。

看下面的情形:

from Account as a where a.person.name.firstName like "dd%" //正确

from Account as a where a.person.name like "dd%" //错误

4.3.7 表达式

HQL的功能非常丰富,where子句后支持的运算符异常丰富,不仅包括SQL的运算符,还包括EJB-QL的运算符等。

where子句中允许使用大部分SQL支持的表达式:

   ● 数学运算符+、–、*、/ 等。

   ● 二进制比较运算符=、>=、<=、<>、!=、like等。

   ● 逻辑运算符and、or、not等。

   ● in、not in、between、is null、is not null、is empty、is not empty、member of和not member of等。

   ● 简单的case、case ... when ... then ... else ... end和case、case when ... then ... else ...       end等。

   ● 字符串连接符value1 || value2或使用字符串连接函数concat(value1 , value2)。

   ● 时间操作函数current_date()、current_time()、current_timestamp()、second()、minute()、hour()、day()、month()、year()等。

   ● HQL还支持EJB-QL 3.0所支持的函数或操作substring()、trim()、lower()、upper()、length()、locate()、abs()、sqrt()、bit_length()、coalesce()和nullif()等。

   ● 还支持数据库的类型转换函数,如cast(... as ...),第二个参数是Hibernate的类型名,或者extract(... from ...),前提是底层数据库支持ANSI cast() 和extract()。

   ● 如果底层数据库支持如下单行函数sign()、trunc()、rtrim()、sin()。则HQL语句也完全可以支持。

   ● HQL语句支持使用?作为参数占位符,这与JDBC的参数占位符一致,也可使用命名参数占位符号,方法是在参数名前加冒号 :,例如 :start_date和:x1等。

   ● 当然,也可在where子句中使用SQL常量,例如'foo'、69、'1970-01-01 10:00:         01.0'等。

   ● 还可以在HQL语句中使用Java public static final 类型的常量,例如eg.Color.TABBY。

除此之外,where子句还支持如下的特殊关键字用法。

   ● in与between...and可按如下方法使用:

from DomesticCat cat where cat.name between 'A' and 'B'

from DomesticCat cat where cat.name in ( 'Foo','Bar','Baz')

   ● 当然,也支持not in和not between...and的使用,例如:

from DomesticCat cat where cat.name not between 'A' and 'B'

from DomesticCat cat where cat.name not in ( 'Foo','Bar','Baz' )

   ● 子句is null与is not null可以被用来测试空值,例如:

from DomesticCat cat where cat.name is null;

from Person as p where p.address is not null;

如果在Hibernate配置文件中进行如下声明:

<property name="hibernate.query.substitutions">true 1, false 0</property>

上面的声明表明,HQL转换SQL语句时,将使用字符1和0来取代关键字true和false。然后将可以在表达式中使用布尔表达式,例如:

from Cat cat where cat.alive = true

   ● size关键字用于返回一个集合的大小,例如:

from Cat cat where cat.kittens.size > 0

from Cat cat where size(cat.kittens) > 0

   ● 对于有序集合,还可使用minindex与maxindex函数代表最小与最大的索引序数。同理,可以使用minelement与maxelement函数代表集合中最小与最大的元素。         例如:

from Calendar cal where maxelement(cal.holidays) > current date

from Order order where maxindex(order.items) > 100

from Order order where minelement(order.items) > 10000

   ● 可以使用SQL函数any、some、all、exists、in操作集合里的元素,例如:

//操作集合元素

select mother from Cat as mother, Cat as kit

where kit in elements(foo.kittens)

//p的name属性等于集合中某个元素的name属性

select p from NameList list, Person p

where p.name = some elements(list.names)

//操作集合元素

from Cat cat where exists elements(cat.kittens)

from Player p where 3 > all elements(p.scores)

from Show show where 'fizard' in indices(show.acts)

注意这些结构变量size、elements、indices、minindex、maxindex、minelement、maxelement 等,只能在where子句中使用。

   ● where子句中,有序集合的元素(arrays, lists, maps)可以通过[ ]运算符访问。例如:

//items是有序集合属性,items[0]代表第一个元素

from Order order where order.items[0].id = 1234

//holidays是map集合属性,holidays[national day]代表其中一个元素

select person from Person person, Calendar calendar

where calendar.holidays['national day'] = person.birthDay

and person.nationality.calendar = calendar

//下面同时使用list 集合和map集合属性

select item from Item item, Order order

where order.items[ order.deliveredItemIndices[0] ] = item and order.id = 11

select item from Item item, Order order

where order.items[ maxindex(order.items) ] = item and order.id = 11

在[]中的表达式甚至可以是一个算术表达式,例如:

select item from Item item, Order order

where order.items[ size(order.items) - 1 ] = item

借助于HQL,可以大大简化选择语句的书写,提高查询语句的可读性,看下面的HQL语句:

select cust

from Product prod,

    Store store

    inner join store.customers cust

where prod.name = 'widget'

    and store.location.name in ( 'Melbourne', 'Sydney' )

    and prod = all elements(cust.currentOrder.lineItems)

如果翻译成SQL语句,将变成如下形式:

SELECT cust.name, cust.address, cust.phone, cust.id, cust.current_order

FROM customers cust,

    stores store,

    locations loc,

    store_customers sc,

    product prod

WHERE prod.name = 'widget'

    AND store.loc_id = loc.id

    AND loc.name IN ( 'Melbourne', 'Sydney' )

    AND sc.store_id = store.id

    AND sc.cust_id = cust.id

    AND prod.id = ALL(

        SELECT item.prod_id

        FROM line_items item, orders o

        WHERE item.order_id = o.id

            AND cust.current_order = o.id

    )

4.3.8 order by子句

查询返回的列表(list)可以根据类或组件属性的任何属性进行排序,例如:

from Person as p

order by p.name, p.age

还可使用asc或desc关键字指定升序或降序的排序规则,例如:

from Person as p

order by p.name asc , p.age desc

如果没有指定排序规则,默认采用升序规则。即是否使用asc关键字是没有区别的,加asc是升序排序,不加asc也是升序排序。

4.3.9 group by子句

返回聚集值的查询可以对持久化类或组件属性的属性进行分组,分组所使用的group by子句。看下面的HQL查询语句:

select cat.color, sum(cat.weight), count(cat)

from Cat cat

group by cat.color

类似于SQL的规则,出现在select后的属性,要么出现在聚集函数中,要么出现在group by的属性列表中。看下面示例:

//select后出现的id出现在group by之后,而name属性则出现在聚集函数中

select foo.id, avg(name), max(name)

from Foo foo join foo.names name

group by foo.id

having子句用于对分组进行过滤,如下:

select cat.color, sum(cat.weight), count(cat)

from Cat cat

group by cat.color

having cat.color in (eg.Color.TABBY, eg.Color.BLACK)

注意:having子句用于对分组进行过滤,因此having子句只能在有group by子句时才可以使用,没有group by子句,不能使用having子句。

Hibernate的HQL语句会直接翻译成数据库SQL语句。因此,如果底层数据库支持的having子句和group by子句中出现一般函数或聚集函数,HQL语句的having子句和order by 子句中也可以出现一般函数和聚集函数。

例如:

select cat

from Cat cat

join cat.kittens kitten

group by cat

having avg(kitten.weight) > 100

order by count(kitten) asc, sum(kitten.weight) desc

注意:group by子句与 order by子句中都不能包含算术表达式。

4.3.10 子查询

如果底层数据库支持子查询,则可以在HQL语句中使用子查询。与SQL中子查询相似的是,HQL中的子查询也需要使用()括起来。如:

from Cat as fatcat

where fatcat.weight > ( select avg(cat.weight) from DomesticCat cat )

如果select中包含多个属性,则应该使用元组构造符:

from Cat as cat

where not ( cat.name, cat.color ) in (

    select cat.name, cat.color from DomesticCat cat

)

4.3.11 fetch关键字

对于集合属性,Hibernate默认采用延迟加载策略。例如,对于持久化类Person,有集合属性scores。加载Person实例时,默认不加载scores属性。如果Session被关闭,Person实例将无法访问关联的scores属性。

为了解决该问题,可以在Hibernate映射文件中取消延迟加载或使用fetch join,例如:

from Person as p join p.scores

上面的fetch语句将会初始化person的scores集合属性。

如果使用了属性级别的延迟获取,可以使用fetch all properties来强制Hibernate立即抓取那些原本需要延迟加载的属性,例如:

from Document fetch all properties order by name

from Document doc fetch all properties where lower(doc.name) like '%cats%'

4.3.12 命名查询

HQL查询还支持将查询所用的HQL语句放入配置文件中,而不是代码中。通过这种方式,可以大大提供程序的解耦。

使用query元素定义命名查询,下面是定义命名查询的配置文件片段:

<!-- 定义命名查询 -->

<query name="myNamedQuery">

    <!-- 此处确定命名查询的HQL语句 -->

    from Person as p where p.age > ?

</query>

该命名的HQL查询可以直接通过Session访问,调用命名查询的示例代码如下:

private void findByNamedQuery()throws Exception

{

    //获得Hibernate Session对象

    Session sess = HibernateUtil.currentSession();

    //开始事务

    Transaction tx = sess.beginTransaction();

    System.out.println("执行命名查询");

    //调用命名查询

    List pl = sess.getNamedQuery("myNamedQuery")

                        //为参数赋值

                       .setInteger(0 , 20)

                        //返回全部结果

                       .list();

    //遍历结果集

    for (Iterator pit = pl.iterator() ; pit.hasNext(); )

    {

        Person p = ( Person )pit.next();

        System.out.println(p.getName());

    }

    //提交事务

    tx.commit();

    HibernateUtil.closeSession();

}

posted @ 2009-07-19 08:48 jadmin 阅读(1602) | 评论 (0)编辑 收藏

Hibernate的批量处理

Hibernate完全以面向对象的方式来操作数据库,当程序里以面向对象的方式操作持久化对象时,将被自动转换为对数据库的操作。例如调用Session的delete()方法来删除持久化对象,Hibernate将负责删除对应的数据记录;当执行持久化对象的set方法时,Hibernate将自动转换为对应的update方法,修改数据库的对应记录。

问题是如果需要同时更新100 000条记录,是不是要逐一加载100 000条记录,然后依次调用set方法——这样不仅繁琐,数据访问的性能也十分糟糕。对这种批量处理的场景,Hibernate提供了批量处理的解决方案,下面分别从批量插入、批量更新和批量删除3个方面介绍如何面对这种批量处理的情形。

1) 批量插入

如果需要将100 000条记录插入数据库,通常Hibernate可能会采用如下做法:

Session session = sessionFactory.openSession();

Transaction tx = session.beginTransaction();

for ( int i=0; i<100000; i++ ) {

    User u = new User (.....);

    session.save(customer);

}

tx.commit();

session.close();

但随着这个程序的运行,总会在某个时候运行失败,并且抛出OutOfMemoryException(内存溢出异常)。这是因为Hibernate的Session持有一个必选的一级缓存,所有的User实例都将在Session级别的缓存区进行了缓存的缘故。

为了解决这个问题,有个非常简单的思路:定时将Session缓存的数据刷新入数据库,而不是一直在Session级别缓存。可以考虑设计一个累加器,每保存一个User实例,累加器增加1。根据累加器的值决定是否需要将Session缓存中的数据刷入数据库。

下面是增加100 000个User实例的代码片段:

private void testUser()throws Exception

{

    //打开Session

    Session session = HibernateUtil.currentSession();

    //开始事务

    Transaction tx = session.beginTransaction();

    //循环100 000次,插入100 000条记录

    for (int i = 0 ; i < 1000000 ; i++ )

    {

        //创建User实例

        User u1 = new User();

        u1.setName("xxxxx" + i);

        u1.setAge(i);

        u1.setNationality("china");

        //在Session级别缓存User实例

        session.save(u1);

        //每当累加器是20的倍数时,将Session中的数据刷入数据库,并清空Session缓存

        if (i % 20 == 0)

        {

            session.flush();

            session.clear();

            tx.commit();

            tx = session.beginTransaction();

        }

    }

    //提交事务

    tx.commit();

    //关闭事务

    HibernateUtil.closeSession();

}

上面代码中,当i%20 == 0时,手动将Session处的缓存数据写入数据库,并手动提交事务。如果不提交事务,数据将依然缓存在事务处——未进入数据库,也将引起内存溢出的异常。

这是对Session级别缓存的处理,还应该通过如下配置来关闭SessionFactory的二级      缓存。

hibernate.cache.use_second_level_cache false

注意:除了要手动清空Session级别的缓存外,最好关闭SessionFactory级别的二级缓存。否则,即使手动清空Session级别的缓存,但因为在SessionFactory级别还有缓存,也可能引发异常。

2) 批量更新

上面介绍的方法同样适用于批量更新数据,如果需要返回多行数据,可以使用scroll()方法,从而可充分利用服务器端游标所带来的性能优势。下面是进行批量更新的代码片段:

private void testUser()throws Exception

{

    //打开Session

    Session session = HibernateUtil.currentSession();

    //开始事务

    Transaction tx = session.beginTransaction();

    //查询出User表中的所有记录

    ScrollableResults users = session.createQuery("from User")

        .setCacheMode(CacheMode.IGNORE)

        .scroll(ScrollMode.FORWARD_ONLY);

    int count=0;

    //遍历User表中的全部记录

    while ( users.next() )

    {

        User u = (User) users.get(0);

        u.setName("新用户名" + count);

        //当count为20的倍数时,将更新的结果从Session中flush到数据库

        if ( ++count % 20 == 0 )

        {

            session.flush();

            session.clear();

        }

    }

    tx.commit();

    HibernateUtil.closeSession();

}

通过这种方式,虽然可以执行批量更新,但效果非常不好。执行效率不高,而且需要先执行数据查询,然后再执行数据更新,并且这种更新将是逐行更新,即每更新一行记录,都需要执行一条update语句,性能非常低下。

为了避免这种情况,Hibernate提供了一种类似于SQL的批量更新和批量删除的HQL语法。

3) SQL风格的批量更新/删除

Hibernate提供的HQL语句也支持批量的UPDATE和DELETE语法。

批量UPDATE和DELETE语句的语法格式如下:

UPDATE | DELETE FROM? ClassName [WHERE WHERE_CONDITIONS]

关于上面的语法格式有以下四点值得注意:

   ● 在FROM子句中,FROM关键字是可选的。即完全可以不写FROM关键字。

   ● 在FROM子句中只能有一个类名,该类名不能有别名。

   ● 不能在批量HQL语句中使用连接,显式的或隐式的都不行。但可以在WHERE子句中使用子查询。

   ● 整个WHERE子句是可选的。

假设,需要批量更改User类实例的name属性,可以采用如下代码片段完成:

private void testUser()throws Exception

{

    //打开Session

    Session session = HibernateUtil.currentSession();

    //开始事务

    Transaction tx = session.beginTransaction();

    //定义批量更新的HQL语句

    String hqlUpdate = "update User set name = :newName";

    //执行更新

    int updatedEntities = session.createQuery( hqlUpdate )

                           .setString( "newName", "新名字" )

                           .executeUpdate();

    //提交事务

    tx.commit();

    HibernateUtil.closeSession();

}

从上面代码中可以看出,这种语法非常类似于PreparedStatement的executeUpdate语法。实际上,HQL的这种批量更新就是直接借鉴了SQL语法的UPDATE语句。

注意:使用这种批量更新语法时,通常只需要执行一次SQL的UPDATE语句,就可以完成所有满足条件记录的更新。但也可能需要执行多条UPDATE语句,这是因为有继承映射等特殊情况,例如有一个Person实例,它有Customer的子类实例。当批量更新Person实例时,也需要更新Customer实例。如果采用joined-subclass或union-subclass映射策略,Person和Customer实例保存在不同的表中,因此可能需要多条UPDATE语句。

执行一个HQL DELETE,同样使用 Query.executeUpdate() 方法,下面是一次删除上面全部记录的代码片段:

private void testUser()throws Exception

{

    //打开Session实例

    Session session = HibernateUtil.currentSession();

    //开始事务

    Transaction tx = session.beginTransaction();

    //定义批量删除的HQL语句

    String hqlUpdate = "delete User";

    //执行批量删除

    int updatedEntities = session.createQuery( hqlUpdate )

                           .executeUpdate();

    //提交事务

    tx.commit();

    //关闭Session

    HibernateUtil.closeSession();

}

由Query.executeUpdate()方法返回一个整型值,该值是受此操作影响的记录数量。实际上,Hibernate的底层操作是通过JDBC完成的。因此,如果有批量的UPDATE或DELETE操作被转换成多条UPDATE或DELETE语句,该方法返回的是最后一条SQL语句影响的记录行数。

posted @ 2009-07-19 08:42 jadmin 阅读(74) | 评论 (0)编辑 收藏

  作为昔日开发语言的王者,Java已经有14岁了,它创新变革的脚步一直没有停下来。现在Java已经不单单是一种计算机语言,Java更是一个平台,一个社区,以及一个生态系统。在2009年,Oracle收购了SUN,Java留下的,只是一个王者的背影。

  JavaSE

  目前JDK的正式版本是JDK 6 Update 12。JDK 6 Update 10以来的版本,关键功能包括:

  Java内核大大缩小了,由原来的大约十几兆缩小到4兆,这样提高了启动Java程序的速度。而其它的Java库在需要的时候可以后台下载,这样也缩短了等待和安装的时间。

  下一代的Plug-in架构。Applet运行在自己的进程中,而不再依赖浏览器,提高了性能和可伸缩性。在2008 JavaOne有一段很酷的演示,就是将Applet直接从浏览器中拖拽到桌面上,或者从桌面上拖到浏览器中。这个功能的实现有赖于JDK 6中重写了连接Java运行环境和浏览器的代码。这个新的Plug-in架构还提供一个有意思的功能,可以在Web页面通过JavaScript调用Web页面上任何的Applet,不管这个Applet是用哪种语言写的,比如JavaFX Script, JRuby, Jython。

  这里要提到JDK 6中Java虚拟机(JVM)对动态语言的支持(JSR 223)。这个框架可以使Java应用程序中支持脚本引擎,这样各种脚本语言就可以运行在JVM上。JVM发展的一个重要方向就是去掉“J”,让JVM成为能支持各种语言的,全能的“VM(虚拟机)”。目前JVM支持的脚本语言包括:JavaFX Script, Groovy, JRuby, Jython, JavaScript, Scala, Clojure。可以预计到的是,将来会有更多的脚本语言运行在Java虚拟机上。

  还在草案阶段的JDK7,一些新特性也值得期待:

  一个是实现JDK 7模块化(JSR 294, JSR 277),将与OSGi联盟更紧密的配合,以便JSR 294模块可以被OSGi所使用。

  另外一个是并行包。这个包致力于通过充分利用底层硬件来达到真正的并发。随着硬件多核系统的广泛应用,并行计算的需求对Java的性能提出了更高要求。

  Java EE

  Java EE 6 (JSR 316)的公众审议将在2009年2月23号结束。Java EE 6继承了Java EE 5改进的目标,就是简化开发,另外,还增加了一个目标,更好地满足开发人员的需求。关于Java EE 6的讨论持续了很长时间,而争议最大的就是Java EE 6引入的Profile。

  Java EE十年来的发展结果,是这个平台变得越来越庞大,但对很多用户和开发者来说,也许他只需要使用众多功能中的很小一部分,却不得不安装整个平台。Profile就是为解决这个问题而定义的。Profile实际上是Java EE API的子集。讨论最热烈的Web Profile就集中在,哪些API应该被放在标准Profile中?

  Jave EE 6包括了一系列的新技术和升级,篇幅所限制,仅罗列一些名词:WebBean 1.0, JSF 2.0, EJB 3.1, JPA 2.0, Servlet 3.0, JAX-RS 1.1。

  Java ME

  Mobile Service Architecture 2 (MSA 2)目前已经到了公众审议的尾声(JSR 249)。预计2009年,MSA 2将进入实用阶段。MSA 2是下一代Java ME平台技术,提供了更多移动开发的新特性,比如可以访问手持设备上的各种传感器,如加速计传感器,电池容量(JSR256);可以在手机上看电视(JSR 272);如同信用卡支付功能的手机钱包(JSR 257);使用XML,脚本,与Java一同构造GUI (JSR 290);通过手机使用VOIP服务(JSR 281)。

  Java FX

  Java在企业应用程序的开发中一直占主导地位,但现在面向消费者的富互联网应用(Rich Internet Application, RIA)软件数量在急剧增加,这种情形下,JavaFX应运而生。和Java语言相比,JavaFX Script更适合开发高效,快速地开发集合各种媒体,交互性强,界面吸引用户的RIA应用程序。2009年2月,JavaFX SDK 1.1与JavaFX Mobile都正式发布了,下一个要期待的是JavaFX TV。借助Java这个强大的平台,JavaFX目标是提供给开发者更好的RIA平台与技术,除了继续要在传统的PC桌面保持优势外,更是面向未来的终端设备,手机和电视。

  JAVA是有SUN公司开发的新一代编程语言,它可以用在各种不同的机器、操作系统的网络环境中进行开发。不论你使用哪种浏览器或者使用哪种操作系统(Windows、Unix等等),只要浏览器支持JAVA,你就可以看到生动的主页。JAVA正在逐步成为Internet应用的主要开发语言,它彻底改变了应用软件的开发模式,为迅速发展的信息世界增添了新的活力。所以作为Internet应用的开发技术人员不可不看JAVA,而JAVA程序不可不先从基础学起。

  希望Java能够继续发展下去,作为一个影响着整个业界和无数技术人员的开发语言,Java不应该仅仅是给我们留下一个背影而已。

posted @ 2009-07-07 15:30 jadmin 阅读(85) | 评论 (0)编辑 收藏

struts.action.extension
          The URL extension to use to determine if the request is meant for a Struts action
           用URL扩展名来确定是否这个请求是被用作Struts action,其实也就是设置 action的后缀,例如login.do的'do'字。

struts.configuration
          The org.apache.struts2.config.Configuration implementation class
            org.apache.struts2.config.Configuration接口名

struts.configuration.files
          A list of configuration files automatically loaded by Struts
           struts自动加载的一个配置文件列表

struts.configuration.xml.reload
          Whether to reload the XML configuration or not
           是否加载xml配置(true,false)

struts.continuations.package
           The package containing actions that use Rife continuations
           含有actions的完整连续的package名称

struts.custom.i18n.resources
          Location of additional localization properties files to load
           加载附加的国际化属性文件(不包含.properties后缀)

struts.custom.properties
          Location of additional configuration properties files to load
           加载附加的配置文件的位置


struts.devMode
          Whether Struts is in development mode or not
           是否为struts开发模式

struts.dispatcher.parametersWorkaround
          Whether to use a Servlet request parameter workaround necessary for some versions of WebLogic
            (某些版本的weblogic专用)是否使用一个servlet请求参数工作区(PARAMETERSWORKAROUND)

struts.enable.DynamicMethodInvocation
          Allows one to disable dynamic method invocation from the URL
            允许动态方法调用

struts.freemarker.manager.classname
          The org.apache.struts2.views.freemarker.FreemarkerManager implementation class
           org.apache.struts2.views.freemarker.FreemarkerManager接口名

struts.i18n.encoding
          The encoding to use for localization messages
           国际化信息内码

struts.i18n.reload
          Whether the localization messages should automatically be reloaded
           是否国际化信息自动加载

struts.locale
          The default locale for the Struts application
           默认的国际化地区信息

struts.mapper.class
          The org.apache.struts2.dispatcher.mapper.ActionMapper implementation class
            org.apache.struts2.dispatcher.mapper.ActionMapper接口

struts.multipart.maxSize
          The maximize size of a multipart request (file upload)
           multipart请求信息的最大尺寸(文件上传用)

struts.multipart.parser
          The org.apache.struts2.dispatcher.multipart.
          MultiPartRequest parser implementation for a multipart request (file upload)
          专为multipart请求信息使用的org.apache.struts2.dispatcher.multipart.MultiPartRequest解析器接口(文件上传用)


struts.multipart.saveDir
          The directory to use for storing uploaded files
           设置存储上传文件的目录夹

struts.objectFactory
          The com.opensymphony.xwork2.ObjectFactory implementation class
           com.opensymphony.xwork2.ObjectFactory接口(spring)

struts.objectFactory.spring.autoWire
          Whether Spring should autoWire or not
           是否自动绑定Spring

struts.objectFactory.spring.useClassCache
          Whether Spring should use its class cache or not
           是否spring应该使用自身的cache

struts.objectTypeDeterminer
          The com.opensymphony.xwork2.util.ObjectTypeDeterminer implementation class
            com.opensymphony.xwork2.util.ObjectTypeDeterminer接口

struts.serve.static.browserCache
If static content served by the Struts filter should set browser caching header properties or not
           是否struts过滤器中提供的静态内容应该被浏览器缓存在头部属性中

struts.serve.static
          Whether the Struts filter should serve static content or not
           是否struts过滤器应该提供静态内容

struts.tag.altSyntax
          Whether to use the alterative syntax for the tags or not
           是否可以用替代的语法替代tags

struts.ui.templateDir
          The directory containing UI templates
           UI templates的目录夹

struts.ui.theme
          The default UI template theme
           默认的UI template主题

struts.url.http.port
          The HTTP port used by Struts URLs
           设置http端口

struts.url.https.port
          The HTTPS port used by Struts URLs
           设置https端口

struts.url.includeParams
          The default includeParams method to generate Struts URLs
          在url中产生 默认的includeParams


struts.velocity.configfile
          The Velocity configuration file path
           velocity配置文件路径

struts.velocity.contexts
          List of Velocity context names
           velocity的context列表


struts.velocity.manager.classname
          org.apache.struts2.views.velocity.VelocityManager implementation class
           org.apache.struts2.views.velocity.VelocityManager接口名

struts.velocity.toolboxlocation
          The location of the Velocity toolbox
           velocity工具盒的位置


struts.xslt.nocache
          Whether or not XSLT templates should not be cached
           是否XSLT模版应该被缓存

摘自:http://li445970924.javaeye.com/blog/420056

posted @ 2009-07-04 15:33 jadmin 阅读(107) | 评论 (0)编辑 收藏
仅列出标题
共50页: First 上一页 13 14 15 16 17 18 19 20 21 下一页 Last