不同事务中执行hibernate query,则查询出来的对象会在不同session中,或一个在托管态,一个在session中管理,所以是不同实例。
如果在同一事务中的话,则多次query出来的对象实际上是同一个实例,当你改变第一次query出来的对象时,之后你再query出来的实例就会反映第一次的变化。
同一事务下:
1.
swapTrade = swapTradeService.getTradeByLqtId(incomingTrade.getLqttradeid());
swapTrade = positionConverter.toSwapTrade(incomingTrade, swapTrade);
2.
SwapTrade persistedTrade = swapTradeService.getTradeByLqtId(swapTrade.getLqttradeid());
persistedTrade 会反映swapTrade的变化。
========================
http://www.iteye.com/topic/1113462
用过Hibernate的人都知道Hibernate最原始的使用Session方式(异常忽略):
获取SessionFactory 打开Session 打开事务(可选) 执行操作 关闭事务(可选) 关闭Session |
当然还有另外一个方法getCurrentSession() 这个方法就是通过SessionContext来减少Session创建的。比如常用的ThreadLocalSessionContext:
- Session current = existingSession( factory );
- if (current == null) {
- current = buildOrObtainSession();
- current.getTransaction().registerSynchronization( buildCleanupSynch() );
- if ( needsWrapping( current ) ) {
- current = wrap( current );
- }
- doBind( current, factory );
- }
- return current;
Session current = existingSession( factory );
if (current == null) {
current = buildOrObtainSession();
current.getTransaction().registerSynchronization( buildCleanupSynch() );
if ( needsWrapping( current ) ) {
current = wrap( current );
}
doBind( current, factory );
}
return current;
currentSession()内部先从ThreadLocal中获取Session。若不为null直接返回,若为null则openSession(...)一个Session并把这个Session对象绑定在ThreadLocal上。它就是通过在ThreadLocal中注册绑定Session来确保线程中最多只有一个Session对象。
上面的地球人都知道
==============================================================================
Spring提供许多Template对各种底层ORM等进行集成,如JdbcTemplate、HibernateTemplate、JpaTemplate等同时也提供了相应的Dao模板类,如JdbcDaoSupport、HibernateDaoSupport、JpaDaoSupport等
既然说Spring对Hibernate的集成,就得看HibernateTemplate和HibernateDaoSupport这两个类。
使用HibernateDaoSupport进行数据操作时常用两种方式访问Session:getSession()和HibernateCallback。
注意:使用HibernateDaoSupport时候、如果再通过SessionFactory进行getCurrentSession()获取Session的话就有可能出现问题了。因为Spring的Bean工厂把Hibernate的SessionFactory动了点小手脚 ~。就是前面说到的ThreadLocalSessionContext被Spring替换为 SpringSessionContext !
SessionFactoryUtils#doGetSession()是通过TransactionSynchronizationManager来获取Session资源的,doGetSession()会通过 TransactionSynchronizationManager访问线程资源、但是整个操作中没有打开事务的话、此方法会抛出异常:
这个异常就是出现在SessionFactoryUtils#doGetSession()方法的最后:
|
getSession和getCurrentSession实现过程差不多,但是精简了SessionContext那一段。并且可以设置allowCreate,如果设置为false,此方法应该和getCurrentSession同样效果。
=========================================================================
了解了这些之后、我进行了一个HibernateTemplate和getSession之间的测试,主要就是测试
单线程下多次重复请求创建Session与事务之间的关系、HibernateTemplate和getSession()两者之间在功能实现和效率上有什么样的不同。(多线程下卡的要死、不测试了)
Dao层: | testTS()方法——临时创建用于测试的方法 通过HibernateTemplate和直接getSession等方法获取Session并保存。 打印已经保存的Session的状态。 打印已经保存的Session之间是否相同。 打印SessionFactory的状态记录。 |
Service层: | testTS()方法——临时创建用于测试的方法 内部代码为执行三次请求: getDao().testTS(); getDao().testTS(); getDao().testTS(); |
第一次测试
Service层的testTS(e)方法不打开事务。 使用HibernateTemplate。 | 结论: Service层没有打开事务,但是每次操作CURD操作时,HibernateTemplate都会自动创建 Transactional。同一线程获取到的前后两个Session之间互不相同。 |
--------------Closed Transactional && Single-Thread--------------
测试数据扩大100倍并记时: |
第二次测试
Service层的testTS(e)方法打开事务。 | 结论: 在事务边界内获取的Session由Spring管理、不必手动关闭 虽然打开了事务,但是同一线程下同一事务边界内前后获取的两个Session仍然不同! 这是为什么??? 后面会继续剖析HibernateTemplate源码,给出解释 |
--------------Open Transactional && Single-Thread--------------
测试数据扩大100倍并记时: |
第三次测试
Service层的testTS(e)方法不打开事务。 | 可以看到getSession()由于缺少Spring的支持,在无事务环境下。 它为每个CURD请求创建了一个Session。并且更让人震惊的是,它好像为每个Session都创建了新 Connection 。这些Session使用之后,它也并不关闭。 |
--------------Closed Transactional && Single-Thread--------------
测试数据扩大100倍并记时,令人极其震惊且极其坑爹的一幕出现了!我亲眼看到程序以一种 肉眼可见的速度 慢腾腾的执行一次又一次的循环,它竟然执行了一分钟!!!: |
第四次测试
Service层的testTS(e)方法打开事务。 使用getSession() 。 | 这个最容易让人理解、由于同一线程且在同一事务边界内,前后两个Session相同。并且也不会重复创建 Connection。 |
--------------Open Transactional && Single-Thread-------------- 测试数据扩大100倍并记时: |
总结:
getSession()在事务边界内会通过TransactionSynchronizationManager获取Session资源,同一线程内它不会重复创建Connection , 那些获取到的Session()不需要手动关闭。但是在无声明式事务环境下,它就会表现出极其坑爹的状况,它反复获取Connection,也不关闭Session。非常消耗资源
HibernateTemplate有一个安全且高效的Session环境,它的CRUD都是位于HibernateCallback内部,如果它的CRUD并没有位于事务之中,它会自己创建一个事务(Spring集成Hibernate时,所有的资源都是由TransactionSynchronizationManager管理的 )。同一个线程中它只需要一个Connection。Session也会在事务边界处自动关闭,程序员不需要关注此事(这个事务边界可能是@Transactional显式声明的也可能是HibernateTemplate#doExecute隐式声明的)。
在同一个事务边界内,两个HibernateTemplate操作内部的Session互也不相同这个问题可以在HibernateTemplate源码中找到答案:
- protected <T> T doExecute(HibernateCallback<T> action, boolean enforceNewSession, boolean enforceNativeSession)
- throws DataAccessException {
- ......
- Session session = (enforceNewSession ?
- SessionFactoryUtils.getNewSession(getSessionFactory(), getEntityInterceptor()) : getSession());
- boolean existingTransaction = (!enforceNewSession &&
- (!isAllowCreate() || SessionFactoryUtils.isSessionTransactional(session, getSessionFactory())));
- if (existingTransaction) {
- logger.debug("Found thread-bound Session for HibernateTemplate");
- }
- FlushMode previousFlushMode = null;
- try {
- previousFlushMode = applyFlushMode(session, existingTransaction);
- enableFilters(session);
- Session sessionToExpose =
- (enforceNativeSession || isExposeNativeSession() ?
- session : createSessionProxy(session));
- T result = action.doInHibernate(sessionToExpose);
- flushIfNecessary(session, existingTransaction);
- return result;
- ......
- }
protected <T> T doExecute(HibernateCallback<T> action, boolean enforceNewSession, boolean enforceNativeSession)
throws DataAccessException {
......
Session session = (enforceNewSession ?
SessionFactoryUtils.getNewSession(getSessionFactory(), getEntityInterceptor()) : getSession());
boolean existingTransaction = (!enforceNewSession &&
(!isAllowCreate() || SessionFactoryUtils.isSessionTransactional(session, getSessionFactory())));
if (existingTransaction) {
logger.debug("Found thread-bound Session for HibernateTemplate");
}
FlushMode previousFlushMode = null;
try {
previousFlushMode = applyFlushMode(session, existingTransaction);
enableFilters(session);
Session sessionToExpose =
(enforceNativeSession || isExposeNativeSession() ?
session : createSessionProxy(session));
T result = action.doInHibernate(sessionToExpose);
flushIfNecessary(session, existingTransaction);
return result;
......
}
- 通过
- Session sessionToExpose =
- (enforceNativeSession || isExposeNativeSession() ?
- session : createSessionProxy(session));
- 红色部分可以看到HibernateTemplate中获取的Session不是原生的,而是代理的。那个代理类是一个比较简单的内部类,源代码位于HibernateTemplate类文件最下部分。每次HibernateTemplate#doExecute执行时除非声明不使用代理类,Spring都会使用线程中的Session资源来创建代理。但是这个代理类的创建对性能的影响微不足道了,Spring这样做肯定有它的道理。
通过
Session sessionToExpose =
(enforceNativeSession || isExposeNativeSession() ?
session : createSessionProxy(session));
红色部分可以看到HibernateTemplate中获取的Session不是原生的,而是代理的。那个代理类是一个比较简单的内部类,源代码位于HibernateTemplate类文件最下部分。每次HibernateTemplate#doExecute执行时除非声明不使用代理类,Spring都会使用线程中的Session资源来创建代理。但是这个代理类的创建对性能的影响微不足道了,Spring这样做肯定有它的道理。
====================================
http://www.kuqin.com/docs/hibernate/transactions.html
Hibernate本身并不是数据库,它只是一个轻量级的对象-关系数据库映射(object-relational)工具。它的事务交由底层的数据库连接管理,如果数据库连接有JTA的支持,那么在Session中进行的操作将是整个原子性JTA事务的一部分。Hibernate可以看作是添加了面向对象语义的JDBC瘦适配器(thin adapter)。
SessionFactory的创建需要耗费大量资源,它是线程安全(threadsafe)的对象,在应用中它被所有线程共享。而Session的创建耗费资源很少,它不是线程安全的对象,对于一个简单商业过程(business process),它应该只被使用一次,然后被丢弃。举例来说,当Hibernate在基于servlet的应用中,servlet能够以下面的方式得到SessionFactory。
SessionFactory sf = (SessionFactory)getServletContext().getAttribute("my.session.factory");
每次调用SessionFactory的service方法能够生成一个新的Session对象,然后调用Session的flush(),调用commit()提交它的连接,调用close()关闭它,最终丢弃它。(SessionFactory可能被保存在JNDI或者一个静态的单例(Singleton)辅助变量中。)
在无状态的session bean中,可以同样使用类似的方法。bean在setSessionContext()中得到SessionFactory的实例,每个商业方法会生成一个Session对象,调用它的flush()和close(),当然,应用不应该commit()connection. (把它留给JTA.在容器管理的事务中,数据库连接会自动完成事务。)
我们用上述方法使用Hibernate 的Transaction API,对Transaction执行一次commit()会把所有状态同步,把底层的数据库连接提交(对JTA 事务会特殊处理。)
这里需要理解flush()的含义。 flush()将持久化存储与内存中的变化进行同步,但不是将内存的变化与持久化存储进行同步。注意对所有的Hibernate JDBD 连接/事务来说,其隔离级别将施加于所有的Hibernate执行的操作之上!
接下来的几小节将讨论利用版本化的方法来确保事务原子性,这些“高级”方法需要小心使用。
在创建Hibernate会话(Session)时,你应该留意以下的实践(practices):
-
对于一个数据库连接,不要创建一个以上的Session或Transaction。
-
在对于一个数据库连接、一个事务使用多个Session时,你尤其需要格外地小心。Session对象会记录下调入数据更新的情况,所以另一个Session对象可能会遇到过时的数据。
-
Session不是线程安全的。决不要在两个并发的线程中访问同一个Session。一个Session一般只对应一批需要一次性完成的单元操作!
程序可能在两批单元操作中并发访问同一个对象的持久化状态。不管怎样,持久化类的一个实例不可能在两个Session中共享。所以有两种不同的辨别方式:
-
数据库辨别
-
foo.getId().equals( bar.getId() )
JVM 辨别
-
foo==bar
对于依附于某个特定Session的对象,两种辨别方式是等价的。然而,当程序可能在两个不同的session中并发访问“同一个”(持久化辨别)商业对象时,两个实例(对于JVM辨别来说)却可能是“不同”的。
这种方式把关于并发的头疼问题留给了Hibernate和数据库。程序不需要对任何商业对象进行同步,只要程序坚持每个Session一个线程,或者对象辨别的策略(在一个Session重,程序可以安全的使用==来比较对象)。
许多商业过程需要一系列与用户进行交互的过程,数据库访问穿插在这些过程中。对于web和企业应用来说,跨一个用户交互过程的数据事务是不可接受的。
维护各商业事务间的隔离(isolocation)就成为应用层的部分责任,我们把这种过程称为长时间运行的应用事务(application transaction)。单一的应用事务可能跨越多个数据库事务。如果这些数据库事务中只有一个(最后一个)保存了被修改的数据,其他事务只是简单地读数据,则这个应用事务就是原子性的。
唯一满足高并发性以及高可扩展性的方法是使用带有版本化的乐观并发控制。Hibernate为使用乐观并发控制的代码提供了三种可能的方法。
在整个商业过程中使用一个单独的Session实例以及它的持久化实例,这个Session使用带有版本化的乐观锁定机制,来确保多个数据库事务对于应用来说只是一个逻辑上的事务。在等待用户交互时,Session断开与数据库的连接。这个方法从数据库访问方面来看是最有效的,应用不需要关心对自己的版本检查或是重新与不需要序列化(transient)的实例进行关联。
在整个应用事务中,使用单一的Session 实例和它的持久化实例。
Session 使用带有版本化的乐观锁定来保证多个数据库事务对程序来说就如同是单一的逻辑应用事务。在等待用户交互的时候,Session 脱离所有的底层JDBC连接。对于数据库访问来说,这种方法是最高效的。程序自己不需要关心版本检查或者把已经脱离session的实例重新关联到session。
// foo is an instance loaded earlier by the Session session.reconnect(); foo.setProperty("bar"); session.flush(); session.connection().commit(); session.disconnect();
foo对象仍然知道是哪个Session把自己装载的。 只要Session 拥有一个JDBC连接,我们可以把对象的更改提交。
如果我们的 Session 太大,以至于在用户思考的时间内无法保存住,这种模式就会出现问题。比如,HttpSession应该保持尽量小。因为Session也持有(必须的)第一级缓存,包含所有被装载的对象,我们只能在很少的request/response周期中使用这一策略。这种少用是被鼓励的,因为Session 很快就会出现过时的数据。
每个与持久化存储的交互出现在一个新的Session中,在每次与数据库的交互中,使用相同的持久化实例。应用操作那些从其它Session调入的已经脱离session的实例的状态,通过使用Session.update()或者Session.saveOrUpdate()来重新建立与它们的关联。
// foo is an instance loaded by a previous Session foo.setProperty("bar"); session = factory.openSession(); session.saveOrUpdate(foo); session.flush(); session.connection().commit(); session.close();
你也可以调用lock()而非update(),如果你确信对象没有被修改过,可以使用LockMode.READ(进行一次版本检查,而跳过所有的缓存)。
每当一个新的Session中与数据库出现交互的时候,这个session会在操作持久化实例前重新把它们从数据库中装载进来。我们现在所说的方式就是你的应用程序自己使用版本检查来确保应用事务的隔离性。(当然,Hibernate仍会为你更新版本号)。从数据库访问方面来看,这种方法是最没有效率的,与entity EJB方式类似。
// foo is an instance loaded by a previous Session session = factory.openSession(); int oldVersion = foo.getVersion(); session.load( foo, foo.getKey() ); if ( oldVersion!=foo.getVersion ) throw new StaleObjectStateException(); foo.setProperty("bar"); session.flush(); session.connection().commit(); session.close();
当然,如果在低数据并行(low-data-concurrency)的环境中,并不需要版本检查,你仍可以使用这个方法,只需要忽略版本检查。
The first approach described above is to maintain a single Session for a whole business process thats spans user think time. (For example, a servlet might keep a Session in the user's HttpSession.) For performance reasons you should
上面提到的第一种方法是对于对一个用户的一次登录产生的整个商业过程维护一个Session。(举例来说,servlet有可能会在用户的HttpSession中保留一个Session)。为性能考虑,你必须
-
提交Transaction(或者JDBC连接),然后
-
(在等待用户操作前,)断开Session与JDBC连接。
Session.disconnect()方法会断开会话与JDBC的连接,把连接返还给连接池(除非是你自己提供这个连接的)。
Session.reconnect()方法会得到一个新的连接(你也可以自己提供一个),重新开始会话。在重新连接后,你可以通过对任何可能被其它事务更新的对象调用Session.lock()方法,来强迫对你没有更新的数据进行版本检查。你不需要对正在更新的数据调用lock()。
这是一个例子:
SessionFactory sessions; List fooList; Bar bar; .... Session s = sessions.openSession(); Transaction tx = null; try { tx = s.beginTransaction(); fooList = s.find( "select foo from eg.Foo foo where foo.Date = current date" // uses db2 date function ); bar = (Bar) s.create(Bar.class); tx.commit(); } catch (Exception e) { if (tx!=null) tx.rollback(); s.close(); throw e; } s.disconnect();
接下来:
s.reconnect(); try { tx = s.beginTransaction(); bar.setFooTable( new HashMap() ); Iterator iter = fooList.iterator(); while ( iter.hasNext() ) { Foo foo = (Foo) iter.next(); s.lock(foo, LockMode.READ); //check that foo isn't stale bar.getFooTable().put( foo.getName(), foo ); } tx.commit(); } catch (Exception e) { if (tx!=null) tx.rollback(); throw e; } finally { s.close(); }
从上面的例子可以看到Transaction和Session之间是多对一的关系。一个Session表示了应用程序与数据库之间的一个对话,Transaction把这个对话分隔成一个个在数据库级别具有原子性的单元。
用户不需要在锁定策略上花费过多时间,通常我们可以对JDBC连接选定一种隔离级别(isolationn level),然后让数据库完成所有的工作。高级用户可能希望得到悲观锁定或者在新的事务开始时重新得到锁。
Hibernate一直都会使用数据库的锁定机制,而不会在内存中锁定对象。
LockMode类定义了Hibernate需要的不同的锁级别。锁由以下的机制得到:
-
LockMode.WRITE在Hibernate更新或插入一行数据时自动得到。
-
LockMode.UPGRADE在用户通过SELECT ... FOR UPDATE这样的特定请求得到,需要数据库支持这种语法。
-
LockMode.UPGRADE_NOWAIT在用户通过SELECT ... FOR UPDATE NOWAIT这样的特定请求在Oracle数据库环境下得到。
-
LockMode.READ在Hibernate在不断读(Repeatable Read)和序列化(Serializable)的隔离级别下读取数据时得到。也可以通过用户的明确请求重新获得。
-
LockMode.NONE表示没有锁。所有对象在Transaction结束时会切换到这种锁模式,通过调用update()或者saveOrUpdate()与会话进行关联的对象,开始时也会在这种锁模式。
“明确的用户请求”会以下的几种方式出现:
-
调用Session.load(),指定一种LockMode。
-
调用Session.lock()。
-
调用Query.setLockMode()。
如果在调用Session.load()时指定了UPGRADE或者UPGRADE_NOWAIT,并且请求的对象还没有被会话调入,那么这个对象会以SELECT ... FOR UPDATE的方式调入。如果调用load()在一个已经调入的对象,并且这个对象调入时的锁级别没有请求时来得严格,Hibernate会对这个对象调用lock()。
Session.lock()会执行版本号检查的特定的锁模式是:READ,UPGRADE或者UPGRADE_NOWAIT。(在UPGRADE或者UPGRADE_NOWAIT,SELECT ... FOR UPGRADE使用的情况下。)
如果数据库不支持所请求的锁模式,Hibernate将会选择一种合适的受支持的锁模式替换(而不是抛出一个异常)。这确保了应用具有可移植性。
============http://www.redsaga.com/hibernate-ref/3.x/zh-cn/html/objectstate.html
Hibernate是完整的对象/关系映射解决方案,它提供了对象状态管理(state management)的功能,使开发者不再需要理会底层数据库系统的细节。 也就是说,相对于常见的JDBC/SQL持久层方案中需要管理SQL语句,Hibernate采用了更自然的面向对象的视角来持久化Java应用中的数据。
换句话说,使用Hibernate的开发者应该总是关注对象的状态(state),不必考虑SQL语句的执行。 这部分细节已经由Hibernate掌管妥当,只有开发者在进行系统性能调优的时候才需要进行了解。
Hibernate定义并支持下列对象状态(state):
-
瞬时(Transient) - 由new操作符创建,且尚未与Hibernate Session 关联的对象被认定为瞬时(Transient)的。瞬时(Transient)对象不会被持久化到数据库中,也不会被赋予持久化标识(identifier)。 如果程序中没有保持对瞬时(Transient)对象的引用,它会被垃圾回收器(garbage collector)销毁。 使用Hibernate Session可以将其变为持久(Persistent)状态。(Hibernate会自动执行必要的SQL语句)
-
持久(Persistent) - 持久(Persistent)的实例在数据库中有对应的记录,并拥有一个持久化标识(identifier)。 持久(Persistent)的实例可能是刚被保存的,或刚被加载的,无论哪一种,按定义对象都仅在相关联的Session生命周期内的保持这种状态。 Hibernate会检测到处于持久(Persistent)状态的对象的任何改动,在当前操作单元(unit of work)执行完毕时将对象数据(state)与数据库同步(synchronize)。 开发者不需要手动执行UPDATE。将对象从持久(Persistent)状态变成瞬时(Transient)状态同样也不需要手动执行DELETE语句。
-
脱管(Detached) - 与持久(Persistent)对象关联的Session被关闭后,对象就变为脱管(Detached)的。 对脱管(Detached)对象的引用依然有效,对象可继续被修改。脱管(Detached)对象如果重新关联到某个新的Session上, 会再次转变为持久(Persistent)的(Detached其间的改动将被持久化到数据库)。 这个功能使得一种编程模型,即中间会给用户思考时间(user think-time)的长时间运行的操作单元(unit of work)的编程模型成为可能。 我们称之为应用程序事务,即从用户观点看是一个操作单元(unit of work)。
接下来我们来细致的讨论下状态(states)及状态间的转换(state transitions)(以及触发状态转换的Hibernate方法)。
Hibernate认为持久化类(persistent class)新实例化的对象是瞬时(Transient)的。 我们可将瞬时(Transient)对象与session关联而变为持久(Persistent)的。
DomesticCat fritz = new DomesticCat(); fritz.setColor(Color.GINGER); fritz.setSex('M'); fritz.setName("Fritz"); Long generatedId = (Long) sess.save(fritz);
如果Cat的持久化标识(identifier)是generated类型的, 那么该标识(identifier)会自动在save()被调用时产生并分配给cat。 如果Cat的持久化标识(identifier)是assigned类型的,或是一个复合主键(composite key), 那么该标识(identifier)应当在调用save()之前手动赋予给cat。 你也可以按照EJB3 early draft中定义的语义,使用persist()替代save()。
此外,你可以用一个重载版本的save()方法。
DomesticCat pk = new DomesticCat(); pk.setColor(Color.TABBY); pk.setSex('F'); pk.setName("PK"); pk.setKittens( new HashSet() ); pk.addKitten(fritz); sess.save( pk, new Long(1234) );
如果你持久化的对象有关联的对象(associated objects)(例如上例中的kittens集合) 那么对这些对象(译注:pk和kittens)进行持久化的顺序是任意的(也就是说可以先对kittens进行持久化也可以先对pk进行持久化), 除非你在外键列上有NOT NULL约束。 Hibernate不会违反外键约束,但是如果你用错误的顺序持久化对象(译注:在pk持久之前持久kitten),那么可能会违反NOT NULL约束。
通常你不会为这些细节烦心,因为你很可能会使用Hibernate的 传播性持久化(transitive persistence)功能自动保存相关联那些对象。 这样连违反NOT NULL约束情况都不会出现了 - Hibernate会管好所有的事情。 传播性持久化(transitive persistence)将在本章稍后讨论。
如果你知道某个实例的持久化标识(identifier),你就可以使用Session的load()方法 来获取它。 load()的另一个参数是指定类的.class对象。 本方法会创建指定类的持久化实例,并从数据库加载其数据(state)。
Cat fritz = (Cat) sess.load(Cat.class, generatedId);
// you need to wrap primitive identifiers long pkId = 1234; DomesticCat pk = (DomesticCat) sess.load( Cat.class, new Long(pkId) );
此外, 你可以把数据(state)加载到指定的对象实例上(覆盖掉该实例原来的数据)。
Cat cat = new DomesticCat(); // load pk's state into cat sess.load( cat, new Long(pkId) ); Set kittens = cat.getKittens();
请注意如果没有匹配的数据库记录,load()方法可能抛出无法恢复的异常(unrecoverable exception)。 如果类的映射使用了代理(proxy),load()方法会返回一个未初始化的代理,直到你调用该代理的某方法时才会去访问数据库。 若你希望在某对象中创建一个指向另一个对象的关联,又不想在从数据库中装载该对象时同时装载相关联的那个对象,那么这种操作方式就用得上的了。 如果为相应类映射关系设置了batch-size, 那么使用这种操作方式允许多个对象被一批装载(因为返回的是代理,无需从数据库中抓取所有对象的数据)。
如果你不确定是否有匹配的行存在,应该使用get()方法,它会立刻访问数据库,如果没有对应的行,会返回null。
Cat cat = (Cat) sess.get(Cat.class, id); if (cat==null) { cat = new Cat(); sess.save(cat, id); } return cat;
你甚至可以选用某个LockMode,用SQL的SELECT ... FOR UPDATE装载对象。 请查阅API文档以获取更多信息。
Cat cat = (Cat) sess.get(Cat.class, id, LockMode.UPGRADE);
注意,任何关联的对象或者包含的集合都不会被以FOR UPDATE方式返回, 除非你指定了lock或者all作为关联(association)的级联风格(cascade style)。
任何时候都可以使用refresh()方法强迫装载对象和它的集合。如果你使用数据库触发器功能来处理对象的某些属性,这个方法就很有用了。
sess.save(cat); sess.flush(); //force the SQL INSERT sess.refresh(cat); //re-read the state (after the trigger executes)
此处通常会出现一个重要问题: Hibernate会从数据库中装载多少东西?会执行多少条相应的SQLSELECT语句? 这取决于抓取策略(fetching strategy),会在第 19.1 节 “ 抓取策略(Fetching strategies) ”中解释。
如果不知道所要寻找的对象的持久化标识,那么你需要使用查询。Hibernate支持强大且易于使用的面向对象查询语言(HQL)。 如果希望通过编程的方式创建查询,Hibernate提供了完善的按条件(Query By Criteria, QBC)以及按样例(Query By Example, QBE)进行查询的功能。 你也可以用原生SQL(native SQL)描述查询,Hibernate提供了将结果集(result set)转化为对象的部分支持。
HQL和原生SQL(native SQL)查询要通过为org.hibernate.Query的实例来表达。 这个接口提供了参数绑定、结果集处理以及运行实际查询的方法。 你总是可以通过当前Session获取一个Query对象:
List cats = session.createQuery( "from Cat as cat where cat.birthdate < ?") .setDate(0, date) .list(); List mothers = session.createQuery( "select mother from Cat as cat join cat.mother as mother where cat.name = ?") .setString(0, name) .list(); List kittens = session.createQuery( "from Cat as cat where cat.mother = ?") .setEntity(0, pk) .list(); Cat mother = (Cat) session.createQuery( "select cat.mother from Cat as cat where cat = ?") .setEntity(0, izi) .uniqueResult();
一个查询通常在调用list()时被执行,执行结果会完全装载进内存中的一个集合(collection)。 查询返回的对象处于持久(persistent)状态。如果你知道的查询只会返回一个对象,可使用list()的快捷方式uniqueResult()。
某些情况下,你可以使用iterate()方法得到更好的性能。 这通常是你预期返回的结果在session,或二级缓存(second-level cache)中已经存在时的情况。 如若不然,iterate()会比list()慢,而且可能简单查询也需要进行多次数据库访问: iterate()会首先使用1条语句得到所有对象的持久化标识(identifiers),再根据持久化标识执行n条附加的select语句实例化实际的对象。
// fetch ids Iterator iter = sess.createQuery("from eg.Qux q order by q.likeliness").iterate(); while ( iter.hasNext() ) { Qux qux = (Qux) iter.next(); // fetch the object // something we couldnt express in the query if ( qux.calculateComplicatedAlgorithm() ) { // delete the current instance iter.remove(); // dont need to process the rest break; } }
(译注:元组(tuples)指一条结果行包含多个对象) Hibernate查询有时返回元组(tuples),每个元组(tuples)以数组的形式返回:
Iterator kittensAndMothers = sess.createQuery( "select kitten, mother from Cat kitten join kitten.mother mother") .list() .iterator(); while ( kittensAndMothers.hasNext() ) { Object[] tuple = (Object[]) kittensAndMothers.next(); Cat kitten = tuple[0]; Cat mother = tuple[1]; .... }
查询可在select从句中指定类的属性,甚至可以调用SQL统计(aggregate)函数。 属性或统计结果被认定为"标量(Scalar)"的结果(而不是持久(persistent state)的实体)。
Iterator results = sess.createQuery( "select cat.color, min(cat.birthdate), count(cat) from Cat cat " + "group by cat.color") .list() .iterator(); while ( results.hasNext() ) { Object[] row = results.next(); Color type = (Color) row[0]; Date oldest = (Date) row[1]; Integer count = (Integer) row[2]; ..... }
接口Query提供了对命名参数(named parameters)、JDBC风格的问号(?)参数进行绑定的方法。 不同于JDBC,Hibernate对参数从0开始计数。 命名参数(named parameters)在查询字符串中是形如:name的标识符。 命名参数(named parameters)的优点是:
-
命名参数(named parameters)与其在查询串中出现的顺序无关
-
它们可在同一查询串中多次出现
-
它们本身是自我说明的
//named parameter (preferred) Query q = sess.createQuery("from DomesticCat cat where cat.name = :name"); q.setString("name", "Fritz"); Iterator cats = q.iterate();
//positional parameter Query q = sess.createQuery("from DomesticCat cat where cat.name = ?"); q.setString(0, "Izi"); Iterator cats = q.iterate();
//named parameter list List names = new ArrayList(); names.add("Izi"); names.add("Fritz"); Query q = sess.createQuery("from DomesticCat cat where cat.name in (:namesList)"); q.setParameterList("namesList", names); List cats = q.list();
如果你需要指定结果集的范围(希望返回的最大行数/或开始的行数),应该使用Query接口提供的方法:
Query q = sess.createQuery("from DomesticCat cat"); q.setFirstResult(20); q.setMaxResults(10); List cats = q.list();
Hibernate 知道如何将这个有限定条件的查询转换成你的数据库的原生SQL(native SQL)。
如果你的JDBC驱动支持可滚动的ResuleSet,Query接口可以使用ScrollableResults,允许你在查询结果中灵活游走。
Query q = sess.createQuery("select cat.name, cat from DomesticCat cat " + "order by cat.name"); ScrollableResults cats = q.scroll(); if ( cats.first() ) { // find the first name on each page of an alphabetical list of cats by name firstNamesOfPages = new ArrayList(); do { String name = cats.getString(0); firstNamesOfPages.add(name); } while ( cats.scroll(PAGE_SIZE) ); // Now get the first page of cats pageOfCats = new ArrayList(); cats.beforeFirst(); int i=0; while( ( PAGE_SIZE > i++ ) && cats.next() ) pageOfCats.add( cats.get(1) ); } cats.close()
请注意,使用此功能需要保持数据库连接(以及游标(cursor))处于一直打开状态。 如果你需要断开连接使用分页功能,请使用setMaxResult()/setFirstResult()
你可以在映射文件中定义命名查询(named queries)。 (如果你的查询串中包含可能被解释为XML标记(markup)的字符,别忘了用CDATA包裹起来。)
<query name="eg.DomesticCat.by.name.and.minimum.weight"><![CDATA[ from eg.DomesticCat as cat where cat.name = ? and cat.weight > ? ] ]></query>
参数绑定及执行以编程方式(programatically)完成:
Query q = sess.getNamedQuery("eg.DomesticCat.by.name.and.minimum.weight"); q.setString(0, name); q.setInt(1, minWeight); List cats = q.list();
请注意实际的程序代码与所用的查询语言无关,你也可在元数据中定义原生SQL(native SQL)查询, 或将原有的其他的查询语句放在配置文件中,这样就可以让Hibernate统一管理,达到迁移的目的。
集合过滤器(filter)是一种用于一个持久化集合或者数组的特殊的查询。查询字符串中可以使用"this"来引用集合中的当前元素。
Collection blackKittens = session.createFilter( pk.getKittens(), "where this.color = ?") .setParameter( Color.BLACK, Hibernate.custom(ColorUserType.class) ) .list() );
返回的集合可以被认为是一个包(bag, 无顺序可重复的集合(collection)),它是所给集合的副本。 原来的集合不会被改动(这与“过滤器(filter)”的隐含的含义不符,不过与我们期待的行为一致)。
请注意过滤器(filter)并不需要from子句(当然需要的话它们也可以加上)。过滤器(filter)不限定于只能返回集合元素本身。
Collection blackKittenMates = session.createFilter( pk.getKittens(), "select this.mate where this.color = eg.Color.BLACK.intValue") .list();
即使无条件的过滤器(filter)也是有意义的。例如,用于加载一个大集合的子集:
Collection tenKittens = session.createFilter( mother.getKittens(), "") .setFirstResult(0).setMaxResults(10) .list();
HQL极为强大,但是有些人希望能够动态的使用一种面向对象API创建查询,而非在他们的Java代码中嵌入字符串。对于那部分人来说,Hibernate提供了直观的Criteria查询API。
Criteria crit = session.createCriteria(Cat.class); crit.add( Expression.eq( "color", eg.Color.BLACK ) ); crit.setMaxResults(10); List cats = crit.list();
Criteria以及相关的样例(Example)API将会再第 15 章 条件查询(Criteria Queries) 中详细讨论。
你可以使用createSQLQuery()方法,用SQL来描述查询,并由Hibernate处理将结果集转换成对象的工作。 请注意,你可以在任何时候调用session.connection()来获得并使用JDBC Connection对象。 如果你选择使用Hibernate的API, 你必须把SQL别名用大括号包围起来:
List cats = session.createSQLQuery( "SELECT {cat.*} FROM CAT {cat} WHERE ROWNUM<10", "cat", Cat.class ).list();
List cats = session.createSQLQuery( "SELECT {cat}.ID AS {cat.id}, {cat}.SEX AS {cat.sex}, " + "{cat}.MATE AS {cat.mate}, {cat}.SUBCLASS AS {cat.class}, ... " + "FROM CAT {cat} WHERE ROWNUM<10", "cat", Cat.class ).list()
和Hibernate查询一样,SQL查询也可以包含命名参数和占位参数。 可以在第 16 章 Native SQL查询找到更多关于Hibernate中原生SQL(native SQL)的信息。
事务中的持久实例(就是通过session装载、保存、创建或者查询出的对象) 被应用程序操作所造成的任何修改都会在Session被刷出(flushed)的时候被持久化(本章后面会详细讨论)。 这里不需要调用某个特定的方法(比如update(),设计它的目的是不同的)将你的修改持久化。 所以最直接的更新一个对象的方法就是在Session处于打开状态时load()它,然后直接修改即可:
DomesticCat cat = (DomesticCat) sess.load( Cat.class, new Long(69) ); cat.setName("PK"); sess.flush(); // changes to cat are automatically detected and persisted
有时这种程序模型效率低下,因为它在同一Session里需要一条SQL SELECT语句(用于加载对象) 以及一条SQL UPDATE语句(持久化更新的状态)。 为此Hibernate提供了另一种途径,使用脱管(detached)实例。
请注意Hibernate本身不提供直接执行UPDATE或DELETE语句的API。 Hibernate提供的是状态管理(state management)服务,你不必考虑要使用的语句(statements)。 JDBC是出色的执行SQL语句的API,任何时候调用session.connection()你都可以得到一个JDBC Connection对象。 此外,在联机事务处理(OLTP)程序中,大量操作(mass operations)与对象/关系映射的观点是相冲突的。 Hibernate的将来版本可能会提供专门的进行大量操作(mass operation)的功能。 参考第 13 章 批量处理(Batch processing),寻找一些可用的批量(batch)操作技巧。
很多程序需要在某个事务中获取对象,然后将对象发送到界面层去操作,最后在一个新的事务保存所做的修改。 在高并发访问的环境中使用这种方式,通常使用附带版本信息的数据来保证这些“长“工作单元之间的隔离。
Hibernate通过提供使用Session.update()或Session.merge()方法 重新关联脱管实例的办法来支持这种模型。
// in the first session Cat cat = (Cat) firstSession.load(Cat.class, catId); Cat potentialMate = new Cat(); firstSession.save(potentialMate); // in a higher layer of the application cat.setMate(potentialMate); // later, in a new session secondSession.update(cat); // update cat secondSession.update(mate); // update mate
如果具有catId持久化标识的Cat之前已经被另一Session(secondSession)装载了, 应用程序进行重关联操作(reattach)的时候会抛出一个异常。
如果你确定当前session没有包含与之具有相同持久化标识的持久实例,使用update()。 如果想随时合并你的的改动而不考虑session的状态,使用merge()。 换句话说,在一个新session中通常第一个调用的是update()方法,以便保证重新关联脱管(detached)对象的操作首先被执行。
希望相关联的脱管对象(通过引用“可到达”的脱管对象)的数据也要更新到数据库时(并且也仅仅在这种情况), 应用程序需要对该相关联的脱管对象单独调用update() 当然这些可以自动完成,即通过使用传播性持久化(transitive persistence),请看第 10.11 节 “传播性持久化(transitive persistence)”。
lock()方法也允许程序重新关联某个对象到一个新session上。不过,该脱管(detached)的对象必须是没有修改过的!
//just reassociate: sess.lock(fritz, LockMode.NONE); //do a version check, then reassociate: sess.lock(izi, LockMode.READ); //do a version check, using SELECT ... FOR UPDATE, then reassociate: sess.lock(pk, LockMode.UPGRADE);
请注意,lock()可以搭配多种LockMode, 更多信息请阅读API文档以及关于事务处理(transaction handling)的章节。重新关联不是lock()的唯一用途。
其他用于长时间工作单元的模型会在第 11.3 节 “乐观并发控制(Optimistic concurrency control)”中讨论。
Hibernate的用户曾要求一个既可自动分配新持久化标识(identifier)保存瞬时(transient)对象,又可更新/重新关联脱管(detached)实例的通用方法。 saveOrUpdate()方法实现了这个功能。
// in the first session Cat cat = (Cat) firstSession.load(Cat.class, catID); // in a higher tier of the application Cat mate = new Cat(); cat.setMate(mate); // later, in a new session secondSession.saveOrUpdate(cat); // update existing state (cat has a non-null id) secondSession.saveOrUpdate(mate); // save the new instance (mate has a null id)
saveOrUpdate()用途和语义可能会使新用户感到迷惑。 首先,只要你没有尝试在某个session中使用来自另一session的实例,你应该就不需要使用update(), saveOrUpdate(),或merge()。有些程序从来不用这些方法。
通常下面的场景会使用update()或saveOrUpdate():
-
程序在第一个session中加载对象
-
该对象被传递到表现层
-
对象发生了一些改动
-
该对象被返回到业务逻辑层
-
程序调用第二个session的update()方法持久这些改动
saveOrUpdate()做下面的事:
-
如果对象已经在本session中持久化了,不做任何事
-
如果另一个与本session关联的对象拥有相同的持久化标识(identifier),抛出一个异常
-
如果对象没有持久化标识(identifier)属性,对其调用save()
-
如果对象的持久标识(identifier)表明其是一个新实例化的对象,对其调用save()
-
如果对象是附带版本信息的(通过<version>或<timestamp>) 并且版本属性的值表明其是一个新实例化的对象,save()它。
-
否则update() 这个对象
merge()可非常不同:
-
如果session中存在相同持久化标识(identifier)的实例,用用户给出的对象的状态覆盖旧有的持久实例
-
如果session没有相应的持久实例,则尝试从数据库中加载,或创建新的持久化实例
-
最后返回该持久实例
-
用户给出的这个对象没有被关联到session上,它依旧是脱管的
使用Session.delete()会把对象的状态从数据库中移除。 当然,你的应用程序可能仍然持有一个指向已删除对象的引用。所以,最好这样理解:delete()的用途是把一个持久实例变成瞬时(transient)实例。
sess.delete(cat);
你可以用你喜欢的任何顺序删除对象,不用担心外键约束冲突。当然,如果你搞错了顺序,还是有可能引发在外键字段定义的NOT NULL约束冲突。 例如你删除了父对象,但是忘记删除孩子们。
偶尔会用到不重新生成持久化标识(identifier),将持久实例以及其关联的实例持久到不同的数据库中的操作。
//retrieve a cat from one database Session session1 = factory1.openSession(); Transaction tx1 = session1.beginTransaction(); Cat cat = session1.get(Cat.class, catId); tx1.commit(); session1.close(); //reconcile with a second database Session session2 = factory2.openSession(); Transaction tx2 = session2.beginTransaction(); session2.replicate(cat, ReplicationMode.LATEST_VERSION); tx2.commit(); session2.close();
ReplicationMode决定数据库中已存在相同行时,replicate()如何处理。
-
ReplicationMode.IGNORE - 忽略它
-
ReplicationMode.OVERWRITE - 覆盖相同的行
-
ReplicationMode.EXCEPTION - 抛出异常
-
ReplicationMode.LATEST_VERSION - 如果当前的版本较新,则覆盖,否则忽略
这个功能的用途包括使录入的数据在不同数据库中一致,产品升级时升级系统配置信息,回滚non-ACID事务中的修改等等。 (译注,non-ACID,非ACID;ACID,Atomic,Consistent,Isolated and Durable的缩写)
每间隔一段时间,Session会执行一些必需的SQL语句来把内存中的对象的状态同步到JDBC连接中。这个过程被称为刷出(flush),默认会在下面的时间点执行:
-
在某些查询执行之前
-
在调用org.hibernate.Transaction.commit()的时候
-
在调用Session.flush()的时候
涉及的SQL语句会按照下面的顺序发出执行:
-
所有对实体进行插入的语句,其顺序按照对象执行Session.save()的时间顺序
-
所有对实体进行更新的语句
-
所有进行集合删除的语句
-
所有对集合元素进行删除,更新或者插入的语句
-
所有进行集合插入的语句
-
所有对实体进行删除的语句,其顺序按照对象执行Session.delete()的时间顺序
(有一个例外是,如果对象使用native方式来生成ID(持久化标识)的话,它们一执行save就会被插入。)
除非你明确地发出了flush()指令,关于Session何时会执行这些JDBC调用是完全无法保证的,只能保证它们执行的前后顺序。 当然,Hibernate保证,Query.list(..)绝对不会返回已经失效的数据,也不会返回错误数据。
也可以改变默认的设置,来让刷出(flush)操作发生的不那么频繁。 FlushMode类定义了三种不同的方式。 仅在提交时刷出(仅当Hibernate的Transaction API被使用时有效), 按照刚才说的方式刷出, 以及除非明确使用flush()否则从不刷出。 最后一种模式对于那些需要长时间保持Session为打开或者断线状态的长时间运行的工作单元很有用。 (参见 第 11.3.2 节 “长生命周期session和自动版本化”).
sess = sf.openSession(); Transaction tx = sess.beginTransaction(); sess.setFlushMode(FlushMode.COMMIT); // allow queries to return stale state Cat izi = (Cat) sess.load(Cat.class, id); izi.setName(iznizi); // might return stale data sess.find("from Cat as cat left outer join cat.kittens kitten"); // change to izi is not flushed! ... tx.commit(); // flush occurs
刷出(flush)期间,可能会抛出异常。(例如一个DML操作违反了约束) 异常处理涉及到对Hibernate事务性行为的理解,因此我们将在第 11 章 事务和并发中讨论。
对每一个对象都要执行保存,删除或重关联操作让人感觉有点麻烦,尤其是在处理许多彼此关联的对象的时候。 一个常见的例子是父子关系。考虑下面的例子:
如果一个父子关系中的子对象是值类型(value typed)(例如,地址或字符串的集合)的,他们的生命周期会依赖于父对象,可以享受方便的级联操作(Cascading),不需要额外的动作。 父对象被保存时,这些值类型(value typed)子对象也将被保存;父对象被删除时,子对象也将被删除。 这对将一个子对象从集合中移除是同样有效:Hibernate会检测到,并且因为值类型(value typed)的对象不可能被其他对象引用,所以Hibernate会在数据库中删除这个子对象。
现在考虑同样的场景,不过父子对象都是实体(entities)类型,而非值类型(value typed)(例如,类别与个体,或母猫和小猫)。 实体有自己的生命期,允许共享对其的引用(因此从集合中移除一个实体,不意味着它可以被删除), 并且实体到其他关联实体之间默认没有级联操作的设置。 Hibernate默认不实现所谓的可到达即持久化(persistence by reachability)的策略。
每个Hibernate session的基本操作 - 包括 persist(), merge(), saveOrUpdate(), delete(), lock(), refresh(), evict(), replicate() - 都有对应的级联风格(cascade style)。 这些级联风格(cascade style)风格分别命名为 create, merge, save-update, delete, lock, refresh, evict, replicate。 如果你希望一个操作被顺着关联关系级联传播,你必须在映射文件中指出这一点。例如:
<one-to-one name="person" cascade="create"/>
级联风格(cascade style)是可组合的:
<one-to-one name="person" cascade="create,delete,lock"/>
你可以使用cascade="all"来指定全部操作都顺着关联关系级联(cascaded)。 默认值是cascade="none",即任何操作都不会被级联(cascaded)。
注意有一个特殊的级联风格(cascade style) delete-orphan,只应用于one-to-many关联,表明delete()操作 应该被应用于所有从关联中删除的对象。
建议:
-
通常在<many-to-one>或<many-to-many>关系中应用级联(cascade)没什么意义。 级联(cascade)通常在 <one-to-one>和<one-to-many>关系中比较有用。
-
如果子对象的寿命限定在父亲对象的寿命之内,可通过指定cascade="all,delete-orphan"将其变为自动生命周期管理的对象(lifecycle object)。
-
其他情况,你可根本不需要级联(cascade)。但是如果你认为你会经常在某个事务中同时用到父对象与子对象,并且你希望少打点儿字,可以考虑使用cascade="create,merge,save-update"。
可以使用cascade="all"将一个关联关系(无论是对值对象的关联,或者对一个集合的关联)标记为父/子关系的关联。 这样对父对象进行save/update/delete操作就会导致子对象也进行save/update/delete操作。
此外,一个持久的父对象对子对象的浅引用(mere reference)会导致子对象被同步save/update。 不过,这个隐喻(metaphor)的说法并不完整。除非关联是<one-to-many>关联并且被标记为cascade="delete-orphan", 否则父对象失去对某个子对象的引用不会导致该子对象被自动删除。 父子关系的级联(cascading)操作准确语义如下:
-
如果父对象被persist(),那么所有子对象也会被persist()
-
如果父对象被merge(),那么所有子对象也会被merge()
-
如果父对象被save(),update()或 saveOrUpdate(),那么所有子对象则会被saveOrUpdate()
-
如果某个持久的父对象引用了瞬时(transient)或者脱管(detached)的子对象,那么子对象将会被saveOrUpdate()
-
如果父对象被删除,那么所有子对象也会被delete()
-
除非被标记为cascade="delete-orphan"(删除“孤儿”模式,此时不被任何一个父对象引用的子对象会被删除), 否则子对象失掉父对象对其的引用时,什么事也不会发生。 如果有特殊需要,应用程序可通过显式调用delete()删除子对象。
Hibernate中有一个非常丰富的元级别(meta-level)的模型,含有所有的实体和值类型数据的元数据。 有时这个模型对应用程序本身也会非常有用。 比如说,应用程序可能在实现一种“智能”的深度拷贝算法时, 通过使用Hibernate的元数据来了解哪些对象应该被拷贝(比如,可变的值类型数据), 那些不应该(不可变的值类型数据,也许还有某些被关联的实体)。
Hibernate提供了ClassMetadata接口,CollectionMetadata接口和Type层次体系来访问元数据。 可以通过SessionFactory获取元数据接口的实例。
Cat fritz = ......; ClassMetadata catMeta = sessionfactory.getClassMetadata(Cat.class); Object[] propertyValues = catMeta.getPropertyValues(fritz); String[] propertyNames = catMeta.getPropertyNames(); Type[] propertyTypes = catMeta.getPropertyTypes(); // get a Map of all properties which are not collections or associations Map namedValues = new HashMap(); for ( int i=0; i<propertyNames.length; i++ ) { if ( !propertyTypes[i].isEntityType() && !propertyTypes[i].isCollectionType() ) { namedValues.put( propertyNames[i], propertyValues[i] ); } }