Hibernate
入门
是一种全自动的ORM映射,自动生成sql,而Mybatis是半自动的需要手写sql语句;后面还有Hibernate的升级版本JPA(无配置)
主要工作过程:
区别Mybatis主要在于中间的类-表之间的映射文件xml
Hibernate的优点:
- 对JDBC访问数据库的代码做了封装,大大简化了数据访问层繁琐的重复性代码
- 使用java的反射机制,而不是字节码增强程序类实现透明性
- 最终生成sql语句并执行,性能较好
- 灵活性高,支持各种关联关系
- 支持多种数据库类型,使用hibernate开发的程序可以在不同数据库中移植
基础配置及使用
其中要注意表之间的对应关系(一对一、一对多、多对多)
使用hibernate操作数据库的步骤:
- 导入hibernate相关包
- 创建数据库表对应的类
- 创建对象关系映射文件
- 创建hibernate主配置文件,并配置hibernate访问数据库的基本信息(如数据库地址、用户名、密码等)
- 调用ORM接口完成数据库操作
其中pojo里面的实体类和对应的xml文件都可以一起自动生成(数据库可以逆向生成类,反之是正向生成数据库)
基础配置及用法讲解示例:
主配置文件hibernate.cfg.xml:
<?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">
<!-- Generated by MyEclipse Hibernate Tools. -->
<hibernate-configuration>
<session-factory>
<property name="dialect">
org.hibernate.dialect.MySQLDialect
</property>
<property name="connection.url"> <!-- 连接数据库并解决乱码问题 -->
<![CDATA[jdbc:mysql://localhost:3306/jkstudy?useUnicode=true&characterEncoding=utf8]]>
</property>
<property name="connection.username">root</property>
<property name="connection.password">123456</property>
<property name="connection.driver_class">
com.mysql.jdbc.Driver <!-- 连接数据库的驱动 -->
</property>
<property name="myeclipse.connection.profile">
myshujuku <!-- 自动生成的(不重要) -->
</property>
<property name="show_sql">true</property>
<property name="format_sql">true</property>
<mapping resource="com/cwl/study/pojo/Student.hbm.xml" /> <!-- 加载分/子配置文件 -->
</session-factory>
</hibernate-configuration>
Student实体类略
其对应的映射文件Student.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">
<!--
Mapping file autogenerated by MyEclipse Persistence Tools
-->
<hibernate-mapping>
<class name="com.cwl.study.pojo.Student" table="student" catalog="jkstudy">
<id name="sid" type="java.lang.Integer">
<column name="sid" />
<generator class="identity" />
</id>
<property name="sname" type="java.lang.String">
<column name="sname" />
</property>
<property name="ssex" type="java.lang.String">
<column name="ssex" />
</property>
</class>
</hibernate-mapping>
测试类对数据库的增删改查操作:
public class Test {
public static void main(String[] args) {
Configuration cfg=new Configuration().configure("hibernate.cfg.xml"); //读取配置文件
SessionFactory sf=cfg.buildSessionFactory(); //构建SessionFactory工厂
Session session=sf.openSession(); //创建与数据库之间的会话
//查
Student stu=(Student) session.get(Student.class, 1);
System.out.println(stu);*/
//增
Transaction t=session.beginTransaction(); //开启事务,注意用什么框架就导什么框架的包
Student stu=new Student("大傻子","男");
session.save(stu); //通过session保存
t.commit(); //提交事务,增删改需要有事务并提交
session.close(); //先开后闭原则
sf.close();*/
//删
Transaction t=session.beginTransaction();
Student stu=(Student) session.get(Student.class, 4); //先查出来再删,4是主键
session.delete(stu);
t.commit();
session.close();
sf.close();*/
//改
Transaction t=session.beginTransaction();
Student stu=(Student) session.get(Student.class, 6); //先查出来再改,6是主键
stu.setSname("傻子");
session.update(stu);
t.commit();
session.close();
sf.close();
}
}
对象持久化
- 持久化(Persistence): 即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内存中的数据存储在关系型的数据库中,当然也可以存储在磁盘文件中、XML数据文件中等等。JDBC就是一种持久化机制,文件IO也是一种持久化机制。保存到内存为非持久化数据。
- 对象持久化: 将对象中的属性信息进行解析,并保存到可永久保存的存储设备中(一般是数据库)。用于保存信息的对象称为持久化对象。ORM与持久化的关系:对象持久化的一种解决方案,通过将关系数据库的表与程序的类映射,完成了将表中的记录映射成为对象的过程。ORM的目的是为了方便开发人员以面向对象的思想来实现对数据库的操作。Hibernate:ORM的具体实现,通过ORM实现了java语言的对象持久化。
Web的分层开发:
Hibernate映射类型:
hibernate-mapping里面标签/属性的讲解及用法
class
property
补充:equals()是比较值,==是比较地址
id
也叫映射标识符OID:分辨javabean对象
常用OID生成器 (generator):
- increment指id由自己插入,但其值由查询的最大值+i1得来
- identity指id直接不管,交给数据库自动增长(建议用)
其中的uuid
System.out.println(UUID.randomUUID()); //可生成随机字符串
session缓存
session缓存的概念: Session对象的集合属性,用于保存本次session创建/修改/查询出来的对象,又称为一级缓存
session缓存的作用:
有一定的缓存空间大小,适当缓存适当清除
管理缓存的API接口:
- session.flush:执行一系列sql语句,但不提交事务
- transation.commit:先调用flush() 方法,然后提交事务. 则意味着提交事务意味着对数据库操作永久保存下来
- session.refresh:刷新缓存内容,此时缓存和数据库信息一致
- session.evict(obj):将参数中的对象在session缓存中清除
- session.clear:清空缓存,等价于list.removeAll()
- session.close:清空Session的缓存,关闭session资源
快照
持久化状态
面试会提问
Hibernate 把对象分为 4 种状态:
- 持久化状态:当执行session的查询、修改或创建操作时,对象进入持久化状态
- 临时状态:刚new出来的对象,和hibernate无关,此时不处于 Session 的缓存中,在数据库中没有对应的记录
- 游离状态 :游离态代表当前对象已经不在session内存中,不被session引用,将被垃圾回收;清除缓存再次查询需要重新访问数据库
- 删除状态:执行session.delete方法后进入删除状态,持久化对象从缓存中移除,但在执行提交前,数据库仍保存记录
session 的特定方法能使对象从一个状态转换到另一个状态
4 种状态流程图:
注意以上的几种状态只是Hibernate认为的状态,与java无关,java一般只有两种状态:新建new和垃圾回收GC
持久化状态的快照功能演示:只有访问数据库后才建立快照
UserDetail pd = new UserDetail();
pd.setId(400011);
pd.setName("张三2");
//update将当前对象加入session缓存,进入持久态,但由于没有访问数据库,因此不具备快照功能。
session.update(pd);
//已经在缓存中,不执行查询
UserDetail pd1 = (UserDetail) session.get(UserDetail.class, 400011);
System.out.println(pd1.getName());
//由于没有快照,因此提交时默认将会执行update语句
ts.commit();
关联映射
主要有以下三种关联关系:
- 一对一
- 一对多
- 多对多
一对一
是特殊的一对多
一对多
一对多关联映射的方式:
- 单项关联:仅仅建立一对多或多对一关联,此时操作设定关联关系的一方不会影响另一方数据
- 双项关联:同时建立一对多或多对一关联,操作一方会影响另一方
many-to-one属性:
- name:关联映射的属性名字(在多方类里对一方的指代名)
- column:数据库里关联一方的外键名称
- class:name:指代属性对应持久化类的全路径
- not-null:是否允许为空
单项和双向关联关系的代码体现
主配置文件省略,只是连接数据库和加载子配置文件
Student实体类:
//多方
public class Student {
private Integer sid;
private String sname;
private String ssex;
private String ssubject;
private Classes cla; //关联一方Classes
//基础方法略
}
Classes实体类:
//一方
public class Classes {
private Integer cid;
private String cname;
//private Set<Student> stu; //一方找多方需要用set集合,不能用list
public Integer getCid() {
return cid;
}
public void setCid(Integer cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
/* public Set<Student> getStu() {
return stu;
}
public void setStu(Set<Student> stu) {
this.stu = stu;
}*/
public Classes(Integer cid, String cname, Set<Student> stu) {
super();
this.cid = cid;
this.cname = cname;
//this.stu = stu;
}
public Classes() {
super();
}
@Override
public String toString() {
return "Classes [cid=" + cid + ", cname=" + cname + "]";
}
/* //一方找多方时不能重写一方的toString中多方的指定,否则会无限套娃,多方也会找一方,最后导致栈溢出
public String toString() {
return "Classes [cid=" + cid + ", cname=" + cname + ", stu=" + stu
+ "]";
}*/
}
Student.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">
<!--
Mapping file autogenerated by MyEclipse Persistence Tools
-->
<hibernate-mapping>
<class name="com.cwl.study.pojo.Student" table="student" catalog="jkstudy">
<id name="sid" type="java.lang.Integer"> <!-- 映射标识符OID:分辨javabean对象 -->
<column name="sid" />
<generator class="increment" />
</id>
<property name="sname" type="java.lang.String">
<column name="sname" />
</property>
<property name="ssex" type="java.lang.String">
<column name="ssex" />
</property>
<property name="ssubject" type="java.lang.String">
<column name="ssubject" />
</property>
<!-- 多方找一方 -->
<many-to-one name="cla" class="com.cwl.study.pojo.Classes" column="cid"></many-to-one>
</class>
</hibernate-mapping>
Classes.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">
<!--
Mapping file autogenerated by MyEclipse Persistence Tools
-->
<hibernate-mapping>
<class name="com.cwl.study.pojo.Classes" table="classes" catalog="jkstudy">
<id name="cid" type="java.lang.Integer"> <!-- 映射标识符OID:分辨javabean对象 -->
<column name="cid" />
<generator class="increment" />
</id>
<property name="cname" type="java.lang.String">
<column name="cname" />
</property>
<!-- <set name="stu"> <!-- 找多方用set,配置也是 -->
<key column="cid" /> <!-- 多方的外键 -->
<one-to-many class="com.cwl.study.pojo.Student" /> <!-- 一方找多方 -->
</set> -->
</class>
</hibernate-mapping>
测试类:
public class Test {
public static void main(String[] args) {
Session session=HibernateSessionFactory.getSession();//构建工厂并获取session,以便于和数据库会话,这里不用加载主配置文件,代替了以下三行:
/* Configuration cfg=new Configuration().configure("hibernate.cfg.xml"); //读取配置文件
SessionFactory sf=cfg.buildSessionFactory(); //构建SessionFactory工厂
Session session=sf.openSession(); //创建与数据库之间的会话
*/
//新增一个学生有以下几种情况:
//学生不存在,但班级存在
/* Transaction t=session.beginTransaction();
Student stu=new Student(null,"呵呵","女","信科", null);
Classes c=(Classes) session.get(Classes.class, 1);
stu.setCla(c);
session.save(stu);
t.commit();
session.close();*/
//学生存在,但班级不存在
/* Transaction t=session.beginTransaction();
Student stu=(Student) session.get(Student.class, 8);
Classes c=new Classes();
c.setCname("摆烂班");
session.save(c);
stu.setCla(c);
session.update(stu);
t.commit();
session.close();*/
//学生和班级都不存在
Transaction t=session.beginTransaction();
Classes c=new Classes();
c.setCname("精英班");
session.save(c);
Student s=new Student();
s.setSname("cwl");
s.setSsex("男");
s.setSsubject("信科");
s.setCla(c);
session.save(s);
t.commit();
session.close();
}
}
总结:单向/双向关联关系只适合查,用在增删改的话就是个灾难,麻烦,后续有解决方案
多对多
要深入理解其中的关系!
检索
Hibernate 提供了以下几种检索对象的方式:
- HQL 检索方式: 使用面向对象的 HQL 查询语言(推荐使用)
- 本地 SQL 检索方式: 使用本地数据库的 SQL 查询语句(使用hql无法解决的情况下使用)
- OID 检索方式: 按照对象的OID 来检索对象(根据ID获取持久化对象时使用)
- QBC 检索方式: 使用 QBC(Query By Criteria) API 来检索对象. 这种 API 封装了基于字符串形式的查询语句, 提供了更加面向对象的查询接口(功能较弱)
HQL查询
HQL参照SQL的大体语法即可,但要注意做区分,HQL是通过查询对象(类),而SQL是直接查数据库的数据
执行HQL的步骤如下:
- 创建一个 Query 对象: 它包括一个 HQL 查询语句。HQL 查询语句中可以包含命名参数。
Query query = session.createQuery(“hql");
- 动态绑定参数
//方式1
Query query = session.createQuery("from Employee where employeeId = :eId");
query.setInteger("eId", 1);
//方式2
Query query = session.createQuery("from Employee where employeeId = ?");
//从第0个位置开始,与jdbc不同(从1开始的)
query.setInteger(0, 1);
- 调用 Query 查询方法执行查询语句
① query.iterate():返回迭代器Iterator对象,采用延迟加载模式加载查询结果
Iterator<Employee> it = query.iterate();
while (it.hasNext()) {
Employee em = it.next();
System.out.println(em.getName());
}
② query.list():返回集合List,采用立即加载模式加载对象
List<Employee> employList = query.list();
for(Employee em:employList) {
System.out.println(em.getName());
}
③ query.uniqueResult():返回唯一的结果,采用立即加载模式加载对象
Employee em = (Employee) query.uniqueResult();
HQL的连接:与sql语句类似,HQL也支持连接(如内连接、外连接等)
迫切内连接(inner join fetch): 等同于sql语句中的inner join,但查询结果List封装的为连接左边的持久化对象,同时将连接右边的持久化对象封装到左边的属性中。
Query query = session.createQuery("from Employee e inner join fetch e.department d");
List<Employee> list = query.list();
for(Employee em:list) {
//innerjoinfetch返回左边的持久化对象,并将右边的持久化对象封装在左边对象的属性中
Department de = em.getDepartment();
System.out.println("员工姓名" + em.getName() + "部门名称" + de.getDepartmentName());
}
隐式内连接: 在hql中没有声明连接方式,但是调用了连接的持久化对象的属性作为条件限定,hibernate按照内连接(inner join)方式处理
Query query = session.createQuery(“from Employee e where e.department.departmentId=?”);//使用关联属性作为限定条件
query.setInteger(0, 1);
List<Employee> list = query.list();
for(Employee em:list) {
System.out.println("员工姓名" + em.getName());
}
迫切左外连接(outer join fetch): 等同于sql语句中的left outer join,返回结果与迫切内连接类似。右外连接与左外连接使用方式一致
Query query = session.createQuery("from Employee e left outer join fetch e.department d ");
List<Employee> list = query.list();
for(Employee em:list) {
//leftouterfetch返回左边的持久化对象,并将右边的持久化对象封装在左边对象的属性中
Department de = em.getDepartment();
System.out.println("员工姓名" + em.getName() + "部门名称" + (de==null?"没有部门":de.getDepartmentName()));
}
交叉连接(了解): 通过where子句声明内连接(不建议使用,应使用内连接方式)
Query query = session.createQuery("from Employee e,Department d where e.department.departmentId = d.departmentId");
List list = query.list();
for(int i = 0 ; i < list.size();i++) {
//innerjoin默认返回的集合元素为数组,数组顺序按照hql语句的顺序封装对象
Object[] obj = (Object[]) list.get(i);
}
HQL用法代码示例
创建 Query 对象
主配置文件省略,就只是连接数据库和加载子配置文件
Student实体类和对应的映射文件也省略
主要详细看以下的Test类:
public class Test {
public static void main(String[] args) {
Session session=HibernateSessionFactory.getSession();
//用HQL查询所有
/* String hql="from Student"; //注意这里的Student是类名不是表名,HQL会通过orm映射文件转换为sql语句再执行
Query q=session.createQuery(hql); //创建Query对象并传入hql
List<Student> list=q.list(); //全部添加到list集合里
for (Student s : list) {
System.out.println(s);
}*/
//用HQL条件查询
//String hql="from Student where sname=?"; //问号传参,另外一种传参如下:
/* String hql="from Student where sname=:str"; //用:占位传参
Query q=session.createQuery(hql);
//q.setString(0, "陳不錯"); //注意下标从0开始!
q.setString("str", "陳不錯");
List<Student> list=q.list();
for (Student s : list) {
System.out.println(s);
}*/
//用HQL分页查询
/* String hql="from Student";
Query q=session.createQuery(hql);
q.setFirstResult(3); //起始下标=(当前页-1)*每页显示的条数
q.setMaxResults(2); //查询每页显示的最大条数
List<Student> list=q.list();
for (Student s : list) {
System.out.println(s);
}*/
//用HQL查多个属性
//String hql="select sname,ssex from Student"; //不能像sql这样写!!
/* String hql="select new Student(sname,ssex) from Student"; //要写成这样的,中间的是构造方法
Query q=session.createQuery(hql);
List<Student> list=q.list();
for (Student s : list) {
System.out.println(s);
}*/
//用HQL分组查询
String hql="select count(ssubject) from Student group by ssex";
Query q=session.createQuery(hql);
List<Object> list=q.list();
for (Object s : list) {
System.out.println(s);
}
//等等~
}
}
QBC查询
创建Criteria对象,并指定要检索的实体类
一样的只详细看Test测试类:
public class Test1 {
public static void main(String[] args) {
Session session=HibernateSessionFactory.getSession();
//QBC查询
Criteria c=session.createCriteria(Student.class); //创建Criteria对象,并指定要检索的实体类,该行相当于select * from student
Criterion c1=Restrictions.gt("sid", 5); //sid>5,gt表示大于
Criterion c2=Restrictions.eq("ssex", "男"); //查ssex=男
c.add(Restrictions.and(c1,c2)); //做and连接,sid>5 and ssex=男
List<Student> list=c.list(); //全部添加到list集合里
for (Student s : list) {
System.out.println(s);
}
}
}
OID查询
OID检索方式:通过OID获取对象
//立即加载方式
Employee em1 = session.get(Employee.class, 1);
//延迟加载方式
Employee em2 = session.load(Employee.class, 2);
System.out.println(em1.getName());
System.err.println(em2.getName());
Get方法与load方法返回结果一致,但是查询机制不同
注解
注意在Hibernate里面主要还是用javax的注解!
主配置文件hibernate.cfg.xml:
<?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">
<!-- Generated by MyEclipse Hibernate Tools. -->
<hibernate-configuration>
<session-factory>
<property name="dialect">
org.hibernate.dialect.MySQLDialect
</property>
<property name="connection.url">
<![CDATA[jdbc:mysql://localhost:3306/jkstudy?useUnicode=true&characterEncoding=utf8]]>
</property>
<property name="connection.username">root</property>
<property name="connection.password">123456</property>
<property name="connection.driver_class">
com.mysql.jdbc.Driver
</property>
<property name="myeclipse.connection.profile">myshujuku</property>
<property name="show_sql">true</property>
<mapping class="com.cwl.study.pojo.Student" /> <!-- 注意这里是class -->
<mapping class="com.cwl.study.pojo.Classes" />
</session-factory>
</hibernate-configuration>
Student实体类:
package com.cwl.study.pojo;
import javax.annotation.Generated;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
//多方
@Entity //注册为一个实体类
@Table(name="student") //让类对应student表
public class Student {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY) //主键生成策略默认是AUTO,也可以直接写@GeneratedValue即可
private Integer sid;
private String sname;
@Column(name="ssex") //让属性sex对应表中的列ssex,同名就不用写
private String sex;
private String ssubject;
@ManyToOne(targetEntity=Classes.class) //指定一方的类型
@JoinColumn(name="cid") //指定外键名称
private Classes cla; //关联一方
public Integer getSid() {
return sid;
}
public void setSid(Integer sid) {
this.sid = sid;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getSsubject() {
return ssubject;
}
public void setSsubject(String ssubject) {
this.ssubject = ssubject;
}
public Classes getCla() {
return cla;
}
public void setCla(Classes cla) {
this.cla = cla;
}
public String toString() {
return "Student [sid=" + sid + ", sname=" + sname + ", sex=" + sex
+ ", ssubject=" + ssubject + ", cla=" + cla + "]";
}
}
Classes实体类:
package com.cwl.study.pojo;
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
//一方
@Entity
@Table(name="classes")
public class Classes {
@Id
@GeneratedValue
private Integer cid;
private String cname;
@OneToMany(targetEntity=Student.class,mappedBy="cla") //目标实体指定多方,cla是多方关联的一方的指代
private Set<Student> stu; //注意关联多方要用set集合,不能用list!
public Integer getCid() {
return cid;
}
public void setCid(Integer cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
public Set<Student> getStu() {
return stu;
}
public void setStu(Set<Student> stu) {
this.stu = stu;
}
public String toString() { //注意这里不能重写多方stu的toString,不然会无限套娃造成栈溢出
return "Classes [cid=" + cid + ", cname=" + cname + "]";
}
}
Test测试类:
package com.cwl.study.test;
import java.text.Annotation;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.cfg.Configuration;
import com.cwl.study.pojo.Classes;
import com.cwl.study.pojo.Student;
import com.cwl.study.util.HibernateSessionFactory;
public class Test {
public static void main(String[] args) {
/* Configuration cfg=new AnnotationConfiguration().configure("hibernate.cfg.xml");
SessionFactory sf=cfg.buildSessionFactory();
Session session=sf.openSession();*/
//以上三行也可以直接写成:
Session session=HibernateSessionFactory.getSession();
//添加学生
/* Transaction t=session.beginTransaction();
Student stu=new Student(null, "测试", "男", "信科", 6);
session.save(stu);
t.commit();
session.close();*/
//查询学生
/* Student stu=(Student) session.get(Student.class, 1); //session可直接获取Student,后面的数字代表主键,这里1是主键
System.out.println(stu);*/
Classes cla=(Classes) session.get(Classes.class, 6); //获取Classes,6是主键
System.out.println(cla); //只输出一方本身的数据
for (Student s : cla.getStu()) { //遍历输出一方关联的所有多方数据
System.out.println(s);
}
}
}
当然还有一对一(@OneToOne)和多对多
其中的fetch属性可以设置延迟加载(懒加载)和立即加载
- 延迟加载(懒加载):在用的时候才加载,优点:省空间,但和SpringMVC整合的时候容易报错
- 立即加载:不管用不用都事先加载好,优点:效率高,但不用时比较耗空间