一、Hibernate简介
- hibernate是一个开源的ORM框架,Object-Relational Mapping,在关系型数据库和对象之间做了一个映射,Hibernate是对JDBC的进一步封装,将JDBC封装起来,从而在和数据库进行操作的时候不需要直接对数据库进行读写,只需要对Hibernate接口进行调用就可以完成数据库方面的操作。
- Hibernate位于JPA接口和Native接口和JDBC之间,从而实现对JDBC的封装和对关系型数据库的对象映射。
- Hibernate核心
- Configuration接口负责配置并启动Hibernate
- SessionFactory接口负责初始化Hibernate
- Session接口负责持久化对象的CRUD操作
- Transaction接口负责事务性
- Query接口负责执行数据库的查询
Hibernate的优缺点
优点
- 对象化,只需要操作对象就可以完成数据库的操作
- 一致性,代码可复用程度高
- POJO对象,简单的Java对象
- 测试方便 JUnit
- 效率高
缺点
- 使用数据库特性的语句,调优比较困难
- 对大批量数据库更新存在问题
- 系统中存在大量的攻击查询功能
优缺点参考了http://blog.csdn.net/jiuqiyuliang/article/details/39078749这篇博客,自己还不是很理解。
二、Hibernate环境搭建
开发环境
Win10 + Intellij2017 + Mysql-Front + jdk1.8 + hibernate5.2,
其中hibernate5.2改动比较大,我学习hibernate的目的在于了解这个经典的持久层框架,现在主流公司的框架为ssm,所以主要参考马士兵老师的hibernate视频学习,版本为hibernate3.2,有一些区别,不过一遍学习一边修正。
Intelij创建项目流程:
- file->new Project,勾选hibernate,会自动下载好需要的hibernate的包,但是JDBC和Junit单元测试的包需要手动导入,添加方式如下:
选中自己的lib文件夹就可以。
包结构如下: - 创建一个测试文件夹,标记为测试文件夹,用于测试。
- 根据提供的或者自己拷贝的hibernate.cfg.xml文件,完成基本的配置,了解基本的参数的意思即可。
- 写实体层代码
- 建立一个Student类123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869package com.hibernate;import javax.persistence.Basic;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.Id;@Entitypublic class Student {private int id;private String name;private int age;public void setAge(Integer age) {this.age = age;}@Id@Column(name = "id", nullable = false)public int getId() {return id;}public void setId(int id) {this.id = id;}@Basic@Column(name = "name", nullable = true, length = 20)public String getName() {return name;}public void setName(String name) {this.name = name;}@Basic@Column(name = "age", nullable = true)public Integer getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Student student = (Student) o;if (id != student.id) return false;if (age != student.age) return false;if (name != null ? !name.equals(student.name) : student.name != null) return false;return true;}@Overridepublic int hashCode() {int result = id;result = 31 * result + (name != null ? name.hashCode() : 0);result = 31 * result + age;return result;}}
- 建立一个Student类
这里采用注解的方式完成实体类和数据库表之间的映射。在hibernate.cfg.xml文件中加入对应的mapping语句。<mapping class="com.hibernate.Student"/>
- 测试映射是否正常工作,为了调试方便,推荐将hibernate默认的日志slf4j换成log4j,用JUnit进行单元测试。测试代码如下:12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970public class StudentTest extends TestCase{private SessionFactory sessionFactory;@Overrideprotected void setUp() throws Exception {final StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build();try {sessionFactory = new MetadataSources(registry).buildMetadata().buildSessionFactory();} catch (Exception e) {e.printStackTrace();StandardServiceRegistryBuilder.destroy(registry);}ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().configure().build();Metadata metadata = new MetadataSources(serviceRegistry).buildMetadata();SchemaExport schemaExport = new SchemaExport();schemaExport.create(EnumSet.of(TargetType.DATABASE),metadata);}@Overrideprotected void tearDown() throws Exception {if (sessionFactory != null) {sessionFactory.close();}}@Testpublic void testInsert() {Student student = new Student();student.setName("aa");student.setId(1);student.setAge(12);Session session = sessionFactory.getCurrentSession();session.beginTransaction();session.save(student);session.getTransaction().commit();}@Testpublic void testUpdate() {testInsert();Session session = sessionFactory.getCurrentSession();session.beginTransaction();Student student = (Student)session.get(Student.class, new Integer(1));student.setName("penny");session.getTransaction().commit();}@Testpublic void testGetById() {testInsert();Session session = sessionFactory.getCurrentSession();session.beginTransaction();Student student = session.get(Student.class, new Integer(1));session.getTransaction().commit();System.out.println("id" + student.getId() + " name = " + student.getName() + " Age = " + student.getAge());}@Testpublic void testDelete() {testInsert();Session session = sessionFactory.getCurrentSession();session.beginTransaction();Student student = session.get(Student.class, new Integer(1));session.delete(student);session.getTransaction().commit();}}
需要注意的是在hibe.cfg.xml文件中,我们配置了<property name="hbm2ddl.auto">update</property>
,从而每次运行的时候会再数据库中删除原有的表,再执行后续操作,所有后面测试查找和删除功能时,需要首先插入数据才可以进行正常操作,不然会报空指针错误。
- NOTE: Junit的测试类开头需要用testXXX, 不然会检测不到测试文件。
- 测试结果:
- 插入:
- 更新
- ID查找
- 删除
三、Hibernate核心接口
- Configuration : 配置并启动Hibernate,创建SessionFactory对象
- SessionFactory :初始化Hibernate,创建Session对象
- Session : 持久化对象的CRUD操作
- Transaction : 管理事务
- Query,Criteria : 执行数据库的查询
1、Configuration
一个Configuration实例允许应用指定在创建一个SessionFactory时使用的属性和映射文件,一个应用一般创建一个SessionFactory,Configuration是一个厨师时的对象,一个Configuration实例代表Hibernate所有Java类到SQL映射的集合。
Configuration配置SessionFactory的方法为:
2、sessionFactory
- SessionFactory的作用是用于产生和管理Session的,创建SessionFactory成本比较大,一般一个程序只创建一个。SessionFactory一旦被创建,与Configuration对象就不再关联,内部状态是不可变的。
- SessionFactory可以通过两种方式创建Session:
- openSession: 每次打开都是新的Session,需要调用close方法关闭
- getCurrentSession: 从上下文中获取Session并绑定到当前线程,第一次调用会自动创建一个Session实例,没有手动关闭的时候获取的是同一个Session,事务提交或者回滚时会自动关闭Session,不需要调用close方法。
- 使用getCurrentSession的时候注意区分本地事务和全局事务:
- 本地事务:JDBC事务,也就是一个数据库的事务
<property name="current_session_context_class">thread</property>
- 全局事务 JTA事务,分布式数据库的事务,跨越多个数据库
<property name="current_session_context_class">jta</property>
- 本地事务:JDBC事务,也就是一个数据库的事务
3、Session
Session用于管理一个数据库的CRUD操作,是Java应用和Hibernate之间主要运行接口。
Session的生命周期是以一个逻辑事务的开始和结束为边界,Session的主要功能是提供创建,读取和删除映射的实体类操作。
- 实体对象的三种状态:
- Transient:瞬时的,没有内置ID,只是内存中的一个对象,在缓存和数据库里面都找不到这个对象
- Persistent:持久化的,缓存中有ID,数据库中有,内存中有,缓存中有
- Detached:游离的,数据库中有ID,数据库和内存里面都有,缓存中没有
Session中存在一个缓存,成为Hibernate一级缓存,存放了当前单元加载的对象,缓存中存在一个Map,在执行save方法后,会对改对象生成一个id,并存储在Map中,提交之后,数据库就同步了这条数据,此时实例就处于持久化状态,关闭Session之后处于游离状态。
Session接口的方法:
- save():将Transient状态的实体转换为Persistent状态,并给其分配一个ID标识符。
- Delete():从数据库中删除持久化的实体
- load()/get():将给定标识符的实体有Detached状态转变为Persistent状态。
load返回的是代理对象,真正需要对象的内容时才会发出SQL语句
get直接从数据库中加载对象,不会产生延迟
当对象不存在时,get会立即报错,load只有需要使用该对象时才报错。 - update():
- 更新指定标识符的Deta状态为Persistent状态
- 更新transient对象会报错
- 更新自己设定ID的transient对象可以(数据库中需要有相应的ID)
- persistent状态的对象只要设定不同对象就会发生更新
- 更新部分更改的字段
- xml设定property标签的update属性,annotation设定@Column的updateable属性,很少用,不灵活
- 使用xml中的dynamic-update,annotation在实体类上设定@DynamicUpdate
- 使用HQL(EJBQL)语句–建议
- clear:无论是load还是get,都会优先查找缓存,没找到才会去数据库中查找,调用clear可以强制清楚session缓存。
4、Transaction
Hibernate中的Transaction是对JDBC或者JTA的Transaction的封装,一个典型的事务会在创建完Session之后启动session.beginTransaction()连启动事务,commit提交事务:12345Session session = sessionFactory.getCurrentSession();session.beginTransaction();Teacher t = (Teacher)session.get(Teacher.class, 1);t.setName("zhangsan2");session.getTransaction().commit();
5、Query
通过SessionFactory获取session对象,可以通过get方法来获取相应的对象,也可以通过获取Query对象来获得需要的对象:
四、Hibernate关系映射
Hibernate之间的关系是指对象之间的关系,不是数据库之间的关系。ORM的思想是将关系型数据库中表单的数据映射成对象,以对象的形式展现,这样开发就可以把对数据库的操作转变为对对象的操作。
Hibernate实现ORM功能的主要文件有:
- 映射类(xx.java):描述数据库表的结构,表中的字段在类中被描述成属性,可以实现把表中的记录映射成该类的对象。
- 映射文件(xx.hbm.xml):指定数据库表和映射类之间的关系,包括映射类和数据库表的对应关系,表字段和类属性类型的对应关系以及表子弹和类属性名称的对应关系等
- 数据库配置文件(hibernate.cfg.xml):指定与数据库连接时需要的连接信息,比如数据库类型,用户名,密码等。
1. 单向一对一关联
两个实体对象之间是一对一的关联映射,也就是一个对象只能与另外一个唯一的对象相对应。比如实体Student和StudentCard是典型的一对一的关系。一个学生有唯一的一个学号。一个学号对应唯一的一个学生。完成关系模型映射的方式有两种:
主键关联:让两个对象具有相同的主键值,不需要其余的外键字段来维护关系。
- Annotation : @OneToOne
@PrimaryKeyJoinColumn
- XML:
- Annotation : @OneToOne
外键关联:两个实体对象用一个外键来关联,本质上是多对一关联映射的特例,多的一端加上唯一的限制之后就成为了一对一的关联映射。
1.1 XML实现方式
StudentCard.java:
Student.java:
Student.hbm.xml:
StudentCard.hbm.xml:
hibernate.cfg.xml:
建表结果:
1.2 Annotation方式
采用Husband和Wife两个实体对象,其中在Husband里面有对Wife的单向关联。
Wife:
Husband:
hibernate.cfg.xml加上映射类关系:
2. 双向一对一关联
上面说的一对一单向映射只能从一方加载另一方,双向关联映射可以互相加载。与单向关联映射相比,并不影响其再数据库中的存储方式,只影响其加载方式。
主键
修改StudentCard.hbm.xml文件:
12345678910111213<hibernate-mapping><class name="com.bjsxt.hibernate.StuIdCard"><id name="id"><generator class="foreign"><param name="property">student</param></generator></id><property name="num"/><one-to-one name="student" constrained="true"></one-to-one></class></hibernate-mapping>Annotation 将添加注解
@PrimaryKeyJoinColumn
即可
- 外键
- Annotation方式在Wife中创建一个Husband的引用, 在get方法上面添加注解
@OneToOne(mappedBy = "wife")
即可 - XML方式在Student.hbm.xml中添加:
<one-to-one name="studentCard" property-ref="student"/>
即可
- Annotation方式在Wife中创建一个Husband的引用, 在get方法上面添加注解
3. 一对多关联映射
一对多和多对一的关联映射原理一致,都是在多的一端加一个外键,指向一的一端。
- 与一对多的区别在于维护的关系不同:多对一维护的关系是多指向一的关系,有了此关系,在加载多的时候可以将一加载上来,一对多维护的是多的关系,有了此关系,在加载一的时候可以将多加载上来。
选取的是Group和User类型,显然一个Group拥有多个User,一个User属于一个Group,是典型的一对多的关系。
Group:
User:
在hibernate.cfg.xml中注册之后即可进行测试:
这里只写了注解实现的,因为考虑到注解是后面的主流,工作后也是注解用的比较多,XML的只写下思路。在Group配置文件下:
完成其一对多的单向映射。
4、一对多,多对一双向关联映射
完成一对多多对一的双向关联映射的关键在于在多的哪段添加对一的那端的引用。也就是在User实体里面创建一个Group类型的私有变量,从而持有Group的一个引用。与一对多的区别在于:
- Annotation方式下,User实体类对Group的getter方法上添加注解:@ManyToOne(),说明这是多对一的关联。
- XML方式下,在User的配置文件下添加:
<many-to-one name="group" column="group_Id"/>
即可。
5、单向多对多关联映射
多对多映射比较常见,比如一个老师教多个学生,一个学生有多个老师。关联是通过创建一个中间表来实现的。中间表可以很好的解决数据冗余的问题。
Student.java:
Teacher.java:
生成结果:
6、双向多对多关联映射
与单向的相比,在Student类中添加了Teacher的引用,从而实现多对多的导航,其余的和5一样。
7、关联数据的CRUD操作
- 设定cascade可以设定在持久化时对关联对象的操作
- cascade属性指明做什么操作的时候关联对象是绑定在一起的
- 双向关系在程序中要设定双向关联
- 双向mappedBy
- Fetch
* 双向不要两边设置Eager,会有多余的查询语句发出 * 对多方设置fetch时要谨慎吗,结合具体的应用,一般用Lazy。
- ORMapping编程模型
* 映射模型 * JPA annotation * Hibernate annotation extension * Hibernate xml * Jpa xml * 编程接口 * Jpa * Hibernate * 数据查询语言 * HQL * EJBQL
- TIps:
测试程序:* *删除和更新之前需要load* * *如果想要删除关联关系,先设定关系为null,再删除对应记录,如果不删除,记录变为垃圾数据*
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139public class HibernateORMTest extends TestCase{private SessionFactory sessionFactory;@Overrideprotected void setUp() throws Exception {try {sessionFactory = new Configuration().configure().buildSessionFactory();} catch (Exception e) {e.printStackTrace();}ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().configure().build();Metadata metadata = new MetadataSources(serviceRegistry).buildMetadata();SchemaExport schemaExport = new SchemaExport();schemaExport.create(EnumSet.of(TargetType.DATABASE),metadata);}@Overrideprotected void tearDown() throws Exception {if (sessionFactory != null) {sessionFactory.close();}}@Testpublic void testSaveUser() {User user = new User();user.setName("g1");Group g = new Group();g.setName("g1");user.setGroup(g);Session session = sessionFactory.openSession();session.beginTransaction();// session.save(g);session.save(user);session.getTransaction().commit();session.close();}@Testpublic void testSaveGroup() {User u1 = new User();u1.setName("u1");User u2 = new User();u2.setName("u2");Group g = new Group();g.setName("g1");g.getUsers().add(u1);g.getUsers().add(u2);u1.setGroup(g);u2.setGroup(g);Session session = sessionFactory.openSession();session.beginTransaction();// session.save(g);session.save(g);session.getTransaction().commit();session.close();}@Testpublic void testGetUser() {testSaveGroup();Session session = sessionFactory.openSession();session.beginTransaction();User user = session.get(User.class, 1);System.out.println(user.getGroup().getName());session.getTransaction().commit();session.close();}@Testpublic void testGetGroup() {testSaveGroup();Session session = sessionFactory.openSession();session.beginTransaction();Group g = session.get(Group.class, 1);session.getTransaction().commit();session.close();}@Testpublic void testLoadUser() {testSaveGroup();Session session = sessionFactory.openSession();session.beginTransaction();User user = session.load(User.class, 1);System.out.println(user.getGroup().getName());session.getTransaction().commit();session.close();}@Testpublic void testUpdateUser() {testSaveGroup();Session session = sessionFactory.getCurrentSession();session.beginTransaction();User user = new User();user.setName("hhhhhh");Group g = new Group();g.setName("g2");user.setGroup(g);// user.getGroup().setName("ggggg");session.persist(user);session.getTransaction().commit();}@Testpublic void testDeleteUser() {testSaveGroup();Session session = sessionFactory.getCurrentSession();session.beginTransaction();// User user = session.load(User.class, 1);// user.setGroup(null);// session.delete(user);session.createQuery("delete from User u where u.id = 1").executeUpdate();session.getTransaction().commit();}@Testpublic void testDeleteGroup() {testSaveGroup();Session session = sessionFactory.getCurrentSession();session.beginTransaction();Group g = session.load(Group.class, 1);session.delete(g);// session.createQuery("delete from User u where u.id = 1").executeUpdate();session.getTransaction().commit();}}
8、集合映射
对象之间存储的容器可以选择Set, List, Map三种,根据实际中的具体需求来选取不同的容器,其中Map需要设定键值对,相对来说要复杂一点。采用的是User和Group的双向的一对多的关系,在User中设置好ID生成策略和多对一的导航,在Group中设置装载user的容器为map:
单元测试如下:
9、继承关系
hibernate中有三种继承关系:
- 一张总表 single_table
- 每个类分别一张表 table_per_class
- 每个子类一张表 joined
找到了一个说的很详细的博客,可以参考:http://blog.csdn.net/pursuer211/article/details/17318379
五、Hibernate查询方式
Hibernate支持多种查询方式:
- NativeSQL查询语言
- HQL
Query q = session.createQuery("from Category");
- EJBQL
- QBC –query by Cretiral
Criteria c = session.createCriteria(Topic.class)
- QBE – query by Example12345678910Topic tExample = new Topic();tExample.setTitle("T_");Example e = Example.create(tExample).ignoreCase().enableLike();Criteria c = session.createCriteria(Topic.class).add(Restrictions.gt("id", 2)).add(Restrictions.lt("id", 8)).add(e);
六、Hibernate性能优化
注意session.clear的运用,清除不用的缓存空间。
1+N问题:hibernate默认表和表的关联方式为fetchType.EAGER,这时候hibernate在一对多关系中查询多的一方的数据时,会额外发出N条SQL语句。原因在于设置了多到一的导航,而一那方的fetch默认为Eager,从而会取出一那方的所有的值。
解决方案:
- 多对一那边的fetch属性设置为LAZY。但是这样会影响级联查询。
- 设置BatchSize(),发出的SQL会减少,但是没有在根本上解决问题。
- HQL语句中使用join fetch,推荐使用:1List<Topic> topics = (List<Topic>)session.createQuery("from Topic t left join fetch t.category c").list();
list和iterate的区别
- list取出所有的元素
- iterate先取ID,等到用的时候再根据ID来取对象
- session中list第二次发出仍然回去数据库查询
- iterate第二次发出,首先查找session缓存
缓存机制:
- 缓存的作用:Hibernate是一个持久层框架,经常访问物理数据库,为了降低应用程序对物理数据源访问的频次,从而提高应用程序的运行性能。缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据。
- 缓存的分类:Hibernate一级缓存和Hibernate二级缓存Hibernate一级缓存又称为“Session的缓存”,它是内置的,不能被卸载(不能被卸载的意思就是这种缓存不具有可选性,必须有的功能,不可以取消session缓存)。由于Session对象的生命周期通常对应一个数据库事务或者一个应用事务,因此它的缓存是事务范围的缓存。第一级缓存是必需的,不允许而且事实上也无法卸除。在第一级缓存中,持久化类的每个实例都具有唯一的OID。 Hibernate二级缓存又称为“SessionFactory的缓存”,由于SessionFactory对象的生命周期和应用程序的整个过程对应,因此Hibernate二级缓存是进程范围或者集群范围的缓存,有可能出现并发问题,因此需要采用适当的并发访问策略,该策略为被缓存的数据提供了事务隔离级别。第二级缓存是可选的,是一个可配置的插件,在默认情况下,SessionFactory不会启用这个插件。
- 第二级缓存存放的数据:
- 很少被修改的数据
- 不是很重要的数据,
- 不会被并发访问的数据
- 常量数据
- 经常被访问的数据
不适合存放在第二级缓存的数据:
- 经常修改的数据
- 不允许出现并发访问的数据
- 与其他应用共享的数据
HIbernate查找对象中缓存的应用:
- 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;如果都查不到,再查询数据库,把结果按照ID放入到缓存,删除、更新、增加数据的时候,同时更新缓存。Hibernate管理缓存实例无论何时,当你给save()、update()或saveOrUpdate()方法传递一个对象时,或使用load()、 get()、list()、iterate() 或scroll()方法获得一个对象时, 该对象都将被加入到Session的内部缓存中。 当随后flush()方法被调用时,对象的状态会和数据库取得同步。 如果你不希望此同步操作发生,或者你正处理大量对象、需要对有效管理内存时,你可以调用evict() 方法,从一级缓存中去掉这些对象及其集合。
load默认使用二级缓存,iterate默认使用二级缓存,list默认往二级缓存中加数据,Query需要设置才可以使用二级缓存。
缓存算法:
- LRU Least Recently Used
- LFU Least Frequently Used
- FIFO
事务并发处理
- 事务 :ACID
- Atomic 原子性
- Consistency 一致性
- Isolation 隔离性
- Durability 持久性
- 事务并发可能出现的问题:
- 第一类丢失更新, 把后启动事务的更新丢弃
- 脏读 读到了其他事务还没有提交的数据
- 第二类丢失更新 不可重复读,多次读取一个事务读出了不同的值
- 幻读 读到了不存在的事务
- 数据库的隔离机制
- Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
* Repeatable read (可重复读):可避免脏读、不可重复读的发生。
* Read committed (读已提交):可避免脏读的发生。
* Read uncommitted (读未提交):最低级别,任何情况都无法保证。
- Hibernate的事务隔离机制:
1:读操作未提交(Read Uncommitted)
2:读操作已提交(Read Committed)
4:可重读(Repeatable Read)
8:可串行化(Serializable)
设置方式为:<property name=" hibernate.connection.isolation">4</property>
- 事务 :ACID