Hibernate笔记 3:核心API
关于
注意:以下 API 多数为接口,在开发时使用的一般是其实现类。 如: 1、SessionFactory 接口 —— SessionFactoryImpl 类 2、Session 接口 —— SessionImpl 类 见:Hibernate源码分析:入门代码流程
Configuration 类
Configuration 仅仅是作为一个初始化时的对象,一个 Configeration 实例代表 Hibernate 所有 Java 类到 Sql 数据库映射的集合。 Configuration 接口的作用是:对 Hibernate 进行配置、并启动 Hibernate 和连接数据库系统。
作用:
- 在 Hibernate 的启动过程中,Configuration 类的实例首先定位缺省 XML 配置文件(hibernate.cfg.xml),并读取关的配置项目,然后创建出一个 SessionFactory 对象。
- 读取配置文件:
- 根据默认的 XML 配置文件:(位置:src/hibernate.cfg.xml)
Configuration cfg = new Configuration().configure();
- 根据自定义的 XML 配置文件:(少用)
Configuration cfg = new Configuration().configure("cn/config/hibernate2.cfg.xml");
- 加载“映射配置文件”:(如果使用“hibernate.properties”作为核心配置文件,则无法在其中配置“映射配置文件”,需要使用 Configuration 对象加载)
Configuration cfg = new Configuration().configure(); cfg.addResource("com/eijux/domain/Customer.hbm.xml");
- 根据默认的 XML 配置文件:(位置:src/hibernate.cfg.xml)
- 创建 SessionFactory 对象:
- 在 Hibernate 4 之前:
Configuration cfg = new Configuration(); cfg.configure(); SessionFactory sessionFactory = cfg.buildSessionFactory();
- 在 Hibernate 4 之后:【???】
Configuration cfg = new Configuration().configure(); ServiceRegistry sr = new ServiceRegistryBuilder().applySettings(cfg.getProperties()).buildServiceRegistry(); SessionFactory sessionFactory = cfg.buildSessionFactory(sr);
- 在 Hibernate 4 之前:
SessionFactory 接口
SessionFactory 接口负责 Hibernate 的初始化和建立 Session 对象。 特点: 1、线程安全,同一个实例能够供多个线程共享。 2、重量级,不能随意的创建和销毁它的实例。 所以,通常情况下每一个应用只需要一个 SessionFactory,当需要操作多个数据库时,可以为每个数据库指定一个 SessionFactory。 ——【整个应用中共享,一个 SessionFactory 实例”对应“一个数据存储源”】
因此,在实际项目使用中,通常会抽取一个 HibernateUtils 的工具类,用于提供 Session 对象:
public class HibernateUtils { private static final Configuration configuration; private static final SessionFactory sessionFactory; Static{ configuration = new Configuration().configure(); sessionFactory = configuration.buildSessionFactory(); } /** * 提供获得 session 的方法: */ public static Session openSession() { return sessionFactory.openSession(); } /** * 获取当前线程绑定的会话: */ public static Session getCurrentSession(){ return sessi.onFactory.getCurrentSession(); } }
连接池
SessionFactory 内部还维护了一个连接池,如果需要使用第三方连接池(如:C3PO),则需要手动进行配置。
详见:Hibernate笔记 2:配置文件详解#配置:连接池
Session 接口
Session 用于管理一个数据库的任务单元(增、删、改、查),它是 Java 应用和 Hibernate 之间主要运行接口,是抽象持久性服务概念的主要 API。 特点: 1、非线程安全,因此最好是一个线程只创建一个Session对象(将它设计为局部对象)。 2、轻量级,创建和销毁 Session 对象不会有太大开销。 ——【不共享:“一请求”>>“一线程”>>“一session”>>“一事务”】
类似于 JDBC 中的连接(“connection”)。
生成方式
可以通过两种方法产生 Session。
生成方式:
- openSession():获取一个新的 Session;
Session session = sessionFactory.openSession();
- 每次打开都是新的 Session;
- 需要调用 close 方法以关闭 Session。
- getCurrentSession():从当前上下文中获取 Session,并绑定到当前线程;
Session session = sessionFactory.getCurrentSession();
- 第一次调用会创建新的 Session 实例,(如未手动管理,则)同一线程中多次获取的是同一个 Session;
- 无需手动关闭,线程结束时会自动关闭 Session。
- 必须配置“Session 管理方式”为:(否则会报错“org.hibernate.HinerbateException: No CurrentSessionContext configured!”)
<property name="current_session_context_class">thread</property>
- 【详见:Hibernate笔记 4:核心知识#事务管理】
常用方法
Session 的所有方法,见 JBoss 的 Hibernate 文档:https://docs.jboss.org/hibernate/core/3.5/api/org/hibernate/Session.html
常见方法:
- save():向数据库中保存(持久化)实体。
- 示例:
session.save(Object);
- delete():从数据库中删除实体。
- 对象删除后,其状态为“Transistent”;
- 对象需要有 ID;
- 示例:
session.delete(Object);
- load():从数据库中加载实体。
Session.load(Class arg0, Serializable arg1) throws HibernateException
- 参数:
- “arg0”:需要加载对象的类,例如:User.class;
- “arg1”:查询条件(实现了序列化接口的对象),例如:"4028818a245fdd0301245fdd06380001" 字符串已经实现了序列化接口;
- 如果是数值类类型,则 hibernate 会自动使用包装类。
- 返回:
- 此方法返回类型为 Object,但返回的是“代理对象”(并非实际可用的对象,因为“延迟加载”)。【第一次使用对象时,session 不能关闭】
- 如果数据库中没有相应的记录,则会抛出异常“找不到对象”(org.hibernate.ObjectNotFoundException)。【记录不存在时抛出异常】
- 执行:因为 load 方法实现了“延迟加载”(懒加载,“lazy load”),所以,只有在使用返回的对象时,它才发出查询 SQL 语句,加载对象。
- 其延迟加载实现原理是代理方式。【???】
- 示例:
try { session = sf.openSession(); session.beginTransaction(); User user = (User)session.load(User.class,1); // 在使用对象时,才发出查询SQL语句,加载对象 System.out.println("user.name=" + user.getName()); // 此时 user 为 persistent 状态 user.setName("eijux"); session.getTransaction().commit(); } catch (HibernateException e) { e.printStackTrace(); session.getTransaction().rollback(); } finally{ if (session != null){ if (session.isOpen()) session.close(); }
- get():从数据库中加载实体。
Session.get(Class arg0, Serializable arg1)
- 参数:
- “arg0”:需要加载对象的类,例如:User.class;
- “arg1”:查询条件(实现了序列化接口的对象),例如:"4028818a245fdd0301245fdd06380001" 字符串已经实现了序列化接口;
- 如果是数值类类型,则 hibernate 会自动使用包装类。
- 返回:
- 此方法返回类型为 Object,然后我们再强行转换为需要加载的对象就可以了。【返回值需要强制类型转换???】
- 如果数据库中没有相应的记录,则返回 null。【不会抛出异常】
- 执行:该方法会立即发出查询 SQL 语句,加载对象。
- 示例:
try { session = sf.openSession(); session.beginTransaction(); // 方法返回类型为Object,需要再强行转换为需要加载的对象 // 方法会立即发出查询语句 User user = (User)session.get(User.class, 1); // 数据加载完后的状态为 persistent 状态。数据将与数据库同步 System.out.println("user.name=" + user.getName()); // 此时 user 为 persistent 状态 user.setName("eijux"); session.getTransaction().commit(); } catch (HibernateException e) { e.printStackTrace(); session.getTransaction().rollback(); } finally{ if (session != null){ if (session.isOpen()){ session.close(); } }
- update():向数据库中更新实体。
- 用于更新“detached”对象,更新完成后转为为“persistent”状态;【默认更新全部字段】
- 更新“transient”对象(没有 ID)会报错;
- 更新自己设定 ID 的“transient”对象则可以。
- “persistent”状态的对象,只要设定字段不同的值,在 session 提交时,会自动更新(默认更新全部字段)。
- 如果需要更新部分字段,有两种方法:
- update / updatable 属性:用于设置参与更新的字段。【适合 xml、注解 方式】【较少用,不灵活】
- xml 方式:(映射关系配置文件中)设置“<property>”标签的“update”属性,以设置在更新时是否参与更新。
- false:不参与更新;true(默认):参与更新。
- 示例:
<property name="name" update="false"/>
- annotation 方式:(映射关系类中)设定“@Column”的“updatable”属性值,以设置在更新时是否参与更新。
- 示例:
@Column(updatable=false) public String getTitle() {return title;}
- xml 方式:(映射关系配置文件中)设置“<property>”标签的“update”属性,以设置在更新时是否参与更新。
- dynamic-update 属性:用于设置仅更新变更的字段。【只适合 xml 方式,JAP1.0 annotation 没有对应属性】
- 在实体类的映射文件(映射关系配置文件)中的“<class>”标签中,设置“dynamic-update”属性:true,表示修改了哪个字段就更新哪个字段,其它字段不更新。
- 但要求是同一个 session(不能跨 session),如果跨了 session 同样会更新所有的字段内容。
- 示例:
... <class name="com.bjsxt.Student" dynamic-update="true"> ...
@Test public void testUpdate() { Session session = sessionFactory.getCurrentSession(); session.beginTransaction(); Student s = (Student)session.get(Student.class, 1); s.setName("zhangsan"); // 提交时,会只更新 name 字段,因为此时的 s 为 persistent 状态 session.getTransaction().commit(); s.setName("zhangsi"); Session session2 = sessionFactory.getCurrentSession(); session2.beginTransaction(); // 更新时,会更新所有的字段,因为此时的 s 不是 persistent 状态 session2.update(s); session2.getTransaction().commit(); }
- 如果需要跨 session 实现更新修改的部分字段,需要使用 session.merget() 方法,合并字段内容:
- 示例:
@Test public void testUpdate() { Session session = sessionFactory.getCurrentSession(); session.beginTransaction(); Student s = (Student)session.get(Student.class, 1); s.setName("zhangsan"); // 提交时,会只更新 name 字段,因为此时的 s 为 persistent 状态 session.getTransaction().commit(); s.setName("zhangsi"); Session session2 = sessionFactory.getCurrentSession(); session2.beginTransaction(); // 使用s ession.merget() 方法,合并字段内容 session2.merge(s); // 提交时,会只更新 name 字段 session2.getTransaction().commit() }
- 如上,虽然可以实现部分字段更新,但这样会多出一条 select 语句,因为在字段数据合并时,需要比较字段内容是否已变化,就需要从数据库中取出这条记录进行比较。
- 所以,跨 Session 更新部分字段,建议使用 HQL(EJBQL)面向对象的查询语言:
- 示例:
@Test public void testUpdate7() { Session session = sessionFactory.getCurrentSession(); session.beginTransaction(); Query q = session.createQuery("update Student s set s.name='z5' where s.id = 1"); q.executeUpdate(); session.getTransaction().commit(); }
- update / updatable 属性:用于设置参与更新的字段。【适合 xml、注解 方式】【较少用,不灵活】
- saveOrUpdate():向数据库中保存/更新实体。
- 在执行的时候 hibernate 会检查,如果对象在数据库中已经有对应的记录(存在同样主键的记录),则会更新(update),否则会添加数据(save)。
- clear():清除 session 缓存。
- 无论是 load 还是 get,都会首先查找缓存(一级缓存,也叫“session 缓存”),如果没有,才会去数据库查找,调用 clear() 方法可以强制清除 session 缓存。
- 示例:
// 使用 getCurrentSession,所以不需要手动关闭 Session Session session = sessionFactory.getCurrentSession(); session.beginTransaction(); Teacher t = (Teacher)session.load(Teacher.class, 1); System.out.println(t.getName()); session.clear(); Teacher t2 = (Teacher)session.load(Teacher.class, 1); System.out.println(t2.getName()); session.getTransaction().commit();
- 如上,两个 load 方法均会发送查询到数据库。如果注释掉 clear,则第二次 load 不发送查询到数据库,而是直接使用第一次 load 的缓存(两次 load 的是同一个记录)。
- flush():Session 缓存刷出(到数据库)。
- 【即,“缓存清理”,强制同步数据(从内存到数据库) ——见:Hibernate笔记 4:核心知识:Session缓存#缓存清理机制(缓存刷出)】
- 在默认的情况下 session.commit() 之前时,其实执行了一个 flush 命令。 ——【一般不需要手动调用该方法】
- 可以通过
session.setFlushMode(FlushMode)
来设置“刷新时机”:
- evict():从 session 缓存中逐出该对象。
- 与 commit 同时使用,会抛出异常。【???】
- 示例:
session = HibernateUtils.getSession(); tx = session.beginTransaction(); User user = new User(); user.setName("李四"); user.setPassword("123"); user.setCreateTime(new Date()); user.setExpireTime(new Date()); /* 利用 Hibernate 将实体类对象保存到数据库中。 因为 user 主键生成策略采用的是 uuid,所以调用完成 save 后,只是将 user 纳入 session 的管理,不会发出 insert 语句,但是 id 已经生成,session 中的 existsInDatabase 状态为 false */ session.save(user); // 从 session 缓存中逐出该对象 session.evict(user); /* 无法成功提交。 因为 hibernate 在清理缓存时,在 session 的临时集合(insertions)中取出 user 对象进行 insert 操作后,需要更新 entityEntries 属性中的 existsInDatabase 为 true,而我们采用 evict 已经将 user 从 session 中逐出了,所以找不到相关数据,无法更新,抛出异常。 */ tx.commit();
- 解决在逐出 session 缓存中的对象不抛出异常的方法:在 session.evict() 之前进行显示的调用 session.flush() 方法就可以了。【???】
- 示例:
session.save(user); /* flush 后 hibernate 会清理缓存,会将 user 对象保存到数据库中,将 session 中的 insertions 中的 user 对象清除,并且会设置 session 中的 existsInDatabase 状态为 false */ session.flush(); // 从 session 缓存中逐出该对象 session.evict(user); /* 可以成功提交。 因为 hibernate 在清理缓存时,在 Session 的 insertions 中集合中无法找到 user 对象所以不会发出 insert 语句,也不会更新 session 中 existsInDatabase 的状态。 */ tx.commit();
- 与 commit 同时使用,会抛出异常。【???】
- persist():做一个瞬态的实例持久化。
- 在事务里执行 session.persist(),不会向数据库插数据,事务 commit 了才会插入数据。
load() 与 get()
load() 与 get(): 1、不存在对应记录时: load 抛出异常。 get 返回 null。 2、返回对象: load 返回的是代理对象,等到真正使用对象的内容时才发出 sql 语句,这样就要求在第一次使用对象时,要求 session 处于 open 状态,否则出错。 get 返回的对象即为 Object。(需要进行强制类型转换,来得到需要的对象类型) 3、加载时机: load 进行延迟加载。 get 进行立即加载。 此外,get() 和 load() 只根据主键查询,不能根据其它字段查询,如果想根据非主键查询,可以使用 HQL。
Transaction 接口
Hibernate 进行持久化操作时(CRUD)必须进行事务控制。
使用:
- 开启事务:
Transaction tx = session.beginTransaction();
- 提交事务:
tx.commit();
- 回滚事务:
tx.rollback();
示例:
Transaction tx = null; try{ tx = session.beginTransaction(); session.save(obj); tx.commit(); }catch(HibernateException e){ if(tx != null){ tx.rollback(); } throw e; }finally{ if(session != null){ session.close(); } }
其他 API
以下包括了 Hibernate 的几种种对象检索方式。
- Query:HQL 查询;(类似于 SQL)
- Criteria:QBC 查询;(无任何语句)
- SQLQuery:SQL 查询;
Query
Query 代表面向对象的一个【Hibernate 查询】操作,所使用的语句为 HQL(Hibernate Query Language)。 其语法很像 SQL 语法,但它是完全“面向对象”的。
使用
在 Hibernate 中,通常使用 session.createQuery() 方法接受一个 HQL 语句, 然后调用 Query 的 list() 或 uniqueResult() 方法执行查询。
使用 Query 对象的步骤:
- 获得 Hibernate 的 Session 对象;
- 通过 session.createQuery("HQL 语句") 创建查询对象,并编写 HQL 语句;
- 如果 HQL 语句包含参数,则调用 Query 的 setXxx() 设置参数;
- 调用 Query 对象的 list() 或 uniqueResult() 方法执行查询。
示例,Query 执行 HQL 语句的常用查询:
public void demo_Query() { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); Query query = null; List<Customer> list = null; // 1. 查询所有记录 query = session.createQuery("from Customer"); list = query.list(); System.out.println(list); // 2. 条件查询: query = session.createQuery("from Customer where name = ?"); query.setString(0, "张三"); list = query.list(); System.out.println(list); // 3. 条件查询: query = session.createQuery("from Customer where name = :aaa and age = : bbb"); query.setString ("aaa", "张三"); query.setinteger("bbb", 38); list = query.list(); System.out.println(list); // 4. 分页查询: query = session.createQuery("from Customer"); query.setFirstResult(3); query.setMaxResults(3); list = query.list(); System.out.println(list); tx.commit(); session.close(); }
常用方法
Query 中除了使用 list() 方法查询全部数据外,还有一些常用方法。
常用方法:
- setterXxx():Query 接口中提供了一系列的 setter 方法用于设置查询语句中的参数。
- 针对不同的数据类型,需要用到不同的 setter 方法。
- iterator():用于查询语句,返回的结果是一个 Iterator 对象(只能按照顺序读取)。
- 它仅把使用到的数据转换为 Java 实体对象;【???】
- uniqueResult():用于返回唯一的结果。
- 确保查询结果仅有一条记录才可使用;
- executeUpdate():用于支持 HQL 语句的更新和删除操作。
- Hibernate3 的新特性;
- setFirstResult():用于设置获取第一个记录的位置(从第几条记录开始查询)。
- 默认从 0 开始计算;
- setMaxResult():用于设置结果集的最大记录数。
setFirstResult() 通常与 setMaxResult() 方法结合使用,用于限制结果集的范围,以实现分页功能。
Criteria
Criteria,又称“QBC 查询”(Query By Critena),是 Hibernate 框架的核心查询对象。 它是一个完全“面向对象,可扩展”的条件查询 API,通过它完全“不需要考虑数据库底层如何实现,以及 SQL 语句如何编写”。
使用
用到的“接口/类”:【注意区别:“Criteria”与“Criterion”】 1、org.hibernate.Criteria :Hibernate 提供的一个面向对象“查询接口”。 2、org.hibernate.criterion.Criterion :Hibernate 提供的一个面向对象“查询条件接口”。 ——“一个单独的查询就是 Criterion 接口的一个实例”。 3、org.hibernate.criterion.Restrictions 类:提供了一系列用于设定查询条件的静态方法,用于创建 Criterion 对象。
使用 Criteria 对象的步骤:
- 获得 Hibernate 的 Session 对象。
- 通过 Session 获得 Criteria 对象。
- 需要
- 使用 Restrictions 的静态方法创建 Criterion 条件对象。
- Restrictions 类中提供了一系列用于设定查询条件的静态方法,这些静态方法都返回 Criterion 实例,每个 Criterion 实例代表个查询条件。
- 向 Criteria 对象中添加 Criterion 查询条件。
- Criteria 的 add() 方法用于加入查询条件。
- 执行 Criteria 的 list() 或 uniqueResult() 获得结果。
示例,Criteria 的常用查询:
public void demo_Criteria() { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); Criteria criteria = null; List<Customer> list = null; // 1. 查询所有记录 criteria = session.createCriteria(Customer.class); list = criteria.list(); System.out.println(list); // 2. 条件查询: criteria = session.createCriteria(Customer.class); criteria.add(Restrictions.eq("name", "张三")); list = query.list(); System.out.println(list); // 3. 条件查询: criteria = session.createCriteria(Customer.class); criteria.add(Restrictions.eq("name", "张三")); criteria.add(Restrictions.eq("age", 38)); list = query.list(); System.out.println(list); // 4. 分页查询: criteria = session.createCriteria(Customer.class); criteria.setFirstResult(3); criteria.setMaxResults(3); list = query.list(); System.out.println(list); tx.commit(); session.close(); }
常用方法
Criteria 中除了使用 list() 方法查询全部数据外,还有一些常用方法。
常用方法:
- uniqueResult():用于返回唯一的结果。
- 确保查询结果仅有一条记录才可使用;
- setFirstResult():用于设置获取第一个记录的位置(从第几条记录开始查询)。
- 默认从 0 开始计算;
- setMaxResult():用于设置结果集的最大记录数。
setFirstResult() 通常与 setMaxResult() 方法结合使用,用于限制结果集的范围,以实现分页功能。
SQLQuery
SQLQuery 接口:用于接收一个【sql 语句】进行查询,然后调用 list() 或者 uniqueResult() 方法进行查询。
但是 sql 语句不会直接封装到实体对象中,需要手动写代码才可以封装为实体中。
示例:
- 未封装的查询:(查询结果为基本对象集合)
SQLQuery sqlQuery = session.createSQLQuery("select * from cst_customer"); List<Object[]> list = sqlQuery.list(); for (Object [] objects : list) { System.out.println(Arrays.toString(objects)); }
- 封装的查询:(查询结果为实体对象集合)
SQLQuery sqlQuery = session.createSQLQuery("select * from cst_customer"); // 封装到对象中 sqlQuery.addEntity(Customer.class); List<Customer> list = sqlQuery.list(); for(Customer customer:list) { System.out.println(customer); }