Hibernate学习
hibernate集成
第一步:必须把官网下载的hibernate包解压,lib/required目录下是必须要集成的,还有需要集成jpa的包
第二步:在WEB-INF/classes目录下创建hibernate.cfg.xml,可以在project/etc 目录下找到示例。
第三步:创建javabean 在实体类的同一个目录下 ,创建 类名.hbm.xml,该文件用来配置实体和数据库字段的映射的。
示例:
JavaBean Customer
package com.wgp.domain;
public class Customer {
private int id;
private String name;
private int age;
private String city;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
@Override
public String toString() {
return "Customer{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", city='" + city + '\'' +
'}';
}
}
在实体类的同目录下创建Customer.hbm.xml映射文件
创建hibernate的配置文件hibernate.cfg.xml
com.mysql.cj.jdbc.Driver
jdbc:mysql://localhost:3306/hibernateday1
root
root
org.hibernate.dialect.MySQL5Dialect
true
true
update
配置完成后,就是用hibernate进行正删改查操作了
模板代码:
Configuration configure = new Configuration().configure();
//创建会话工厂
SessionFactory sessionFactory = configure.buildSessionFactory();
//创建会话
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
/**
*处理具体业务,正删改查
*/
//提交事务、释放资源
transaction.commit();
session.close();
sessionFactory.close();
插入数据示例:
@Test
public void insert(){//模板代码
//实例化配置对象,加载配置文件hibernate.cfg.xml
Configuration configure = new Configuration().configure();
//创建会话工厂
SessionFactory sessionFactory = configure.buildSessionFactory();
//创建会话
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
Customer customer = new Customer();
customer.setName("小明");
customer.setAge(10);
customer.setCity("北京");
session.save(customer);
//提交事务、释放资源
transaction.commit();
session.close();
sessionFactory.close();
}
查找数据:
//查询 用get和load方法 两个方法有什么区别 后面再讲
@Test
public void find(){
Configuration configure = new Configuration().configure();
//创建会话工厂
SessionFactory sessionFactory = configure.buildSessionFactory();
//创建会话
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
Customer customer = session.get(Customer.class, 1);
System.out.println(customer);
//提交事务、释放资源
transaction.commit();
session.close();
sessionFactory.close();
}
更新示例:
@Test
public void update(){
Configuration configure = new Configuration().configure();
//创建会话工厂
SessionFactory sessionFactory = configure.buildSessionFactory();
//创建会话
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
/**
*
* hibernate是全字段的修改,也就是你要修改一个字段的数据,
* hibernate会吧其他的字段也一起修改掉,所以在做修改的时候要小心
* 把所有其他的字段的数据也要带上,要不其他字段的数据就成null了
*/
//要先查询 后修改
Customer customer = session.get(Customer.class, 1);
customer.setCity("上海");
session.update(customer);
//提交事务、释放资源
transaction.commit();
session.close();
sessionFactory.close();
}
删除示例:
@Test
public void dalete(){
Configuration configure = new Configuration().configure();
//创建会话工厂
SessionFactory sessionFactory = configure.buildSessionFactory();
//创建会话
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
Customer customer = new Customer();
customer.setId(1);
session.delete(customer);
//提交事务、释放资源
transaction.commit();
session.close();
sessionFactory.close();
}
Hibernate 核心API
hibernate体系结构
- hibernate的持久化方案,将用户从原始的jdbc底层sql访问中解救出来。
- 用户无须关注底层数据操作,只要通过操作映射到数据表的java对象,就可以对数据库进行增删改查。
- hibernate框架支持两种hibernate属性配置方式:hibernate.properties和hibernate.cfg.xml。
- 采用properties方式,必须编程加载hbm文件或者持久化类。
- 采用xml配置方式,可以配置hbm文件
hibernate常见属性:
hibernate.connection.driver_class:加载数据库驱动
hibernate.connection.url:连接数据库的url
connection.username:连接数据库的用户名
connection.password:连接数据库的密码
hibernate.dialect:配置方言(方言就是不同的数据库sql不完全一样)
hibernate.show_sql:控制台输出sql语句
hibernate.format_sql:格式化输出的sql语句
hibernate.hbm2ddl.auto:DDL策略
策略1:create:表示启动的时候先drop,再create,也就是先删除表再创建表
策略2:create-drop:也表示创建,只不过再细谈关闭前执行一下drop
策略3: update:这个操作启动的时候会去检查schema是否一致,如果不一致会做schema更新。说白了就是hibernate在执行增删改查的时候发现表Customer.hb.xml的配置实体对应的列发生变化了,数据库中如果已经有表那么就会添加新字段在表中,如果没有表就会创建表。
策略4:validate:启动时验证现有schema与你配置的hibernate是否一致,如果不一致就抛出异常,并不做更新。这个说白了就和策略3不太一样,这个如果有变化,hibernate直接抛异常。hibernate并不会帮你创建列
策略怎么使用呢?一般情况 测试环境用create/create-drop 正式环境 update validate
hibernate.connection.autocommit: 事务是否自动提交。如果没设置,采用数据库默认事务是否自动提交,mysql数据库默认提交,Oracle默认回滚。
Hibernate体系结构详解
核心api:
Configuration :配置文件的加载
SessionFactory : 连接池工厂
Session :会话 相当于Connection
Transaction : 事务
Query : 查询对象
Criteria : 高级查询对象
Configuration类
- Configuration类负责管理Hibernate的配置信息,包括加载hibernate.properties和hibernate.cfg.xml,持久化类和数据表的映射关系(*.hbm.xml文件)
- 创建Configuration的两种方式,方式一:属性文件hibernate.properties Configuration cfg = new Configuration(),手动加载hbm;方式二:xml文件hibernate.cfg.xml Configuration cfg = new Configuration().configure();
采用手动方式加载hbm或者持久化类
-
通过Configuration对象 addResource方法添加hbm文件映射,
示例代码:new Configuration().addResource("com/wgp/domain/Customer.hb.xml");
-
另一种方式,通过addClass添加持久化类,hibernate会在类所在包,自动搜索hbm映射文件
示例代码:new Configuration().addClass(Customer.class);
现在我们不是我们常用的方式,因为我们之间在配置文件中配置了映射文件。学了spring 配置能省了,spring可以自动扫描。
SessionFactory接口
Configuration对象根据当前的配置信息生成SessionFactory对象,SessionFactory对象中保存了当前的数据库信息和所有映射关系以及预定义的sql语句。
SessionFactory对象是线程安全的。
SessionFactory还负责维护Hibernate的二级缓存:
Configuration configure = new Configuration().configure();
SessionFactory sessionFactory = configure.buildSessionFactory();
可以通过SessionFactory对象获取Session对象。
构造SessionFactory很销毁资源,一般情况下一个应用只初始化一个。
Session接口
相当于jdbc的Connection
Session是应用程序和数据库之间交互操作的一个单线程对象,是Hibernate运作的中心。
Session是线程不安全的。
所有持久化对象必须在Session的管理下才可以进行持久化操作。
Session对象有一个一级缓存,显示执行flush之前,所有的持久层操作的数据库都缓存在Session对象处。
持久化类与Session关联起来后就具有了持久化的能力。
常用方法:
save()/persist()、update()、saveOrUpdate()增加和修改对象。
delete()删除对象。
get()/load()根据主键查询。
createQuery()/createSQLQuery()数据库操作对象。
createCriteria()条件查询。
Session代表连接,线程不安全,那么怎么才能安全的使用Session呢?那就是在方法内使用Session,没有线程安全问题,为什么呢 ,是因为方法内的所有引用,都是位于栈空间,栈空间是线程私有的。
Transaction接口
代表数据库操作的事务对象。
Transaction transaction = session.beginTransaction();
提供事务管理的方法:
commit() 提交相关联的Session实例;
rollback(): 撤销事务操作;
wasCommitted(): 检查事务是否提交。
如果没有开启事务,那么每个Session的操作,都相当于一个独立的事务。
Query接口
- Query代表面向对象的一个Hibernate查询操作。
- Session.createQuery 接收一个hql语句。
- hql是Hibernate query language缩写,语法很想sql语法,但是完全面向对象的。
- 使用query对象步骤
- 获取Hibernate Session对象
- 编写hql语句
- 调用Session.createquery 创建查询对象
- 如果hql语句包含参数,则调用query的setXXX设置参数
- 调用query对象的list() 或uniqueResult()方法执行查询
- Query还包含两个方法,用于控制返回结果
- setFirstResult( int firstResult) 设置返回结果从第几条开始
- setMaxResult(int maxResult) 设置本次返回结果记录条数
示例代码:查询所有记录
@Test
public void query(){
Session session = HibernateUtils.getSession();
Transaction transaction = session.beginTransaction();
//编写hql Customer是类
String hql = "from Customer";
//获得query对象
Query query = session.createQuery(sql);
//查询结果list()返回结果,uniqueResult 返回单一结果
List list = query.list();
System.out.println(list);
transaction.commit();
session.close();
}
带条件查询:
@Test
public void query2(){
Session session = HibernateUtils.getSession();
Transaction transaction = session.beginTransaction();
//Customer是类名
String hql = "from Customer";
Query query = session.createQuery(hql);
//代表从第21条开始
query.setFirstResult(20);
//查询10条
query.setMaxResults(10);
transaction.commit();
session.close();
}
查询某一个列是数据:
@Test
public void query3(){
Session session = HibernateUtils.getSession();
Transaction transaction = session.beginTransaction();
//Customer是类名 name是Customer的属性
String hql = "select name from Customer";
//查询所有名字
Query query = session.createQuery(hql);
List list = query.list();
System.out.println(list);
transaction.commit();
session.close();
}
条件添加使用占位符(:name),也可以使用匿名占位符(?)
匿名占位符 from Customer where name = ? 设值 query.setParameter(0,值);
有名称占位符 from Customer where name = :a 设值 query.setParameter("a",值);
Criteria接口 高级条件查询接口
当查询非常复杂,具有多个条件,使用Criteria,可以以面向对象方式,添加条件查询。
- 通过session对象获得Criteria对象
- 使用Restrictions的静态方法创建Criterion条件对象
- 向Criteria对象中添加Criterion查询条件
- 执行Criteria的list() 或uniqueResult()获得结果
示例代码:
@Test
public void criteria(){
Session session = HibernateUtils.getSession();
Transaction transaction = session.beginTransaction();
//session.createCriteria()//这个方法已经过时
CriteriaQuery query = session.getCriteriaBuilder().createQuery(Customer.class);
query.from(Customer.class);
List resultList = session.createQuery(query).getResultList();
for (int i = 0; i < resultList.size(); i++) {
System.out.println(resultList.get(i).getName());
}
transaction.commit();
session.close();
}
Hibernate持久化配置和操作
Hibernate采用普通、传统的java对象(pojo),作为持久化类,与数据表进行映射。
-
编写规则
- 提供一个无参public访问控制符的构造器
- 提供一个标识属性,映射数据表主键字段
- 所有属性提供public访问控制符的setter 和getter方法
- 标识属性应尽量使用基本数据类型的包装类型
- 不要用final修饰(将无法生成代理对象进行优化)
-
理解Session的get方法和load方法区别
get方法是立即执行查询,获取数据;
load 返回的是代理对象,并没有立即执行查询;当我们使用到数据的时候才会去执行操作数据库。
持久化的唯一标识OID
- java按地址区分同一个类的不同对像。
- 关系数据库用主键区分同一条记录
- Hibernate使用OID来建立内存中的对象和数据库中记录的对应关系
- 对象的OID和数据库的表的主键对应,为保证OID的唯一性,应该让Hibernate来为OID赋值
区分自然主键和代理主键
在关系数据库表中,用主键来识别记录并保证每条记录的唯一性,作为主键的字段必须满足以下条件:
- 不允许为null
- 每条记录具有唯一的主键值,不允许主键值重复
- 每条记录的主键值永远不会改变
在Customer表中,如果把name字段作为主键,前提条件是:
- 每条记录的客户姓名不允许为null
- 不允许客户重名
- 不允许修改客户姓名
name字段具有业务含义的字段,把这种字段作为主键,称为自然主键。尽管也是可行的,但是不能满足不断变化的业务需求,一点出现了允许重名的业务需求,就必须修改数据模型,重新定义表的主键,这给数据库的维护增加了难度。
因此,更合理的方式是使用代理主键,即不具备业务含义的字段,该字段一般取名为“ID”。代理主键通常为整数类型,因为整数类型比字符串类型要节省更多的数据库空间。那么代理主键的值从何而来呢?许多数据库系统提供了自动生成代理主键值的机制。
自然主键:采用数据库中有意义的列的值作为主键。
代理主键:杜宇主键列,采用自动生成、流水号、uuid,主键列无意义。
现在企业开发中,更常用的是代理主键。
是用基本类型?还是包装类型?
基本属性类型和包装类型对应Hibernate的映射类型相同。
基本类型可以直接运算、无法表达null、数字类型的默认值为0
包装类型默认值是null。当对于默认值有业务意义的时候需要使用包装类。
主键生成策略
在配置JavaBean的映射文件时,我们需要配置主键的生成策略,
主键生成策略常用的有6种:
- increment
- identity
- sequence
- native
- uuid
- assigned
increment 策略:
- increment标识符生成器有Hibernate以递增的方式为代理主键赋值。
- Hibernate会先读取news表中的主键的最大值,而接下来想news表中插入记录时,就在max(id)的基础上递增,增量为1。
- 适用范围:
- 由于increment生成标识符机制不依赖于底层数据库系统,因此他适合所有的数据库系统。
- 适用于只有单个Hibernate应用进程访问同一个数据库的场合。
- OID必须为long、int或short类型,如果把OID定义为byte类型,在运行时会抛出异常。
- 有线程并发问题。
identity策略:
由底层数据库来完成自增,要求数据库必须支持自增主键,mysql支持,Oracle不支持。线程安全。
OID必须为long、int或short类型,如果把OID定义为byte类型,在运行时抛出异常。
sequence策略:
由底层数据库提供序列,来完成主键自增,要求数据库必须支持序列,mysql不支持,Oracle支持。
sequence标识符生成器利用底层数据库提供的序列来生成标识符。
news_seq
Hibernate在持久化一个news对象时,先从底层数据库的news_seq序列中获得一个唯一的标识号,再把它作为主键值。
OID必须为long、int或short类型,如果把OID定义为byte类型,在运行时抛出异常。
适用范围:
依赖底层数据库必须支持序列,支持序列的数据库包括DB2 、Oracle等。
OID必须为long、int或short类型,如果把OID定义为byte类型,在运行时抛出异常。
native策略:
native标识符生成器依据底层数据库对自动生成标识符的支持能力,来选择使用identity、sequence或hilo标识符生成器。
适用范围:
由于native能根据底层数控系统的类型,自动选择合适的标识符生成器,因此很适合跨数据平台开发。
OID必须为long、int或short类型,如果把OID定义为byte类型,在运行时抛出异常。
increment、identity、sequence和native策略要求数据库主键必须为数字,因为只有数字才能自增。
uuid策略:
32位,唯一字符串,主键使用varchar类型。
assigned策略:
映射单个自然主键。
假如Customer表没有定义id代理主键,而是以name字段作为主键,那么相应地,在Customer类中不必定义id属性,Customer类的OID为name属性,它的映射代码如下:
映射复合主键:
复合主键,是一种特殊assigned类型的自然主键(通常需要手动指定),联合主键实体类必须实现序列化接口
持久化类的属性及访问方法
Hibernate访问持久化类属性的策略:
property默认值:表明Hibernate通过getter和setter来访问类的属性。推荐使用,提供域模型透明性。private也
field:Hibernate通过java反射机制直接访问类属性。对于没有javabean方法的属性可设置该方法策略。
-
noop(了解):它映射java持久化类中不存在的属性,即主要用于hql(query接口测试,使用hql语句)中,当数据库中有某列,而实体中不存在的情况。
设置派生属性及访问方法
实体类中有的属性,数据库没有该字段对应的列情况。
利用
例如:
在Customer类中增加两个属性 private Double price; private Double totalprice; //totalprice在数据库中没有对应的列。
在Customer.hbm.xml文件中增加如下配置:
控制insert、update语句
设置列是否参与插入和更新
映射属性 | 作用 |
---|---|
若为false,在insert语句中不包含该字段,该字段永远不能被插入。默认为true | |
若为false,update语句不包含该字段,字段永远不能被更新。默认为true | |
若为false,等价于所有的 |
|
若为true,等价于所有的 |
|
若为true,等价于所有的 |
插入特殊列名
Hibernate持久化对象状态
Hibernate持久化对象存在三种状态
- 瞬时态 transient 尚未与Hibernate Session关联对象,被认为处于瞬时状态,失去引用将被jvm回收。无持久化表示OID,未与Session关联。
- 持久态 persistent 数据库中有数据与之对应并与当前Session有关联,并且相关联的Session没有关闭数据库并且事务未提交。存在持久化标识OID,与Session关联。
- 托管态 detached 数据库中有数据与之对应,但当前没有Session与之关联,托管状态改变Hibernate不能检测到。存在持久化标识OID,未与Session关联。
持久化状态转换图:
Session缓存(一级缓存)
Hibernate中分两级缓存,一级缓存是Session、二级缓存是SessionFactory缓存。
- 在Session接口的实现中包含一系列的java集合,这些java集合构成了Session缓存,只要Session实例没有结束生命周期,存放在它缓存中的需也不会结束生命周期。
- 当Session的save()方法持久化一个对象时,该对象被载入缓存,以后即使程序中不再引用该对象,只要缓存不清空,该对象仍然处于生命周期中。当试图get()、load()对象时,会判断缓存中是否存在该对象,有则返回,此时不查询数据库。没有再查询数据库。
- Session能够在某些时间点,按照缓存中对象的变化来执行相关的sql语句,来同步更新数据库,这个过程被称为刷出缓存(flush)。
- 默认情况下Session在一些时间点刷出缓存:
- 当应用程序调用Transaction的commit()方法的时,该方法先刷出缓存(session.flush()),然后在向数据库提交事务。
- 当应用程序执行一些操作时,如果缓存中持久化对象的属性已经发生了变化,会先刷出缓存,以保证查询结果能够反映持久化对象的最新状态。
- 调用session的flush()方法。
对象在什么时候放入一级缓存?
当对象称为持久态对象,就会被放入一级缓存 save、update、get、load、query、criteria。
清理Session的缓存
- Session的flush方法让缓存的数据刷出到数据库。
- Session的clear方法清空缓存数据。
- Session的evict方法清空指定对象一级缓存数据,使对象变为脱离状态。
Session加载对象后,会为对象的值类型的属性复制一份快照,当刷出缓存时,通过比较对象的当前属性和快照,来判断对象的那些属性发生了变化。从而产生更新语句。
什么是快照? 向一级缓存存放数据时,在快照区存放一个备份数据。
refresh刷新一级缓存
当Session.load加载对象后,修改属性,调用refresh方法更新一级缓存,此时设置的属性值重新被数据表中激励覆盖。
也就是说,refresh后会重新加载数据库中数据,覆盖一级缓存的数据。之前的修改就无效了。
一级缓存的FlushMode
清理Session的缓存(设置缓存的flush模式),Session.setFlushMode(FlushMode.AUTO)是默认模式;
FlushMode.AUTO:默认设置,Session查询方法会刷出,事务提交会刷出,Session的flush方法会刷出。
FlushMode.COMMIT:Session查询方法不刷出,事务提交会刷出,Session执行flush方法会刷出。
FlushMode.ALWAYS:Session查询方法刷出,事务提交会刷出,Session执行flush方法会刷出。
FlushMode.MANYAL:Session查询方法不刷出,事务提交不会刷出,Session执行flush方法会刷出。
ALWAYS和AUTO的区别:当Hibernate缓存中的对象被改动之后,会被标记为脏数据(即与数据库不同步了)。当Session设置为FlushMode.AUTO时,Hibernate在进行查询的时候会判断缓存中的数据是否为脏数据,是则刷出数据库,不是则不刷,而ALWAYS是直接刷新,不进行任何判断。很显然auto比ALWAYS要高效的多。
操作持久化对象的方法
操作持久化对象save()
Session的save()方法使一个瞬时对象转变为持久化对象.
-
Session的save方法完成以下操作:
- 把瞬时对象加入到Session缓存中,使他进入持久化状态。
- 选用映射文件指定的标识符生成器,为持久化对象分配唯一的OID,在使用代理主键的情况下,setId()方法为瞬时对象设置OID是无效的。
- 计划执行一条insert语句,把对象当前的属性值组装到insert语句中。
Hibernate通过持久化对象的OID来位置它和数据库相关记录的对应关系。当对象处于持久化状态时,不允许程序随意修改它的id。
Session的update方法使一个托管对象转变为持久化对象,并且计划执行一条update语句。如果程序对象,已经是持久化对象,不需要手动update;如果程序中出现两个不同对象,都是持久化对象具有相同OID会报错。
若希望Session仅修改了对象的属性时,才执行update()语句,可以把映射文件中的
元素的select-before-update(更新之前先查询)设置为true,该属性默认值为false。 当update()方法关联一个脱管对象时,如果在数据库中不存在相应的记录,也会抛出异常。
saveOrUpdate()方法:
该方法同上包含save和update方法,如果参数是瞬时对象就用save方法,如果是脱管对象就用update方法,如果持久化对象就直接返回。
判断对象为临时对象的标准:
java对象的OID为null。
映射文件中
操作持久化对象get、load方法:
都是可以根据给定的OID从数据库中加载一个持久化对象。
区别是:当数据库中不存在与OID对应的记录时,load()方法抛出ObjectNotFoundException异常,而get()方法返回null。
两者采用不同的延迟检索策略。
操作持久化对象delete方法:
Session的delete()方法即可以删除一个脱管对象,也可以删除一个持久化对象。
如果参数是持久化对象,就执行一个delete语句,若为脱管对象,先使对象被Session关联,使他变为持久化对象,执行delete语句,把对象从Session缓存中删除,该对象进入脱管状态,刷新缓存后,该对象将被从数据库中删除。
数据库实体表之间关系映射
- 数据库采用外键来描述数据表之间的关系
- 一对多:在多的一方添加一的一方的外键
- 一对一:在任意一方添加对方的主键作为外键
- 多对多:必须创建第三张关系表,分别引入双方的主键作为外键
Hibernate关联关系映射
Hibernate是采用java对象关系来描述数据表实体之间的关系。
一对一示例:
class A{
B b;
}
class B{
A a;
}
一对多:
class A{
Set bs;//B的集合
}
class B{
A a;
}
多对多:
class A{
Set bs;//B的集合
}
class B{
Set as;//A的集合
}
一对多 关联关系映射
客户和订单的示例,可以两边都配置也可以配置一边
package com.wgp.domain;
import java.util.HashSet;
import java.util.Set;
public class Customer {
private Integer id;
private String name;
private Integer age;
//一个客户关联多个订单
private Set orders = new HashSet<>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Set getOrders() {
return orders;
}
public void setOrders(Set orders) {
this.orders = orders;
}
}
Customer.hbm.xml
package com.wgp.domain;
public class Order {
private Integer id;
private String address;
private Double totalprice;
//关联一个客户
private Customer customer;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Double getTotalprice() {
return totalprice;
}
public void setTotalprice(Double totalprice) {
this.totalprice = totalprice;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
}
Order.hbm.xml
一对多操作 ,级联保存
常见操作:增加save-update、修改save-update、删除delete
持久化对象 级联 瞬时对象,将瞬时对象变为持久化对象。级联需要配置标签属性cascade="";
Customer级联Order 也就是说保存customer对象时,顺便把订单信息也保存起来,不用显示的调用session.save(order)来保存订单信息。级联要在Customer.hbm.xml配置
Order 级联Customer的话,就修改Order.hbm.xml
一对多操作,级联删除
采用级联删除操作 cascade="delete"
当没有设置级联删除时:
删除客户时会删除订单吗?删除订单时会删除客户吗?
举例因为订单表中的外键是客户表的id,所以我们删除的时候有几种情况。
删除脱离对象Customer,会自动先解除关联关系关系,再删除。
//删除脱离对象,没有配置级联删除,删除操作是成功的,但是会先解除orders表中的外键关系(如果外键列为非空,无法删除,会报错),然后再删除客户
Customer c = new Customer();
c.setId(1);
session.delete(c);
//持久的 效果和上面是一样的
Customer c2 = session.get(Customer.class,1)
session.delete(c2);
配置级联删除
删除Customer 同时删除Order 修改Customer.hbm.xml
删除Order 同时删除Customer 修改Order.hbm.xml
只有配置了级联的才能联动执行操作,如果没有配置 就不会。
删除还有一个情况:(孤儿删除也叫孤子模式删除)配置delete-orphan级联
从查询的客户对象中移除订单对象,订单对象是否删除?
客户和订单,先有客户,后有订单,订单依赖于客户-----存在父子依赖关系。
当客户解除了一个订单的关系,订单没有下单客户,不完整,通过配置delete-orphan级联模式,完成对孤儿订单的删除。
在Customer.hbm.xml中
cascade取值非常多,值来源于JPA和Hibernate
JPA:persist、merge、lock、replicate、evict、refresh
Hibernate是JPA的实现,宽展了save-update、delete、delete-orphan(上面的不用要用了)。
cascade=“all” 包含了除去delete-orphan的所有级联,cascade=“all-delete-orphan”包含所有级联关系。
inverse
//变更20号订单的客户为9号
Customer c2 = session.get(Customer.class,9);//查询9号客户
Order order = session.get(Order.class,20);//查询20号订单
c2.getOrders().add(order);
order.setCustomer(c2);
上面的这段代码会产生2条update语句,为什么呢?因为session的一级缓存的原因,之前我们说过,当执行查询的时候,会把结果防止session的缓存中,并把结果创建一份快照。当我们调用c2.getOrders().add(order);和order.setCustomer(c2);分别修改了持久化对象也就是一级缓存中的数据,那么一级缓存中的信息会自动和快照区的信息比对,如果不一样了,那么Hibernate就会执行update操作。
向上面的代码,只需要修改订单归属那个客户就行了,结果会产生两条update语句,显然不是最优的解决,Hibernate给我们提供了一个标签inverse=false 默认的。这个标签就是设置为true 表示由谁来维持数据表之间的关系。
通过inverse属性来设置由双方关联的哪一方来维护表和表之间的关系。inverse=false的为主动方,inverse=true的为被动方,由主动方负责维护关联关系。像客户和订单 我们在one的一方也就是客户的一方设置inverse=true ,这样就是客户方放弃了订单数据的关系表外键的维护。
面试题:inverse和cascade的区别?
cascade是级联,cascade="save-update" 保存A时会级联保存B。
inverse设置为true,表示放弃外键设置的权利,无法设置外键列的值。谁设置了就是谁放弃了。
一对多关联中的父子关系
理解什么是父子关系。
所谓父子关系:是指父方来控制子方的持久化生命周期,子方对象必须和一个父方对象关联。
一对一 关联关系映射
- 一对一关联指两个表之间的记录是一一对应的关系。分为两种:外键关联和主键关联。
- 比如一家公司Company和它所在地址Address。在业务逻辑中要求一家公司只能有唯一的地址,一个地址也只有一家公司。
映射一对一外键双向关联
-
对于基于外键的1-1关联,其外键可以存放在任意一边,在需要存放外键的一端,添加many-to-one元素。为many-to-one元素,添加unique="true"属性来表示1-1关联,并用name属性来指定关联属性的属性名。
-
另一端需要使用one-to-one元素,该元素使用property-ref属性指定使用被关联实体主键以外的字段作为关联字段
映射一对一主键双向关联
一对一的另一种解决方式就是主键关联,在这种关联关系中,要求两个对象的主键必须保持一致,通过两个表的主键建立关联关系,无须外键参与。
-
基于主键的映射策略:指一端的主键生成器使用foreign策略,表明根据"对方"的主键来生成自己的主键,自己并不能独立生成主键。 子元素指定使用当前持久化类的那个熟悉作为"对方"
company 采用foreign主键生成器策略的一端增加one-to-one元素映射关联属性,其one-to-one属性还应增加constrained="true"属性;另一端(Company)增加one-to-one元素映射关联属性。
-
constrained(约束):指定为当前持久化类对应的数据库表的主键添加一个外键约束,引用被关联的对象("对方")所对应的数据库表主键。
多对多关联关系映射
多对多的实体关系模型也是很常见的,比如学生和课程的关系。一个学生可以选修多门课程,一个课程可以被多名学生选修。在关系数据库中对于多对多关联关系的处理一般采用中间表的形式,将多对多的关系转化成两个一对多的关系。也就是建立中间表。
映射多对多双向关联关系1
双向n-n关联需要两端都使用集合属性
双向n-n关联必须使用中间表
集合属性应增加key子元素用以映射外键列,集合元素里还应增加many-to-many子元素关联实体类
-
在双向n-n关联的两边都需要指定连接表的表名及外键列的列名,两个自己和元素set的table元素的值必须指定,而且必须相同。
映射多对多双向关联关系2
- set元素的两个子元素:key和many-to-many都必须制定column属性。
- 其中,key和many-to-many分别指定本持久化类和关联类在连接表中的外键名。
- 因此两个的key与many-to-many的column属性交叉相同。也就是说,一边的set元素的key的column值为a,many-to-many的column为b;则另一边的set元素的key的column值为b,many-to-many的column为a;
- 注意:对应双向的n-n关联,需把其中的一端的inverse设置为true,否则可能会造成主键冲突。
多对多没有父子关系,实际开发中不建议级联。
当两个持久化对象,建立关系,在关系表插入一条数据(防止重复数据)1,设置inverse=true使一端放弃外键维护;2.不要建立双向关系。
持久化对象学生移除持久化课程,就会自动产生一条delete语句。
假如我们删除学生,那么在多对多关系中Hibernate会自动帮我们删除中间表该学生的信息。
Hibernate 映射组成关系
映射组成关系
什么叫映射组成关系呢?
当我们一张数据表的数据比较的多和复杂的时候,我们会对该表对应的实体类进行分解,可以用多个实体类来共同映射该数据表,那么多个实体类之间的关系就是组成关系。
比如,客户表中有地址,那么我们可以吧地址这个信息单独提前出来一个Address类,没必要吧所有的地址相关信息都放在Customer类中。多个类映射到一张表。
示例:
package com.wgp.domain;
public class Person {
private Integer id;
private String name;
private Address address;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
public class Address {
private String city;
private String street;
private String zipcode;
private String province;
private Person person;
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getZipcode() {
return zipcode;
}
public void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
}
Person.hbm.xml
name:设定被映射的持久化类的属性名。
class:设定属性的类型。
组件操作:其实就是单表操作,只是把实体类拆分了而已。
继承关系映射
将数据结构中的类的继承关系,保存到数据库表中。
例如:试题(选择题、填空题等);
员工管理:分为正式员工和临时工;
Hibernate提供三种 继承映射策略:
- 每个具体类一张表,将域模型中的每一个实体对象映射到一个独立的表中,也就是说不用在关系数据模型中考虑域模型中的继承关系和多态。
- 每个类分层结构一张表对于继承关系中的子类使用同一个表,这就需要在数据库表中增加额外的区分子类类型的字段。
- 每个子类一张表,域模型中的每个类映射到一张表,通过关系数据模型中的外键来描述表之间的继承关系。也就是说相当于按照域模型的结构来建立数据库中的表,并通过外键来建立表之间的继承关系。
白话描述:
1.将父类和子类数据 存放到同一张表中,在表中引入一列“辨别者列”,通过辨别者列的值,来区分该条记录是父类数据还是子类数据。
2.将公共数据放入父类表,将子类个体数据存放子类表,父子表之间通过外键关联。
3.父子类分别建表,父子表之间无关联,将父类数据存放到父类表,子类数据存放到子类表,父子表采用连续增长主键。
采用subclass元素的继承映射
- 采用subclass的继承映射可以实现对于继承关系中父类和子类使用同一张表。
- 因为父类和子类的实例全部保存在同一个表中,因此需要在该表内增加一列,使用该列来区分每行记录到底是那个类的实例——这个列被称为辨别者列(discriminator)。
- 在这种映射策略下,使用subclass来映射子类,使用class或subclass的discriminator-value属性指定辨别者列的值。
- 所有子类定义的字段都不能有非空约束。如果为那些字段添加了非空约束,那么父类的实例在那些列其实并没有值,这将引起数据库完整性冲突,导致父类的实例无法保存到数据库中。
注意:对应继承关系映射,只需要配置父类的hbm.xml文件就行。
示例:
/**
* 员工
*/
public class Employee {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/**
* 正式员工 继承 员工
*/
public class SalaryEmployee extends Employee {
private Double salary;//月薪
public Double getSalary() {
return salary;
}
public void setSalary(Double salary) {
this.salary = salary;
}
}
/**
* 小时工 继承 员工
*/
public class HourEmployee extends Employee {
private Double rate;//时薪
public Double getRate() {
return rate;
}
public void setRate(Double rate) {
this.rate = rate;
}
}
Employee.hbm.xml
对应增删改查的操作,就按照单表操作就行了。继承映射查询一般用hql来查询。
采用joinde-subclass元素的继承映射
joined-subclass 这种方式是为每个父类和自类建立单独的数据表,将公共的数据存放入父类表,将个体信息存放子类表,子类表通过外键与父类表关联。
- 采用joined-subclass元素的继承映射可以实现每个子类单独一张表。
- 采用这种映射策略时,父类实例保存在父类表中,子类实例由父类表和子类表共同存储。因为子类实例也是一个特殊的父类实例,隐藏必然也包含了父类实例的属性。于是将子类和父类共有的属性保存在父类表中,子类增加的属性,则保存在子类表中。
- 在这种映射策略下,无须使用鉴别者列,但需要为每个子类使用key元素映射共有主键,该主键必须与父类标识属性的列名相同。但如果继承树的深度很深,可能查询一个子类实例时,需要跨越多个表,因为子类的数据一次保存在多个父类中。
- 子类增加的属性可以添加非空约束。因为子类的属性和父类的属性没有保存在同一个表中。
示例:
上面的员工、正式工、小时工的实体类不用变,只需要修该Employee.hbm.xml文件。
注意:对数据进行操作的时候,Hibernate会自动帮我们进行级联操作,不用管,当单表操作。删除操作父类离线对象不行,用持久化对象删除操作。
采用union-subclass元素的继承映射(了解知识)
关系树的每个类存放在各种独立的表中,没有主表和子表的概念,每个表都有全部的自身信息,所有表和在一起后,记录仍然是唯一的。
三种继承方式的比较
Hibernate检索(抓取)策略
检索策略主要进行Hibernate的查询优化的。
学习目标:掌握各种检索策略特点,能够在开发中判断该使用哪种检索策略对,对查询进行优化。
立即检索和延迟检索
类级别延迟策略 配置hbm文件中的
针对load方法,get方法 一定使用立即检索。
query查询 对于类级别 使用立即检索。
对于get或query类级别检索都是立即检索。load通过hbm配置 使用立即或延迟检索,默认延迟。
关联级别的检索策略
如果已经获得持久化对象,通过关联的关系调用对象,而使用检索策略 如: customer.getOrders().size();
在映射文件中,用
- lazy:主要决定orders集合被初始化的时机。即到底是在加载Customer对象时就被初始化,还是在程序访问orders集合时被初始化。
- fetch:取值为"select"或者“subselect”时,决定初始化orders的查询语句的形式;若取值为“join”,则决定orders集合被初始化的时机。
- 若把fetch设置为“join”,lazy属性将被忽略。
一对多和多对多检索策略
用带子查询的select语句整批初始化orders集合(fetch属性为subselect):
1.
2.当fetch属性为subselect时,嘉定session缓存中有n个orders集合代理类实例没有被初始化,Hibernate能够通过带子查询的select语句,来批量初始化n个orders集合代理实例。
3.当fetch属性为join时,检索Customer对象时,采用迫切左外连接策略来检索所有关联的order对象,lazy属性被忽略。query的list()方法会忽略映射文件中配置的迫切左外连接检索策略,而依旧采用立即检索还是延迟加载策略有set集合的lazy属性决定。
多对一和一对一关联的检索策略
批量检索
1.从一的一端查询,查询所有的客户:
注:query.list()属于hql检索,hql检索忽略关联级别的迫切左外连接检索,只与lazy属性有关。
Customer.hbm.xml
2.多的一端 关联
Customer.hbm.xml
三种检索策略的比较
Hibernate的注解开发
Hibernate能干什么?可以取代hbm映射文件,cfg文件也可以取代 但一般我们不用注解去取代.
http://docs.jboss.org/hibernate/orm/5.4/quickstart/html_single/ 这是Hibernate官网的文档,怎么用注解是在第3个标题中介绍的
@Entity 实体类
@Table 生成目标表
@Id 主键
@GeneratedValue 主键生成策略,IDENTITY 自增用于mysql、SEQUENCE使用序列oracle、AUTO 相当于native
@Column 定义生成列
在hibernate.cfg.xml中引入使用注解的类
自定义主键生成策略
如果使用其他类型的生成策略需要自定义。@GenericGenerator用于自定义生成策略,@GeneratedValue的generator用于指定自定义生成策略。
例如:
@Entity
@Table(name="books")
public class Book{
@Id
@GenericGenerator(name="bookGenerator",strategy="uuid")//使用Hibernate的uuid
@GeneratedValue(generator="bookGenerator")
private String id;
}
其他注解
@Temporal 控制日期对象生成格式。DATE 只会保存日期部分到数据库;TIME 只会保存时间部分到数据库;DATETIME 日期和时间都会保存到数据库。
java.utils.Date 日期对象有3个子类 java.sql.Date java.sql.Time java.sql.TimeStamp
@Transient 控制实体类中属性,不生成到数据库表中列。
一对多配置onetomany
关联属性配置,在一的一方mappedBy(相当于inverse),在多的一方@JoinColumn。