Hibernate.org Community Documentation

- 上一页
- 下一页

## 第 11 章 与对象共事

Hibernate 是完整的对象/关系映射解决方案，它提供了对象状态管理（state management）的功能，使开发者不再需要理会底层数据库系统的细节。也就是说，相对于常见的 JDBC/SQL 持久层方案中需要`管理 SQL 语句`，Hibernate 采用了更自然的面向对象的视角来持久化 Java 应用中的数据。

换句话说，使用 Hibernate 的开发者应该总是关注对象的状态（state），不必考虑 SQL 语句的执行。这部分细节已经由 Hibernate 掌管妥当，只有开发者在进行系统性能调优的时候才需要进行了解。

## 11.1. Hibernate 对象状态（object states）

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 方法）。

## 11.2. 使对象持久化

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()`。

- `persist()` 使一个临时实例持久化。然而，它不保证立即把标识符值分配给持久性实例，这会发生在冲刷（flush）的时候。`persist()` 也保证它在事务边界外调用时不会执行 `INSERT` 语句。这对于长期运行的带有扩展会话/持久化上下文的会话是很有用的。

- `save()` 保证返回一个标识符。如果需要运行 INSERT 来获取标识符（如 "identity" 而非 "sequence" 生成器），这个 INSERT 将立即执行，不管你是否在事务内部还是外部。这对于长期运行的带有扩展会话/持久化上下文的会话来说会出现问题。

此外，你可以用一个重载版本的 `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）将在本章稍后讨论。

## 11.3. 装载对象

如果你知道某个实例的持久化标识（identifier），你就可以使用 `Session` 的 `load()` 方法来获取它。`load()` 的另一个参数是指定类的对象。本方法会创建指定类的持久化实例，并从数据库加载其数据（state）。

    Cat fritz = (Cat) sess.load(Cat.class, generatedId);

    // you need to wrap primitive identifiers

    long id = 1234;

    DomesticCat pk = (DomesticCat) sess.load( DomesticCat.class, new Long(id) );

此外，你可以把数据（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)

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）”.

## 11.4. 查询

如果不知道所要寻找的对象的持久化标识，那么你需要使用查询。Hibernate 支持强大且易于使用的面向对象查询语言（HQL）。如果希望通过编程的方式创建查询，Hibernate 提供了完善的按条件（Query By Criteria，QBC）以及按样例（Query By Example，QBE）进行查询的功能。你也可以用原生 SQL（native SQL）描述查询，Hibernate 额外提供了将结果集（result set）转化为对象的支持。

### 11.4.1. 执行查询

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();]]

    Query mothersWithKittens = (Cat) session.createQuery(

        "select mother from Cat as mother left join fetch mother.kittens");

    Set uniqueMothers = new HashSet(mothersWithKittens.list());

一个查询通常在调用 `list()` 时被执行，执行结果会完全装载进内存中的一个集合（collection）。查询返回的对象处于持久（persistent）状态。如果你知道的查询只会返回一个对象，可使用 `list()` 的快捷方式 `uniqueResult()`。注意，使用集合预先抓取的查询往往会返回多次根对象（他们的集合类都被初始化了）。你可以通过一个`集合（Set）`来过滤这些重复对象。

#### 11.4.1.1. 迭代式获取结果（Iterating results）

某些情况下，你可以使用 `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;

        }

    }

#### 11.4.1.2. 返回元组（tuples）的查询

（译注：元组（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];

        ....

    }

#### 11.4.1.3. 标量（Scalar）结果

查询可在 `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 = (Object[]) results.next();

        Color type = (Color) row[0];

        Date oldest = (Date) row[1];

        Integer count = (Integer) row[2];

        .....

    }

#### 11.4.1.4. 绑定参数

接口 `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();

#### 11.4.1.5. 分页

如果你需要指定结果集的范围（希望返回的最大行数/或开始的行数），应该使用 `Query` 接口提供的方法：

    Query q = sess.createQuery("from DomesticCat cat");

    q.setFirstResult(20);

    q.setMaxResults(10);

    List cats = q.list();

Hibernate 知道如何将这个有限定条件的查询转换成你的数据库的原生 SQL（native SQL）。

#### 11.4.1.6. 可滚动遍历（Scrollable iteration）

如果你的 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()`。

#### 11.4.1.7. 外置命名查询（Externalizing named queries）

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.

例 11.1. Defining a named query using `@NamedQuery`

    @Entity

    @NamedQuery(name="night.moreRecentThan", query="select n from Night n where n.date >= :date")

    public class Night {

        ...

    }

    public class MyDao {

        doStuff() {

            Query q = s.getNamedQuery("night.moreRecentThan");

            q.setDate( "date", aMonthAgo );

            List results = q.list();

            ...

        }

        ...

    }

Using a mapping document can be configured using the `<query>` node. Remember to use a `CDATA` section if your query contains characters that could be interpreted as markup.

例 11.2. Defining a named query using `<query>`

    <query name="ByNameAndMaximumWeight"><![CDATA[

        from eg.DomesticCat as cat

            where cat.name = ?

            and cat.weight > ?

    ] ]></query>

Parameter binding and executing is done programatically as seen in 例 11.3 “Parameter binding of a named query”.

例 11.3. Parameter binding of a named query

    Query q = sess.getNamedQuery("ByNameAndMaximumWeight");

    q.setString(0, name);

    q.setInt(1, minWeight);

    List cats = q.list();

请注意实际的程序代码与所用的查询语言无关，你也可在元数据中定义原生 SQL（native SQL）查询，或将原有的其他的查询语句放在配置文件中，这样就可以让 Hibernate 统一管理，达到迁移的目的。

也请注意在 `<hibernate-mapping>` 元素中声明的查询必须有一个全局唯一的名字,而在 `<class>` 元素中声明的查询自动具有全局名,是通过类的全名加以限定的。比如 `eg.Cat.ByNameAndMaximumWeight`。

### 11.4.2. 过滤集合

集合过滤器（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();

### 11.4.3. 条件查询（Criteria queries）

HQL 极为强大，但是有些人希望能够动态的使用一种面向对象 API 创建查询，而非在他们的 Java 代码中嵌入字符串。对于那部分人来说，Hibernate 提供了直观的 `Criteria` 查询 API。

    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）.

### 11.4.4. 使用原生 SQL 的查询

你可以使用 `createSQLQuery()` 方法，用 SQL 来描述查询，并由 Hibernate 将结果集转换成对象。请注意，你可以在任何时候调用 `session.connection()` 来获得并使用 JDBC `Connection` 对象。 如果你选择使用 Hibernate 的 API，你必须把 SQL 别名用大括号包围起来：

    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 查询.

## 11.5. 修改持久对象

事务中的持久实例（就是通过 `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）实例。

## 11.6. 修改脱管（Detached）对象

很多程序需要在某个事务中获取对象，然后将对象发送到界面层去操作，最后在一个新的事务保存所做的修改。在高并发访问的环境中使用这种方式，通常使用附带版本信息的数据来保证这些“长“工作单元之间的隔离。

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）对象的操作首先被执行。

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）”.

## 11.7. 自动状态检测

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 上，它依旧是脱管的

## 11.8. 删除持久对象

使用 `Session.delete()` 会把对象的状态从数据库中移除。当然，你的应用程序可能仍然持有一个指向已删除对象的引用。所以，最好这样理解：`delete()` 的用途是把一个持久实例变成瞬时（transient）实例。

    sess.delete(cat);

你可以用你喜欢的任何顺序删除对象，不用担心外键约束冲突。当然，如果你搞错了顺序，还是有可能引发在外键字段定义的 `NOT NULL` 约束冲突。例如你删除了父对象，但是忘记删除其子对象。

## 11.9. 在两个不同数据库间复制对象

偶尔会用到不重新生成持久化标识（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 的缩写）

## 11.10. Session 刷出（flush）

每间隔一段时间，`Session` 会执行一些必需的 SQL 语句来把内存中的对象的状态同步到 JDBC 连接中。这个过程被称为刷出（flush），默认会在下面的时间点执行：

- 在某些查询执行之前

- 在调用 `org.hibernate.Transaction.commit()` 的时候

- 在调用 `Session.flush()` 的时候

涉及的 SQL 语句会按照下面的顺序发出执行：

1. 所有对实体进行插入的语句，其顺序按照对象执行 `Session.save()` 的时间顺序

2. 所有对实体进行更新的语句

3. 所有进行集合删除的语句

4. 所有对集合元素进行删除，更新或者插入的语句

5. 所有进行集合插入的语句

6. 所有对实体进行删除的语句，其顺序按照对象执行 `Session.delete()` 的时间顺序

有一个例外是，如果对象使用 `native` 方式来生成 ID（持久化标识）的话，它们一执行 save 就会被插入。

除非你明确地发出了 `flush()` 指令，关于 Session 何时会执行这些 JDBC 调用是完全无法保证的，只能保证它们执行的前后顺序。当然，Hibernate 保证，`Query.list(..)` 绝对不会返回已经失效的数据，也不会返回错误数据。

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 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

    sess.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 章 事务和并发 .

## 11.11. 传播性持久化（transitive persistence）

对每一个对象都要执行保存，删除或重关联操作让人感觉有点麻烦，尤其是在处理许多彼此关联的对象的时候。一个常见的例子是父子关系。考虑下面的例子：

如果一个父子关系中的子对象是值类型（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="persist"/>

级联风格（cascade style）是可组合的：

    <one-to-one name="person" cascade="persist,delete,lock"/>

你可以使用 `cascade="all"` 来指定全部操作都顺着关联关系级联（cascaded）。默认值是 `cascade="none"`，即任何操作都不会被级联（cascaded）。

In case you are using annotatons you probably have noticed the `cascade` attribute taking an array of `CascadeType` as a value. The cascade concept in JPA is very is similar to the transitive persistence and cascading of operations as described above, but with slightly different semantics and cascading types:

- `CascadeType.PERSIST`: cascades the persist (create) operation to associated entities persist() is called or if the entity is managed

- `CascadeType.MERGE`: cascades the merge operation to associated entities if merge() is called or if the entity is managed

- `CascadeType.REMOVE`: cascades the remove operation to associated entities if delete() is called

- `CascadeType.REFRESH:` cascades the refresh operation to associated entities if refresh() is called

- `CascadeType.DETACH:` cascades the detach operation to associated entities if detach() is called

- `CascadeType.ALL`: all of the above

## 注意

CascadeType.ALL also covers Hibernate specific operations like save-update, lock etc...

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.

例 11.4. `@OneToMany` with `orphanRemoval`

    @Entity

    public class Customer {

       private Set<Order> orders;

       @OneToMany(cascade=CascadeType.ALL, orphanRemoval=true)

       public Set<Order> getOrders() { return orders; }

       public void setOrders(Set<Order> orders) { this.orders = orders; }

       [...]

    }

    @Entity

    public class Order { ... }

    Customer customer = em.find(Customer.class, 1l);

    Order order = em.find(Order.class, 1l);

    customer.getOrders().remove(order); //order will be deleted by cascade

建议：

- 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 的时候才作用到所有可触及的被关联对象上的。

## 11.12. 使用元数据

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] );

        }

    }

版权 © 2004 Red Hat, Inc.

- 上一页第 10 章 继承映射（Inheritance Mapping）
- 上一级
- 起始页
- 下一页第 12 章 Read-only entities