学到这里, 我发现自己又要开启两个大的分支, 一个是PostgreSQL的学习和使用, 一个是Hibernate的学习和使用. 这两块又是两个重要的支线. 果然是学无止境啊.
这篇东西重在Spring中使用Hibernate, 而不是单独研究Hibernate的ORM机制. 说道Hibernate又不能不提JPA, 总之想想要学的东西实在是刺激.
最近买了两本幼儿编程书, 给女儿讲的时候发现对于循环, 判断和分支小家伙比以前能够更了解了, 看来我也必须抓紧学习, 后边要装备的知识除了PostgreSQL, Hibernate, 还有Linux使用和Nginx配置, 感觉这些都搞定, 就可以顺利的教女儿了.
好了, 研究过这一部分之后, 接下来是缓存和异步任务, 都完成之后, 总算就具备了开发Web应用的技术准备, 才能够重新学习Spring MVC.
Spring整合Hibernate的方式
通过前边的学习, 已经知道了Spring对第三方ORM框架整合的原理不外乎这几板斧:
- 为ORM创建基础设施, 也就是XXXTemplate.
- 为基础设施统一异常封装
- 为不同的基础设施提供对应的事务管理器, 统一进行事务管理
相比之前的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的详细增删改查以及缓存的秘密还没有系统的学习.
Serializable save(Object entity)
, 保存实体对象并返回主键值void update(Object entity)
, 更新对象void saveOrUpdate(Object entity)
, 保存或更新一个实体, 还有一个类似的 T merge(T entity), 是JPA规定的方法.void delete(Object entity)
, 删除一个实体.<T> List<T> findByExample(T exampleEntity)
, 传入要查找的一个对象, 会查找这个对象所在表的所有对象, 有很多详细控制的方法.List<?> findByCriteria(DetachedCriteria var1)
, 按条件查询, 由于现在没有学Hibernate, 所以不知道如何使用.
读到这里我也会想, 如何判断一个实体是不是已经存在数据库中, 如何做到save Or Update呢, 剩下的这些内容估计要好好的去学习Hibernate5的具体使用了.
回调接口
Spring提供了一个回调接口HibernateCallback, 其中的唯一一个方法doInHibernate(Session session), 其中可以操作Session对象来进行更底层的操作.
HibernateTemplate中有三个方法可以传入回调对象:
public <T> T execute(HibernateCallback<T> action)
public <T> T executeWithNativeSession(HibernateCallback<T> action)
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到底有什么样的魅力.