1、Hibernate入门
1.1、理解类和表的映射关系
ORM(对象关系映射)
1.1.1、Hibernate的优点
- 简化了JDBC 繁琐的编码
- 对面向对象特性支持良好
- 可移植性好
1.1.2、Hibernate的缺点
- 不适合需要使用数据库的特定优化机制的情况
- 不适合大规模的批量数据处理
1.1.3、和 MyBatis的区别
/ | MyBatis | Hibernate |
---|---|---|
映射ORM | SQL-Mapping | 对象状态管理、级联操作 |
/ | 需要关注SQL的生成 | 完全面向对象,语句与数据库无关 |
/ | 灵活 | 数据库移植性好 |
1.1.4、使用hibernate的步骤
- 下载并部署jar文件
- 编写hibernate配置文件
- 创建持久化类和映射文件
- 使用hibernate API
1.1.4.1、配置文件
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">
<hibernate-configuration>
<session-factory>
<!--数据库URL -->
<property name="connection.url">
<!-- 如果用MySQL的则把oracle改掉,@127.0.0.1:也可以写localhost或者别的 ,1521是oracle的端口号,MySQL是3306-->
jdbc:oracle:thin:@127.0.0.1:1521:orcl
</property>
<!--数据库用户 -->
<property name="connection.username">scott</property>
<!--数据库用户密码 -->
<property name="connection.password">tiger</property>
<!--数据库JDBC驱动 ,在安装里找jar包-->
<property name="connection.driver_class">
oracle.jdbc.driver.OracleDriver
</property>
<!--每个数据库都有其对应的Dialect(方言),说明白用哪种数据库,以匹配其平台特性 -->
<property name="dialect">org.hibernate.dialect.Oracle10gDialect</property>
<!--指定当前session范围和上下文 会话是线程级的(当前会话)-->
<property name="current_session_context_class">thread</property>
<!--是否将运行期生成的SQL输出到日志以供调试,默认控制台 -->
<property name="show_sql">true</property>
<!--是否格式化SQL -->
<property name="format_sql">true</property>
<mapping resource="cn/st/hibernatedemo/entity/Department.hbm.xml"/>
</session-factory>
</hibernate-configuration>
连接数据库的jar,到自己安装oracle里的文件夹里找
1.1.4.2、创建持久化类和映射文件
实体类Department.java
package cn.st.hibernatedemo.entity;
import java.io.Serializable;
public class Department implements Serializable{
private static final long serialVersionUID = 1L;
public Department() {
}
public Department(Integer deptNo, String deptName, String location) {
super();
this.deptNo = deptNo;
this.deptName = deptName;
this.location = location;
}
private Integer deptNo;
private String deptName;
private String location;
public Integer getDeptNo() {
return deptNo;
}
public void setDeptNo(Integer deptNo) {
this.deptNo = deptNo;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
}
映射文件Department.hbm.xml
一个实体类对应一个实体文件
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<!-- 完整的包名和类型,table="`关联的数据库的表`” ,`波浪线`-->
<class name="cn.st.hibernatedemo.entity.Department" table="`DEPT`">
<!-- 类里的属性,name=“实体类的属性名”type="属性的类型" column="`对应的列`" -->
<id name="deptNo" type="java.lang.Integer" column="`DEPTNO`">
<!--生成器,assigned表示不是自动生成,要手动输入 -->
<generator class="assigned"/>
</id>
<property name="deptName" type="java.lang.String" column="`DNAME`"/>
<property name="location" type="java.lang.String" column="`LOC`"/>
</class>
</hibernate-mapping>
注意Date类型<property name="hiredate" type="java.util.Date" column="
HIREDATE"/>
然后在配置文件添加映射
修改hibernate.cfg.xml配置文件
<mapping resource="cn/st/hibernatedemo/entity/Department.hbm.xml"/>
关于Hibernate的Generator属性的常用class
identity用于MySql数据库 | 递增:需在建表时对主键指定为auto_increment属性 |
assigned:用户自定义id | 手动输入 |
native:跨数据库时使用 | 数据库决定用何种方式 |
Generator子元素的一些内置生成器的快捷名字:
increment(递增):用于为long, short或者int类型生成唯一标识。只有在没有其他进程往同一张表中插入数据时才能使用。 在集群下不要使用。
1.1.4.3、使用Hibernate API实现
package cn.st.hibernatedemo.test;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import cn.st.hibernatedemo.entity.Department;
public class Test {
public static void main(String[] args) {
//关键接口和类
//使用一次就丢,可放在局部
Configuration conf = null;
SessionFactory factory = null;
Session session = null;
//事务管理
Transaction tx = null;
try {
//1.读取配置文件
conf=new Configuration().configure();
//2.创建SessionFactory
factory=conf.buildSessionFactory();
//3.打开session
session=factory.getCurrentSession();
//4.开始一个事务
tx=session.beginTransaction();
//使用构造方法新增一条数据
// Department dt=new Department(50,"开发","xxxx");
//5.持久化操作
// session.save(dt);
//6.提交或回滚事务
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
}
}
}
插入成功后,控制台会出现如图下
1.2、掌握单表的增删改
修改
//修改
//先获取一条记录
Department dt=session.get(Department.class, 50);
dt.setDeptName("生产部");;
session.save(dt);
删除
Department dt=session.get(Department.class, 50);
session.delete(dt);
1.2、掌握按主键查询
load | get |
---|---|
session.load(Department.class, 50); | session.get(Department.class, 50); |
若数据不存在,使用时抛出ObjectNotFoundException | 若数据不存在,返回null |
1.3、理解持久化对象的状态及其转换
瞬时状态(Transient) | 持久状态(Persistent) | 游离状态(Detached) |
---|---|---|
new一个实体类对象,和数据库没有关联 | 使用save保存,则数据库多一条记录,对象和记录关联 | 会话结束,连接断开,但曾经关联过commit |
1.4、脏检查与刷新缓存
-
当刷新缓存(调用Session的flush()方法)时,Hiberante会对Session中持久状态的对象进行检测,判断对象的数据是否发生了改变(修改)
commit()方法会首先刷新缓存,没提交没更新,但是标记这个对象已经脏了,但是判断到缓存不是脏的,就不同步 -
刷新缓存就是将数据库同步为与Session缓存一致
刷新缓存时会执行脏检查 -
Session会在以下时间点刷新缓存
调用Session的flush()方法
调用Transaction的commit()方法
1.4.1更新数据的方法
update()、save方法
saveOrUpdate()方法(保存或者更新)
merge():merge方法在执行前回去缓存中找是不是有相应的记录,可以解决在一个session里有不同的两个对象但有相同标识的问题
2、HQL基础
2.1、Hibernate支持的查询方式
2.1.1、HQL查询(对对象的查询)
HQL是Hibernate查询语言(Hibernate Query Language)是面向对象的查询语句
区分大小写
//1、创建hql语句,Department实体类名
String hql="from Department";
//String hql="from Department where deptName like '%s%'";
//2、得到一个查询的query接口对象
Query<Department> query = session.createQuery(hql);
List<Department> list=query.list();
for (Department department : list) {
System.out.println(department.getDeptNo()+"/"+department.getDeptName());
}
导包注意import org.hibernate.query.Query;
query.list() | query.iterate() |
---|---|
立即执行 | 延迟加载 |
List empList = query.list(); | Iterator empIterator = query.iterate(); |
、、、、、、、、、、、、、 |
延迟加载:
代理对象:除了主键其余值为空
通过代理对象再查询其余的值,一条记录一条记录的查询
Iterator<Department> it =query.iterate();
while(it.hasNext()) {
Department department = new Department();
System.out.println(department.getDeptNo()+"/"+department.getDeptName());
}
模糊查询
String hql="from Department where deptName like '%S%'";
Query<Department> query = session.createQuery(hql);
List<Department> list=query.list();
for (Department department : list) {
System.out.println(department.getDeptNo()+"/"+department.getDeptName());
}
2.2、理解并使用动态参数绑定实现数据查询
2.2.1、使用字符串拼接查询条件
"from User where name = '" + name + "'"
存在各种弊端、性能低、不安全
DepartmentDao.java
List<Department> select(String deptName);
DepartmentDaoImpl.java
@Override
public List<Department> select(String deptName) {
return this.currentSession().createQuery("from Department where deptName='"+deptName+"'").list();
}
DepartmentService.java
List<Department> list(String deptName);
@Override
public List<Department> list(String deptName) {
Transaction tx = null;
List<Department> list = null;
try {
tx=HibernateUtil.CurrentSession().beginTransaction();
//搜索的部门名称要传递过去
list = dao.select(deptName);
tx.commit();
} catch (Exception e) {
tx.rollback();
}
return list;
}
测试类
package cn.st.hibernatedemo.test;
import java.util.List;
import cn.st.hibernatedemo.entity.Department;
import cn.st.hibernatedemo.service.DepartmentService;
import cn.st.hibernatedemo.service.impl.DepartmentServiceImpl;
public class Test1 {
public static void main(String[] args) {
DepartmentService di = new DepartmentServiceImpl();
List<Department> list = di.list("开发");
for (Department department : list) {
System.out.println(department.getDeptNo()+"、"+department.getDeptName());
}
}
}
2.2.2、使用占位符
2.2.2.1、按参数位置绑定
下标从0开始from User where name = ?
DaoImple.java
@Override
public List<Department> select(String deptName) {
Query<Department> query = this.currentSession().createQuery("from Department where deptName=?0");
query.setString(0, deptName);
return query.list();
}
2.2.2.2、按参数名称绑定
可读性好,易维护,推荐使用from User where name = :name
@Override
public List<Department> select(String deptName) {
Query<Department> query = this.currentSession().createQuery("from Department where deptName=:deptName");
//注意key值要“”
query.setString("deptName", deptName);
return query.list();
}
2.2.3、为参数赋值
setXXX():针对具体数据类型 | setParameter():任意类型参数 | setProperties():专为命名参数定制 |
---|---|---|
setXXX( int position, XXX value) | setParameter( int position, Object value) | setProperties():专为命名参数定制 |
setXXX( String name, XXX value) | setParameter( String name, Object value) | / |
setParameter优点:查询语句有很多条件,但是也只需要传一个条件
@Override
public List<Department> select(String deptName) {
Query<Department> query = this.currentSession().createQuery("from Department where deptName=:deptName");
Department dept = new Department();
dept.setDeptName(deptName);
query.setProperties(dept);
return query.list();
2.3、分页查询
Query接口的相关方法 | \ |
---|---|
uniqueResult() | 获取唯一对象 |
setFirstResult() | 设置从第几条开始 |
setMaxResults() | 设置读取最大记录数 |
2.4、HQL投影查询
HQL投影查询是查询一个持久化类的一个或多个属性值,或者是通过表达式或聚合函数得到的值
对象不是持久化状态,仅用于封装结果
优点:效率更高,不需要维护对象,也不需要刷新缓存
查询结果的封装主要分三种情况 | |
---|---|
封装成Object对象 | List list = query.list(); |
封装成Object数组 | List<Object[]> list = query.list(); |
通过构造方法封装成实体类对象 | List list = query.list(); |
Object对象
String hql = "select d.deptName,d.deptNo from Department d";
Query query = session.createQuery(hql);
List<Object[]> list = query.list();
for (Object[] object : list) {
//第一列和第二列
System.out.println(object[0]+"\t"+object[1]);
}
Object数组
String hql = "select d.deptName,d.deptNo from Department d";
Query query = session.createQuery(hql);
List<Object[]> list = query.list();
for (Object[] object : list) {
//第一列和第二列
System.out.println(object[0]+"\t"+object[1]);
}
实体类对象
可以有不同的构造方法
//实体类对象,但是这些对象都不是持久化对象
String hql = "select new Department(d.deptNo,d.deptName) from Department d";
Query query = session.createQuery(hql);
List<Department> list = query.list();
for (Department dept : list) {
System.out.println(dept.getDeptNo()+"\t"+dept.getDeptName());
}
3、映射
3.1、理解Hibernate的关联映射
表与表的关联关系:
单向关联、双向关联(根据主键外键来引用)
3.1.1、多对一关联
例:多个员工对应一个部门,一个部门有多个员工
在员工实体类里添加部门封装的属性和方法
注:不能两个列关联到一个属性上去,有部门对象就不能有部门编号
private Department dept;
public Department getDept() {
return dept;
}
public void setDept(Department dept) {
this.dept = dept;
}
在Employee.hbm.xml添加
name是属性名,类名,对应的数据库列名
<many-to-one name="dept" class="cn.st.hibernatedemo.entity.Department" column="`DEPTNO`" />
test类
查询
public static void main(String[] args) {
Session session = null;
Transaction tx = null;
try {
session = HibernateUtil.CurrentSession();
tx = session.beginTransaction();
Employee emp = session.get(Employee.class, 50);
System.out.println(emp.getEmpName());
tx.commit();
}catch(Exception e){
tx.rollback();
e.printStackTrace();
}
HibernateUtil.factory.close();
}
关联的添加,插入一条数据
Department dept = new Department();
dept.setDeptNo(10);
Employee emp = new Employee();
emp.setEmpNo(111);
emp.setEmpName("kk");
emp.setDept(dept);
session.save(emp);
查询多条,查询属于部门编号20的所有元
Department dept = session.get(Department.class, 20);
Query<Employee> query = session.createQuery("from Employee where dept=:dept");
query.setParameter("dept", dept);
List<Employee> list = query.list();
for (Employee emp : list) {
System.out.println(emp.getDept().getDeptName()+"\t"+emp.getEmpNo()+"\t"+emp.getEmpName());
}
优点:查询了员工,部门信息已经包含在里面
缺点:每个员工里面都包含了部门对象,查询了很多无用的数据,占据太多存储空间
3.1.2、一对多关联
//如果不new一个实例,将会空指针
private Set<Employee> employees = new HashSet<Employee>();
public Set<Employee> getEmployees() {
return employees;
}
public void setEmployees(Set<Employee> employees) {
this.employees = employees;
}
Department.hbm.xml配置文件
<set name="employees">
<key column="`DEPTNO`" />
<one-to-many class="cn.st.hibernatedemo.entity.Employee" />
</set>
测试类
Department dept = session.get(Department.class, 20);
for (Employee emp : dept.getEmployees()) {
System.out.println(emp.getEmpName());
}
如果即配了多对一,又配了一对多,就是双向的一对多
3.2、级联cascade属性(级联属性)
cascade属性值 | / |
---|---|
none | 当Session操纵当前对象时,忽略其他关联的对象。它是cascade属性的默认值 |
save-update | 当通过Session的save()、update()及saveOrUpdate()方法来保存或更新当前对象时,级联保存所有关联的新建的瞬时状态的对象,并且级联更新所有关联的游离状态的对象 |
merge | 当通过Session的merge()方法来保存或更新当前对象时,对其关联对象也执行merge()方法 |
delete | 当通过Session的delete()方法删除当前对象时,会级联删除所有关联的对象 |
all | 包含所有的级联行为 |
//级联保存
Department dept = new Department(50,"开发部","深圳");
Employee emp = new Employee();
emp.setEmpName("kk");
emp.setEmpNo(111);
//双向的关联
emp.setDept(dept);
dept.getEmployees().add(emp);
session.save(dept);
<set name="employees" cascade="save-update">
<key column="`DEPTNO`" />
<one-to-many class="cn.st.hibernatedemo.entity.Employee" />
</set>
级联删除
Department dept = session.get(Department.class, 50);
session.delete(dept);
//也可以改成all
<set name="employees" cascade="delete">
<key column="`DEPTNO`" />
<one-to-many class="cn.st.hibernatedemo.entity.Employee" />
</set>
级联更新
Department dept = session.get(Department.class, 20);
Employee emp = session.get(Employee.class, 7369);
dept.setDeptName("开发部");
emp.setDept(dept);
dept.getEmployees().add(emp);
session.update(dept);
3.3、inverse属性(维护关系控制权)
nverse属性指定了关联关系中的方向
inverse设置为false,则为主动方,由主动方负责维护关联关系,默认是false
inverse设置为true,不负责维护关联关系
通常set这边不维护关系,由对方维护
<set name="employees" cascade="delete" inverse="true">
<key column="`DEPTNO`" />
<one-to-many class="cn.st.hibernatedemo.entity.Employee" />
</set>
- 在建立两个对象的双向关联时,应该同时修改两个关联对象的相关属性
- 建议inverse设置为true
3.4、多对多映射
例:一个员工可以参加多个项目,一个项目可以有多个员工
要有一个关系表PROEMP
注:级联操作建议save-update,all或delete,删除员工同时也会删除项目
3.5、延迟加载
延迟加载(lazy load懒加载)是在真正需要数据时才执行SQL语句进行查询,避免了无谓的性能开销
多对一关联的查询策略 | / |
---|---|
类级别的查询策略 | true或false |
一对多和多对多关联的查询策略 | / |
延迟加载策略的设置分为 | 延迟、延迟增强、不延迟 |
3.5.1、一对多和多对多关联的查询策略
lazy属性值 | 加载策略 |
---|---|
true(默认) | 延迟加载 |
false | 立即加载 |
extra | 增强延迟加载 |
只有执行到这个对象,如果dept..getDeptName()
才会去查询这对象
Dep artment dept1 = session.load(Department.class, 50);
System.out.println(dept1.getDeptName());
get没有调用对象也会去查询
Department dept = session.get(Department.class, 50);
如果想要取消掉延迟加载,但是又想用load方法
可以到配置文件关闭延迟加载(load默认延迟加载)
<class name="cn.st.hibernatedemo.entity.Department" table="`DEPT`" lazy="false">
查部门时把所有员工一起查出来
3.5.2、多对一关联查询策略
lazy属性值 | 加载策略 |
---|---|
proxy(默认) | 延迟加载 |
no-proxy | 无代理延迟加载 |
false | 立即加载 |
<many-to-one name="dept" class="cn.st.hibernatedemo.entity.Department" column="`DEPTNO`" lazy="proxy" />
4、HQL连接查询和注解
4.1、掌握Hibernate的连接查询
连接类型 | hql语法 |
---|---|
内连接 | inner join 或 join |
迫切内连接 | inner join fetch 或 join fetch |
左外连接 | left outer join 或 left join |
迫切左外连接 | left outer join fetch 或 left join fetch |
右外连接 | right outer join 或 right join |
- 延迟加载,返回的是数组类型
//内连接
String hql = "from Department d join d.employees";
Query query = session.createQuery(hql);
List<Object[]> list = query.list();
for (Object[] obj : list) {
Department dept = (Department)obj[0];
System.out.println(dept.getDeptName());
for (Employee emp : dept.getEmployees()) {
System.out.println("\t"+emp.getEmpName());
}
System.out.println("-----------------------");
}
- 立即加载,查询返回的结果是实体类型
//迫切内连接
String hql = "select distinct d from Department d join fetch d.employees";
Query<Department> query = session.createQuery(hql);
List<Department> list = query.list();
for (Department dept : list) {
System.out.println(dept.getDeptName());
for (Employee emp : dept.getEmployees()) {
System.out.println("\t"+emp.getEmpName());
}
System.out.println("-----------------------");
}
- 左外连接
查询结果会包含没有员工的部门
//左外连接
String hql = "from Department d left join d.employees";
Query query = session.createQuery(hql);
List<Object[]> list = query.list();
for (Object[] obj : list) {
Department dept = (Department)obj[0];
System.out.println(dept.getDeptName());
for (Employee emp : dept.getEmployees()) {
System.out.println("\t"+emp.getEmpName());
}
System.out.println("-----------------------");
}
- 迫切左外连接
// 迫切内连接
String hql = "select distinct d from Department d left join fetch d.employees";
Query<Department> query = session.createQuery(hql);
List<Department> list = query.list();
for (Department dept : list) {
System.out.println(dept.getDeptName());
for (Employee emp : dept.getEmployees()) {
System.out.println("\t"+emp.getEmpName());
}
System.out.println("-----------------------");
}
- 右外连接
String hql = "from Department d right join d.employees";
Query<Department> query = session.createQuery(hql);
List<Department> list = query.list();
for (Department dept : list) {
System.out.println(dept.getDeptName());
for (Employee emp : dept.getEmployees()) {
System.out.println("\t"+emp.getEmpName());
}
System.out.println("-----------------------");
}
区别
迫切 | 不迫切 |
---|---|
立即查询 | 延迟加载 |
返回Object数组类型 | 延返回实体类型 |
连接前提要求有关联
没有关联为等值连接
- 等值关联
适用于两个类之间没有定义任何关联时
在where子句中通过属性作为筛选条件
Hibernate会根据关联关系自动使用等值连接(等效于内连接)查询
允许以更加面向对象的方式编写HQL语句,更多地依据对象间的关系,而不必考虑数据库结构
// //等值连接
String hql = "from Department d ,Employee e where e.dept=d";
Query query = session.createQuery(hql);
List<Object[]> list = query.list();
for (Object[] obj : list) {
Department dept = (Department)obj[0];
System.out.println(dept.getDeptName());
for (Employee emp : dept.getEmployees()) {
System.out.println("\t"+emp.getEmpName());
}
System.out.println("-----------------------");
}
隐式内连接
from Emp e where e.dept.dname = 'SALES' -- 用于where子句
select empno, ename, dept.dname from Emp -- 用于select子句
4.2、聚合函数
函数名称 | 说明 |
---|---|
count() | 统计记录条数 |
sum() | 求和 |
max() | 求最大值 |
min() | 求最小值 |
avg() | 求平均值 |
4.3、分组查询、子查询
关键字 | 说明 |
---|---|
all | 返回的所有记录 |
any | 返回的任意一条记录 |
some | 和“any”意思相同 |
in | 与“=any”意思相同 |
exists | 至少返回一条记录 |
大于小于(><)可以和all、any
比子查询的所有值都大
String hql = "from Employee e " +
"where e.salary < any (select salary from Employee where job='CLERK')";
Query<Employee> query = session.createQuery(hql);
List<Employee> list = query.list();
for(Employee emp : list) {
System.out.println(emp.getEmpName());
}
HQL提供了操作集合的函数或属性
函数或属性 | 说明 |
---|---|
size() 或size | 获取集合中元素的数目 |
minIndex()或minIndex | 对于建立了索引的集合,获得最小的索引 |
maxIndex()或maxIndex | 对于建立了索引的集合,获得最大的索引 |
minElement()或minElement | 对于包含基本类型元素的集合,获取最小值元素 |
maxElement()或maxElement | 对于包含基本类型元素的集合,获取最大值元素 |
elements() | 获取集合中的所有元素 |
4.4、查询性能优化
- Hibernate查询优化策略
使用延迟加载等方式避免加载多余数据
通过使用连接查询,配置二级缓存、查询缓存等方式减少select语句数目
结合缓存机制,使用iterate()方法减少查询字段数及数据库访问次数
对比list()方法和iterate()方法 - HQL优化
注意避免or、not、like(避免使用前模糊)使用不当导致的索引失效
注意避免having子句、distinct(尽量查询出结果就是没有重复的)导致的开销
注意避免对索引字段使用函数或进行计算导致的索引失效
%a,a%(前面的确定,可以使用后模糊)
4.5、掌握注解
代替hbm.xml文件完成对象-关系映射
使用步骤如下:
使用注解配置持久化类以及对象关联关系
在Hibernate配置文件(hibernate.cfg.xml)中声明持久化类
注解 | 含义和作用 |
---|---|
@Entity | 将一个类声明为一个持久化类(实体类中加) |
@Table | 为持久化类映射指定表 |
@Id | 声明了持久化类的标识属性(实体类的属性) |
@GeneratedValue | 定义标识属性值的生成策略 |
@SequenceGenerator | 定义序列生产器 |
@Column | 将属性映射到列(字段)(实体列的属性中对应的列名) |
@Transient | 将忽略这些属性 |