Hibernate入门3-批量操作

Hibernate 快速入门3 - 批量处理和查询

4 批量处理

4.1 批量插入

考虑一个批量插入100万条学生数据到数据库中。

我们当然可以写一个循环,然后session.save(student)。(我看到一本书,给10万的数据就OOM,我自己测试了100万条,还是没崩)

为了从节省内存的角度出发以及避免可能的OOM,我们通常要手动刷新session。

for (int i = 0; i < 1_000_000; i++) {   Student student = new Student();
    // ...
    session.save(student);
    if (i % 20 == 0) {
        session.flush();
        session.clear();
    }
}

4.2 批量更新

想到最直接的是取出数据然后更新。然后我们知道SQL有批量更新的功能UPDATE r SET a WHERE P 。下面分别描述一下。

取出数据更新:

public static void batchUpdate() {
        Configuration conf = new Configuration().configure();
        StandardServiceRegistry registry = conf.getStandardServiceRegistryBuilder().build();
        try (SessionFactory sessionFactory = conf.buildSessionFactory(registry); Session session = sessionFactory.openSession()) {
            Transaction transaction = session.beginTransaction();

            ScrollableResults users = session.createQuery("from Student")
                    .setCacheMode(CacheMode.IGNORE)
                    .scroll(ScrollMode.FORWARD_ONLY);
            int count = 0;
            while (users.next()) {
                Student student = (Student) users.get(0);   // fetch and update. 2 operations
                student.setName("another name " + count);
                if (++count % 20 == 0) {
                    session.flush();
                    session.clear();
                }
            }
            transaction.commit();
        }
    }

批量更新:

    public static void batchUpdateDML() {
        Configuration conf = new Configuration().configure();
        StandardServiceRegistry registry = conf.getStandardServiceRegistryBuilder().build();
        try (SessionFactory sessionFactory = conf.buildSessionFactory(registry); Session session = sessionFactory.openSession()) {
            Transaction tx = session.beginTransaction();
            String hqlUpdate = "update Student stud set name = :newName";
            int nAffected = session.createQuery(hqlUpdate)
                    .setParameter("newName", "new name")
                    .executeUpdate();
            tx.commit();
            System.out.println(nAffected + " rows affected");
        }
    }

注意,上面createQuery中,表的名字必须是Class Name,还不能是@Entitye(name = "tableName")中指定的名字。

5 HQL查询

HQL(Hibernate Query Language)和 SQL类似,但HQL是面向对象的查询语言。SQL的操作对象是数据表、列等数据库对象,而HQL的操作对象是类、实例、属性等。大体和SQL是差不多的,这里就只简单介绍一下。

5.1 HQL查询

步骤:

  1. 获取Hibernate Session对象
  2. 编写HQL语句
  3. 已HQL语句作为参数,调用Session.createQuery()
  4. 如果HQL语句包含参数,调用Query.setXxx()方法为参数赋值
  5. 调用Query.list() 或 Query.uniqueResult()方法返回查询结果列表。像上一节中一样,可以用游标ScrollableResults。

HQL具体语法请参考官网 https://docs.jboss.org/hibernate/orm/3.3/reference/en-US/html/queryhql.html#queryhql-select

直接用FROM 子句。这也是最简单的

public static void findStudent() {
    Configuration configuration = new Configuration().configure();
    StandardServiceRegistry registry = configuration.getStandardServiceRegistryBuilder().build();
    try (SessionFactory factory = configuration.buildSessionFactory(registry); Session session = factory.openSession()) {
        Transaction tx = session.getTransaction();
        tx.begin();
        List list = session.createQuery("from Student AS s WHERE s.age > ?")
                .setParameter(0, 17)
                .list();
        for (Object o : list) {
            Student s = (Student) o;
            System.out.println(s.getId());
        }
        tx.commit();
    }
}

用SELECT 子句。

public static void selectStudent() {
    Configuration conf = new Configuration().configure();
    StandardServiceRegistry registry = conf.getStandardServiceRegistryBuilder().build();
    try (SessionFactory factory = conf.buildSessionFactory(registry); Session session = factory.openSession()) {
        Transaction tx = session.getTransaction();
        tx.begin();
        List list = session.createQuery("SELECT stud FROM Student AS stud WHERE stud.age > ?")
                .setParameter(0, 20)
                .list();
        for (Object o: list) {
            Student s = (Student) o;
            System.out.println(s.getId());
        }
        tx.commit();
    }
}

注意: 查询也会返回子类。这里是PostGraduate。

5.2 关联连接

隐式连接:

FROM Student AS stud WHERE stud.name.someAttr = :someValue

FROM Student AS stud WHERE stud.addresses.zipCode = 12345

假设addresses是个集合属性(另外在一个表上), Hibernate会自动执行JOIN操作。

显示连接:

FROM Student AS stud INNER JOIN p.addresses AS addrList WHERE addrList.zipCode = 12345

JOIN还有LEFT OUTER JOIN, RIGHT OUTER JOIN

5.3 聚集函数

SELECT MAX(stud.age) FROM Student AS stud

5.3 条件查询

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

  • Criteria:代表一次查询
  • Criterion:代表一个查询条件
  • Restrictions:产生查询条件的工具类
public static void selectStudentByCriteria() {
    Configuration conf = new Configuration().configure();
    StandardServiceRegistry registry = conf.getStandardServiceRegistryBuilder().build();
    try (SessionFactory factory = conf.buildSessionFactory(registry); Session session = factory.openSession()) {
        Transaction tx = session.getTransaction();
        tx.begin();
        List list = session.createCriteria(Student.class)
                .add(Restrictions.gt("age", 20))
                .list();
        for (Object o: list) {
            Student student = (Student) o;
            System.out.println(student.getId());
        }
        tx.commit();
    }
}

Session.createCriteria已经被标记为Deprecated,建议采用JPA的Criteria。所以暂时先不仔细说这个。

5.4 SQL查询

Hibernate支持原生SQL查询。

5.4.1 基本查询

// 返回student_info表中所有数据
session.createSQLQuery("SELECT * from student_info").list()

Hibernate会通过ResultSetMetadata来判定所返回数据列的实际顺序和类型。

JDBC中过多使用ResultSetMetadata会降低性能。建议指明数据列的类型,这可以通过addScalar()方法来实现。

要注意SQL查询和HQL查询,中table名字的指定。HQL中指定的是类名,SQL中必须指定数据库中的表名。

List list = session.createSQLQuery("SELECT * FROM student_info")
        .addScalar("age", StandardBasicTypes.INTEGER)
        .addScalar("id", StandardBasicTypes.STRING)
        .list()

上面指明了age的数据类型是INTEGER, id类型是string同时,Hibernate也会返回这两个column的值。省略第二个参数(类型)的意思是返回对应column,不指明column类型。

返回的结果是标量(数值)列表,仅仅是一些值的组合,而不是一个Entity。

意思是,对于结果,我们应该这样取出来:

for (Object o: list) {
    Object[] row = (Object[]) o;
    System.out.println("id: " + row[0] + ", name: " + row[1]);
}

5.4.2 实体查询

为了让返回的结果是Entity,即能转换为对应类的对象。可以调用SQLQuery.addEntity()。

List list = session.createSQLQuery("SELECT * FROM student_info")
        .addEntity(Student.class)
        .addScalar("age", StandardBasicTypes.INTEGER)
        .addScalar("id", StandardBasicTypes.STRING)
        .list()
        

for (Object o: list) {
    Student stud = (Student)o;
    System.out.println("id: " + stud.getId() + ", name: " + stud.getName());
}   

5.4.3 命名SQL查询 (NamedNativeQuery)

命名SQL查询作用于类上。

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@Table(name = "student_info")
@NamedNativeQuery(name = "simpleNamedQuery", query = "SELECT stud.id, stud.name FROM student AS stud", 
        resultClass = Student.class)
public class Student {
    @Id
    @GenericGenerator(name = "student_uuid", strategy = "org.hibernate.id.UUIDGenerator")
    @GeneratedValue(generator = "student_uuid")
    private String id;

    private String name;

    private int age;
}

public class SQLQuery {
    public static void namedSqlQuery() {
        Configuration conf = new Configuration().configure();
        StandardServiceRegistry registry = conf.getStandardServiceRegistryBuilder().build();
        try (SessionFactory factory = conf.buildSessionFactory(registry); Session session = factory.openSession()) {
            Transaction tx = session.getTransaction();

            List list = session.getNamedNativeQuery("simpleNamedQuery")
                    .list();
            for (Object o: list) {
                Student student = (Student) o;
                System.out.println("id: " + student.getId() + ", name: " + student.getName());
            }

            tx.commit();
        }
    }
}

关于@NamedNativeQuery请参考官方文档。

5.4.4 定制SQL

通过定制SQL可以扩展或者完全改变Hibernate底层持久化的所用的SQL语句。

@SQLInsert(sql="INSERT INTO student_info(name, age) VALUES(UPPER(?), ?")
public class Student {
}

插入Student对象时,SQL就会使用我们指定的SQL语句。

同样还有更新,删除对应的: @SQLUpdate, @SQLDelete, @SQLDeleteAll

6 数据郭略

数据过滤使用 @FilterDef, @Filter。我个人觉得在SQL中指明过滤更好。所以,暂时先不讲这个。

推荐阅读更多精彩内容