学到这里, 我发现自己又要开启两个大的分支, 一个是PostgreSQL的学习和使用, 一个是Hibernate的学习和使用. 这两块又是两个重要的支线. 果然是学无止境啊.

这篇东西重在Spring中使用Hibernate, 而不是单独研究Hibernate的ORM机制. 说道Hibernate又不能不提JPA, 总之想想要学的东西实在是刺激.

最近买了两本幼儿编程书, 给女儿讲的时候发现对于循环, 判断和分支小家伙比以前能够更了解了, 看来我也必须抓紧学习, 后边要装备的知识除了PostgreSQL, Hibernate, 还有Linux使用和Nginx配置, 感觉这些都搞定, 就可以顺利的教女儿了.

好了, 研究过这一部分之后, 接下来是缓存和异步任务, 都完成之后, 总算就具备了开发Web应用的技术准备, 才能够重新学习Spring MVC.

  1. Spring整合Hibernate的方式
  2. 使用HibernateTemplate
  3. 回调接口
  4. 其他知识

Spring整合Hibernate的方式

通过前边的学习, 已经知道了Spring对第三方ORM框架整合的原理不外乎这几板斧:

  1. 为ORM创建基础设施, 也就是XXXTemplate.
  2. 为基础设施统一异常封装
  3. 为不同的基础设施提供对应的事务管理器, 统一进行事务管理

相比之前的JDBC这种需要直接使用DataSource对象的更底层的数据库操作技术, Hibernate是一个完整的ORM框架.

Hibernate的基础设施不再是DataSource, SessionFactory和Session对象, 从名字能看出来, 前者生产出后者. 二者的关系和从DataSource中获取Connection很类似.

先来看Hibernate如何创建SessionFactory.

首先需要创建Hibernate的配置文件, 一般叫做hibernate.cfg.xml, 如果是maven项目, 要将其放入main/resources目录下:

<?xml version='1.0' encoding='UTF-8'?>
    <!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

    <session-factory>
        <property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost:3306/sia5</property>
        <property name="connection.username">root</property>
        <property name="connection.password">fflym0709</property>
        <property name="dialect">org.hibernate.dialect.MySQL8Dialect</property>
        <property name="show_sql">true</property>
        <mapping class="cc.conyli.jdbc.Course"/>
    </session-factory>

</hibernate-configuration>

可见其中封装了需要连接数据库的信息. 和DataSource需要的信息很类似. 注意红色部分, 指定了一个ORM类, 这里没有使用xxx.hbm.xml的方式, 所以来使用注解改造一下Course类:

import javax.persistence.*;

@Entity
@Table(name = "course")
public class Course {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @Column(name = "course_name")
    private String courseName;

    public Course() {

    }

    public Course(int id, String courseName) {
        this.id = id;
        this.courseName = courseName;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getCourseName() {
        return courseName;
    }

    public void setCourseName(String courseName) {
        this.courseName = courseName;
    }

    @Override
    public String toString() {
        return "Course{" +
                "id=" + id +
                ", courseName='" + courseName + '\'' +
                '}';
    }
}

这几个注解就不再多解释了, 毕竟之前也学过一些Hibernate的使用, 意思还是知道的.

然后启动Hibernate即可:

import cc.conyli.jdbc.Course;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

import java.time.LocalDateTime;

public class Test1 {

    public static void main(String[] args) {
        Configuration configuration = new Configuration().configure();
        SessionFactory sessionFactory = configuration.buildSessionFactory();

        //获取一个会话
        Session session = sessionFactory.openSession();
        //启动事务
        Transaction transaction = session.beginTransaction();

        //创建一个新对象
        Course newCourse = new Course();
        newCourse.setCourseName(LocalDateTime.now().toString());

        //更新或者保存
        session.saveOrUpdate(newCourse);

        //提交事务
        transaction.commit();

        //关闭会话
        session.close();
    }
}

SessionFactory就是Hibernate的最基础的设施. 之后的获取会话像极了JDBC获取连接. 当然, 为什么叫ORM框架, 就是可以直接存取Course对象.

好了, 如何使用Hibernate本身要另外去研究, 这里要看Spring如何创建基础设施.

其实看到这里也就明白了, 既然Hibernate可以使用XML文件去创建一个SessionFactory, 那么Spring用IOC容器, 也可以根据配置文件创建一个基于SessionFactory的Bean, 在容器里使用这个Bean, 就相当于整合了Hibernate.

Spring确实这么做了, 干这个活的就是org.springframework.orm.hibernate5.LocalSessionFactoryBean.

这个Bean可以使用Hibernate的配置文件, 也可以完全不使用. 最后会创建一个Hibernate的SessionFactory的代理对象, 能够符合Spring事务管理机制. 所以本质就是这么简单.

在Spring的XML配置中添加一个Bean:

<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"
      p:configLocation="hibernate.cfg.xml"/>

然后启动:

public static void main(String[] args) {

    SessionFactory sessionFactory = (SessionFactory) Util.getIOCContainer().getBean("sessionFactory");
    System.out.println(sessionFactory);

    Session session = sessionFactory.openSession();

    Course newCourse = new Course();
    newCourse.setCourseName("New" + LocalDateTime.now().toString());

    session.saveOrUpdate(newCourse);
    session.close();
}

可以看到神奇的先出现了Hibernate5的启动日志, 后边的使用, 就和使用一个Hibernate原生的SessionFactory没有任何区别.

当然, Bean里写了要去读取Hibernate的配置文件, 那也没有什么意思了, LocalSessionFactoryBean最强的地方就是完全不需要额外读取Hibernate的配置文件, 只要在Spring的配置文件中写上相应的信息就可以了. 配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"

       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">

    <util:properties id="properties" location="config.properties"/>
    <context:property-placeholder properties-ref="properties"/>
    <context:component-scan base-package="cc.conyli"/>

    <!--DataSource还是需要的-->
    <bean class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close" p:defaultAutoCommit="true" id="source"
          p:driverClassName="com.mysql.cj.jdbc.Driver"
          p:url="jdbc:mysql://localhost:3306/sia5"
          p:username="root"
          p:password="fflym0709"
    />

    <!--实体类映射文件-->
    <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"
          p:dataSource-ref="source">
        <property name="annotatedClasses">
            <list>
                <value>cc.conyli.jdbc.Course</value>
            </list>
        </property>

        <!--Hibernate一些配置属性-->
        <property name="hibernateProperties">
            <props>
                <prop key="dialect">org.hibernate.dialect.MySQL8Dialect</prop>
                <prop key="show_sql">true</prop>
            </props>
        </property>
    </bean>
</beans>

对于已经熟悉了XML的我们, 其实就和用代码去配置差不多, LocalSessionFactoryBean的属性也确实有这些内容. 现在完全不需要hibernate.cfg.xml文件了. 直接再启动程序, 就会发现成功执行了.

这就是Spring整合Hibernate的方式. 在容器中, 实际使用的就是这个LocalSessionFactoryBean代理类, 这个代理类符合Spring事务管理要求, 也能够从其中取得线程绑定的Session.

当然这只是最简单的XML文件配置, LocalSessionFactoryBean也可以配置成载入其他hbm.xml文件, 或者加载通配符标注的多个类, 只要参数是数组类型, 都可以使用通配符. 还记得之前说过的, 如果要使用Hibernate, 必须要配置Hibernate的事务管理器.

要使用HibernateTemplate, 就需要配置HibernateTemplate和事务管理器, 这样就不用通过具体代码来管理事务了.

所以把上边的XML文件补全:

<!--配置HibernateTemplate, 要使用到SessionFactory对象-->
<bean id="hibernateTemplate" class="org.springframework.orm.hibernate5.HibernateTemplate"
      p:sessionFactory-ref="sessionFactory"/>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager"
      p:sessionFactory-ref="sessionFactory"/>
<tx:annotation-driven transaction-manager="transactionManager"/>

经过这么配置之后, 事务管理器和HibernateTemplate都OK了, 就可以通过容器直接获取HibernateTemplate对象来进行操作了. 通过配置也可以看出, SessionFactory封装DataSource, HibernateTemplate 和事务管理器又依赖SessionFactory这样一个体系.

其和JDBC的配置数据源, 事务管理器依赖数据源, 也是非常类似的.

使用HibernateTemplate

这里目前只能简单写过, 就是因为Hibernate的详细增删改查以及缓存的秘密还没有系统的学习.

  1. Serializable save(Object entity), 保存实体对象并返回主键值
  2. void update(Object entity), 更新对象
  3. void saveOrUpdate(Object entity), 保存或更新一个实体, 还有一个类似的 T merge(T entity), 是JPA规定的方法.
  4. void delete(Object entity), 删除一个实体.
  5. <T> List<T> findByExample(T exampleEntity), 传入要查找的一个对象, 会查找这个对象所在表的所有对象, 有很多详细控制的方法.
  6. List<?> findByCriteria(DetachedCriteria var1), 按条件查询, 由于现在没有学Hibernate, 所以不知道如何使用.

读到这里我也会想, 如何判断一个实体是不是已经存在数据库中, 如何做到save Or Update呢, 剩下的这些内容估计要好好的去学习Hibernate5的具体使用了.

回调接口

Spring提供了一个回调接口HibernateCallback, 其中的唯一一个方法doInHibernate(Session session), 其中可以操作Session对象来进行更底层的操作.

HibernateTemplate中有三个方法可以传入回调对象:

  1. public <T> T execute(HibernateCallback<T> action)
  2. public <T> T executeWithNativeSession(HibernateCallback<T> action)
  3. protected <T> T doExecute(HibernateCallback<T> action, boolean enforceNativeSession)

第三个方法很显然不是为了外部使用, 所以只有前两个方法可以使用这个回调接口. 使用这个接口的目的就是可以更底层的操作数据, 看一个例子:

public static void main(String[] args) {
    HibernateTemplate template = (HibernateTemplate) Util.getIOCContainer().getBean("hibernateTemplate");

    Course result = template.execute(new HibernateCallback<Course>() {
        @Override
        public Course doInHibernate(Session session) throws HibernateException {
            Transaction transaction = session.beginTransaction();
            String hql = "from Course course where course.id = 4";
            Course course = session.createQuery(hql, Course.class).getSingleResult();
            transaction.commit();
            session.close();
            return course;
        }
    });
    System.out.println(result);
}

这实际上就是详细的去控制了如何查询一个对象.

其他知识

其他一些细节比如Hibernate的事件监听器, 以及Hibernate 3.0开始的新特性, 可以使用SessionFactory.getCurrentSession()来获取线程绑定的Session, 让直接使用原生的Hibernate API变得可能.

Hibernate支持JPA的注解, 所以看到的注解都是Hibernate实现的javax.persistance其中的类.

事务只要配置了事务管理器, 加载了@Transactional注解就可以了, 简单易用. 这里看了一点关于Spring boot的自动配置, 发现只要配置了start-jdbc, 就会自动创建jdbc的事务管理器, 如果配置了JPA, 则会自动创建Hibernate的事务管理器.

至于延迟加载的技术, 就等学习了Hibernate 5再说吧.

HibernateTemplate的内容比较少, 是因为目前Hibernate这条大支线任务还没有开始做.

看来必须要马上提上日程了, 看一看这个JPA标准的推动和实现者, 外加完整ORM支持的Hibernate到底有什么样的魅力.