目录

Hibernate与ORM的关系是什么?

Hibernate执行流程

搭建一个Hibernate环境,步骤如下:

dao层保存一个对象示例

Hibernate主配置文件

Hibernate映射配置

一对多与多对一映射

多对多的映射

如何提高hibernate执行效率

cascade属性与inverse属性的区别

对象的状态

一级缓存

不同的Session会不会共享缓存数据

createQuery接口下的list()与iterator()查询的区别

懒加载 lazy

get()与load()的区别

一对一的映射

HQL查询

HQL查询优化

Hibernate对分页的支持

二级缓存


Hibernate与ORM的关系是什么?

Hibernate是ORM的实现,ORM是一种思想。

O:object,对象

R:relationship,关系

M:mapping,映射

ORM可以解决:

存储:把对象的数据直接保存到关系型数据库。

获取:直接从关系型数据库获取到一个对象。

Hibernate实现了ORM,就可以做到以上2点,在XML中配置映射文件,有三个对应,分别是:

① 对象与表的对应

② 属性与字段的对应

③ 类型的对应

Hibernate执行流程

1 加载Hibernate主配置文件。

创建Configuration对象,也就是配置管理类对象。通过配置管理类对象调用它的configure(),默认加载src/hibernate.cfg.xml文件;配置管理类对象的作用就是读取这个主配置文件,然后根据读取到的主配置文件创建SessionFactory,通过配置管理类对象调用它的buildSessionFactory()来创建建SessionFactory;

2 得到SessionFactory对象。

通过配置管理类对象调用buildSessionFactory()得到SessionFactory对象。SessionFactory是用来统一管理数据库连接的,一个SessionFactory代表一个数据库。可以说它代表整个hibernate.cfg.xml;它是单例的,一个应用程序只需要这一个对象

3 得到Session对象。通过SessionFactory对象调用openSession()得到Session对象。Session对象维护了一个连接(Connection),代表与数据库连接会话

4 得到Transaction对象,开启事物。通过Session对象调用beginTransaction()得到Transaction对象

5 在事物之间进行持久化操作

6 提交事务。进行查询操作,不需要提交事务,其他操作需要提交事务

7 关闭会话(Session)

8 关闭会话工厂(SessionFactory)注意:会话工厂一般不关,关闭再次获取session就报错了。

搭建一个Hibernate环境,步骤如下:

1 下载源码

版本:hibernate-distribution-3.6.0.Final

2 引入jar文件

hibernate3.jar(核心) + required目录下的6个(必须引入) + jpa目录(事物相关) + 数据库驱动包

3 对象以及对象的映射

4 Hibernate主配置文件。主配置文件在源码的.../project/etc/hibernate.cfg.xml复制一份即可,放在src目录下,主配置文件包括:

① 数据库连接参数配置

② 其他相关配置

③ 加载映射文件,映射文件可在源码全局搜索复制一份(*.hbm.xml),如何加载?

<mapping resource="映射文件的路径">

dao层保存一个对象示例

//创建配置管理类对象(作用:读取hibernate主配置文件、根据读取到的主配置文件创建sessionFactory)
Configuration cfg = new Configuration();
//默认加载src/hibernate.cfg.xml文件,如果不是默认配置文件,方法中需指定
cfg.configure();
//创建session的工厂对象,或者说代表整个hibernate.cfg.xml
SessionFactory sessionFactory = cfg.buildSessionFactory();
//打开session。session对象维护了一个连接(Connection),代表与数据库连接会话
Session session = sessionFactory.openSession();
//开启事务
Transaction trans = session.beginTransaction();
session.save(对象); //在事物之间执行数据库操作
//事物提交
trans.commit();
//关闭session
session.close();
//关闭session工厂
sessionFactory.close();

开启事物还有一种写法,如下:

session.beginTransaction(); //不返回Transaction对象
... ...
session.getTransaction().comit(); //直接用session对象得到Transaction对象提交事务

session的其它方法解释:

更新 update(obj)

需要根据对象的主键进行更新

保存和更新

saveOrUpdate(obj)

没有设置主键,执行保存;有设置主键,执行更新

删除 void delete(Object arg0)

根据id删除

先根据id查询对象,在判断(判断查询出来的对象是否为null)删除

Object merge(Object o) 这个方法没有研究过,牵扯到session的一级缓存,如果当一级缓存中有不同的对象,使用update可能会修改失败,遇到修改失败的情况下,使用merge()
查询

1 get(Class arg0, Serializable arg1)

2 load()

3 Query createQuery(String arg0)

4 Criteria createCriteria(Class arg0)

5 SQLQuery createSQLQuery(String arg0)

1 根据id(主键)查询。需要强转成对象,强转的对象就是get()的第一个参数类型的对象。

2 根据id(主键)查询。支持懒加载

3 HQL查询: hibernate query language的缩写。查询的是对象以及对象的属性,所以区分大小写

返回Query,是个接口,此接口常用的方法有:

setParameter(int arg0, Object arg1):第一个参数从0开始

setParameterList(String s, Object[] objects):凯越删除用过,尚未研究

executeUpdate():凯越删除用过,尚未研究

setFirstResult(int arg0):分页的方法。设置返回第一条起始行号,从0开始

setMaxResults(int arg0):分页的方法。设置返回起始行号的条目数

Object uniqueResult():返回唯一结果

List<E> list():查询全部,调用这个方法才会执行去数据库查询

4 QBC查询(了解):query by criteria的缩写。属于完全面向对象的查询

p.s. 如果加了条件:用Criteria对象的add(Criterion agr0);

方法中的Criterion就是条件表达式,在方法中用Restrictions的静态方法eq(String propertyName, Object value),判断某个属性等于某个值。Restrictions的静态方法
常用的有:

allEq(Map arg0): 查询的属性多的话用这个,key就是eq()的第一个参数,value就是eq()的第二个参数

idEq(Object value): 根据主键查询,跟eq()效果是一样的

5 SQL查询:structured query language的缩写。查询的是表以及字段,不区分大小写它会把每一行记录封装为对象数组,再添加到List集合。复杂的查询,就需要使用原生SQL去查询

p.s. 也可以把每一行记录封装为指定的对象类型,调用SQLQuery的addEntity(Class arg0)

缺点:不能跨数据库平台。

Hibernate主配置文件

hibernate主配置文件中主要配置:

1 数据库连接参数配置

2 其他相关配置,例如:

//控制台显示hibernate在运行的时候执行的sql语句
<property name="hibernate.show_sql">true</property>//控制台格式化hibernate在运行时候执行的sql语句
<property name="hibernate.format_sql">true</property>//*自动建表,ddl是DBMS的组成之一“数据定义语言”,用来描述数据库结构*
//每次都重新创建表,如果表已经存在就先删除再创建 “掌握”
<property name="hibernate.hbm2ddl.auto">create</property>
//如果表不存在就创建,表存在就不创建 “掌握”
<property name="hibernate.hbm2ddl.auto">update</property>
//每次在创建sessionFactory的时候去执行创建表,也就是应用程序启动的时候去创建表,当调用SessionFactory的close()删除表 “了解”
<property name="hibernate.hbm2ddl.auto">create-drop</property>
//表示执行验证。当映射文件的内容与数据库表结构不一样的时候就报错
<property name="hibernate.hbm2ddl.auto">validate</property>

3 要注册的映射文件。注意:映射文件要放到最下面

提示:常用的配置查看源码,有些配置源码是没有的!如果在写配置的过程中,property的name属性值弹不出来,只要知道这一个目录就ok了,hibernate-distribution-3.6.0Final\project\etc\hibernate.properties。如果property标签体的类弹不出来,首先看引入的jar包有没有,如果有引入,还是弹不出来,就只能手写。

自动建表可以通过上面配置来建表,也可以通过代码来建表,如下:

//创建配置管理类对象
Configuration cfg = new Configuration();
//加载主配置文件
cfg.Configure();//创建工具类对象
SchemaExport export = new SchemaExport(cfg);
//建表。create(boolean script, boolean export) 方法的第一个参数代表是否在控制台显示建表语句,第二个参数代表是否执行建表语句
export.create(true, true);

Hibernate映射配置

映射配置文件除了在主配置文件中配置以外,还可以使用代码的方式来加载配置文件,这种方式一般在测试时候用

Configuration cfg = new Configuration();
cfg.configure();//addClass(Class persistentClass) 会自动加载当前包下的与类名称一样的后缀为.hbm.xml文件
cfg.addClass();SessionFactory sessionFactory = cfg.buildSessionFactory();
... ...

映射文件的作用:映射一个实体类对象,可实现将对象直接保存在数据库中。

为什么要设置主键?

数据库存储的数据都是有效的,必须保持唯一。

为什么把id作为主键?

因为表中通常找不到合适的列作为唯一列(主键),所以为了方便用id列,它可以保证每行数据的“唯一性”。

除了用id列以外,如果找不到合适的列作为主键,还可以使用联合/复合主键,即多列的值作为一个主键,从而确保记录的唯一性。

映射文件的各个元素以及各个元素常用的属性介绍
元素 元素的属性 对元素以及元素属性的说明
<hibernate-mapping>

1 package

2 auto-import

映射文件的根节点元素

1 这个属性是可选的。因为映射文件的配置就是对象要保存到数据库中,那么到底是哪个对象要保存在数据库中呢?当然是当前包下的类,所以,如果没有指定package属性,那么在class元素的name属性中必须指定包名+类名

2 默认为true。该属性的作用是在写hql语句的时候,会自动在对象的前面加上包名,如果显示设置为false,则在写hql语句的时候,在对象前面加上包名,比如:session.createQuery("from net.csdn.bk.User").list();

<class>

1 name

2 table

1 指定要映射的对象

2 指定对象对应的表,如果没有指定,默认与对象名称一样

<id>

1 name

2 column

表示单列作为主键的映射

1 指定对象的id属性名称

2 指定对象id属性对应表的主键字段的名称。凡是元素有column属性的,既可以作为该元素的属性,也可以作为该元素的子元素

<generator/> 1 class

<id>的子元素。

1 代表主键的生成策略,可在hibernate API中查看到(5.1.2.2.1 Various additional generators),常用的属性值有:

identity:自增长(mysql、db2 ...)

sequence:自增长(序列,oracle中自增长是以序列的方式实现)

native:自增长(会根据底层数据库的自增长方式选择identity或sequence),比如,如果是mysql数据库,采用的自增长方式是identity
,如果是oracle数据库,采用的自增长方式是sequence,这个属性值比较智能

uuid:指定uuid随机生成唯一值。前提是实体类的id类型必须是String类型。

foreign:外键的方式,one-to-one

assigned:手动指定主键的值

increment:自增长。会有并发访问的问题,一般在服务器集群环境下使用会存在问题,所以不怎么使用

<composite-id> 1name

表示要对多个属性作为主键,也就是联合/复合主键的映射

name属性值要的是一个对象,这个对象需要实现序列化接口,需要哪些属性作为主键,这个对象就写哪些属性,这个类称之为复合主键类。之后,这个对象写在另一个类当中,作为成员变量。

<key-property>

1 name

2 type

<composite-id>的子元素。

代表复合主键类的属性

1 name属性值就是复合主键实体类对象的属性。
复合主键实体类为什么要实现Serializable接口?

在通过主键查询get()第二个参数是根据主键查询的,主键要的是Serializable接口,所以作为复合主键的实体类需要实现此接口。如果没有实现此接口,编译期间就会报错,其他方法比如save()、update(),编译期间不会报错,但是运行就会报错,提示必须实现Serializable接口

<property>

1 name

2 column

3 length

4 type

1 指定对象的属性名称

2 指定对象属性对应表的字段的名称,如果不写,默认与对象属性一致

注意:如果列名称是数据库关键字,需要用反引号或者修改列名称,反引号(``)是在Esc键下面,英文状态下

3 指定字符长度,如果不写,字符串默认为255,整数默认11

4 指定映射表的字段的类型,如果不指定会默认匹配对象属性的类型,分别有:

java类型:必须写全名,如:java.lang.String

hibernate类型:直接写类型,都是小写

建议将type写出来,在集合映射中,element标签的type就要写出来,否则报错。

<set>

1 name

2 table

3 inverse

4 cascade

5 lazy

6 order-by

set集合属性的映射

比如:一个用户可以有多个地址,那么就需要在用户类设置一个set集合来保存这些多个地址,这个时候就需要用到set集合属性的映射

1 要映射的set集合的属性

2 集合属性要映射到的表,可指定可不指定。如果是一对多与多对一映射关系中,也就是集合对象映射关系中,table最好不要写,因为多方肯定会有映射文件,class标签肯定写了table属性了,这里指定,你需要与class标签的table属性值保持一致,否则报错。

3 是在双向关联维护关系中起作用的,表示控制权是否转移,默认false,就是控制权不转移,也就是一方有控制权可以维护(保存、获取)多方。

注意:inverse属性只在一方才有。
以下是在双向关联维护关系中inverse属性操作数据是否对数据有影响

① 保存数据:有影响。如果显示设置inverse="true",表示一方的控制权维护转移,一方不再有维护多方的权利。执行保存操作,一方与多方的数据都会保存,但多方外键字段为null。

inverse="false",不会出现多方外键字段为null的情况。

② 获取数据:如果显示设置inverse=“true ”,对获取数据没有影响

③ 解除关联关系:意思就是将多方表的外键字段设置为null,就叫解除关联关系。主键查询的时候用,inverse属性默认前提下,再调用集合的clear()就能解除双向关联关系;如果设置为true,再调用集合的clear()不能解除关联关系,不会报错,不会生成update语句

④ 删除数据:默认inverse=“false”,在删除一方数据的时候,一方关联的多方数据也会删除。会先生成一条清空外键引用的语句(update语句,将引用的外键字段设置为null),再生成删除(delete)语句删除数据;如果设置为true,表示一方没有控制权维护的权利,因为存在外键引用,所以一方删除失败,违反主外键引用约束,除非删除的数据不存在引用,可以直接删除

4 表示是否级联的意思,默认值为none不级联。常用的值有:

save-update:级联保存或更新。如果仅仅保存了一方的对象,没有保存一方关联的对象,会报TransientObjectException,要解决此错误,就需要设置cascade="save-update",一方关联的对象就会自动保存。

save-update,delete:级联保存、更新、删除。inverse属性值不管是false还是true,在删除一方的时候,会将一方关联的多方的数据也会删除,会先执行一条update语句,将多方的外键清空(设置为null),在执行delete语句

delete:删除

all:级联保存、更新、删除

all-delete-orphan:未知,凯越项目中使用过。

5 集合属性默认懒加载。详见目录(懒加载 lazy)

6 因为set集合是无序的,这个属性可以进行排序,例:

order-by="reply_time asc"

<key> 1 column

<set>元素的子元素。

1 集合表的外键字段的名称

这个标签很重要,因为这个标签是外键字段的名称,所以在查询的时候才能查出来,查询靠的就是这个配置。

<element>

1 column

2 type

<set>元素的子元素。关联映射简单集合映射会用到,简单集合映射指的是集合存放的是非JavaBean类型的,也就是存放字符串等类型的数据用这个元素。

1 集合表的非外键字段的名称

2 集合表的非外键字段的名称的类型,这个必须显示指定。因为没有实体类去对应,不指定就报错

<one-to-many/> 1 class

<set>元素的子元素。一对多映射,一方维护多方。

1 要映射Set集合的属性存储元素的数据类型,也就是多方的类

<many-to-many>

1 column

2 class

<set>元素的子元素。

1 中间表或者多方表的外键字段的名称。(中间表外键字段的名称是中间表引用<set>标签的子标签的<key>标签的column属性的属性值,需要保持一致)

2 中间表引用<set>的name属性值对应的泛型数据类型

<many-to-one/>

1 name

2 column

3 class

4
cascade

5 fetch

注意,该元素是在<class>元素下。多对一映射,多方维护一方。

它可以将一方的主键字段映射为多方表的外键字段。在hibernate中,唯有这个标签才能做到这一点。

1 一方的对象属性;

2 一方的主键字段;

3 一方的类

4 同<set>的cascade

5 未知,凯越项目中使用过

<list>

1 name

2 table

list集合属性的映射

1 指定要映射的list集合的属性

2 集合属性要映射到的表

<key> 1 column

<list>元素的子元素。

1 指定集合表的外键字段的名称

<list-index> 1 column

<list>元素的子元素。

1 与set集合映射有所不同,因为list是有序的,需要保证list集合的有序。所以column属性值是额外指定排序列字段的名称

<element>

1 column

2 type

<list>元素的子元素。

同set元素的element元素

<array>

数组的映射

映射方式和list集合一样,只不过数组长度不可变

<map>

1 name

2 table

map集合属性的映射

1 指定要映射的map集合的属性

2 集合属性要映射到的表

<key> 1 column

<map>元素的子元素。

1 指定集合表的外键字段的名称

<map-key>

1 column

2 type

<map>元素的子元素。

1 指定集合表的非外键字段的key名称

2 指定集合表的非外键字段的key名称的类型

<element>

1 column

2 type

<map>元素的子元素。

1 指定集合表的非外键字段的value名称

2 指定集合表的非外键字段的value名称的类型


一对多与多对一映射

需求:部门与员工

一对多:一个部门 拥有 多个员工

多对一:多个员工 属于 一个部门

设计截图如下:

代码结构以及代码如下:

Dept类,省略get() set()

public class Dept {private int deptId;private String deptName;private Set<Employee> emps = new HashSet<Employee>();}

Employee类,省略get() set()

public class Employee {private int empId;private Dept dept;private String empName;private double salary;}

Dept.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 package="hibernateMapping"><class name="Dept" table="dept"><id name="deptId"><generator class="native"/></id><property name="deptName" length="20"></property><set name="emps" table="employee"><key column="deptId"></key><one-to-many class="Employee"/></set></class>
</hibernate-mapping>

Employee.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 package="hibernateMapping"><class name="Employee" table="employee"><id name="empId"><generator class="native"/></id><many-to-one name="dept" column="deptId" class="Dept"></many-to-one><property name="empName" length="20"></property><property name="salary" type="double"></property></class>
</hibernate-mapping>

hibernate.cfg.xml配置文件

<!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="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property><property name="hibernate.connection.url">jdbc:mysql:///hibernate</property><property name="hibernate.connection.username">root</property><property name="hibernate.connection.password">123456</property><property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property><property name="show_sql">true</property><!-- <property name="hibernate.format_sql">true</property> --><property name="hibernate.hbm2ddl.auto">create</property></session-factory>
</hibernate-configuration>

TestHibernate测试类。以保存为例

public class TestHibernate {private static SessionFactory sessionFactory;static {sessionFactory = new Configuration().configure().addClass(Dept.class).addClass(Employee.class).buildSessionFactory();}//从一方进行保存操作@Testpublic void save() {Session session = sessionFactory.openSession();session.beginTransaction();//创建部门对象Dept dept = new Dept();dept.setDeptName("开发部");//创建员工对象Employee employee = new Employee();employee.setEmpName("张三");Employee employee2 = new Employee();employee2.setEmpName("赵六");//通过部门Dept维护员工Employeedept.getEmps().add(employee);dept.getEmps().add(employee2);session.save(employee);session.save(employee2);session.save(dept); //保存部门对象同时保存员工对象。这里保存前后顺序没有影响session.getTransaction().commit();session.close();}}

执行save(),控制台打印如下:

之所以打印了两条update语句,是因为部门维护员工,hibernate要确保每个员工的外键是同一个部门的主键。

数据库如下:

以上是通过部门Dept维护员工Employee,现在通过员工Employee来维护部门Dept。只编写持久化操作部分,如下:

//从多方进行保存操作
@Test
public void save2() {... ...Dept dept = new Dept();dept.setDeptName("人力资源部");Employee employee = new Employee();employee.setEmpName("李四");Employee employee2 = new Employee();employee2.setEmpName("王五");//通过员工Employee维护部门Deptemployee.setDept(dept);employee2.setDept(dept);session.save(dept); //先保存一方session.save(employee); //再保存多方session.save(employee2);... ...
}

更改hibernate配置文件中的自动建表配置为update,执行save2(),控制台打印以及数据库如下:

如果是先保存多方,再保存一方,会多生成update语句,因为在保存员工对象的时候没有deptId,所以要update语句来维护。所以执行保存操作一般从多方来进行,这样会少生成语句。从这个例子可以得出结论:在一对多映射中,由“多方来维护映射的关系”,“先从一方保存数据,再保存多方的数据”,满足这两个条件就不会生成update语句,从而提高hibernate的执行效率。

这个结论可以用我们日常中老师与学生的关系来讲,要是一个老师记住班里面全部学生的名字,会很费劲,但是学生要记老师的名字就会很容易。

例:以获取为例,通过一方获取多方的数据,通过多方获取一方的数据省略

@Test
public void get() {... ...//通过一方获取多方的数据Dept dept = (Dept) session.get(Dept.class, 1);System.out.println(dept.getDeptName());Set<Employee> emps = dept.getEmps(); //懒加载,用到(员工)的时候才去查询,而不是只需要查询部门就把该部门下的所有员工都查询出来for (Employee employee : emps) {System.out.println(employee.getEmpName());}... ...
}

执行get(),控制台打印如下:

一对多与多对一的关联关系有:

双向关联:如果一方映射与多方的映射都配置了,这种就叫双向关联。这种双方都可以维护

单向一对多:只配置一方,这种就叫单向一对多,维护的话只能从一方进行维护。

单向多对一:只配置多方,维护的话只能从多方进行维护。

多对多的映射

需求:项目与开发人员

多对多:一个项目有多个开发人员;一个开发人员参与多个项目

如:

电商系统 <— —> 乃万、艾薇儿

OA系统   <— —> 乃万、霉霉

以下是设计的截图,代码与双向关联类似

注意:在多对多保存的时候,只能通过一方来维护另一方,不能通过另一方再次维护一方,会报BatchUpdateException: Duplicate entry 'xxxx' for key 'PRIMARY',也就是重复的主键

inverse属性在多对多关联维护关系中操作数据是否对数据有影响

① 保存数据:有影响。如果显示设置inverse=“true ”,表示双向一对多的一方的控制权维护转移,双向一对多的一方不再有维护的权利。执行保存操作,不会往中间表插入数据

② 获取数据:如果显示设置inverse=“true ”,对获取数据没有影响

③ 解除关联关系:有影响。默认inverse=“false”,可以解除关联关系,调用集合的clear(),会删除中间表外键字段的数据,但中间表外键字段引用的表的数据不会删除;如果设置为true,则不能解除关联关系,也不会报错,也不会生成delete语句

④ 删除数据:有影响。默认inverse=“false”,先删除中间表外键字段的数据,会先生成一条删除(delete)语句,将中间表外键字段删除,再生成一条删除(delete)中间表引用表的数据;如果设置为true,表示双向一对多的一方没有控制权维护的权利,因为存在外键引用,所以删除失败,违反主外键引用约束,除非删除的数据不存在引用,可以直接删除


如何提高hibernate执行效率

在一对多与多对一的关联关系中,保存数据最好通过多方来维护关系,这样可以减少update语句的生成,从而提高hibernate的执行效率。

cascade属性与inverse属性的区别

设置了级联属性值是save-update,delete,在删除数据的时候,不管inverse属性的值是false还是true,都会将一方以及多方引用的数据删除,只不过如果inverse属性值是false,在删除数据的时候,会先清空外键引用(也就是先将外键表的外键字段设置为null),然后执行delete语句。

以下是测试控制台打印的语句截图:

如果inverse属性值是true,在删除数据的时候,没有update语句生成,直接生成的是delete语句。因为没有update语句生成,所以此设置执行效率高。

以下是测试控制台打印的语句截图:

对象的状态

举例:User user = new User();

hibernate中对象的状态:① 临时/瞬时状态 ② 持久化状态 ③ 离线/游离状态

① 临时/瞬时状态

直接new出来的对象就是临时/瞬时状态

特点:

不处于Session的管理;

数据库中没有对象的记录

② 持久化状态

当调用Session的方法的时候,对象就是持久化状态。处于持久化状态的对象,当对 对象属性进行更改的时候,会反映到数据库中。

特点:

处于Session的管理;

数据库中有对象的记录

③ 离线/游离状态
Session关闭后,对象的持久化状态就变成游离/游离状态了,对象处于离线状态表示这个对象不再受hibernate管理。

不处于Session的管理;

数据库中可能还有对象的记录

一级缓存

hibernate的一级缓存是由Session提供的,也叫Session的缓存,存在于Session的生命周期内。当调用Session的方法(save/saveOrUpdate/get/load/list/iterator)的时候,除了delete(),都会把对象放入Session缓存中,比如在第一次调用get()查询数据的时候,会执行一条select语句去数据库查询数据,把查询出来的对象放入缓存,当再次调用update()更新数据,会去缓存中对比,如果更新的对象和缓存中的对象是一样的,直接获取缓存中的对象,不会再生成update语句,如果缓存中没有,才会生成update语句。所以Session的缓存可以在Session范围内减少对数据库的访问次数,它只在Session范围内有效,Session关闭,一级缓存失效。

为什么要用一级缓存?

减少对数据库的访问次数,从而提高hibernate的执行效率。

一级缓存特点:只在当前Session范围内有效,作用时间短,效果不是特别明显,在短时间内多次操作数据库,效果比较明显。

Session的缓存由hibernate维护,不能直接去操作缓存中的内容,如果想操作缓存中的内容,必须通过hibernate提供的evit()/clear()操作。

Session的缓存相关的几个方法的作用:

flush(): 让一级缓存中的内容与数据库同步

evict(Object arg0): 清空一级缓存中指定对象

clear(): 清空一级缓存中所有对象

flush()测试如下:

只编写重要部分代码,其他对象如sessionFactory、Dept在一对多与多对一映射部分TestHibernate类已有

@Test
public void flush() {Session session = sessionFactory.openSession();session.beginTransaction();Dept dept = (Dept) session.get(Dept.class, 9);dept.setDeptName("测试部1");dept.setDeptName("测试部2");session.getTransaction().commit();session.close();
}

commit()等于是同步数据库,hibernate会再次调用Session的flush(),并且以最后一条更改为准,所以上面代码会执行一条update语句,控制台打印如下:

    ... ...    Dept dept = (Dept) session.get(Dept.class, 9);dept.setDeptName("测试部1");session.flush(); //让一级缓存与数据库同步dept.setDeptName("测试部2");... ...

get()会先去查询,执行一条select语句,setDeptName()给对象设置了值,又显示调用了flush(),所以会与数据库同步,执行一条update语句,后面又再次setDeptName()给对象设置了值,commit()本身调用一次flush(),所以会有两条update语句生成,如下:

Session的缓存相关的几个方法的使用场景:

在批量操作的时候使用,比如同一个session批量的执行了update语句或者批量的执行了session的其他方法,除了delete,这种情况下,就需要使用,因为批量操作也就是调用了session的相关方法,调用就会放到session的缓存当中,这样非常容易内存溢出。使用的顺序:

先调用session的flush()再执行session的clear(),一定要按照这样的顺序执行。先执行flush()意思是在批量操作完成后,先去与数据库进行同步,同步完毕然后再清空session的缓存,如果反过来,先清空一级缓存,再与数据库同步,缓存都清空了,还同步什么?

不同的Session会不会共享缓存数据

不会。每个Session维护自己的缓存区

createQuery接口下的list()与iterator()查询的区别

查询的区别:

list(): 会一次把所有的记录都查询出来。如下:

iterator(): 是n+1查询,n表示数据库总记录数,1表示先发送一条语句查询所有记录的主键,再根据每一个主键再去数据库查询,也就是比总记录数多一条select语句。如下:

缓存的区别:

list(): 会放入缓存,但不会从缓存中获取数据

只编写重要部分代码,其他对象如sessionFactory、Dept在# 一对多与多对一映射 #部分TestHibernate类已有,测试代码及控制台截图如下:

@Test
public void list_iterator() {Session session = sessionFactory.openSession();session.beginTransaction();System.out.println("----------第1次执行list()-----------");Query query = session.createQuery("from Dept");List<Dept> list = query.list();for (Dept dept : list) {System.out.println(dept.getDeptName());}System.out.println("----------第2次执行list()-----------");List<Dept> list2 = query.list();for (Dept dept : list2) {System.out.println(dept.getDeptName());}session.getTransaction().commit();session.close();
}

不管执行几次list(),都会是一次把所有的记录都查询出来。证明:list()不会从缓存中取数据

证明:list()会放入缓存,如下:

    ... ...    System.out.println("----------list()-----------");Query query = session.createQuery("from Dept");List<Dept> list = query.list();for (Dept dept : list) {System.out.println(dept.getDeptName());}System.out.println("----------iterator()-----------");Iterator<Dept> iterator = query.iterate();while (iterator.hasNext()) {Dept dept = (Dept) iterator.next();System.out.println(dept.getDeptName());}... ...   

如下:执行iterator(),没有再根据每一个主键再去数据库查询,说明执行list()放入缓存,执行的iterator()去缓存中取的数据。证明:list()会放入缓存

iterator(): 会放入缓存,会从缓存中获取数据

只编写重要部分代码,其他对象如sessionFactory、Dept在# 一对多与多对一映射 #部分TestHibernate类已有,测试代码及控制台截图如下:

@Test
public void list_iterator() {Session session = sessionFactory.openSession();session.beginTransaction();System.out.println("----------第1次执行iterator()-----------");Query query = session.createQuery("from Dept");Iterator<Dept> it = query.iterate();while (it.hasNext()) {Dept dept = it.next();System.out.println(dept.getDeptName());}System.out.println("----------第2次执行iterator()-----------");query.iterate();while (it.hasNext()) {Dept dept = it.next();System.out.println(dept.getDeptName());}session.getTransaction().commit();session.close();
}

第一次执行iterator(),先发送一条语句查询所有记录的主键,再根据每一个主键再去数据库查询,第二次执行没有再根据每一个主键再去数据库查询,证明:会放入缓存,会从缓存中获取数据

get()与load()的区别

相同点:都是根据主键查询

区别:

get()是及时加载,只要调用get(),立刻向数据库执行select语句查询

load()默认使用懒加载,与get()一样,返回的是一个对象,当用到对象属性的时候,才向数据库执行select语句查询,如果单单是返回对象后,对象没有调用自身的属性是不会执行select语句的。

懒加载 lazy

概念:当用到数据的时候,才向数据库查询,这就是hibernate懒加载的特性。

目的:提高程序执行效率

懒加载分为:类级别的懒加载和集合属性的懒加载。

类级别的懒加载:就是在映射文件的class标签写上lazy=true,其中,类级别的懒加载默认就是true,所以一般不写,如果写成false,在调用load()通过主键查询的时候,load()就不会默认为懒加载了,就变成了及时加载了load()的用法就与get用法是一样的了。

集合属性的懒加载:就是在一对多与多对一的时候,在一方的<set>标签上写,默认也是true。

lazy属性值有:true、false和extra。true就是使用懒加载,false就是关闭懒加载;

extra也是关闭懒加载,extra使用场景在一对多与多对一映射中是性能是最佳的,一方映射文件的<set>标签上,写上lazy=extra会提高效率并且使用get()查询的时候。(

lazy=true + get()组合使用结果:一方映射文件的<set>标签上没有写lazy属性,就是默认懒加载为true,一方对象调用集合属性的时候,才去执行查询语句查多方的数据,一方对象的集合属性类型是集合,就有集合的一些方法,比如size(),如果我现在就只想看一下多方表的size,有多少条记录,并不想去查询多方表的数据,结果还是会去执行一条select语句去查询的;如果关闭懒加载,lazy写成false,只是关闭了懒加载,一下同时把一方以及多方的数据都会查出来,调用集合属性的size()等其他方法,还是会去执行select语句。

lazy=extra + get()组合使用结果:extra也是懒加载,查询返回对象的时候,调用对象的集合属性的时候,也就是要查询多方表的时候,才向数据库发送查询的sql,如果调用集合的size(),不想查询多方表的数据,那么它就很智能,就会执行了一条统计语句,返回数据总条目数长度,如果调用集合的isEmpty(),也是会执行一条统计语句,返回true或者false,不会执行查询语句,所以extra更智能,所以相比lazy=true性能更优。

当使用load(),在session关闭后,使用查询出返回的对象,对象调用属性的时候,会报懒加载异常:LazyInitializationException:could not initialize proxy - no Session。如何处理懒加载异常?

方式1(一般不使用):先使用一下数据。就是在查询出来返回对象下面,在调用对象的相关属性使用一下,但是仅仅是为了不让报异常而已,调用对象属性并没什么作用。

方式2(推荐):强迫代理对象初始化

Hibernate.initialize(Object proxy); //hibernate会为对象创建一个代理对象,是hibernate的内部处理。只需要知道这行代码即可,放这行代码放在查询出来返回对象下面。

方式3:关闭类级别的懒加载

设置lazy属性的值为false

方式4:在使用数据之后,再关闭Session(一般使用这种)

一对一的映射

举例:用户与身份证

设计截图如下:

HQL删除

// 删除(适用批量删除)
delete from 实体类 where 主键 in (:主键);    //:跟的主键,主键的数据类型是一个数组,如果是批量删除的话,需要分割,分割后是一个数组,数组的元素就是分割后的一个个元素,

HQL查询

要点:首先确定查询的列、查询的列涉及到哪些表、表与表之间的关系

以下HQL语句用到的对象以及对象的属性均来自 “一对多与多对一映射” 的部门与员工

//查询全部列
session.createQuery("FROM Dept");            //第一种写法
session.createQuery("(SELECT d FROM Dept d"); //第二种写法。d是别名//1 查询指定列。查询出来的是对象数组
session.createQuery("SELECT d.deptId, d.deptName FROM Dept d");
//2 查询指定列,自动封装为对象
session.createQuery("SELECT new Dept(d.deptId, d.deptName) FROM Dept d"); //会找部门的带参数的构造方法,所以这样的写法必须在部门类提供要查询指定列的带参的构造方法//条件查询。包括一个条件/and、or(多个条件)/between and(范围查询)/like(模糊查询)
//1 条件查询(占位符),一个条件
Query query = session.createQuery("FROM Dept d WHERE deptName=?"); //多个条件在deptName=?空格跟and
query.setString(0, "财务部"); //或者调用Query接口的setParameter(int arg0, Object arg1),这个方法是通用的
System.out.println(query.list());
//2 条件查询(命名参数),多个条件and
Query query = session.createQuery("FROM Dept d WHERE deptId=:myId AND deptName=:myName"); //:是命名符号,后面跟要命名的参数
query.setParameter("myId", 9); //第一个参数就是命名的参数
query.setParameter("myName", "财务部");
System.out.println(query.list());
//3 条件查询(占位符),范围查询between and
Query query = session.createQuery("FROM Dept d WHERE deptId between ? AND ?");
query.setParameter(0, 1);
query.setParameter(1, 20); //表示从deptId的1到20
System.out.println(query.list());
//4 条件查询(占位符),模糊查询
Query query = session.createQuery("FROM Dept d WHERE deptName LIKE ?");
query.setString(0, "%部%"); //注意,模糊查询的%是写在这里的
System.out.println(query.list());//分组查询
Query query = session.createQuery("SELECT COUNT(*) FROM Employee GROUP BY dept");
//分组查询后进行筛选
Query query = session.createQuery("SELECT COUNT(*) FROM Employee GROUP BY dept HAVING COUNT(*)>2");
System.out.println(query.list());
//聚合函数统计
Query query = session.createQuery("SELECT COUNT(*) FROM Dept"); //统计部门总记录数
Long num = (Long) query.uniqueResult();
System.out.println(num);

为什么要用Query接口的uniqueResult()而不用list()?因为count统计出来的数据类型是Long,如下:

在数据库中,统计还可以这样编写,如下:

SELECT COUNT(*) FROM dept; --统计总记录数,包括null值
SELECT COUNT(1) FROM dept; --统计总记录数,效率更高,包括null值。括号的值不一定写1,还可以写0,一般写1。在其它数据库中写1都支持,在HQL语句中不能写1,只能写*
SELECT COUNT(deptName) FROM dept; --忽略null值,所有聚合函数都会忽略null值
--连接查询(内连接、左外连接、右外连接)
--内连接(两表交集)
--内连接写法1
SELECT d.deptName,e.empName
FROM dept d,employee e
WHERE d.deptId=e.deptId;
--内连接写法2
SELECT d.deptName,e.empName
FROM dept d
INNER JOIN employee e --与第一种写法不同的是:from子句表与表之间的逗号用INNER JOIN关键字替代
ON d.deptId=e.deptId; --与第一种写法不同的是:where子句用ON关键字代替,也可以不替换--左连接(左边的表的字段的数据一定存在,右边的表的字段的数据不一定存在,不存在的数据显示为null)
SELECT d.deptName,e.empName
FROM dept d
LEFT JOIN employee e --LEFT OUTER JOIN,其中,OUTER关键字可以省略
ON d.deptId=e.deptId;--右连接(与左联接相反)
SELECT d.deptName,e.empName
FROM employee e
RIGHT JOIN dept d --RIGHT OUTER JOIN,其中,OUTER关键字可以省略
ON d.deptId=e.deptId;
//内连接
Query query = session.createQuery("FROM Employee e INNER JOIN e.dept"); //因为在映射配置好了关系,所以可以直接用Employee的别名调用dept属性。控制台生成的SQL语句还是上面原生SQL语句内连接的写法
System.out.println(query.list());//左连接
Query query = session.createQuery("FROM Employee e LEFT JOIN e.dept");
System.out.println(query.list());//右连接
Query query = session.createQuery("FROM Dept d RIGHT JOIN d.emps");
System.out.println(query.list());//迫切内连接(FETCH关键字,会把“fetch关键字右边表的数据”填充到“fetch关键字左边表的数据”)
Query query = session.createQuery("FROM Dept d INNER JOIN FETCH d.emps");
System.out.println(query.list());//迫切右连接
Query query = session.createQuery("FROM Dept d RIGHT JOIN FETCH d.emps");
System.out.println(query.list());//迫切左连接
Query query = session.createQuery("FROM Dept d LEFT JOIN FETCH d.emps");
System.out.println(query.list());

HQL查询优化

HQL语句还可以编写在映射文件当中,示例如下:

<class>... ...</class>
<query name="getAllDept">from Dept
</query>
@Test
public void HQL_Other() {Session session = sessionFactory.openSession();session.beginTransaction();Query query = session.getNamedQuery("getAllDept"); //调用session的getNamedQuery(String arg0),方法的值就是在映射文件中<query>元素的name属性值System.out.println(query.list());session.getTransaction().commit();session.close();
}

如果在映射文件中的HQL语句出现<符号,如下,则会报错

<query name="countHQL">select e.dept,count(*) from Employee e group by e.dept having count(*)<1
</query>

因为和元素语法冲突,所以报错。可以用以下两种方式

//转义。< 用&lt;转义
<query name="countHQL">select e.dept,count(*) from Employee e group by e.dept having count(*) &lt; ?
</query>//包含在CDATA中
<query name="countHQL"><![CDATA[select e.dept,count(*) from Employee e group by e.dept having count(*) < ?]]>
</query>

Hibernate对分页的支持

@Test
public void totalCount() {Session session = sessionFactory.openSession();session.beginTransaction();Query query = session.getNamedQuery("getAllDept");//查询总记录数ScrollableResults Scroll = query.scroll(); //得到滚动的结果集Scroll.last(); //开始滚动每一行直到最后一行int number = Scroll.getRowNumber() + 1; //记录下滚动的每一行,因为是从0开始的,所以要+1//设置分页参数query.setFirstResult(0);query.setMaxResults(3);System.out.println(query.list());System.out.println("总记录数:" + number);session.getTransaction().commit();session.close();
}

二级缓存

基于应用程序级别的缓存,即 二级缓存。它可以让不同的Session访问,只要应用程序一启动,就会分配二级缓存区,把需要缓存的对象保存在二级缓存区里。Hibernate提供的二级缓存有默认的实现,是一种可插拔的缓存框架,只需要在Hibernate.cfg.xml中配置即可,还可以自己实现缓存框架或者换其他的缓存框架都可以。

查看hibernate-distribution-3.6.0Final\project\etc\hibernate.properties配置文件,可以看到缓存是如何配置的。

##########################
### Second-level Cache ###
##########################二级缓存默认false不开启,开启需要设置为true
#hibernate.cache.use_second_level_cache false查询缓存
#hibernate.cache.use_query_cache true二级缓存框架的实现
## choose a cache implementation#hibernate.cache.provider_class org.hibernate.cache.EhCacheProvider
#hibernate.cache.provider_class org.hibernate.cache.EmptyCacheProvider
hibernate.cache.provider_class org.hibernate.cache.HashtableCacheProvider 这个是默认实现
#hibernate.cache.provider_class org.hibernate.cache.TreeCacheProvider
#hibernate.cache.provider_class org.hibernate.cache.OSCacheProvider
#hibernate.cache.provider_class org.hibernate.cache.SwarmCacheProvider

二级缓存使用步骤:

在hibernate.cfg.xml配置文件中配置

<!-- 开启二级缓存 -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- 指定使用哪一个缓存框架(默认提供的) -->
<property name="hibernate.cache.provider_class">org.hibernate.cache.HashtableCacheProvider</property>
<!-- 指定哪一些类需要加入二级缓存 -->
<class-cache usage="read-only" class="hibernateMapping.Dept"/>

二级缓存策略( 指的是<class-cache>元素的usage属性 ),有如下

usage="read-only"  只读。放入二级缓存的对象只能读

usage="nonstrict-read-write"  非严格的读写

usage="read-write"  读写。放入二级缓存的对象可以读、写

usage="transactional"  基于事物的策略,hibernate3.6版本不支持

集合缓存

在hibernate.cfg.xml文件中配置,如下:

<!-- 集合缓存(集合属性类型)-->
<collection-cache usage="read-only" collection="hibernateMapping.Employee"/>
<!-- 集合缓存(集合属性)-->
<collection-cache usage="read-only" collection="hibernateMapping.Dept.emps"/>

还可以在映射文件中配置(一般在总配置文件中配置,便于维护),如下:

... ...
<class name="Dept" table="dept" lazy="true"><cache usage="read-only"/> <!-- 指定哪些类需要加入二级缓存 -->... ...<set name="emps" table="employee" inverse="false"><cache usage="read-only"/> <!-- 集合缓存 -->... ...</set>
</class>

查询缓存(query接口的ist()只会放入缓存,不会从一级缓存中去查找,配置了二级缓存可以让list()去二级缓存中找)

要想使用查询缓存,最基本的二级缓存的开启、缓存框架的提供者、缓存策略得配置,也就是上面的三步,最后在配置查询缓存

<!-- 开启查询缓存 -->
<property name="hibernate.cache.use_query_cache">true</property>

默认情况下,设置了查询缓存,也不会放入二级缓存,也不会从二级缓存中获取,还需手动指明,需要调用Query接口的setCacheable(true),指定从二级缓存找,或者是放入二级缓存。

【Hibernate】相关推荐

  1. 【Hibernate】getHibernateTemplate与getSession的区别以及优缺点

    getHibernateTemplate与getSession有什么区别以及优缺点 getHibernateTemplate已经封装好了一些基本的方法,可以直接去用,也就是template:而getS ...

  2. 【Hibernate】dao层 +getHibernateTemplate()方法解析

    2019独角兽企业重金招聘Python工程师标准>>> spring 中获得由spring所配置的hibernate的操作对象,然后利用此对象进行,保存,修改和删除等操作,此方法是在 ...

  3. 【Hibernate】映射关系总结

    在hibernate中,由于是对对象进行操作,但是要实现表与表之间的关系,是怎么样实现的呢? 一.Hibernate的映射 Hibernate是对对象进行操作,Hibernate的映射是描述数据库表的 ...

  4. 【Hibernate】Hibrenate POJO 类在序列化时遇到的问题

    2019独角兽企业重金招聘Python工程师标准>>> 假设某 POJO 有属性如下: private Set<User> users = new HashSet< ...

  5. 【Hibernate】could not instantiate class.. from tuple] with root cause

    使用hibernate的过程中出现了这个问题,查询语句如下: 1 String hql = "select new GoodsBean(id, name, price, proPic, sa ...

  6. 【Hibernate】getHibernateTemplate.find()和session.createQuery()方法总结

    Spring中常用的hql查询方法(getHibernateTemplate().find()) 一.find(String queryString); 示例:this.getHibernateTem ...

  7. 【Hibernate】HibernateCallback总结

    HibernateCallback总结 HibernateTemplate提供了非常多的常用方法来 完成数据库的基本操作,使得持久层访问模板化 .只要创建HibernateTemplate实例后,注入 ...

  8. 【Hibernate】Hibernate中查询表名、字段名以及字段类型等信息

    Hibernate中查询表名.字段名以及字段类型等信息的工具类 package com.lmb.ui.util;import org.hibernate.cfg.Configuration; impo ...

  9. 【Hibernate】JDBC操作与hibernate的区别

    JDBC: 1).表--JavaBean--操作(间接操作数据库) JavaBean属性名对应数据库字段名,操作JavaBean中属性名达到操作数据库表的效果: 2).表---------操作(直接操 ...

  10. 【Hibernate】hibernate主键生成策略与配置详解

    //####################################################### **Hibernate各种主键生成策略与配置详解** //############# ...

最新文章

  1. 消防管件做的机器人图片_消防管件组装成机器人 PM值临界时会报警并自动喷淋...
  2. go的错误处理(异常捕获、处理):defer+recover机制处理错误、自定义异常(自定义错误)
  3. 教你配置支付宝应用网关和授权回调地址
  4. mysql install and config
  5. 【论文阅读】Deep Modular Co-Attention Networks for Visual Question Answering
  6. 同大取大同小取小口诀图解_七年级下册数学课本内容归纳汇总
  7. weblogic部署微服务项目
  8. 红细胞识别matlab,图像处理—红细胞计数(Matlab).doc
  9. Element UI实现全选和取消功能
  10. 源码分享:打造「螃蟹火星车」,遥控、拍照、测距,还能做人脸检测;
  11. 深度学习中的正则化的应用(最全)
  12. 宝马 OR 奥迪?NONONO,还得看我Li Auto,新款六座SUV强势来袭
  13. SQL语句大全(Mysql)
  14. 端云协同,打造更易用的AI计算平台
  15. 使用IDEA快速画类图
  16. 计算机视觉——KNN算法以及手势识别应用
  17. linux修改证书通用名,linux – 证书通用名称`* .c.ssl.fastly.net’与请求的主机名不匹配...
  18. Python-群发推广邮件
  19. JAVA电子设备销售网站计算机毕业设计Mybatis+系统+数据库+调试部署
  20. 无单位收入证明怎么开?

热门文章

  1. 正则表达式?:代表什么意思
  2. 数据库安全防护几点介绍
  3. TongWeb上应用部署方式
  4. Stable Diffusion 原理介绍与源码分析(一)
  5. 【Unity】 Unity设置材质的渲染模式(RenderingMode)
  6. 钉钉请假单如何下载到电脑打印
  7. mysql 进入_如何进入MySQL
  8. bug的生命周期你知道吗?一张图带你看懂它!
  9. Linux || 查询类命令
  10. MySQL数据库代理技术