目录
createQuery接口下的list()与iterator()查询的区别
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-byset集合属性的映射
比如:一个用户可以有多个地址,那么就需要在用户类设置一个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
cascade5 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>
因为和元素语法冲突,所以报错。可以用以下两种方式
//转义。< 用<转义
<query name="countHQL">
select e.dept,count(*) from Employee e group by e.dept having count(*) < ?
</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),指定从二级缓存找,或者是放入二级缓存。