Hibernate 是完整的对象/关系映射解决方案,它提供了对象状态管理(state management)的功能,使开发者不再需要理会底层数据库系统的细节。也就是说,相对于常见的 JDBC/SQL 持久层方案中需要管理 SQL 语句
,Hibernate 采用了更自然的面向对象的视角来持久化 Java 应用中的数据。
换句话说,使用 Hibernate 的开发者应该总是关注对象的状态(state),不必考虑 SQL 语句的执行。这部分细节已经由 Hibernate 掌管妥当,只有开发者在进行系统性能调优的时候才需要进行了解。
DomesticCat fritz = new DomesticCat();fritz.setColor(Color.GINGER);fritz.setSex('M');fritz.setName("Fritz");Long generatedId = (Long) sess.save(fritz);
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) );
Cat fritz = (Cat) sess.load(Cat.class, generatedId);
// you need to wrap primitive identifierslong id = 1234;DomesticCat pk = (DomesticCat) sess.load( DomesticCat.class, new Long(id) );
此外,你可以把数据(state)加载到指定的对象实例上(覆盖掉该实例原来的数据)。
Cat cat = new DomesticCat();// load pk's state into catsess.load( cat, new Long(pkId) );Set kittens = cat.getKittens();
如果你不确定是否有匹配的行存在,应该使用 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 INSERTsess.refresh(cat); //re-read the state (after the trigger executes)
How much does Hibernate load from the database and how many SQL SELECT
s will it use? This depends on the fetching strategy. This is explained in 第 21.1 节 “抓取策略(Fetching strategies)”.
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();]]Query mothersWithKittens = (Cat) session.createQuery( "select mother from Cat as mother left join fetch mother.kittens");Set uniqueMothers = new HashSet(mothersWithKittens.list());
(译注:元组(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 = (Cat) tuple[0]; Cat mother = (Cat) tuple[1]; ....}
//named parameter (preferred)Query q = sess.createQuery("from DomesticCat cat where cat.name = :name");q.setString("name", "Fritz");Iterator cats = q.iterate();
//positional parameterQuery q = sess.createQuery("from DomesticCat cat where cat.name = ?");q.setString(0, "Izi");Iterator cats = q.iterate();
//named parameter listList 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();
如果你的 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()
。
Queries can also be configured as so called named queries using annotations or Hibernate mapping documents. @NamedQuery
and @NamedQueries
can be defined at the class level as seen in 例 11.1 “Defining a named query using @NamedQuery” . However their definitions are global to the session factory/entity manager factory scope. A named query is defined by its name and the actual query string.
Using a mapping document can be configured using the
node. Remember to use a CDATA
section if your query contains characters that could be interpreted as markup.
Parameter binding and executing is done programatically as seen in 例 11.3 “Parameter binding of a named query”.
请注意实际的程序代码与所用的查询语言无关,你也可在元数据中定义原生 SQL(native SQL)查询,或将原有的其他的查询语句放在配置文件中,这样就可以让 Hibernate 统一管理,达到迁移的目的。
也请注意在
元素中声明的查询必须有一个全局唯一的名字,而在
元素中声明的查询自动具有全局名,是通过类的全名加以限定的。比如 eg.Cat.ByNameAndMaximumWeight
。
集合过滤器(filter)是一种用于一个持久化集合或者数组的特殊的查询。查询字符串中可以使用 "this"
来引用集合中的当前元素。
Collection blackKittens = session.createFilter( pk.getKittens(), "where this.color = ?") .setParameter( Color.BLACK, Hibernate.custom(ColorUserType.class) ) .list());
请注意过滤器(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();
Criteria crit = session.createCriteria(Cat.class);crit.add( Restrictions.eq( "color", eg.Color.BLACK ) );crit.setMaxResults(10);List cats = crit.list();
The Criteria
and the associated Example
API are discussed in more detail in 第 17 章 条件查询(Criteria Queries).
List cats = session.createSQLQuery("SELECT {cat.*} FROM CAT {cat} WHERE ROWNUM<10") .addEntity("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") .addEntity("cat", Cat.class).list()
SQL queries can contain named and positional parameters, just like Hibernate queries. More information about native SQL queries in Hibernate can be found in 第 18 章 Native SQL 查询.
很多程序需要在某个事务中获取对象,然后将对象发送到界面层去操作,最后在一个新的事务保存所做的修改。在高并发访问的环境中使用这种方式,通常使用附带版本信息的数据来保证这些“长“工作单元之间的隔离。
Hibernate 通过提供 Session.update()
或 Session.merge()
重新关联脱管实例的办法来支持这种模型。
// in the first sessionCat cat = (Cat) firstSession.load(Cat.class, catId);Cat potentialMate = new Cat();firstSession.save(potentialMate);// in a higher layer of the applicationcat.setMate(potentialMate);// later, in a new sessionsecondSession.update(cat); // update catsecondSession.update(mate); // update mate
如果具有 catId
持久化标识的 Cat
之前已经被另一Session(secondSession)
装载了, 应用程序进行重关联操作(reattach)的时候会抛出一个异常。
The application should individually update()
detached instances that are reachable from the given detached instance only if it wants their state to be updated. This can be automated using transitive persistence. See 第 11.11 节 “传播性持久化(transitive persistence)” for more information.
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()
的唯一用途。
Other models for long units of work are discussed in 第 13.3 节 “乐观并发控制(Optimistic concurrency control)”.
// in the first sessionCat cat = (Cat) firstSession.load(Cat.class, catID);// in a higher tier of the applicationCat mate = new Cat();cat.setMate(mate);// later, in a new sessionsecondSession.saveOrUpdate(cat); // update existing state (cat has a non-null id)secondSession.saveOrUpdate(mate); // save the new instance (mate has a null id)
通常下面的场景会使用 update()
或 saveOrUpdate()
:
sess.delete(cat);
你可以用你喜欢的任何顺序删除对象,不用担心外键约束冲突。当然,如果你搞错了顺序,还是有可能引发在外键字段定义的 NOT NULL
约束冲突。例如你删除了父对象,但是忘记删除其子对象。
偶尔会用到不重新生成持久化标识(identifier),将持久实例以及其关联的实例持久到不同的数据库中的操作。
//retrieve a cat from one databaseSession session1 = factory1.openSession();Transaction tx1 = session1.beginTransaction();Cat cat = session1.get(Cat.class, catId);tx1.commit();session1.close();//reconcile with a second databaseSession session2 = factory2.openSession();Transaction tx2 = session2.beginTransaction();session2.replicate(cat, ReplicationMode.LATEST_VERSION);tx2.commit();session2.close();
ReplicationMode
决定在和数据库中已存在记录由冲突时,replicate()
如何处理。
每间隔一段时间,Session
会执行一些必需的 SQL 语句来把内存中的对象的状态同步到 JDBC 连接中。这个过程被称为刷出(flush),默认会在下面的时间点执行:
有一个例外是,如果对象使用 native
方式来生成 ID(持久化标识)的话,它们一执行 save 就会被插入。
It is possible to change the default behavior so that flush occurs less frequently. The FlushMode
class defines three different modes: only flush at commit time when the Hibernate Transaction
API is used, flush automatically using the explained routine, or never flush unless flush()
is called explicitly. The last mode is useful for long running units of work, where a Session
is kept open and disconnected for a long time (see 第 13.3.2 节 “扩展周期的 session 和自动版本化”).
sess = sf.openSession();Transaction tx = sess.beginTransaction();sess.setFlushMode(FlushMode.COMMIT); // allow queries to return stale stateCat izi = (Cat) sess.load(Cat.class, id);izi.setName(iznizi);// might return stale datasess.find("from Cat as cat left outer join cat.kittens kitten");// change to izi is not flushed!...tx.commit(); // flush occurssess.close();
During flush, an exception might occur (e.g. if a DML operation violates a constraint). Since handling exceptions involves some understanding of Hibernate's transactional behavior, we discuss it in 第 13 章 事务和并发 .
对每一个对象都要执行保存,删除或重关联操作让人感觉有点麻烦,尤其是在处理许多彼此关联的对象的时候。一个常见的例子是父子关系。考虑下面的例子:
你可以使用 cascade="all"
来指定全部操作都顺着关联关系级联(cascaded)。默认值是 cascade="none"
,即任何操作都不会被级联(cascaded)。
A special cascade style, delete-orphan
, applies only to one-to-many associations, and indicates that the delete()
operation should be applied to any child object that is removed from the association. Using annotations there is no CascadeType.DELETE-ORPHAN
equivalent. Instead you can use the attribute orphanRemoval as seen in
例 11.4 “@OneToMany with orphanRemoval”. If an entity is removed from a @OneToMany
collection or an associated entity is dereferenced from a @OneToOne
association, this associated entity can be marked for deletion if orphanRemoval
is set to true.
建议:
It does not usually make sense to enable cascade on a many-to-one or many-to-many association. In fact the @ManyToOne
and @ManyToMany
don't even offer a orphanRemoval
attribute. Cascading is often useful for one-to-one and one-to-many associations.
If the child object's lifespan is bounded by the lifespan of the parent object, make it a life cycle object by specifying cascade="all,delete-orphan"(
.@OneToMany(cascade=CascadeType.ALL, orphanRemoval=true)
)
其他情况,你可根本不需要级联(cascade)。但是如果你认为你会经常在某个事务中同时用到父对象与子对象,并且你希望少打点儿字,可以考虑使用 cascade="persist,merge,save-update"
。
可以使用 cascade="all"
将一个关联关系(无论是对值对象的关联,或者对一个集合的关联)标记为父/子关系的关联。 这样对父对象进行 save/update/delete 操作就会导致子对象也进行 save/update/delete 操作。
Furthermore, a mere reference to a child from a persistent parent will result in save/update of the child. This metaphor is incomplete, however. A child which becomes unreferenced by its parent is not automatically deleted, except in the case of a one-to-many association mapped with cascade="delete-orphan"
. The precise semantics of cascading operations for a parent/child relationship are as follows:
如果父对象被 persist()
,那么所有子对象也会被 persist()
如果父对象被 merge()
,那么所有子对象也会被 merge()
如果父对象被 save()
,update()
或 saveOrUpdate()
,那么所有子对象则会被 saveOrUpdate()
如果某个持久的父对象引用了瞬时(transient)或者脱管(detached)的子对象,那么子对象将会被 saveOrUpdate()
如果父对象被删除,那么所有子对象也会被 delete()
除非被标记为 cascade="delete-orphan"
(删除“孤儿”模式,此时不被任何一个父对象引用的子对象会被删除),否则子对象失掉父对象对其的引用时,什么事也不会发生。如果有特殊需要,应用程序可通过显式调用 delete() 删除子对象。
最后,注意操作的级联可能是在调用期(call time)或者写入期(flush time)作用到对象图上的。所有的操作,如果允许,都在操作被执行的时候级联到可触及的关联实体上。然而,save-upate
和 delete-orphan
是在Session
flush 的时候才作用到所有可触及的被关联对象上的。
联系客服