1.创建项目并加入maven依赖
<dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>5.2.10.Final</version> </dependency>
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-ehcache</artifactId> <version>5.2.10.Final</version> </dependency>
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.37</version> </dependency>
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> </dependency> </dependencies>
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> |
2.主配置文件:
主配置文件用来指定Hibernate全局参数,以及加载实体类的映射文件,习惯命名为hibernate.cfg.xml
全局参数可以参考Hibernate发布包中project/etc目录下的模板文件,发布包下载地址:http://hibernate.org/orm/downloads/
hibernate.cfg.xml(DTD约束文件在org.hibernate包下)
<?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.jdbc.Driver</property> <property name="connection.url">jdbc:mysql:///hibernateDemo</property> <property name="connection.username">root</property> <property name="connection.password">root</property>
<!-- 指定数据库方言 --> <property name="dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property> <!-- 在控制台打印生成的SQL语句 --> <property name="show_sql">true</property> <!-- 对打印的SQL语句进行格式化 --> <property name="format_sql">true</property> <!-- 根据映射文件创建、修改数据库表 --> <property name="hbm2ddl.auto">update</property>
<!-- 加载User类的映射文件 --> <mapping resource="com/rupeng/pojo/User.hbm.xml"/> </session-factory> </hibernate-configuration> |
由于不同数据库支持的SQL语句在细节上有差别,Hibernate在生成SQL语句时就要参照具体的数据库细节,这称为数据库方言。虽然Hibernate能够识别底层所用数据库,但数据库会有多个版本,Hibernate默认选择的方言版本可能不是我们想要的,所以最好自己指定数据库方言
至于数据库连接池,Hibernate和Spring整合时会交给Spring管理,就不再单独配置,这样的话使用的是Hibernate内置的数据库连接池(不可在生产环境中使用)
3.映射文件
Hibernate的ORM映射包含很多方面,其中一些需要在映射文件中进行配置,比如:
类——表,表现在类名和表名相对应
字段——列,表现在字段名和列名相对应
对象——行,表现在OID和主键相对应
在Hibernate中用来唯一标志实体对象的字段称为OID,一般使用id字段作为OID,Hibernate使用OID判断两个实体对象是否对应同一行数据,
实体类 User.java
public class User {
private Long id; private String name; private Date birthday;
/* get/set方法 */ } |
映射文件习惯上命名为:类名.hbm.xml,DTD约束文件在org.hibernate包下
User.hbm.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.rupeng.pojo.User" table="T_Users"> <id name="id" column="id"> <generator class="native"></generator> </id> <property name="name" column="name"></property> <property name="birthday" column="birthday" type="date"></property> </class> </hibernate-mapping> |
<class>用来映射类和表
<id>用来映射OID和主键列
<generator>用来指定主键生成策略
主键生成策略 |
适用类型 |
说明 |
identify |
int、long |
使用自动递增主键生成主键值,比如MySQL |
sequence |
int、long |
使用序列生成主键值,比如oracle |
native |
int、long |
根据数据库自动选择identity或者sequence |
uuid |
String |
由Hibernate生成UUID主键值 |
increment |
int、long |
由Hibernate生成递增主键值 |
assigned |
String、int、long |
由开发人员自己生成主键值 |
说明:为了降低Hibernate学习成本,课程接下来的内容都不考虑assigned策略(实际影响微弱,但如果考虑就会很绕很繁琐)
<property>用来映射字段和列,至于字段类型和列类型,Hibernate一般都能正确判断,但对于Date类型的字段最好使用type属性指定想要的列类型,如date、time、datetime
4.核心API
Hibernate有两个核心类:SessionFactory和Session
SessionFactory
SessionFactory用来创建Session,一个SessionFactory对象对应一个数据库,如果项目中需要访问多个数据库,就需要配置、创建多个SessionFactory
项目中一般把SessionFactory配置成spring的bean,由spring创建、管理,所以学习阶段创建SessionFactory的代码无需记忆,直接copy即可
StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure("hibernate.cfg.xml").build(); SessionFactory sessionFactory = new MetadataSources(registry).buildMetadata().buildSessionFactory(); |
创建SessionFactory时会加载并解析主配置文件和映射文件,而且这个时候很多常用SQL语句已经生成,比如:
Session
Session表示和数据库的一次会话、一次连接,内部封装了java.sql.Connection
Session提供了save()、get()、update()、delete()等方法,以便对实体对象进行持久化操作,session进行持久化操作需要依赖其内部的持久化上下文,也可以简单的认为session就是持久化上下文
示例代码:
public class PersistenceTest {
private SessionFactory sessionFactory;
@Before public void init() { StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure("hibernate.cfg.xml").build(); sessionFactory = new MetadataSources(registry).buildMetadata().buildSessionFactory(); }
@Test public void testSave() {
Session session = sessionFactory.openSession(); session.beginTransaction();
User user = new User(); user.setName("蛋蛋"); user.setBirthday(new Date());
session.save(user); System.out.println(user);
session.getTransaction().commit(); session.close(); } }
|
学习阶段所有的持久化操作都放在事务中(虽然查询可以没有事务)。在项目中,由于事务会交给Spring管理,Spring可以根据配置正确的给持久化操作提供事务支持,所以项目中一般不需要手动编写事务代码
拓展:
(1):Session工作原理
要想很好的理解session的工作原理,需要熟悉下面的两张图:
实体对象状态 是相对于session来说的,如果session不存在或者已经关闭,实体对象状态也就没有意义。而且实体对象状态是相对于单个session来说的,一个实体对象在sessionA中的状态和在sessionB中的状态没有任何关系
瞬时状态 实体对象和当前session没有任何联系(session没有持有实体对象的引用)
托管状态 一级缓存和档案区中都包含该实体对象的引用,并且档案区中该实体对象的状态标记为MANAGED
被删除状态 一级缓存和档案区中都包含该实体对象的引用,并且档案区中该实体对象的状态标记为DELETED
一级缓存 每个session都有自己的一级缓存,存放处于托管状态或者被删除状态的entity的引用,主要是方便持久化操作时Hibernate内部查找entity。一级缓存的生命周期只和session有关,session创建时创建,session关闭时关闭,和事务没有关系。一个session在提交上一个事务后可以再开启新事务,这些事务共享同一个一级缓存
<1>保存操作:
session.save(entity) 执行保存操作,最终就是执行insert语句向表中插入数据
具体执行过程为:
1 立即执行对应的insert语句,并把新生成的主键值查询出来赋值给OID
2 把entity转变为托管状态,也就是把entity放入一级缓存和档案区,并设置状态标记为MANAGED
<2>加载操作:
session的get()执行加载操作
具体执行过程为:
1 先从一级缓存中查找
如果找到了 2 再检查其状态标记是否是DELETED 3 如果是,表示该entity已经被删除,返回null,否则返回该entity |
如果没找到 2 再从数据库中查找(也没找到则返回null) 3 如果找到了,把entity转变为托管状态并返回该entity |
@Test public void testGet() { Session session = sessionFactory.openSession(); session.beginTransaction();
User user = session.get(User.class, 1L); System.out.println(user);
session.getTransaction().commit(); session.close(); } |
<3>修改操作:
修改操作对应着执行update语句根据OID修改表中其他列,但无法修改OID本身,其他字段的值无论是否为null,都会更新到数据库中
修改有两种方式:
1 直接修改托管状态下的entity的字段
Hibernate会在事务提交前检查entity和快照数据是否一致,如果不一致,就会执行update语句。事务提交后,快照数据更新为entity当前数据
把update语句推迟到事务提交前执行的好处是可以合并多次操作,以及过滤无效操作,如多次修改同一个字段等
@Test public void testUpdate() { Session session = sessionFactory.openSession(); session.beginTransaction();
User user = session.get(User.class, 1L); user.setName("abc");
session.getTransaction().commit(); session.close(); } |
2 调用update(entity)修改瞬时状态下的实体对象,要求OID不为null
具体的执行过程为:
1 先把entity转变为托管状态
2 在事务提交前(总会)执行update语句(不会对比快照),事务提交后快照更新为entity当前数据
@Test public void testUpdate2() { Session session = sessionFactory.openSession(); session.beginTransaction();
User user = new User(); user.setId(1L); user.setName("abc");
session.update(user);
session.getTransaction().commit(); session.close(); } |
<4>删除操作:
session.delete(entity)执行删除操作,最终就是执行delete语句根据OID从表中删除数据
具体执行过程为:
1 (如果entity为瞬时状态,则先转变为托管状态)
2 把状态标记设置为被删除状态
3 事务提交前执行delete语句,事务提交后,一级缓存和档案区中有关数据也会被删除,此时entity转变成瞬时状态
对于被删除状态的entity,逻辑上已经不存在了,之前所做的其他操作就变成了无效操作,之后则不能再执行update等持久化操作(但普通操作如访问entity的字段和方法是可以的)
@Test public void testDelete() { Session session = sessionFactory.openSession(); session.beginTransaction();
// User user = session.get(User.class, 1L); User user = new User(); user.setId(1L);
session.delete(user);
session.getTransaction().commit(); session.close(); } |
延迟加载:
Session的get()第一次执行时会立即从数据库中查询数据,而load()第一次执行时则会返回一个只有OID的代理对象,查询动作会推迟到第一次调用代理对象的方法时才真正进行,这称为延迟加载(注意:调用代理对象的hashCode()或eqauls()时并不会触发查询动作)
@Test public void testLoad() { Session session = sessionFactory.openSession(); session.beginTransaction();
User user = session.load(User.class, 1L); System.out.println("--------------------------"); System.out.println(user.toString());
session.getTransaction().commit(); session.close(); } |
特别的,当load()查询不到数据时会抛出ObjectNotFoundException异常
特别的,当session关闭后再触发查询动作时会抛出LazyInitializationException异常
因为load()牵涉到代理对象、延迟加载等,具体执行细节就相对复杂一些,但前面所讲的一级缓存以及档案区仍然是有效的,Hibernate会正确处理好各种持久化操作
延迟加载属于数据库访问优化技术,单看load()好像优化效果并不明显,其实其效果主要体现在关联对象的加载上面