【Hibernate】

目录

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();
    }

    //从一方进行保存操作
    @Test
    public 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维护员工Employee
        dept.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维护部门Dept
    employee.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),指定从二级缓存找,或者是放入二级缓存。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值