Java
Java Web框架

Hibernate(2)

简介:Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。

1. API详解

本节对Hibernate的API进行详解。首先了解Hibernate的体系结构,示意图如下:

1.hibernate体系结构.png

  • POpersistent object,用于与数据库交互数据。DAO层(JavaBean+hbm )
  • BOBusiness object业务数据对象。Service层
  • VOValue Object值对象。Web层

开发中可能没有分得这么细,直接使用JavaBean描述三个对象。

1.1. Configuration 配置对象

Hibernate核心配置文件种类:

  • hibernate.cfg.xml通常使用xml配置文件,可以配置内容更丰富。
  • hibernate.properties用于配置key/value形式的内容,key不能重复的。配置有很多的局限性。一般不用。

参考文件:hibernate-distribution-3.6.10.Final\project\etc\ hibernate.properties提供了核心配置文件常用的配置项,及选择参数。

  1. 提供构造new Configuration(),hibernate将自动加载hibernate.properties文件hibernate.properties文件,该必须存放在类路径(src)下;
  2. 提供方法·configure()将加载src下的hibernate.cfg.xml
  3. 扩展api。configure(String)加载指定目录下的xml配置文件;
  4. 手动加载配置文件:
  • // 手动加载指定的配置文件
  • config.addResource("com/coderap/hello/User.hbm.xml");
  • // 手动加载指定类,对应的映射文件User.hbm.xml
  • config.addClass(User.class);

Configuration 配置对象测试代码如下:

  • package com.coderap.api;
  • import org.hibernate.cfg.Configuration;
  • import org.junit.Test;
  • //详解Configuration对象
  • public class Configuration_test {
  • @Test
  • //Configuration 用户加载配置文件
  • public void fun1(){
  • // 1.1 调用configure()方法加载src下名为hibernate.cfg.xml
  • Configuration conf = new Configuration().configure();
  • // 1.2 如果配置文件不符合默认加载规则,我们可以调用
  • // new Configuration().configure(file); 通过file加载
  • // new Configuration().configure(path); 通过路径加载
  • // 1.3 可以通过Configuration对象加载映射文件(不推荐)
  • // 推荐在hibernate.cfg.xml中使用mapping属性引入配置文件
  • // 规范: 1>orm映射文件名称与实体的简单类名一致
  • // 2>orm映射文件 需要与实体的类在同一包下
  • conf.addClass(User.class);
  • }
  • }

1.2. SessionFactory工厂

  • SessionFactory相当于Java Web连接池,用于管理所有session
  • 获得方式:config.buildSessionFactory();
  • sessionFactory hibernate缓存配置信息(数据库配置信息、映射文件,预定义HQL语句等);
  • SessionFactory线程安全,可以是成员变量,多个线程同时访问时,不会出现线程并发访问问题。
  • 提供API:
  • //打开一个新的会话 session
  • factory.openSession();
  • //获得当前线程中绑定的会话session
  • factory.getCurrentSession();

hibernate支持将创建的session绑定到本地线程中,底层使用ThreadLocal,在程序之间共享session,方法如下:

  1. 必须在hibernate.cfg.xml配置:
  • <!-- 与本地线程绑定 -->
  • <property name="hibernate.current_session_context_class">thread</property>
  1. 如果提交或回滚事务,底层将自动关闭session

SessionFactory相关的示例代码如下:

  • package com.coderap.api;
  • import org.hibernate.SessionFactory;
  • import org.hibernate.cfg.Configuration;
  • import org.hibernate.classic.Session;
  • import org.junit.Test;
  • // 详解SessionFactory对象
  • public class sessionfactory_test {
  • @Test
  • // SessionFactory 创建session的工厂
  • public void fun1() {
  • // 1 加载配置
  • Configuration conf = new Configuration().configure();
  • // 2 根据Configuration 配置信息创建 SessionFactory
  • SessionFactory sf = conf.buildSessionFactory();
  • // 3 获得session
  • // 3.1 openSession => 获得一个全新的Session对象
  • sf.openSession();
  • // 3.2 getCurrentSession => 获得与当前线程绑定的session对象
  • // 调用getCurrentSession 需要加上一个配置: <property name="hibernate.current_session_context_class">thread</property>
  • sf.getCurrentSession();
  • }
  • }

1.3. Session 会话

  • Session相当于 JDBC的Connection会话;
  • 通过session操作PO对象的增删改查;
  • session单线程,线程不安全,不能编写成成员变量;
  • session相关的API:
  • // 保存
  • session.save()
  • // 更新
  • session.update()
  • // 删除
  • session.delete()
  • // 通过id查询,如果没有 null
  • session.get()
  • // 通过id查询,如果没有抛异常
  • session.load()
  • // 获得Query对象
  • session.createQuery("hql")
  • // 获得Criteria对象
  • session.createCriteria(Class)

下面是这些操作的具体代码:

  • 保存
  • @Test
  • // Session对象用于操作数据库
  • // 增
  • public void add() {
  • // 1 加载配置
  • Configuration conf = new Configuration().configure();
  • // 2 根据Configuration 配置信息创建 SessionFactory
  • SessionFactory sf = conf.buildSessionFactory();
  • // 3 获得session
  • Session session = sf.openSession();
  • User u = new User();
  • u.setName("jerry");
  • u.setPassword("1234");
  • // 调用Session的save方法保存对象到数据库中
  • session.save(u);
  • // 关闭资源
  • session.close();
  • sf.close();
  • }
  • 更新
  • @Test
  • // Session对象用于操作数据库
  • // 更新
  • public void update() {
  • // 1加载配置
  • Configuration conf = new Configuration().configure();
  • // 2 根据Configuration 配置信息创建 SessionFactory
  • SessionFactory sf = conf.buildSessionFactory();
  • // 3 获得session
  • Session session = sf.openSession();
  • // 打开事务
  • Transaction ts = session.beginTransaction();
  • // 先查询出你要修改的对象
  • User user = (User) session.get(User.class, 1);
  • // 在查询结果上,进行修改
  • user.setName("汤姆");
  • session.update(user);
  • // 提交事务
  • ts.commit();
  • // 关闭资源
  • session.close();
  • sf.close();
  • }
  • 删除
  • @Test
  • // Session对象 用于操作数据库
  • // 删
  • public void fun3() {
  • // 1加载配置
  • Configuration conf = new Configuration().configure();
  • // 2 根据Configuration 配置信息创建 SessionFactory
  • SessionFactory sf = conf.buildSessionFactory();
  • // 3 获得session
  • Session session = sf.openSession();
  • // 打开事务
  • Transaction ts = session.beginTransaction();
  • // --------------------------------------------------------
  • // 先查询出你要修改的对象,这里可以查询某行记录并封装为对象,也可以自己创建,只要该对象有id属性值即可
  • User user = (User) session.get(User.class, 2);
  • /*
  • * User user = new User();
  • * user.setId(1);
  • */
  • // 根据ID删除
  • session.delete(user);
  • // ---------------------------------------------------------
  • // 提交事务
  • ts.commit();
  • // 关闭资源
  • session.close();
  • sf.close();
  • }
  • 查询

常用的查询方式有两种:Cet和Load,这两种方式有以下的不同点:

  • Get方式:get方法被调用时会立刻发送sql语句查询;
  • Load方式:load方法调用时并没有查询数据库,当我们需要使用该对象的时候,才查询数据。

下面是相关的测试代码:

  • @Test
  • // Session对象用于操作数据库
  • // 查询get
  • public void get() {
  • // 1 加载配置
  • Configuration conf = new Configuration().configure();
  • // 2 根据Configuration 配置信息创建 SessionFactory
  • SessionFactory sf = conf.buildSessionFactory();
  • // 3 获得session
  • Session session = sf.openSession();
  • // 打开事务
  • Transaction ts = session.beginTransaction();
  • // --------------------------------------------------------
  • // 先查询出你要修改的对象
  • User user = (User) session.get(User.class, 3);
  • // ---------------------------------------------------------
  • // 提交事务
  • ts.commit();
  • // 关闭资源
  • session.close();
  • sf.close();
  • System.out.println(user);
  • }
  • @Test
  • // Session对象用于操作数据库
  • // 查询 load
  • public void load() {
  • // 1加载配置
  • Configuration conf = new Configuration().configure();
  • // 2 根据Configuration 配置信息创建 SessionFactory
  • SessionFactory sf = conf.buildSessionFactory();
  • // 3 获得session
  • Session session = sf.openSession();
  • // 打开事务
  • Transaction ts = session.beginTransaction();
  • // --------------------------------------------------------
  • // 先查询出你要修改的对象
  • User user = (User) session.load(User.class, 3);
  • // ---------------------------------------------------------
  • // 提交事务
  • ts.commit();
  • // 关闭资源
  • session.close();
  • sf.close();
  • System.out.println(user);
  • }

在以上的代码中,都在最后打印了user对象,但是在Load方式查询时,因为它不是立刻进行数据库的查询,所以在Session关闭之后,打印user对象时,会尝试查询数据库,但是由于Session已经关闭,导致查询失败而报错。另外,我们需要注意以下的两个问题:

  1. load()方法返回一个代理对象,获得其内容时会查询数据库,是每次访问属性都会查询数据库吗?

答:不是每次都查,代理对象中有一个标识是否被初始化的boolean型变量,记录是否被初始化过。

  1. 代理都是要基于接口的,用load()方法返回的代理,就没有实现任何接口?

答:Java中的动态代理是基于接口,而Hibernate是使用javassist-3.12.0.GA.jar产生代理对象的,该代理与被代理对象之间的关系是继承关系,与动态代理不是一种,所以不需要接口。

如果我们需要查询所有的User对象,可以使用HQL语句:

  • @Test
  • // Session对象用于操作数据库
  • // 查询所有User
  • public void hql() {
  • // 1加载配置
  • Configuration conf = new Configuration().configure();
  • // 2 根据Configuration 配置信息创建 SessionFactory
  • SessionFactory sf = conf.buildSessionFactory();
  • // 3 获得session
  • Session session = sf.openSession();
  • // 打开事务
  • Transaction ts = session.beginTransaction();
  • // --------------------------------------------------------
  • // HQL语言 => Hibernate Query Language
  • // createQuery传入hql语句查询
  • // select * from t_user;
  • Query query = session.createQuery("from com.coderap.hello.User");
  • // list 将语句执行,并返回结果
  • List<User> list = query.list();
  • System.out.println(list);
  • // ---------------------------------------------------------
  • // 提交事务
  • ts.commit();
  • // 关闭资源
  • session.close();
  • sf.close();
  • }

也可以使用Criteria的方式:

  • @Test
  • // Session对象用于操作数据库
  • // 查询所有User
  • public void Criteria() {
  • // 1 加载配置
  • Configuration conf = new Configuration().configure();
  • // 2 根据Configuration 配置信息创建 SessionFactory
  • SessionFactory sf = conf.buildSessionFactory();
  • // 3 获得session
  • Session session = sf.openSession();
  • // 打开事务
  • Transaction ts = session.beginTransaction();
  • // --------------------------------------------------------
  • // Criteria 查询是Hibernate独创的无语句面向对象的查询
  • Criteria criteria = session.createCriteria(User.class);
  • // select * from t_user;
  • List<User> list = criteria.list();
  • System.out.println(list);
  • // ---------------------------------------------------------
  • // 提交事务
  • ts.commit();
  • // 关闭资源
  • session.close();
  • sf.close();
  • }

另外我们也可以在Hibernate中使用原生的SQL语句进行查询:

  • @Test
  • // Session对象用于操作数据库
  • // 查询所有User
  • public void sql() {
  • // 1 加载配置
  • Configuration conf = new Configuration().configure();
  • // 2 根据Configuration 配置信息创建 SessionFactory
  • SessionFactory sf = conf.buildSessionFactory();
  • // 3 获得session
  • Session session = sf.openSession();
  • // 打开事务
  • Transaction ts = session.beginTransaction();
  • // --------------------------------------------------------
  • // 原生的Sql查询
  • SQLQuery query = session.createSQLQuery("select * from t_user");
  • List<Object[]> list = query.list();
  • for(Object[] objs : list){
  • System.out.println(Arrays.toString(objs));
  • }
  • // ---------------------------------------------------------
  • // 提交事务
  • ts.commit();
  • // 关闭资源
  • session.close();
  • sf.close();
  • }

但是需要注意的是,这种方式查询返回的对象都是装有Object数组的List,在每个Object数组中分别装有查询到的每行数据的每个字段值:

  • [1, Tom, 123456]
  • [2, Jack, 123456]
  • [1, Jerrry, 123456]

这样的查询结果并不方便使用,所以可以使用另一种方式将查询到的结果包装为需要的对象:

  • @Test
  • // Session对象 用于操作数据库
  • // 查询所有User
  • public void sql() {
  • // 1加载配置
  • Configuration conf = new Configuration().configure();
  • // 2 根据Configuration 配置信息创建 SessionFactory
  • SessionFactory sf = conf.buildSessionFactory();
  • // 3 获得session
  • Session session = sf.openSession();
  • // 打开事务
  • Transaction ts = session.beginTransaction();
  • // --------------------------------------------------------
  • // 原生的Sql查询
  • SQLQuery query = session.createSQLQuery("select * from t_user");
  • // addEntity 将查询结果封装到指定对象中
  • query.addEntity(User.class);
  • List<User> list = query.list();
  • System.out.println(list);
  • // ---------------------------------------------------------
  • // 提交事务
  • ts.commit();
  • // 关闭资源
  • session.close();
  • sf.close();
  • }

使用addEntity()方法可以将查询结果封装到指定的对象中。

1.4. Transaction 事务

  • 开启事务beginTransaction()
  • 获得事务getTransaction()
  • 提交事务commit()
  • 回滚事务rollback()
  • try{
  • //开启
  • //session操作
  • //提交
  • } catch(e){
  • //回滚
  • }

注:不需要手动的管理事务,之后所有的事务管理都交予Spring。

  • @Test
  • // Transaction 封装了事务的操作
  • // 开启事务
  • // 提交事务
  • // 回滚事务
  • public void fun1() {
  • // 1 加载配置
  • Configuration conf = new Configuration().configure();
  • // 2 根据Configuration 配置信息创建 SessionFactory
  • SessionFactory sf = conf.buildSessionFactory();
  • // 3 获得session
  • Session session = sf.openSession();
  • // -------------------------------
  • // 打开事务
  • Transaction ts = session.beginTransaction();
  • // 获得已经打开的事务对象(很少用)
  • session.getTransaction();
  • // Transaction 控制如何关闭事务
  • // 提交
  • ts.commit();
  • // 回滚
  • ts.rollback();
  • // -------------------------------
  • session.close();
  • sf.close();
  • }

需要注意的是,当事务关闭时会自动把与当前线程关联的session关闭并删除,再次获得当前线程绑定的session时获得的是新的session

  • // 事务的细节
  • @Test
  • public void fun2() {
  • // 1加载配置
  • Configuration conf = new Configuration().configure();
  • // 2 根据Configuration 配置信息创建 SessionFactory
  • SessionFactory sf = conf.buildSessionFactory();
  • // 3 获得与当前线程绑定的session
  • Session session = sf.getCurrentSession();
  • // -------------------------------
  • // 事务关闭时,会自动把与当前线程关联的session关闭并删除
  • session.beginTransaction().commit();
  • // 再次获得当前线程绑定的session时,获得的是新的session
  • Session session2 = sf.getCurrentSession();
  • System.out.println(session == session2); // false
  • // -------------------------------
  • session.close();
  • sf.close();
  • }

1.5. Query对象

  • Hibernate执行hql语句
  • hql语句:hibernate提供面向对象查询语句,使用对象(类)和属性进行查询。区分大小写。
  • 获得方法:session.createQuery("hql")
  • 相关API:

  • list()查询所有;

  • uniqueResult()获得一个结果。如果没有查询到返回null,如果查询多条抛异常;
  • setFirstResult(int)分页,开始索引数startIndex
  • setMaxResults(int)分页,每页显示个数pageSize
  • @Test
  • // Query对象 封装HQL语句的对象
  • // Query中封装查询细节api
  • public void fun1() {
  • // 1加载配置
  • Configuration conf = new Configuration().configure();
  • // 2 根据Configuration 配置信息创建 SessionFactory
  • SessionFactory sf = conf.buildSessionFactory();
  • // 3 获得session
  • Session session = sf.openSession();
  • // 打开事务
  • Transaction ts = session.beginTransaction();
  • // --------------------------------------------------------
  • Query query = session.createQuery("from com.coderap.hello.User ");
  • // 分页使用limit对应index和count值
  • // 指定结果从第几个开始拿
  • query.setFirstResult(0);
  • // 指定拿几个结果
  • query.setMaxResults(2);
  • // query.list() 将hql语句执行,并返回结果(多行)
  • List<User> list = query.list();
  • System.out.println(list);
  • // uniqueResult 将hql语句执行,并返回结果(一行)
  • // User u = (User) query.uniqueResult();
  • // System.out.println(u);
  • // ---------------------------------------------------------
  • // 提交事务
  • ts.commit();
  • // 关闭资源
  • session.close();
  • sf.close();
  • }

1.6. Criteria对象(了解)

QBC(query by criteria),hibernate提供纯面向对象查询语言,提供直接使用PO对象进行操作。

  • 获得方式:Criteria criteria = session.createCriteria(User.class);
  • 相关的查询条件:
  • criteria.add(Restrictions.eq("username", "tom"));
  • Restrictions.gt(propertyName, value) // 大于
  • Restrictions.ge(propertyName, value) // 大于等于
  • Restrictions.lt(propertyName, value) // 小于
  • Restrictions.le(propertyName, value) // 小于等于
  • Restrictions.like(propertyName, value) // 模糊查询,模糊查询值需要使用%
  • @Test
  • // Cretiaria对象 与 Query对象功能很像
  • // 控制查询
  • public void fun1() {
  • // 1加载配置
  • Configuration conf = new Configuration().configure();
  • // 2 根据Configuration 配置信息创建 SessionFactory
  • SessionFactory sf = conf.buildSessionFactory();
  • // 3 获得session
  • Session session = sf.openSession();
  • // 打开事务
  • Transaction ts = session.beginTransaction();
  • // --------------------------------------------------------
  • // Criteria 查询 => Hibernate独创的面向对象的查询=> 无语句
  • Criteria criteria = session.createCriteria(User.class);
  • // 查找name属性值为tom的 记录
  • criteria.add(Restrictions.eq("name", "tom"));
  • // select * from t_user;
  • // list() 将查询执行,并返回结果(多行)
  • // List<User> list = criteria.list();
  • // System.out.println(list);
  • // 返回一个查询结果
  • User u = (User) criteria.uniqueResult();
  • System.out.println(u);
  • // ---------------------------------------------------------
  • // 提交事务
  • ts.commit();
  • // 关闭资源
  • session.close();
  • sf.close();
  • }
  • @Test
  • // Cretiaria对象 与 Query对象功能很像
  • // 控制查询
  • public void fun2() {
  • // 1 加载配置
  • Configuration conf = new Configuration().configure();
  • // 2 根据Configuration 配置信息创建 SessionFactory
  • SessionFactory sf = conf.buildSessionFactory();
  • // 3 获得session
  • Session session = sf.openSession();
  • // 打开事务
  • Transaction ts = session.beginTransaction();
  • // --------------------------------------------------------
  • // Criteria 查询 => Hibernate独创的面向对象的查询=> 无语句
  • Criteria criteria = session.createCriteria(User.class);
  • // 查找名字中包含字母o的用户
  • criteria.add(Restrictions.like("name", "%o%"));
  • // 返回一个查询结果
  • List<User> list = criteria.list();
  • System.out.println(list);
  • // ---------------------------------------------------------
  • // 提交事务
  • ts.commit();
  • // 关闭资源
  • session.close();
  • sf.close();
  • }
  • @Test
  • // Cretiaria对象 与 Query对象功能很像
  • // > gt
  • // < lt
  • // = eq
  • // >= ge
  • // <= le
  • // like
  • // between
  • public void fun3() {
  • // 1 加载配置
  • Configuration conf = new Configuration().configure();
  • // 2 根据Configuration 配置信息创建 SessionFactory
  • SessionFactory sf = conf.buildSessionFactory();
  • // 3 获得session
  • Session session = sf.openSession();
  • // 打开事务
  • Transaction ts = session.beginTransaction();
  • // --------------------------------------------------------
  • // Criteria 查询 => Hibernate独创的面向对象的查询=> 无语句
  • Criteria criteria = session.createCriteria(User.class);
  • // 查找id大于3的用户
  • criteria.add(Restrictions.gt("id", 3));
  • // 返回一个查询结果
  • List<User> list = criteria.list();
  • System.out.println(list);
  • // ---------------------------------------------------------
  • // 提交事务
  • ts.commit();
  • // 关闭资源
  • session.close();
  • sf.close();
  • }

1.7. 封装Session工具类

在前面的代码中我们会发现,加载配置文件和获取session的代码调用的次数特别多并且是多余的,我们可以考虑将获取session对象的操作封装到一个工具类中:

  • package com.coderap.utils;
  • import org.hibernate.SessionFactory;
  • import org.hibernate.cfg.Configuration;
  • import org.hibernate.classic.Session;
  • // 完成Hibernate工具类
  • // 封装配置文件读取操作
  • // 封装Sessionfactroy创建操作
  • // 封装session获得操作
  • public class HibernateUtils {
  • public static org.hibernate.Session openSession() {
  • // 1 加载配置
  • Configuration conf = new Configuration().configure();
  • // 2 根据Configuration 配置信息创建 SessionFactory
  • SessionFactory sf = conf.buildSessionFactory();
  • // 3 获得session
  • Session session = sf.openSession();
  • return session;
  • }
  • public static org.hibernate.Session getCurrentSession() {
  • // 1 加载配置
  • Configuration conf = new Configuration().configure();
  • // 2 根据Configuration 配置信息创建 SessionFactory
  • SessionFactory sf = conf.buildSessionFactory();
  • // 3 获得session
  • Session session = sf.getCurrentSession();
  • return session;
  • }
  • }

在上面的代码中我们仅仅封装了session的获取操作,但是每次的获取还是会重复的加载配置文件,所以我们可以考虑将加载配置文件及获取SessionFactory的操作放在静态代码块中:

  • package com.coderap.utils;
  • import org.hibernate.SessionFactory;
  • import org.hibernate.cfg.Configuration;
  • import org.hibernate.classic.Session;
  • //完成Hibernate工具类
  • //封装配置文件读取操作
  • //封装Sessionfactroy创建操作
  • //封装session获得操作
  • public class HibernateUtils {
  • private static SessionFactory sf;
  • static {
  • // 1 加载配置
  • Configuration conf = new Configuration().configure();
  • // 2 根据Configuration 配置信息创建 SessionFactory
  • sf = conf.buildSessionFactory();
  • }
  • public static org.hibernate.Session openSession() {
  • // 3 获得session
  • Session session = sf.openSession();
  • return session;
  • }
  • public static org.hibernate.Session getCurrentSession() {
  • // 3 获得session
  • Session session = sf.getCurrentSession();
  • return session;
  • }
  • public static void main(String[] args) {
  • System.out.println(openSession());
  • }
  • }

另外我们还可以加一个功能,在虚拟机关闭的时候,释放session对象,可以使用Runtime.getRuntime().addShutdownHook()方法,最终的封装代码如下:

  • package com.coderap.utils;
  • import org.hibernate.SessionFactory;
  • import org.hibernate.cfg.Configuration;
  • import org.hibernate.classic.Session;
  • //完成Hibernate工具类
  • //封装配置文件读取操作
  • //封装Sessionfactroy创建操作
  • //封装session获得操作
  • public class HibernateUtils {
  • private static SessionFactory sf;
  • static {
  • // 1 加载配置
  • Configuration conf = new Configuration().configure();
  • // 2 根据Configuration 配置信息创建 SessionFactory
  • sf = conf.buildSessionFactory();
  • //
  • Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
  • @Override
  • public void run() {
  • System.out.println("虚拟机关闭!释放资源");
  • sf.close();
  • }
  • }));
  • }
  • public static org.hibernate.Session openSession() {
  • // 3 获得session
  • Session session = sf.openSession();
  • return session;
  • }
  • public static org.hibernate.Session getCurrentSession() {
  • // 3 获得session
  • Session session = sf.getCurrentSession();
  • return session;
  • }
  • public static void main(String[] args) {
  • System.out.println(openSession());
  • }
  • }