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查询
步骤:
- 获取Hibernate Session对象
- 编写HQL语句
- 已HQL语句作为参数,调用Session.createQuery()
- 如果HQL语句包含参数,调用Query.setXxx()方法为参数赋值
- 调用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中指明过滤更好。所以,暂时先不讲这个。