13.4. DML(数据操作语言)风格的操作(DML-style operations)

hence manipulating (using the SQL Data Manipulation Language (DML) statements: INSERT, UPDATE, DELETE) data directly in the database will not affect in-memory state. However, Hibernate provides methods for bulk SQL-style DML statement execution which are performed through the Hibernate Query Language (第 14 章 HQL: Hibernate查询语言). 就像已经讨论的那样,自动和透明的 对象/关系 映射(object/relational mapping)关注于管理对象的状态。 这就意味着对象的状态存在于内存,因此直接操作 (使用 SQL Data Manipulation Language(DML,数据操作语言)语句 :INSERT ,UPDATEDELETE) 数据库中的数据将不会影响内存中的对象状态和对象数据。 不过,Hibernate提供通过Hibernate查询语言(第 14 章 HQL: Hibernate查询语言)来执行大批 量SQL风格的DML语句的方法。

UPDATEDELETE语句的语法为: ( UPDATE | DELETE ) FROM? EntityName (WHERE where_conditions)? 有几点说明:

  • 在FROM子句(from-clause)中,FROM关键字是可选的

  • 在FROM子句(from-clause)中只能有一个实体名,它可以是别名。如果实体名是别名,那么任何被引用的属性都必须加上此别名的前缀;如果不是别名,那么任何有前缀的属性引用都是非法的。

  • 不能在大批量HQL语句中使用第 14.4 节 “join 语法的形式”(显式或者隐式的都不行)。不过在WHERE子句中可以使用子查询。可以在where子句中使用子查询,子查询本身可以包含join。

  • 整个WHERE子句是可选的。

举个例子,使用Query.executeUpdate()方法执行一个HQL UPDATE语句(: (方法命名是来源于JDBC's PreparedStatement.executeUpdate()):

Session session = sessionFactory.openSession();
        Transaction tx = session.beginTransaction();

        String hqlUpdate = "update Customer c set c.name = :newName where c.name = :oldName";
        // or String hqlUpdate = "update Customer set name = :newName where name = :oldName";
        int updatedEntities = s.createQuery( hqlUpdate )
                .setString( "newName", newName )
                .setString( "oldName", oldName )
                .executeUpdate();
        tx.commit();
        session.close();

HQL UPDATE语句,默认不会影响更新实体的第 5.1.7 节 “版本(version)(可选)”或者第 5.1.8 节 “timestamp (可选)”属性值。这和EJB3规范是一致的。但是,通过使用versioned update,你可以强制Hibernate正确的重置version或者timestamp属性值。这通过在UPDATE关键字后面增加VERSIONED关键字来实现的。

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlVersionedUpdate = "update versioned Customer set name = :newName where name = :oldName";
int updatedEntities = s.createQuery( hqlUpdate )
        .setString( "newName", newName )
        .setString( "oldName", oldName )
        .executeUpdate();
tx.commit();
session.close();

注意,自定义的版本类型(org.hibernate.usertype.UserVersionType)不允许和update versioned语句联用。

执行一个HQL DELETE,同样使用 Query.executeUpdate() 方法:

Session session = sessionFactory.openSession();
        Transaction tx = session.beginTransaction();

        String hqlDelete = "delete Customer c where c.name = :oldName";
        // or String hqlDelete = "delete Customer where name = :oldName";
        int deletedEntities = s.createQuery( hqlDelete )
                .setString( "oldName", oldName )
                .executeUpdate();
        tx.commit();
        session.close();

Query.executeUpdate()方法返回的整型值表明了受此操作影响的记录数量。 注意这个数值可能与数据库中被(最后一条SQL语句)影响了的“行”数有关,也可能没有。一个大批量HQL操作可能导致多条实际的SQL语句被执行, 举个例子,对joined-subclass映射方式的类进行的此类操作。这个返回值代表了实际被语句影响了的记录数量。在那个joined-subclass的例子中, 对一个子类的删除实际上可能不仅仅会删除子类映射到的表而且会影响“根”表,还有可能影响与之有继承关系的joined-subclass映射方式的子类的表。

INSERT语句的伪码是: INSERT INTO EntityName properties_list select_statement. 要注意的是:

  • 只支持INSERT INTO ... SELECT ...形式,不支持INSERT INTO ... VALUES ...形式.

    properties_list和SQL INSERT语句中的字段定义(column speficiation)类似。对参与继承树映射的实体而言,只有直接定义在给定的类级别的属性才能直接在properties_list中使用。超类的属性不被支持;子类的属性无意义。换句话说,INSERT天生不支持多态。

  • selectstatement可以是任何合法的HQL选择查询,不过要保证返回类型必须和要插入的类型完全匹配。目前,这一检查是在查询编译的时候进行的,而不是把它交给数据库。注意,在HibernateType间如果只是等价(equivalent)而非相等(equal)_,会导致问题。定义为org.hibernate.type.DateTypeorg.hibernate.type.TimestampType的两个属性可能会产生类型不匹配错误,虽然数据库级可能不加区分或者可以处理这种转换。

  • 对id属性来说,insert语句给你两个选择。你可以明确地在properties_list表中指定id属性(这样它的值是从对应的select表达式中获得),或者在properties_list中省略它(此时使用生成指)。后一种选择只有当使用在数据库中生成值的id产生器时才能使用;如果是“内存”中计算的类型生成器,在解析时会抛出一个异常。注意,为了说明这一问题,数据库产生值的生成器是org.hibernate.id.SequenceGenerator(和它的子类),以及任何org.hibernate.id.PostInsertIdentifierGenerator接口的实现。这儿最值得注意的意外是org.hibernate.id.TableHiLoGenerator,它不能在此使用,因为它没有得到其值的途径。

  • 对映射为versiontimestamp的属性来说,insert语句也给你两个选择,你可以在properties_list表中指定(此时其值从对应的select表达式中获得),或者在properties_list中省略它(此时,使用在org.hibernate.type.VersionType 中定义的seed value(种子值))。

执行HQL INSERT语句的例子如下:

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

String hqlInsert = "insert into DelinquentAccount (id, name) select c.id, c.name from Customer c where ...";
int createdEntities = s.createQuery( hqlInsert )
        .executeUpdate();
tx.commit();
session.close();