2 hibernate入门
2.1 ORM框架
Hibernate是一个数据持久化层的ORM框架.
Object:对象,java对象,此处特指JavaBean
Relational:关系,二维表,数据库中的表。
映射|映射元数据:对象中属性,与表的字段,存在对应关系。
2.2 什么是hibernate
l Hibernate是轻量级JavaEE应用的持久层解决方案,是一个关系数据库ORM框架
- ORM 就是通过将Java对象映射到数据库表,通过操作Java对象,就可以完成对数据表的操作
l Hibernate提供了对关系型数据库增删改成操作
2.3 主流的ORM框架
n JPA Java Persistence API.JPA通过JDK 5.0注解或XML描述对象-关系表的映射关系(只有接口规范)
n Hibernate 最流行ORM框架,通过对象-关系映射配置,可以完全脱离底层SQL
n MyBatis 本是apache的一个开源项目 iBatis,支持普通 SQL查询,存储过程和高级映射的优秀持久层框架
n Apache DBUtils 、Spring JDBCTemplate
2.4 优点
l Hibernate对JDBC访问数据库的代码做了封装,大大简化了数据访问层繁琐的重复性代码 l Hibernate是一个基于jdbc的主流持久化框架,是一个优秀的orm实现,它很大程度的简化了dao层编码工作 session.save(User); l Hibernate使用java的反射机制 l Hibernate的性能非常好,因为它是一个轻量级框架。映射的灵活性很出色。它支持很多关系型数据库,从一对一到多对多的各种复杂关系
|
3 入门案例【掌握】
3.1 编写流程
l 1. 导入jar包
l 2. 创建数据库和表
l 3. 编写核心配置文件(hibernate.cfg.xml)--> 配置获得链接等参数
l 4. 编写映射文件 hibernate mapping(*.hbm.xml)
l 5 使用api测试
3.2 数据库和表
create database h_day01_db; use h_day01_db; create table t_user( id int auto_increment primary key, username varchar(50), password varchar(30) ); |
3.3 导入jar包
l 版本:3.6.10 --> hibernate 4 建议注解开发,hibernate 4 对 3 不兼容。
l 目录结构
l jar介绍
核心:
必须:\lib\required
jpa规范:lib\jpa
mysql驱动:
3.4 编写JavaBean + 映射文件
l 文件位置:javabean同包
l 文件名称:javabean同名
l 扩展名:*.hbm.xml
l 内容:
添加约束
public class User {
/* * create table t_user( id int auto_increment primary key, username varchar(50), password varchar(30) ); */ private Integer uid; private String username; private String password; |
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.itheima.a_hello.User" table="t_user"> <!-- 主键 --> <id name="uid"> <!-- 固定值:主键生成策略 --> <generator class="native"></generator> </id>
<!-- 普通属性 --> <property name="username"></property> <property name="password"></property>
</class> </hibernate-mapping>
|
3.5 编写核心配置文件
l 位置:类路径(classpath、src)-->WEB-INF/classes
l 名称:hibernate.cfg.xml
l 内容:
添加约束
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <!-- SessionFactory,相当于之前学习连接池配置 --> <session-factory> <!-- 1 基本4项 --> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.url">jdbc:mysql:///h_day01_db</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password">1234</property>
<!-- 添加映射文件 --> <mapping resource="com/itheima/a_hello/User.hbm.xml"/> </session-factory>
</hibernate-configuration> |
3.6 测试
@Test public void demo01(){ User user = new User(); user.setUsername("伟哥哥"); user.setPassword("1234");
//1 加载配置文件获得核心配置对象 Configuration config = new Configuration().configure(); //2 获得工厂 SessionFactory,相当于连接池 SessionFactory factory = config.buildSessionFactory(); //3获得会话session,相当于链接Connection Session session = factory.openSession(); //4 开启事务 Transaction transaction = session.beginTransaction();
//操作 session.save(user);
//5 提交事务 | 回滚事务 transaction.commit(); //6 释放资源--关闭session session.close(); //7 释放资源--关闭工厂factory factory.close(); } |
3.7 常见异常
解决方案:
将映射文件添加到核心配置文件中 hbm.xml --> hibernate.cfg.xml
4 api详解【多练】
4.1 体系结构
PO:persistent object ,用于与数据库交互数据。--dao层 (JavaBean + hbm )
BO:Business object 业务数据对象。--service层
VO:Value Object 值对象。--web层
开发中:直接使用JavaBean 描述三个对象。
4.2 Configuration 配置对象
l hibernate 核心配置文件种类
hibernate.cfg.xml 通常使用xml配置文件,可以配置内容更丰富。
hibernate.properties 用于配置key/value 形式的内容,key不能重复的。配置有很多的局限性。一般不用。
参考文件:hibernate-distribution-3.6.10.Final\project\etc\ hibernate.properties
提供了核心配置文件常用的配置项,及选择参数。
1.提供构造 new Configuration() hibernate将自动加载 hibernate.properties文件
hibernate.properties文件必须存放在类路径(src)下
2.提供方法 configure() 将加载src下的hibernate.cfg.xml
3.扩展api
configure(String) 加载指定目录下的 xml文件
4.手动加载配置文件
// 手动加载指定的配置文件
config.addResource("com/itheima/a_hello/User.hbm.xml");
// 手动加载指定类,对应的映射文件 User--> User.hbm.xml
config.addClass(User.class);
l 常见异常:
开发中:将hbm.xml映射 配置 hibernate.cfg.xml
学习中:可以使用 addClass 或 addResource
4.3 SessionFactory工厂
l SessionFactory 相当于java web连接池,用于管理所有session
l 获得方式:config.buildSessionFactory();
l sessionFactory hibernate缓存配置信息 (数据库配置信息、映射文件,预定义HQL语句 等)
l SessionFactory线程安全,可以是成员变量,多个线程同时访问时,不会出现线程并发访问问题。
l 提供api:
//打开一个新的会话 session
factory.openSession();
//获得当前线程中绑定的会话session
factory.getCurrentSession();
hibernate支持,将创建的session绑定到本地线程中,底层使用ThreadLocal,在程序之间共享session。
1.必须在hibernate.cfg.xml 配置
<!-- 2 与本地线程绑定 --> <property name="hibernate.current_session_context_class">thread</property>
|
2.如果提交或回滚事务,底层将自动关闭session
4.4 Session 会话
l Session 相当于 JDBC的 Connection -- 会话
l 通过session操作PO对象 --增删改查
l session单线程,线程不安全,不能编写成成员变量。
l session api
save 保存
update 更新
delete 删除
get 通过id查询,如果没有 null
load 通过id查询,如果没有抛异常
createQuery("hql") 获得Query对象
createCriteria(Class) 获得Criteria对象
4.5 Transaction 事务
开启事务 beginTransaction()
获得事务 getTransaction()
提交事务:commit()
回滚事务:rollback()
try{ //开启 //session操作 //提交 } catch(e){ //回滚 } 扩展:不需要手动的管理事务,之后所有的事务管理都交予spring。 |
4.6 Query对象
l hibernate执行hql语句
l hql语句:hibernate提供面向对象查询语句,使用对象(类)和属性进行查询。区分大小写。
l 获得 session.createQuery("hql")
l 方法:
list() 查询所有
uniqueResult() 获得一个结果。如果没有查询到返回null,如果查询多条抛异常。
setFirstResult(int) 分页,开始索引数startIndex
setMaxResults(int) 分页,每页显示个数 pageSize
4.7 Criteria对象(了解)
l QBC(query by criteria),hibernate提供纯面向对象查询语言,提供直接使用PO对象进行操作。
l 获得方式:Criteria criteria = session.createCriteria(User.class);
l 条件
criteria.add(Restrictions.eq("username", "tom"));
// Restrictions.gt(propertyName, value) 大于
// Restrictions.ge(propertyName, value) 大于等于
// Restrictions.lt(propertyName, value) 小于
// Restrictions.le(propertyName, value) 小于等于
// Restrictions.like(propertyName, value) 模糊查询,注意:模糊查询值需要使用 % _
4.8 工具类
public class H3Utils {
// 会话工厂,整个程序只有一份。 private static SessionFactory factory;
static{ //1 加载配置 Configuration config = new Configuration().configure();
//2 获得工厂 factory = config.buildSessionFactory(); //3 关闭虚拟机时,释放SessionFactory Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override public void run() { System.out.println("虚拟机关闭!释放资源"); sf.close(); } })); }
/** * 获得一个新的session * @return */ public static Session openSession(){ return factory.openSession(); }
/** * 获得当前线程中绑定session * * 注意:必须配置 * @return */ public static Session getCurrentSession(){ return factory.getCurrentSession(); }
} |
|
5 核心配置文件详解
5.1 详细配置【多读】
<!-- SessionFactory,相当于之前学习连接池配置 --> <session-factory> <!-- 1 基本4项 --> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.url">jdbc:mysql:///h_day01_db</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password">1234</property>
<!-- 2 与本地线程绑定 --> <property name="hibernate.current_session_context_class">thread</property>
<!-- 3 方言:为不同的数据库,不同的版本,生成sql语句(DQL查询语句)提供依据 * mysql 字符串 varchar * orcale 字符串 varchar2 --> <property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
<!-- 4 sql语句 --> <!-- 显示sql语句 --> <property name="hibernate.show_sql">true</property> <property name="hibernate.format_sql">true</property>
<!-- 5 自动创建表(了解) ,学习中使用,开发不使用的。 * 开发中DBA 先创建表,之后根据表生产 PO类 * 取值: update:【】 如果表不存在,将创建表。 如果表已经存在,通过hbm映射文件更新表(添加)。(映射文件必须是数据库对应) 表中的列可以多,不负责删除。 create :如果表存在,先删除,再创建。程序结束时,之前创建的表不删除。【】 create-drop:与create几乎一样。如果factory.close()执行,将在JVM关闭同时,将创建的表删除了。(测试) validate:校验 hbm映射文件 和 表的列是否对应,如果对应正常执行,如果不对应抛出异常。(测试) --> <property name="hibernate.hbm2ddl.auto">create</property>
<!-- 6 java web 6.0 存放一个问题 * BeanFactory 空指针异常 异常提示:org.hibernate.HibernateException: Unable to get the default Bean Validation factory * 解决方案:取消bean校验 --> <property name="javax.persistence.validation.mode">none</property>
<!-- 添加映射文件 <mapping >添加映射文件 resource 设置 xml配置文件 (addResource(xml)) class 配置类 (addClass(User.class)) 配置的是全限定类名 --> <mapping resource="com/itheima/a_hello/User.hbm.xml"/> </session-factory> |
6 Hibernate中持久化类
6.1 编写规则
- 提供一个无参数 public访问控制符的构造器
- 提供一个标识属性,映射数据表主键字段
- 所有属性提供public访问控制符的 set get 方法(javaBean)
- 标识属性应尽量使用基本数据类型的包装类型
- 不要用final修饰实体 (将无法生成代理对象进行优化)
6.2 持久化对象的唯一标识 OID
l Java按地址区分同一个类的不同对象.
l 关系数据库用主键区分同一条记录
l Hibernate使用OID来建立内存中的对象和数据库中记录的对应关系
结论: 对象的OID和数据库的表的主键对应。为保证OID的唯一性,应该让Hibernate来为OID付值
6.3 区分自然主键和代理主键
l 主键需要具备: 不为空/不能重复/不能改变
自然主键: 在业务中,某个属性符合主键的三个要求.那么该属性可以作为主键列.
代理主键: 在业务中,不存符合以上3个条件的属性,那么就增加一个没有意义的列.作为主键.
6.4 基本数据与包装类型
l 基本数据类型和包装类型对应hibernate的映射类型相同
l 基本类型无法表达null、数字类型的默认值为0。
l 包装类默认值是null。当对于默认值有业务意义的时候需要使用包装类。
6.5 类型对应
Java数据类型 | Hibernate数据类型 | 标准SQL数据类型 |
byte、java.lang.Byte | byte | TINYINT |
short、java.lang.Short | short | SMALLINT |
int、java.lang.Integer | integer | INGEGER |
long、java.lang.Long | long | BIGINT |
float、java.lang.Float | float | FLOAT |
double、java.lang.Double | double | DOUBLE |
java.math.BigDecimal | big_decimal | NUMERIC |
char、java.lang.Character | character | CHAR(1) |
boolean、java.lang.Boolean | boolean | BIT |
java.lang.String | string | VARCHAR |
boolean、java.lang.Boolean | yes_no | CHAR(1)('Y'或'N') |
boolean、java.lang.Boolean | true_false | CHAR(1)('Y'或'N') |
java.util.Date、java.sql.Date | date | DATE |
java.util.Date、java.sql.Time | time | TIME |
java.util.Date、java.sql.Timestamp | timestamp | TIMESTAMP |
java.util.Calendar | calendar | TIMESTAMP |
java.util.Calendar | calendar_date | DATE |
byte[] | binary | VARBINARY、BLOB |
java.lang.String | text | CLOB |
java.io.Serializable | serializable | VARBINARY、BLOB |
java.sql.Clob | clob | CLOB |
java.sql.Blob | blob | BLOB |
java.lang.Class | class | VARCHAR |
java.util.Locale | locale | VARCHAR |
java.util.TimeZone | timezone | VARCHAR |
java.util.Currency | currency | VARCHAR |
6.6 普通属性
<hibernate-mapping> package 用于配置PO类所在包 例如: package="com.itheima.d_hbm" <class> 配置 PO类 和 表 之间对应关系 name:PO类全限定类名 例如:name="com.itheima.d_hbm.Person" 如果配置 package,name的取值可以是简单类名 name="Person" table : 数据库对应的表名 dynamic-insert="false" 是否支持动态生成insert语句 dynamic-update="false" 是否支持动态生成update语句 如果设置true,hibernate底层将判断提供数据是否为null,如果为null,insert或update语句将没有此项。 普通字段 <property> name : PO类的属性 column : 表中的列名,默认name的值相同 type:表中列的类型。默认hibernate自己通过getter获得类型,一般情况不用设置 取值1: hibernate类型 string 字符串 integer 整形 取值2: java类型 (全限定类名) java.lang.String 字符串 取值3:数据库类型 varchar(长度) 字符串 int 整形 <property name="birthday"> <column name="birthday" sql-type="datetime"></column> </property> javabean 一般使用类型 java.util.Date jdbc规范提供3中 java类型 mysql类型 java.sql.Date date java.sql.time time java.sql.timestamp timestamp null datetime
以上三个类型都是java.util.Date子类
length : 列的长度。默认值:255 not-null : 是否为null unique : 是否唯一 access:设置映射使用PO类属性或字段 property : 使用PO类属性,必须提供setter、getter方法 field : 使用PO类字段,一般很少使用。 insert 生成insert语句时,是否使用当前字段。 update 生成update语句时,是否使用当前字段。 默认情况:hibernate生成insert或update语句,使用配置文件所有项 注意:配置文件如果使用关键字,列名必须使用重音符 |
6.7 主键
主键 <id>配置主键 name:属性名称 access="" 设置使用属性还是字段 column="" 表的列名 length="" 长度 type="" 类型 <generator> class属性用于设置主键生成策略 1.increment 由hibernate自己维护自动增长 底层通过先查询max值,再+1策略 不建议使用,存在线程并发问题 2.identity hibernate底层采用数据库本身自动增长列 例如:mysql auto_increment 3.sequence hibernate底层采用数据库序列 例如:oracle 提供序列 4.hilo
</generator> 5.native 根据底层数据库的能力选择 identity、sequence 或者 hilo 中的一个。【】 ##以上策略使用整形,long, short 或者 int 类型 6.uuid 采用字符串唯一值【】 ##以上策略 代理主键,有hibernate维护。 7.assigned 自然主键,由程序自己维护。【】 |
1 对象状态与一级缓存
1.1 状态介绍
l hibernate 规定三种状态:瞬时态、持久态、脱管态
l 状态
瞬时态:transient,session没有缓存对象,数据库也没有对应记录。
OID特点:没有值
持久态:persistent,session缓存对象,数据库最终会有记录。(事务没有提交)
OID特点:有值
脱管态:detached,session没有缓存对象,数据库有记录。
OID特点:有值
1.2 转换
1.2.1 瞬时态/临时态
l 获得:一般都只直接创建(new)
l 瞬时态 转换 持久态
一般操作:save方法、saveOrUpdate
l 瞬时态 转换 脱管态
一般操作:通过setId方法设置数据
例如:
User user = new User(); //瞬时态
user.setUid(1); //脱管态
1.2.2 持久态
l 获得:
查询操作:get、loat、createQuery、createCriteria 等 获得都是持久态【】
执行save之后持久态
执行update之后持久态
l 持久态 转换 瞬时态
官方规定执行delete() --民间:删除态
l 持久态 转换 脱管态
session没有记录
session.close () 关闭
session.clear() 清除所有
session.evict(obj) 清除指定的PO对象
1.2.3 脱管态/游离态
l 获得:
创建、并设置OID的
通过api获得
l 脱管态 转换 瞬时态
手动去除OID,设置成默认值
l 脱管态 转换 持久态
一般操作:update()、saveOrUpdate
@Test public void demo01(){ User user = new User(); //瞬时态 user.setUsername("jack"); user.setPassword("1234"); //瞬时态(与oid没有关系)
Session session = factory.openSession(); session.beginTransaction();
session.save(user); //持久态 //---- 持久态就应该有持久态的行为(特性)
// user.setUsername("rose"); //持久态对象 被修改后,hibernate将自动生成update语句 // session.flush();
session.getTransaction().commit(); session.close();
System.out.println(user); //脱管态 } |
2 一级缓存
2.1 介绍
l 一级缓存:又称为session级别的缓存。当获得一次会话(session),hibernate在session中创建多个集合(map),用于存放操作数据(PO对象),为程序优化服务,如果之后需要相应的数据,hibernate优先从session缓存中获取,如果有就使用;如果没有再查询数据库。当session关闭时,一级缓存销毁。
2.2 一级缓存操作
2.2.1 证明一级缓存
@Test public void demo02(){ //证明一级缓存 Session session = factory.openSession(); session.beginTransaction();
//1 查询 id = 1 User user = (User) session.get(User.class, 1); System.out.println(user); //2 再查询 -- 不执行select语句,将从一级缓存获得 User user2 = (User) session.get(User.class, 1); System.out.println(user2);
session.getTransaction().commit(); session.close(); } |
2.2.2 移除
@Test public void demo03(){ //清除缓存 Session session = factory.openSession(); session.beginTransaction();
User user = (User) session.get(User.class, 1); //--select System.out.println(user);
//清除 //session.clear(); session.evict(user);
// 一级缓存没有缓存对象,从数据库直接查询 User user2 = (User) session.get(User.class, 1); //--select System.out.println(user2);
session.getTransaction().commit(); session.close(); } |
2.2.3 一级缓存快照【掌握】
l 快照:与一级缓存一样的存放位置,对一级缓存数据备份。保证数据库的数据与 一级缓存的数据必须一致。如果一级缓存修改了,在执行commit提交时,将自动刷新一级缓存,执行update语句,将一级缓存的数据更新到数据库。
2.2.4 refresh 刷新
l refresh 保证 一级缓存的数据 与 数据库的数据 保持一致。将执行select语句查询数据库,将一级缓存中的数据覆盖掉。只要执行refresh都将执行select语句。
@Test public void demo04(){ //刷新 Session session = factory.openSession(); session.beginTransaction();
User user = (User) session.get(User.class, 1); //--select System.out.println(user);
session.refresh(user);
session.getTransaction().commit(); session.close(); } |
2.2.5 快照演示(一级缓存刷新)
@Test public void demo05(){ //快照 Session session = factory.openSession(); session.beginTransaction();
User user = (User) session.get(User.class, 1); //--select System.out.println(user);
//修改持久态对象内容(一级缓存内容)--默认在commit时,将触发update语句。 user.setUsername("rose2");
session.getTransaction().commit(); session.close(); } |
l 问题:一级缓存什么时候刷新?(了解)
默认情况提交(commit())刷新。
@Test public void demo06(){ //设置刷新时机 Session session = factory.openSession(); session.beginTransaction();
//1 设置 session.setFlushMode(FlushMode.MANUAL);
User user = (User) session.get(User.class, 1); user.setUsername("rose4");
//1 查询所有 -- AUTO , 查询之前先更新,保存一级缓存和数据库一样的 //List<User> allUser = session.createQuery("from User").list();
//2手动刷新 --MANUAL 将执行update,注意:一级缓存必须修改后的 session.flush();
// 如果MANUAL 在执行commit 不进行update session.getTransaction().commit(); session.close(); } |
2.3 PO对象操作
2.3.1 save & persist
l save方法:瞬时态 转换 持久态 ,会初始化OID
1.执行save方法,立即触发insert语句,从数据库获得主键的值(OID值)
2.执行save方法前,设置OID将忽略。
3.如果执行查询,session缓存移除了,在执行save方法,将执行insert
@Test public void demo01(){ User user = new User(); user.setUid(100); user.setUsername("jack"); user.setPassword("1234");
Session session = factory.openSession(); session.beginTransaction();
session.save(user);
session.getTransaction().commit(); session.close(); } |
@Test public void demo03(){ //代理 assigned User user = new User(); //user.setUid(100); user.setUsername("jack"); user.setPassword("1234");
Session session = factory.openSession(); session.beginTransaction();
session.save(user);
session.getTransaction().commit(); session.close(); } |
l 注意:持久态对象不能修改OID的值
@Test public void demo04(){
Session session = factory.openSession(); session.beginTransaction();
User user = (User) session.get(User.class, 100); user.setUid(101);
session.save(user);
session.getTransaction().commit(); session.close(); } |
l persist方法:瞬时态 转换 持久态 ,不会立即初始化OID
注意: persist方法不会立即得到ID,所以执行sql语句的时机要靠后.
2.3.2 update
l update:脱管态 转换 持久态
如果OID在数据存放的,将执行update语句
如果OID不存在将抛异常
@Test public void demo01(){ //自然 assigned User user = new User(); user.setUid(101); user.setUsername("jack1"); user.setPassword("12345");
Session session = factory.openSession(); session.beginTransaction();
session.update(user);
session.getTransaction().commit(); session.close(); } |
l 注意1:如果数据没有修改,执行save方法,将触发update语句。
查询速度 比 更新速度快
通过<class select-before-update> 来设置更新前先查询,如果没有改变就不更新。
总结:
update 之后对象 持久态
@Test public void demo03(){ // merge 合并 User user = new User(); user.setUid(1); user.setUsername("jack3"); user.setPassword("12345");
Session session = factory.openSession(); session.beginTransaction();
// 1 oid =1 持久态对象 User user2 = (User) session.get(User.class, 1);
// session.update(user); session.merge(user);
session.getTransaction().commit(); session.close(); } |
2.3.3 saveOrUpdate
l 代理主键:
判断是否有OID
如果没有OID,将执行insert语句
如果有OID,将执行update语句。
@Test public void demo02(){ // 代理 native User user = new User(); // user.setUid(2); user.setUsername("jack2"); user.setPassword("12345");
Session session = factory.openSession(); session.beginTransaction();
session.saveOrUpdate(user);
session.getTransaction().commit(); session.close(); } |
l 自然主键:
先执行select语句,查询是否存放
如果不存在,将执行insert
如果存在,将执行update
@Test public void demo02(){ // 自然 assigned User user = new User(); user.setUid(2); user.setUsername("jack2333"); user.setPassword("12345333");
Session session = factory.openSession(); session.beginTransaction();
session.saveOrUpdate(user);
session.getTransaction().commit(); session.close(); } |
l 注意1:native下,默认OID是否存在,使用默认值。例如:Integer 默认null
通过<id unsaved-value="1"> 修改使用默认值,如果设置1进行insert语句。此内容提供hibernate使用的,录入到数据库后,采用自动增长。
2.3.4 delete
总结:
PO对象状态:瞬时态、持久态、脱管态
3 多表设计
l 在开发中,前期进行需求分析,需求分析提供E--R图,根据ER图编写表结构。
l 表之间关系存在3种:一对多、多对多、一对一。(回顾)
一对多:1表(主表)必须主键 和 多表(从表)必须外键,主表的主键 与 从表外键 形成主外键关系
多对多:提供中间表(从表),提供2个字段(外键)分别对应两个主表。
一对一:???
l 面单对象描述 对象与对象 之间关系?【掌握】
一对多:客户和订单
private class Customer{
//一对多:一个客户 拥有 多个订单
private Set<Order> orderSet;
}
private class Order{
//多对一:多个订单 属于 一个客户
private Customer customer;
}
多对多:Student学生 和 Course课程
private class Student{
//多对多:多个学生(当前)学习 【不同课程】
private Set<Course> courseSet ...;
}
private class Course{
//多对多:多个课程 可以被 【不同学生】学习
private Set<Student> student = ...;
}
一对一:公司company 和 地址address
private class Company{
private Address address;
}
private class Address{
private Company company;
}
4 关联关系映射
4.1 一对多实现【掌握】
4.1.1 实现类
public class Customer {
private Integer cid; private String cname;
//一对多:一个客户(当前客户) 拥有 【多个订单】 // * 需要容器存放多个值,一般建议Set (不重复、无序) // * 参考集合:List、Map、Array等 // ** 建议实例化--使用方便 private Set<Order> orderSet = new HashSet<Order>(); |
public class Order { private Integer xid; private String price;
//多对一:多个订单属于【一个客户】 private Customer customer; |
4.1.2 配置文件
l Customer.hbm.xml
<class name="com.itheima.b_onetomany.Customer" table="t_customer"> <id name="cid"> <generator class="native"></generator> </id> <property name="cname"></property>
<!-- 一对多:一个客户(当前客户) 拥有 【多个订单】 1 确定容器 set <set> 2 name确定对象属性名 3 确定从表外键的名称 4 确定关系,及另一个对象的类型 注意: 在hibernate中可以只进行单向配置 每一个配置项都可以完整的描述彼此关系。 一般情况采用双向配置,双方都可以完成描述表与表之间关系。 --> <!-- 一对多:一个客户(当前客户) 拥有 【多个订单】 --> <set name="orderSet" cascade="delete-orphan"> <key column="customer_id"></key> <one-to-many class="com.itheima.b_onetomany.Order"/> </set> </class> |
l Order.hbm.xml
<class name="com.itheima.b_onetomany.Order" table="t_order"> <id name="xid"> <generator class="native"></generator> </id> <property name="price"></property>
<!-- 多对一:多个订单属于【一个客户】 * name 确定属性名称 * class 确定自定义类型 * column 确定从表的外键名称 --> <many-to-one name="customer" class="com.itheima.b_onetomany.Customer" column="customer_id"></many-to-one>
</class> |
4.2 一对多操作
4.2.1 保存客户
@Test public void demo01(){ // 1 创建客户,并保存客户--成功 Session session = factory.openSession(); session.beginTransaction();
Customer customer = new Customer(); customer.setCname("田志成");
session.save(customer);
session.getTransaction().commit(); session.close(); } |
4.2.2 保存订单
@Test public void demo02(){ // 2 创建订单,保存订单--成功,外键为null Session session = factory.openSession(); session.beginTransaction();
Order order = new Order(); order.setPrice("998");
session.save(order);
session.getTransaction().commit(); session.close(); } |
4.2.3 客户关联订单,只保存客户
@Test public void demo03(){ // 3 创建客户和订单,客户关联订单,保存客户? Session session = factory.openSession(); session.beginTransaction();
//1 客户和订单 Customer customer = new Customer(); customer.setCname("成成");
Order order = new Order(); order.setPrice("998");
//2 客户关联订单 customer.getOrderSet().add(order);
//3 保存客户 session.save(customer);
session.getTransaction().commit(); session.close(); }
|
4.2.4 双向关联,使用inverse
@Test public void demo04(){ // 4 创建客户和订单,客户关联订单,订单也关联客户,保存客户和订单? // * 开发中优化程序 , n + 1 问题? // ** 解决方案1:客户不关联订单 ,不建议 // ** 解决方案2:客户放弃对订单表外键值的维护。 // **** Customer.hbm.xml <set name="orderSet" inverse="true"> // ** inverse 将维护外键值的权利交予对象。相当于自己放弃。(反转) Session session = factory.openSession(); session.beginTransaction();
//1 客户和订单 Customer customer = new Customer(); customer.setCname("成成");
Order order = new Order(); order.setPrice("998");
//2 客户关联订单 customer.getOrderSet().add(order); //3 订单也关联客户 order.setCustomer(customer);
//4 保存客户 // * 1 save(order) -- insert --> 1,998 null // * 2 订单管理客户,此时null --预留update --> 更新所有(正常设置) // * 3 save(customer) -- insert --> 1,成成 // * 4 客户关联订单 --> 预留update --> 更新订单外键 (维护外键) // * 5 提交commit --> 执行2 和 4 session.save(order); session.save(customer);
session.getTransaction().commit(); session.close(); } |
l 在一对多开发中,一方一般都放弃对外键值的维护。及<set inverse="true
4.3 级联操作(读、理解)
4.3.1 save-update 级联保存或更新
@Test public void demo032(){ // 32 创建客户和订单,客户关联订单,保存客户? --抛异常 // ** 解决方案2:级联操作--级联保存或更新 // ** Customer.hbm.xml <set cascade="save-update"> // ** 在保存客户的同时,一并保存订单 Session session = factory.openSession(); session.beginTransaction();
//1 客户和订单 Customer customer = new Customer(); //瞬时态 customer.setCname("成成");
Order order = new Order(); //瞬时态 order.setPrice("998");
//2 客户关联订单 customer.getOrderSet().add(order);
//3 保存客户 session.save(customer); //持久态 // 关联操作都是持久态的,此时 持久态Customer 引用 一个 瞬时态的Order 抛异常
session.getTransaction().commit(); session.close(); } |
4.3.2 delete 级联删除
@Test public void demo05(){ // 5 查询客户,并删除(持久态) // 默认:当删除客户,默认将订单外键设置成null。 // 级联删除:删除客户时,并将客户的订单删除。 // ** Customer.hbm.xml <set name="orderSet" cascade="delete"> Session session = factory.openSession(); session.beginTransaction();
Customer customer = (Customer) session.get(Customer.class, 10);
session.delete(customer);
session.getTransaction().commit(); session.close(); } |
4.3.3 孤儿删除
l 一对多关系,存在父子关系。1表(主表)可以成为父表,多表(从表)也可以子表。
总结:
主表不能删除,从表已经引用(关联)的数据
从表不能添加,主表不存在的数据。
@Test public void demo06(){ // 6 查询客户,并查询订单,解除客户和订单订单的关系 // * 默认:客户和订单解除关系后,外键被设置成null,此时订单就是孤儿。客户和订单都存在。 // * 孤儿删除(孤子删除),当订单称为孤儿,一并删除。客户仍存在。 Session session = factory.openSession(); session.beginTransaction();
//1 查询客户 Customer customer = (Customer) session.get(Customer.class, 9);
//2查询订单 Order order = (Order) session.get(Order.class, 8);
//3 解除关系 customer.getOrderSet().remove(order);
session.getTransaction().commit(); session.close(); } |
4.3.4 总结
save-update:A保存,同时保存B
delete:删除A,同时删除B,AB都不存在
delete-orphan:孤儿删除,解除关系,同时将B删除,A存在的。
如果需要配置多项,使用逗号分隔。<set cascade="save-update,delete">
all : save-update 和 delete 整合
all-delete-orphan : 三个整合
1.1.1 Hibernate的关联关系映射:(多对多)
1.1.1.1 多对多的配置:
步骤一创建实体和映射:
Student:
public class Student {
private Integer sid;
private String sname;
// 学生选择多门课程.
private Set<Course> courses = new HashSet<Course>();
...
}
Course:
public class Course {
private Integer cid;
private String cname;
// 课程可以被多个学生选择:
private Set<Student> students = new HashSet<Student>();
...
}
Student.hbm.xml
<hibernate-mapping>
<class name="cn.itcast.demo3.Student" table="student">
<id name="sid" column="sid">
<generator class="native"/>
</id>
<property name="sname" column="sname"/>
<!-- 配置多对多关联关系 -->
<set name="courses" table="stu_cour">
<key column="sno"/>
<many-to-many class="cn.itcast. demo3.Course" column="cno"/>
</set>
</class>
</hibernate-mapping>
Course.hbm.xml
<hibernate-mapping>
<class name="cn.itcast. demo3.Course" table="course">
<id name="cid" column="cid">
<generator class="native"/>
</id>
<property name="cname" column="cname"/>
<!-- 配置多对多关联关系映射 -->
<set name="students" table="stu_cour">
<key column="cno"/>
<many-to-many class="cn.itcast. demo3.Student" column="sno"/>
</set>
</class>
</hibernate-mapping>
2 抓取策略(优化)
2.1 检索方式
l 立即检索:立即查询,在执行查询语句时,立即查询所有的数据。
l 延迟检索:延迟查询,在执行查询语句之后,在需要时在查询。(懒加载)
2.2 检查策略
l 类级别检索:当前的类的属性获取是否需要延迟。
l 关联级别的检索:当前类 关联 另一个类是否需要延迟。
2.3 类级别检索
l get:立即检索。get方法一执行,立即查询所有字段的数据。
l load:延迟检索。默认情况,load方法执行后,如果只使用OID的值不进行查询,如果要使用其他属性值将查询 。 Customer.hbm.xml <class lazy="true | false">
lazy 默认值true,表示延迟检索,如果设置false表示立即检索。
@Test public void demo02() { //类级别 Session session = factory.openSession(); session.beginTransaction();
//1立即 // Customer customer = (Customer) session.get(Customer.class, 1); //2延迟 Customer customer = (Customer) session.load(Customer.class, 1);
//打印 System.out.println(customer.getCid()); System.out.println(customer.getCname());
session.getTransaction().commit(); session.close(); } |
2.4 关联级别检索
2.4.1 一对多或多对多
2.4.1.1 介绍
l 容器<set> 提供两个属性:fetch、lazy
fetch:确定使用sql格式
lazy:关联对象是否延迟。
l fetch:join、select、subselect
join:底层使用迫切左外连接
select:使用多个select语句(默认值)
subselect:使用子查询
l lazy:false、true、extra
false:立即
true:延迟(默认值)
extra:极其懒惰
2.4.1.2 fetch="join"
l fetch="join" ,lazy无效。底层使用迫切左外连接,使用一条select将所有内容全部查询。
@Test public void demo03() { //关联级别:一对多, // * Customer.hbm.xml <set fetch="join"> // *** select语句使用左外连接,一次性查询所有 Session session = factory.openSession(); session.beginTransaction();
//1 查询客户 Customer customer = (Customer) session.get(Customer.class, 1); System.out.println(customer.getCname());
//2 查询客户订单数 Set<Order> orderSet = customer.getOrderSet(); System.out.println(orderSet.size());
//3 查询客户订单详情 for (Order order : orderSet) { System.out.println(order); }
session.getTransaction().commit(); session.close(); } |
2.4.1.3 fetch="select"
l 当前对象 和 关联对象 使用多条select语句查询。
l lazy="false" , 立即,先查询客户select,立即查询订单select
l lazy="true",延迟,先查询客户select,需要订单时,再查询订单select
l lazy="extra",极其懒惰(延迟),先查询客户select, 如果只需要订单数,使用聚合函数(不查询详情)
2.4.1.4 fetch="subselect"
l 将使用子查询。注意:必须使用Query否则看不到效果。
l lazy= 同上
@Test public void demo04() { //关联级别:一对多, // 演示3:* Customer.hbm.xml <set fetch="subselect"> Session session = factory.openSession(); session.beginTransaction();
//1 查询客户 List<Customer> allCustomer = session.createQuery("from Customer").list(); Customer customer = allCustomer.get(0); System.out.println(customer.getCname());
//2 查询客户订单数 Set<Order> orderSet = customer.getOrderSet(); System.out.println(orderSet.size());
//3 查询客户订单详情 for (Order order : orderSet) { System.out.println(order); }
session.getTransaction().commit(); session.close(); } |
2.4.2 多对一
2.4.2.1 介绍
l <many-to-one fetch="" lazy=""> (<one-to-one>)
l fetch取值:join、select
join:底层使用迫切左外连接
select:多条select语句
l lazy取值:false、proxy、no-proxy
false:立即
proxy:采用关联对象 类级别检索的策略。
订单 关联 客户 (多对一)
订单 立即获得 客户,需要在客户Customer.hbm.xml <class lazy="false">
订单 延迟获得 客户,需要在客户Customer.hbm.xml <class lazy="true">
no-proxy 不研究
2.4.2.2 fetch="join"
l fecth="join" select语句使用左外连接,此时lazy无效。
@Test public void demo05() { //关联级别:多对一, // 演示1:* Order.hbm.xml <set fetch="join"> lazy无效 // * 注意:检查Customer.hbm.xml 和 Order.hbm.xml 没有额外的配置 Session session = factory.openSession(); session.beginTransaction();
//1 查询订单 Order order = (Order) session.get(Order.class, 1); System.out.println(order.getPrice());
//2 查询订单客户信息 Customer customer = order.getCustomer(); System.out.println(customer.getCname());
session.getTransaction().commit(); session.close(); } |
2.4.2.3 fetch="select"
l 将采用多条select语句,lazy="proxy"是否延迟,取决关联对象 类级别检索策略。
l lazy="false"
l lazy="proxy"
2.5 批量查询
l 当客户 关联查询 订单,给每一个客户生产一个select语句查询订单。批量查询使用in语句减少查询订单语句个数。
默认:select * from t_order where customer_id = ?
批量:select * from t_order where customer_id in (?,?,?,?)
l <set batch-size="5"> 5表示括号中?个数。
@Test public void demo06() { //批量查询 Session session = factory.openSession(); session.beginTransaction();
//1 查询所有客户 List<Customer> allCustomer = session.createQuery("from Customer").list();
//2遍历 for (Customer customer : allCustomer) { System.out.println(customer.getCname()); System.out.println(customer.getOrderSet().size()); }
session.getTransaction().commit(); session.close(); } |
2.6 检索总结
检索策略 | 优点 | 缺点 | 优先考虑使用的场合 |
立即检索 | 对应用程序完全透明,不管对象处于持久化状态还是游离状态,应用程序都可以从一个对象导航到关联的对象 | (1)select语句多 (2)可能会加载应用程序不需要访问的对象,浪费许多内存空间。 | (1)类级别 (2)应用程序需要立即访问的对象 (3)使用了二级缓存 |
延迟检索 | 由应用程序决定需要加载哪些对象,可以避免执行多余的select语句,以及避免加载应用程序不需要访问的对象。因此能提高检索性能,并节省内存空间。 | 应用程序如果希望访问游离状态的代理类实例,必须保证她在持久化状态时已经被初始化。 | (1)一对多或者多对多关联 (2)应用程序不需要立即访问或者根本不会访问的对象
|
表连接检索 | (1)对应用程序完全透明,不管对象处于持久化状态还是游离状态,都可从一个对象导航到另一个对象。 (2)使用了外连接,select语句少 | (1)可能会加载应用程序不需要访问的对象,浪费内存。 (2)复杂的数据库表连接也会影响检索性能。 | (1)多对一或一对一关联 (2)需要立即访问的对象 (3)数据库有良好的表连接性能。 |
Customer Get(int id)
Return Session.load(Customer.class,id);
- layz=false
- 在Service层获得在页面要上要用到的属性=> 在Service层中确保数据已经
3 查询方式总结
1.通过OID检索(查询)
get()立即、如果没有数据返回null
load()延迟,如果没有数据抛异常。
2.导航对象图检索方式:关联查询
customer.getOrderSet()
user.getPost().getDepartment().getDepName();
3.原始sql语句
SQLQuery sqlQuery = session.createSQLQuery("sql 语句") --->表,表字段(列)
sqlQuery.list() 查询所有
sqlQuery.uniqueResult() 查询一个
4.HQL,hibernate query language hibernate 查询语言【1】
Query query = session.createQuery("hql语句") --> 对象,对象属性
5.QBC,query by criteria 纯面对对象查询语言【2】
4 HQL【掌握】
4.1 介绍
4.2 查询所有客户
@Test public void demo01(){ //1 查询所有 Session session = factory.openSession(); session.beginTransaction();
//1 使用简单类名 , 存在自动导包 // * Customer.hbm.xml <hibernate-mapping auto-import="true"> // Query query = session.createQuery("from Customer"); //2 使用全限定类名 Query query = session.createQuery("from com.itheima.a_init.Customer");
List<Customer> allCustomer = query.list(); for (Customer customer : allCustomer) { System.out.println(customer); }
session.getTransaction().commit(); session.close(); } |
4.3 选择查询
@Test public void demo02(){ //2 简单条件查询 Session session = factory.openSession(); session.beginTransaction();
//1 指定数据,cid OID名称 // Query query = session.createQuery("from Customer where cid = 1"); //2 如果使用id,也可以(了解) // Query query = session.createQuery("from Customer where id = 1"); //3 对象别名 ,格式: 类 [as] 别名 // Query query = session.createQuery("from Customer as c where c.cid = 1"); //4 查询所有项,mysql--> select * from... Query query = session.createQuery("select c from Customer as c where c.cid = 1");
Customer customer = (Customer) query.uniqueResult(); System.out.println(customer);
session.getTransaction().commit(); session.close(); } |
4.4 投影查询(部分)
@Test public void demo04(){ //4 投影 Session session = factory.openSession(); session.beginTransaction();
//1 默认 //如果单列 ,select c.cname from,需要List<Object> //如果多列,select c.cid,c.cname from ,需要List<Object[]> ,list存放每行,Object[]多列 // Query query = session.createQuery("select c.cid,c.cname from Customer c"); //2 将查询部分数据,设置Customer对象中 // * 格式:new Customer(c.cid,c.cname) // * 注意:Customer必须提供相应的构造方法。 // * 如果投影使用oid,结果脱管态对象。 Query query = session.createQuery("select new Customer(c.cid,c.cname) from Customer c");
List<Customer> allCustomer = query.list(); for (Customer customer : allCustomer) { System.out.println(customer.getCid() + " : " + customer.getOrderSet().size()); }
session.getTransaction().commit(); session.close(); } |
4.5 排序
@Test public void demo03(){ //3排序 ,mysql--> select... order by 字段 [asc]|desc ,.... Session session = factory.openSession(); session.beginTransaction();
Query query = session.createQuery("from Customer order by cid desc");
List<Customer> allCustomer = query.list(); for (Customer customer : allCustomer) { System.out.println(customer.getCid()); }
session.getTransaction().commit(); session.close(); } |
4.6 分页
@Test public void demo05(){ //分页 Session session = factory.openSession(); session.beginTransaction();
Query query = session.createQuery("from Customer"); // * 开始索引 , startIndex 算法: startIndex = (pageNum - 1) * pageSize; // *** pageNum 当前页(之前的 pageCode) query.setFirstResult(0); // * 每页显示个数 , pageSize query.setMaxResults(2);
List<Customer> allCustomer = query.list(); for (Customer customer : allCustomer) { System.out.println(customer.getCid()); }
session.getTransaction().commit(); session.close(); } |
4.7 绑定参数
@Test public void demo06(){ /* 6 绑定参数 * 方式1:占位符,使用? 在hql语句替换具体参数 * 设置参数 query.setXxx(int , object) * 参数1:?位置,从0开始。 * 参数2:实际参数 * 例如:String --> query.setString(int,String) * 方式2:别名 , 格式 “属性= :别名 ” * 设置参数 query.setXxx(String,object) * 参数1:别名 * 参数2:实际参数 * 例如:Integer --> query.setInteger(String,Integer) * 提供公共设置方法 * setParameter(int|string , Object) */ Session session = factory.openSession(); session.beginTransaction();
Integer cid = 1;
//方式1 // Query query = session.createQuery("from Customer where cid = ?"); // query.setInteger(0, cid); //方式2 Query query = session.createQuery("from Customer where cid = :xxx"); // query.setInteger("xxx", cid); query.setParameter("xxx", cid);
Customer customer = (Customer) query.uniqueResult(); System.out.println(customer);
session.getTransaction().commit(); session.close(); } |
4.8 聚合函数和分组
@Test public void demo07(){ /* 7 聚合函数 */ Session session = factory.openSession(); session.beginTransaction();
//1 // Query query = session.createQuery("select count(*) from Customer"); //2 别名 // Query query = session.createQuery("select count(c) from Customer c"); //3 oid Query query = session.createQuery("select count(cid) from Customer");
Long numLong = (Long) query.uniqueResult(); int num = numLong.intValue();
System.out.println(num);
session.getTransaction().commit(); session.close(); } |
4.9 连接查询
1.交叉连接 ,等效 sql 笛卡尔积
2.隐式内连接,等效 sql 隐式内连接
3.内连接,等效sql内连接
4.迫切内连接,hibernate底层使用 内连接。
5.左外连接,等效sql左外连接
6.迫切左外连接,hibernate底层使用 左外连接
7.右外连接,等效sql右外连接
内连接和迫切内连接?
左外连接和迫切左外连接?
@Test public void demo08(){ /* 8 链接查询 : 左外连接和迫切左外连接? * * 左外连接 , left outer join * 底层使用sql的左外连接,hibernate进行数据自动封装,将一条记录,封装给两个对象(Customer,Order) * 将两个对象添加到一个对象数组中Object[Customer,Order] * * 迫切左外链接 left outer join fetch * 底层使用sql的左外连接,hibernate将一条记录封装给Customer,讲order数据封装Order,并将order关联到Customer * customer.getOrderSet().add(order) * 默认查询的数据重复 */ Session session = factory.openSession(); session.beginTransaction();
//左外连接 // List list = session.createQuery("from Customer c left outer join c.orderSet ").list(); //迫切左外链接 (默认数据重复) // List list = session.createQuery("from Customer c left outer join fetch c.orderSet ").list(); //迫切左外链接 (去重复) List list = session.createQuery("select distinct c from Customer c left outer join fetch c.orderSet ").list();
session.getTransaction().commit(); session.close(); } |
4.10 命名查询
l 思想:将HQL从java源码中,提取到配置文件中。
l 分类:全局、布局
l 配置
全局:*.hbm.xml <class></class><query name="名称">HQL语句
局部: <class name="" table=""><id><property> <query name="">HQL</class>
l 获得
全局:
session.getNamedQuery("queryName")
局部:
session.getNamedQuery("className.queryName") 需要使用类的全限定名称
@Test public void demo09(){ /* 9 命名查询 */ Session session = factory.openSession(); session.beginTransaction();
//全局 //List list = session.getNamedQuery("findAll").list(); //局部 List list = session.getNamedQuery("com.itheima.a_init.Customer.findAll").list();
System.out.println(list.size());
session.getTransaction().commit(); session.close(); } |
5 QBC【了解】
5.0.0.1 QBC查询:
QBC:Query By Criteria条件查询.面向对象的查询的方式.
5.0.0.2 QBC简单的查询:
// 简单查询:
List<Customer> list = session.createCriteria(Customer.class).list();
for (Customer customer : list) {
System.out.println(customer);
}
5.0.0.3 QBC分页的查询:
Criteria criteria = session.createCriteria(Order.class);
criteria.setFirstResult(10);
criteria.setMaxResults(10);
List<Order> list = criteria.list();
5.0.0.4 QBC排序查询:
Criteria criteria = session.createCriteria(Customer.class);
// criteria.addOrder(org.hibernate.criterion.Order.asc("age"));
criteria.addOrder(org.hibernate.criterion.Order.desc("age"));
List<Customer> list = criteria.list();
5.0.0.5 QBC条件查询:
// 按名称查询:
/*Criteria criteria = session.createCriteria(Customer.class);
criteria.add(Restrictions.eq("cname", "tom"));
List<Customer> list = criteria.list();*/
// 模糊查询;
/*Criteria criteria = session.createCriteria(Customer.class);
criteria.add(Restrictions.like("cname", "t%"));
List<Customer> list = criteria.list();*/
// 条件并列查询
Criteria criteria = session.createCriteria(Customer.class);
criteria.add(Restrictions.like("cname", "t%"));
criteria.add(Restrictions.ge("age", 35));
List<Customer> list = criteria.list();
5.0.0.6 离线查询(了解)
l DetachedCriteria 离线查询对象,不需要使用Session就可以拼凑查询条件。一般使用在web层或service层拼凑。将此对象传递给dao层,此时将与session进行绑定执行查询。
l 离线查询条件与QBC一样的。
@Test public void demo10(){ /* 10 离线查询 */
//web & service DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Customer.class); detachedCriteria.add(Restrictions.eq("cid", 1));
//---------------dao
Session session = factory.openSession(); session.beginTransaction();
// 离线Criteria 与session绑定 Criteria criteria = detachedCriteria.getExecutableCriteria(session); List<Customer> allCustomer = criteria.list(); System.out.println(allCustomer.size());
session.getTransaction().commit(); session.close(); } |
6 常见配置
6.1 整合c3p0(连接池) (了解)
l 整合c3p0
步骤一:导入c3p0 jar包
步骤二:hibernate.cfg.xml 配置
hibernate.connection.provider_class org.hibernate.connection.C3P0ConnectionProvider
l c3p0具体配置参数
###########################
### C3P0 Connection Pool###
###########################
#hibernate.c3p0.max_size 2
#hibernate.c3p0.min_size 2
#hibernate.c3p0.timeout 5000
#hibernate.c3p0.max_statements 100
#hibernate.c3p0.idle_test_period 3000
#hibernate.c3p0.acquire_increment 2
#hibernate.c3p0.validate false
6.2 事务
6.2.1 回顾
l 事务:一组业务操作,要么全部成功,要么全部不成功。
l 特性:ACID
原子性:整体
一致性:数据
隔离性:并发
持久性:结果
l 隔离问题:
脏读:一个事务读到另一个事务未提交的内容
不可重复读:一个事务读到另一个事务已提交的内容(insert)
虚读(幻读):一个事务读到另一个事务已提交的内容(update)
l 隔离级别--解决问题
read uncommittd,读未提交。存在3个问题。
read committed,读已提交。解决:脏读。存在2个问题。
repeatable read ,可重复读。解决:脏读、不可重复读。存在1个问题。
serializable,串行化。单事务。没有问题。
6.2.2 hibernate设置隔离级别
l 在hibernate.cfg.xml 配置
hibernate.connection.isolation 4
6.2.3 lost update 丢失更新
l 悲观锁:丢失更新肯定会发生。
采用数据库锁机制。
读锁:共享锁。
select .... from ... lock in share mode;
写锁:排他锁。(独占)
select ... from .... for update
l 乐观锁:丢失更新肯定不会发生
在表中提供一个字段(版本字段),用于标识记录。如果版本不一致,不允许操作。
6.2.4 hibernate处理丢失更新
l 悲观锁:写锁
@Test public void demo01(){ //1 查询所有 Session session = factory.openSession(); session.beginTransaction();
Customer customer = (Customer) session.get(Customer.class, 1 ,LockMode.UPGRADE); System.out.println(customer);
session.getTransaction().commit(); session.close(); } |
l 乐观锁:
在PO对象(javabean)提供字段,表示版本字段。一般Integer
在*.hbm.xml 文件配置 <version name="...">
步骤一:
步骤二:
步骤三:测试
@Test public void demo02(){ //1 查询所有 Session session = factory.openSession(); session.beginTransaction();
// Order order = new Order(); // order.setPrice(998d); // session.save(order);
Order order = (Order) session.get(Order.class, 32); order.setPrice(889d);
session.getTransaction().commit(); session.close(); } |
1 整合log4j(了解)
l slf4j 核心jar : slf4j-api-1.6.1.jar 。slf4j是日志框架,将其他优秀的日志第三方进行整合。
l 整合导入jar包
log4j 核心包:log4j-1.2.17.jar
过渡jar(整合jar):slf4j-log4j12-1.7.5.jar
l 导入配置文件
log4j.properties ,此配置文件通知log4j 如何输出日志
l 配置文件内容:
1.记录器
2.输出源
3.布局
l 记录器
例如: log4j.rootLogger=info, stdout,file
格式:log4j.rootLogger=日志级别, 输出源1,输出源2,。。。。
log4j 日志级别 : fatal 致命错误 error 错误 warn 警告 info 信息 debug 调试信息 trace 堆栈信息 (由高到底顺序)
l 输出源:
例如:log4j.appender.file=org.apache.log4j.FileAppender
格式:log4j.appender.输出源的名称=输出源的实现类
名称:自定义
实现类:log4j提供
输出源属性例如:log4j.appender.file.File=d\:mylog.log
输出源属性格式:log4j.appender.名称.属性=值
每一个输出源对应一个实现类,实现类都属性(setter),底层执行setter方法进行赋值
l 常见的输出源实现类
org.apache.log4j.FileAppender 输出文件中
file ,表示文件输出位置
org.apache.log4j.ConsoleAppender 输出到控制台
Target ,表示使用哪种输出方式,在控制台打印内容,取值:System.out / System.err
l 布局 -- 确定输出格式
例如:log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
格式:log4j.appender.数据源.layout=org.apache.log4j.PatternLayout
布局属性:log4j.appender. 数据源.layout.ConversionPattern=值
12:56:30,123 info
l 扩展:对指定的目录设置日志级别
例如:log4j.logger.org.hibernate.transaction=debug
格式:log4j.logger.包结构=级别
2 一对一(了解)
l 情况1:主表的主键,与从表的外键(唯一),形成主外键关系
l 情况2:主表的主键,与从表的主键,形成主外键关系 (从表的主键又是外键)
2.1.1 情况1
2.1.2 情况2
3 二级缓存【掌握】
3.1 介绍
3.1.1 缓存
缓存(Cache): 计算机领域非常通用的概念。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写硬盘(永久性数据存储源)的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝。缓存的物理介质通常是内存
缓存:程序<--(内存)-->硬盘
3.1.2 什么是二级缓存
l hibernate 提供缓存机制:一级缓存、二级缓存
一级缓存:session级别缓存,在一次请求中共享数据。
二级缓存:sessionFactory级别缓存,整个应用程序共享一个会话工厂,共享一个二级缓存。
l SessionFactory的缓存两部分: 内置缓存:使用一个Map,用于存放配置信息,预定义HQL语句等,提供给Hibernate框架自己使用,对外只读的。不能操作。
外置缓存:使用另一个Map,用于存放用户自定义数据。默认不开启。外置缓存hibernate只提供规范(接口),需要第三方实现类。外置缓存有成为二级缓存。
3.1.3 二级缓存内部结构
l 二级就是由4部分构成
n 类级别缓存
n 集合级别缓存
n 时间戳缓存
n 查询缓存(二级缓存的第2大部分,三级缓存)
3.1.4 并发访问策略
l 访问策略:读写型(read-write)、只读型(read-only)
3.1.5 应用场景
l 适合放入二级缓存中的数据:
很少被修改
不是很重要的数据, 允许出现偶尔的并发问题
l 不适合放入二级缓存中的数据:
经常被修改
财务数据, 绝对不允许出现并发问题
与其他应用数据共享的数据
3.1.6 二级缓存提供商
l EHCache: 可作为进程(单机)范围内的缓存, 存放数据的物理介质可以是内存或硬盘, 对 Hibernate 的查询缓存提供了支持。--支持集群。
l OpenSymphony `:可作为进程范围内的缓存, 存放数据的物理介质可以是内存或硬盘, 提供了丰富的缓存数据过期策略, 对 Hibernate 的查询缓存提供了支持
l SwarmCache: 可作为集群范围内的缓存, 但不支持 Hibernate 的查询缓存
l JBossCache:可作为集群范围内的缓存, 支持 Hibernate 的查询缓存
X表示支持
3.2 配置(操作)
1.导入jar包:ehcache-1.5.0.jar/ commons-logging.jar/ backport-util-concurrent.jar
2.开启二级缓存(我要使用二级缓存)
3.确定二级缓存提供商(我要使用哪个二级缓存)
4.确定需要缓存内容
1>配置需要缓存的类
2>配置需要缓存的集合
5.配置ehcache自定义配置文件
3.2.1 导入jar包
3.2.2 开启二级缓存
l 在hibernate.cfg.xml 配置二级缓存
<!-- 9.1 开启二级缓存 --> <property name="hibernate.cache.use_second_level_cache">true</property> |
3.2.3 确定提供商
l hibernate.cfg.xml 配置
<!-- 9.2 提供商 --> <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
|
3.2.4 确定缓存内容
l 在hibernate.cfg.xml 确定 类缓存 和集合缓存配置项
l 配置
<!-- 9.3 确定缓存内容 --> <!-- 类缓存 --> <class-cache usage="read-write" class="com.itheima.a_init.Customer"/> <class-cache usage="read-write" class="com.itheima.a_init.Order"/> <!-- 集合缓存 --> <collection-cache usage="read-write" collection="com.itheima.a_init.Customer.orderSet"/> |
3.2.5 ehcache配置文件
步骤1:从jar包复制xml文件
步骤2:将xml重命名“ehcache.xml”
步骤3:将修改后的xml,拷贝到src下
3.3 演示
3.3.1 证明
@Test public void demo01(){ //1 证明二级缓存存在 // * 修改toString() // * 如果二级缓存开启,查询3 没有select语句,表示从二级缓存获得的。 // * 将二级缓存关闭,查询3将触发select语句。 Session s1 = factory.openSession(); s1.beginTransaction();
//1 查询id=1 -- 执行select (查询后,将数据存放在一级缓存,之后由一级缓存同步到二级缓存) Customer c1 = (Customer) s1.get(Customer.class, 1); System.out.println(c1); //2 查询id=1 --从一级缓存获取 Customer c2 = (Customer) s1.get(Customer.class, 1); System.out.println(c2);
s1.getTransaction().commit(); s1.close();
System.out.println("----------");
Session s2 = factory.openSession(); s2.beginTransaction();
//3 查询id=1 -- 从二级缓存获取 Customer c3 = (Customer) s2.get(Customer.class, 1); System.out.println(c3);
s2.getTransaction().commit(); s2.close(); } |
3.3.2 类缓存
l 类缓存:只存放数据
l 一级缓存:存放对象本身
@Test public void demo02(){ //2 类缓存:只存放数据,散装数据。 // * 使用默认的toString(); Session s1 = factory.openSession(); s1.beginTransaction();
//1 查询id=1 -- 执行select Customer c1 = (Customer) s1.get(Customer.class, 1); System.out.println(c1); //2 查询id=1 -- 从一级缓存获取,一级缓存存放对象本身 Customer c2 = (Customer) s1.get(Customer.class, 1); System.out.println(c2);
s1.getTransaction().commit(); s1.close();
System.out.println("----------");
Session s2 = factory.openSession(); s2.beginTransaction();
//3 查询id=1 -- 对象不一样,数据一样 Customer c3 = (Customer) s2.get(Customer.class, 1); System.out.println(c3);
s2.getTransaction().commit(); s2.close(); } |
3.3.3 集合缓存
@Test public void demo03(){ //3 集合缓存:只存放关联对象OID的值,如果需要数据,从类缓存中获取。 // * 3.1 默认:第一条select 查询客户,第二天 select 查询客户所有订单 // * 3.2 操作:在hibernate.cfg.xml 将 Order 类缓存删除 // *** <!-- <class-cache usage="read-write" class="com.itheima.a_init.Order"/>--> // *** 多了10条select,通过订单的id查询订单 Session s1 = factory.openSession(); s1.beginTransaction();
//1 查询id=1 Customer c1 = (Customer) s1.get(Customer.class, 1); System.out.println(c1); //2 获得订单 for (Order o1 : c1.getOrderSet()) { System.out.println(o1); }
s1.getTransaction().commit(); s1.close();
System.out.println("----------");
Session s2 = factory.openSession(); s2.beginTransaction();
//3 查询id=1 Customer c3 = (Customer) s2.get(Customer.class, 1); System.out.println(c3); //4 获得订单 for (Order o2 : c3.getOrderSet()) { System.out.println(o2); }
s2.getTransaction().commit(); s2.close(); } |
3.3.4 时间戳
l 时间戳:任何操作都在时间戳中记录操作时间。
@Test public void demo04(){ //4 时间戳: 所有的操作都会在时间戳中进行记录,如果数据不一致,将触发select语句进行查询 // * 修改toString() Session s1 = factory.openSession(); s1.beginTransaction();
//1 查询id=1 Integer cid = 1; Customer c1 = (Customer) s1.get(Customer.class, cid); System.out.println(c1); //2 绕过一级和二级缓存,修改数据库,修改客户cname=大东哥 s1.createQuery("update Customer set cname = ? where cid = ?") .setString(0, "大东哥") .setInteger(1, cid) .executeUpdate(); //3打印 System.out.println(c1);
s1.getTransaction().commit(); s1.close();
System.out.println("----------");
Session s2 = factory.openSession(); s2.beginTransaction();
//4 查询id=1 -- ? Customer c3 = (Customer) s2.get(Customer.class, 1); System.out.println(c3);
s2.getTransaction().commit(); s2.close(); } |
3.3.5 查询缓存
l 查询缓存又称为三级缓存(民间)
l 查询缓存默认不使用。需要手动开启
l 查询缓存:将HQL语句与 查询结果进行绑定。通过HQL相同语句可以缓存内容。
默认情况Query对象只将查询结果存放在一级和二级缓存,不从一级或二级缓存获取。
查询缓存就是让Query可以从二级缓存获得内容。
步骤一:开启查询缓存
<!-- 9.4 开启查询缓存 --> <property name="hibernate.cache.use_query_cache">true</property>
|
步骤二:在查询query对象,设置缓存内容(注意:存放和查询 都需要设置)
@Test public void demo05(){ //5 查询缓存 Session s1 = factory.openSession(); s1.beginTransaction();
//1 query查询 Query q1 = s1.createQuery("from Customer"); q1.setCacheable(true); List<Customer> a1 = q1.list(); for (Customer c1 : a1) { System.out.println(c1); }
//2 cid =1 -- 一级缓存获得 Customer customer = (Customer) s1.get(Customer.class, 1); System.out.println(customer);
s1.getTransaction().commit(); s1.close();
System.out.println("----------");
Session s2 = factory.openSession(); s2.beginTransaction();
//2 cid =1 -- 二级缓存获得 Customer customer2 = (Customer) s2.get(Customer.class, 1); System.out.println(customer2);
//3 query查询 Query q2 = s2.createQuery("from Customer"); q2.setCacheable(true); List<Customer> a2 = q2.list(); for (Customer c2 : a2) { System.out.println(c2); }
s2.getTransaction().commit(); s2.close(); } |
3.4 ehcache配置文件
l <diskStore path="java.io.tmpdir"/> 设置临时文件存放位置。(缓存一般内存,一定程度时,写入硬盘。)
l 缓存详细设置
<defaultCache> 所有的缓存对象默认的配置
<cache name="类"> 指定对象单独配置
l 参数设置
maxElementsInMemory="10000" 内存最大数
eternal="false" 是否永久(内存常驻留)
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true" 内存满了,是否写入到硬盘
maxElementsOnDisk="10000000" 硬盘最大数
diskPersistent="false" 关闭JVM,是否将内存保存硬盘中
diskExpiryThreadIntervalSeconds="120" 轮询
memoryStoreEvictionPolicy="LRU"
Least Recently Used (specified as LRU).
First In First Out (specified as FIFO)
Less Frequently Used (specified as LFU)
|