1 ORM映射
ObjectRe alational Mapping对象关系映射,即把对象映射到关系型数据库中。Hibernate与ORM关系:Hibernate是Orm的具体实现。
2 Hibernate环境搭建
2.1 建立数据库和表
2.2 引入相关jar包(可以用myeclipse用户库管理)
hibernate3.jar(核心包);
required目录中所有jar(6个);
jpa目录(1个)简历实体类;
相关数据库驱动包。
2.3 写主配置文件(见Hibernate主配置)
2.4 写实体类Src/hibernate.cfg.xml和映射文件
3 HibernateApi
|--Configuration 管理配置文件类: 加载主配置文件、创建session工厂
config.configure(); 默认加载src/hibernate.cfg.xml
如果加载其他包下主配置文件,
config.configure(“cn/itcast/hibernate.cfg.xml”);
config.buildSessionFactory(); 创建session工厂
config.addClass(Dept.class); 测试时候使用,相当于加载类映射文件
会到当前类所在包下找Dept.hbm.xml
使用方便,但不利于维护!
|--SessionFactory session的工厂,用于创建session对象
(代表整个hibernate.cfg.xml)
一个应用主需要一个SessionFactory即可(单例)
Session创建2种方式:
sf.openSession(); 创建Session对象 【测试】
sf.getCurrentSession(); 获取当前session或者创建session 【项目】
以线程方式常见session
sf.close() ; 关闭sessionFactory; (应用程序结束时候调用!)
|-- Session session表示会话: 与数据库连接的会话信息!
主要维护了一个与数据库连接的对象(Connection)
Session封装:
比Connection具有更强大的功能;
开发时候,可以用session对象方法直接操作数据库!
Hibernate提供的session,操作的是对象!
更新:
session.save(object); 保存一个对象
session.update(dept); 更新一个对象(对象必须设置主键值)
session.saveOrUpdate(dept);
主键自增:
1. 没有设置主键,则保存
2. 有设置主键, 则更新
注意:
如果设置主键不存在,hibernate则报错!
主键非自增:
只要设置的主键不存在,就保存!
否则,就更新!
session.delete(dept); 删除一个对象
查询:
主键查询、
session.get(Dept.class,10000000);
及时加载数据,只要执行get,就向数据发送查询sql,如果查询的主键不存在,返回null;
session.load(Dept.class,10000000);
当用到数据的时候,才向数据发送查询sql; 如果查询的主键不存在,只要使用就报错!
HQL查询
* Hql :hibernate query language Hibernate提供的面向对象的查询语言!
* 面试题: hql 与 sql 区别:
* sql :
* 结构化的查询语言,查询的是表、表中的字段、不区分大小写;
* hql :
* 面向对象的查询语言,查询的是对象、对象属性! 区分大小写!
案例:
设计接口,用hibernate实现CRUD操作!
|--Query 接口
q.list(); 查询全部
q.uniqueResult(); 查询唯一结果
q.executeUpdate(); 执行更新
4 Hibernate实现CRUD操作
4.1
Hibernate工具类获取session对象 |
private static SessionFactory sf;
//创建SessionFactory对象 static { sf = new Configuration() // 创建对象 .configure() // 加载主配置文件 .addClass(Dept.class) // 加载映射文件(测试时候用) .buildSessionFactory(); // 最后,创建session工厂 }
//返回session对象 public static Session getSession() { return sf.openSession(); } |
实现crud操作 |
/** * hibernate作为dao操作的实现 * * @author AdminTH * */ publicclass DeptDao implements IDeptDao {
@Override publicvoid delete(int id) {
Session session = null;
try { // 1. 创建session session = HibernateUtils.getSession(); // 2. 开启事务 Transaction tx = session.beginTransaction(); // 3. 执行操作 Object dept = session.get(Dept.class, id); if (dept != null) { session.delete(dept); } // 4. 提交事务 tx.commit(); } catch (Exception e) { thrownew RuntimeException(e); } finally { // 关闭 session.close(); }
}
@Override public Dept findById(int id) { Session session = null;
try { session = HibernateUtils.getSession(); Transaction tx = session.beginTransaction(); // 3. 执行操作 return (Dept) session.get(Dept.class, id); } catch (Exception e) { thrownew RuntimeException(e); } finally { // 关闭 session.close(); } }
@Override public List<Dept> getAll() { Session session = null;
try { // 1. 创建session session = HibernateUtils.getSession(); // 2. 开启事务 Transaction tx = session.beginTransaction(); // 3. 执行操作 return session.createQuery("from Dept").list(); } catch (Exception e) { thrownew RuntimeException(e); } finally { // 关闭 session.close(); } }
@Override public List<Dept> getAll(int index, int count) { Session session = null;
try { // 1. 创建session session = HibernateUtils.getSession(); // 2. 开启事务 Transaction tx = session.beginTransaction();
Query q = session.createQuery("from Dept"); // 设置起始行 q.setFirstResult(index);// select * from 表 limit 0,3 q.setMaxResults(count);
return q.list(); } catch (Exception e) { thrownew RuntimeException(e); } finally { // 关闭 session.close(); } }
@Override publicvoid save(Dept dept) { Session session = null;
try { session = HibernateUtils.getSession(); Transaction tx = session.beginTransaction(); session.save(dept); // 4. 提交事务 tx.commit(); } catch (Exception e) { thrownew RuntimeException(e); } finally { // 关闭 session.close(); } }
@Override publicvoid update(Dept dept) { Session session = null;
try { // 1. 创建session session = HibernateUtils.getSession(); // 2. 开启事务 Transaction tx = session.beginTransaction(); // 3. 执行操作 session.update(dept); // 4. 提交事务 tx.commit(); } catch (Exception e) { thrownew RuntimeException(e); } finally { // 关闭 session.close(); } }
}
|
5 Hibernate执行流程
-
创建配置管理器configuration对象执行configure方法加载主配置文件(也加载了映射文件),不带参数的构造参数默认加载src目录下的hibernate.cfg.xml
-
创建sessionFactory对象
-
创建或者获取session操作数据库的对象
-
开启事务
-
操作数据库
-
查询操作:get()/load()/list()/uniqueResult()方法
-
更新操作:save()/update()/()saveOrUpdate()/delete()
更新操作必须提交事务。
Hibernate主配置
-
-
即src/hibernate.cfg.xml;主要包含:连接数据库的配置信息(连接字符串,数据库方言等);其他配置(显示sql,自动建表等);加载实体映射文件:*.hbm.xml;
参考hibernate常用配置:
hibernate-distribution-3.6.0.Final\project\etc\hibernate.properties
6.1 主配置文件示例:
<?xml version="1.0" encoding="UTF-8" ?> <!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> <!-- 1. 连接数据库配置 --> <property name="hibernate.connection.url">jdbc:mysql:///emp_sys</property> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password">root</property> <!-- 数据库方言(hibernate会根据方言配置,生成相应符合数据库语法的SQL!) --> <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- 2. 常用的配置 --> <!-- 显示hibernate运行时期,生成的sql语句 --> <property name="hibernate.show_sql">true</property> <!-- 格式化显示sql --> <property name="hibernate.format_sql">false</property> <!-- 自动建表 --> <property name="hibernate.hbm2ddl.auto">update</property>
<!-- 3. 加载映射文件 【项目工程中,加载映射必须通过mapping节点配置;】--> <mapping resource="cn/itcast/a_hello/Dept.hbm.xml"/> </session-factory> </hibernate-configuration> |
6.2 为什么要配置数据库方言?
告诉Hibernate使用的是哪种数据库。(hibernate会根据方言配置,生成相应符合数据库语法的SQL! )
6.3 自动建表:
#hibernate.hbm2ddl.autocreate-drop 每次在创建SessionFactory的时候建表,
在执行sf.close(); 方法的时候,就删除表!
#hibernate.hbm2ddl.autocreate 每次运行程序都会新创建表,如果表存在先删除再创建!
有时Hibernate可能有bug,表删掉不能自动建表!!!
#hibernate.hbm2ddl.autoupdate 创建表,如果表已经存在,则不创建!
#hibernate.hbm2ddl.autovalidate 验证映射文件与数据库是否一致,不一致就报错! (生产环境用!)
-
直接通过配置建表
<propertyname="hibernate.hbm2ddl.auto">create</property>
-
代码创建
-
// 代码的方式创建表 @Test publicvoid testApp() throws Exception { // 创建配置管理器对象 Configuration cfg = new Configuration().configure(); // 创建工具类对象 SchemaExport export = new SchemaExport(cfg); // 建表 /* * @param script print the DDL to the console 是否在控制台打印DDL语句(数据创建语言) * @param export export the script to the database 是否执行SQL脚本语句 */ export.create(true, true); } |
7 Hibernate实体映射配置
作用: 把对象通过这个映射文件(xml), 映射到数据库中;映射文件描述了对象与表的对应关系;映射文件要能完全描述一张表。Hibernate也就是通过这个配置文件实现了ORM(对象关系模型)。
7.1 基本配置介绍
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <!-- package (可选) 指定当前映射文件中,出现的所有对象,所属的包。 如果不写,下面的class 的name属性的值要写类全名。 --> <hibernate-mapping package="cn.itcast.c_crud"> <!-- class 表示要映射一个javabean对象到数据库表! name 表示要映射的对象!值为实体类名。 table (可选) 对象,对应的表。如果没有写,对象名与表名应保持一致 子结点主要由2部分组成: 1. 主键配置 【id节点】 2. 普通字段配置 【property节点】 --> <class name="Dept" table="t_dept"> <!-- id 表示主键映射 generator 指定主键生成策略!即主键怎样生成(手动指定、自增长...) (api : 章节 5.1.2.2.1. Various additional generators) increment 自增长(不适合多线程项目使用! ) identity 自增长, mysql中自增长的方式! sequence 序列的方式自增长;Oracle数据库中自增长的方式;或DB2数据库 native 自增长会根据底层数据库的能力选择 identity、sequence 等中的一个。 如果是oracle数据库,使用 sequence 序列的方式自增长 如果是mysql数据库,使用identity作为自增长方式;
assigned 手动指定主键的值! 【自己要控制主键不重复!】 uuid 自动生成的uuid的值作为主键 foreign (主键的生成策略,外键的策略!把外键作为主键!) 一对一 --> <id name="id" column="id"> <generator class="uuid"></generator> </id>
<!-- name 必填,表示的是Dept对象中的属性 column 属性对应的字段名称,如果不写默认与属性名同名 type 指定字段的类型 # hibernate类型, 首字母小写 string # java类型 要指定为类的全名 java.lang.String (可选) 如果没有指定,匹配bean的属性的类型! length 指定字符的长度 (字符类型有效) unique="true" 给某个字段添加唯一约束! --> <property name="detpName" column="detp_name" type="java.lang.String" length="20"/> <property name="remark"></property> <property name="desc" column="`desc`"></property> </class> </hibernate-mapping> |
另一种配置方式是:将column属性分为property的子节点来写,这样更加清晰。 |
<id name="infoId" type="java.lang.String"> <column name="info_id" length="32"/> <generator class="uuid.hex" /> </id> <property name="type" type="java.lang.String"> <column name="type" length="10" /> </property> |
7.2 联合主键映射配置
数据库设计中,当表中找不到合适的列作为主键时(因为主键列,必须非空且唯一)有时就会用到联合主键(多列,联合在一起,作为一个主键),当然一般会采用id列作为主键,主键的维护交给数据库服务端!
注:一个表只能有一个主键。但一个表可以没有主键!
例:假设用数据库存储用户名、地址、年龄等信息!用户名、地址,作为一个联合主键!
联合主键需要定义联合主键实体类对象,当一个实体涉及到联合主键一般写的步骤为:联合主键映射,步骤:1. 联合主键实体类对象;2. 实体类对象;3.写映射文件
联合主键实体类 |
/** * 1. 联合主键实体类对象 * 注意:必须实现可序列接口! hibernate主键查询的时候,要求主键要实现可序列接口! */ publicclass CompositeKeys implements Serializable{ private String userName; private String address; } |
实体类 |
publicclass User { // 联合主键对象 private CompositeKeys keys; privateintage; } |
实体类映射文件 |
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="cn.itcast.d_composite"> <class name="User" table="t_user">
<!-- 联合主键映射 --> <composite-id name="keys" class="CompositeKeys"> <key-property name="userName"></key-property> <key-property name="address"></key-property> </composite-id>
<property name="age"></property> </class> </hibernate-mapping> |
7.3 集合映射配置
集合映射,映射的数据都只有一个、两个字段!如果要映射到另外一张表中有多个字段:此时,映射的集合元素,要封装为对象!即:多对一、一对多映射 (关联对象的映射!)
例:假设一个用户对应多个地址,需要建立两个表。用户表:id,name.地址表:user_id,name.
// 用户信息 publicclass User { privateintid; private String name; // 存储多个地址 (必须用接口, 因为hibernate会根据接口生成代理对象【jdk代理】) //用set集合保存(常用) private Set<String> addressSet = new HashSet<String>(); // 无序
// 用list集合保存 private List<String> addressList = new ArrayList<String>();
// map 集合 private Map<String,String> addressMap = new HashMap<String,String>(); } |
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="cn.itcast.a_collection"> <class name="User" table="t_users"> <id name="id"> <generator class="native"></generator> </id> <property name="name" type="string" length="20"></property>
<!-- 用户对应的多个地址 --> <!-- set 集合映射 name 映射的集合属性 table 集合数据要映射到哪一张表 key 集合表(t_adddressSet), 中的外键字段 【引入t_users(id)】 element 指定集合中的数据,映射到集合表的哪个字段上 【必须指定类型!】 --> <set name="addressSet" table="t_adddressSet"> <key column="user_id"></key> <element column="address" type="string"></element> </set>
<!-- list集合映射 list-index 对应的数据库中排序字段,保证list集合的数据在db中有序 --> <list name="addressList" table="t_addressList"> <key column="user_id"></key> <list-index column="index_"></list-index> <element column="address" type="string"></element> </list>
<!-- map集合映射 key 外键字段 map-key 指定map集合的key映射到集合表的哪个字段上 element 指定map集合的value 映射到集合表的哪个字段 --> <map name="addressMap" table="t_adddressmap"> <key column="user_id"></key> <map-key column="addressKey" type="string"></map-key> <element column="addressValue" type="string"></element> </map> </class> </hibernate-mapping> |
publicclass App {
privatestatic SessionFactory sf; static { sf = new Configuration()// .configure()// .addClass(User.class)// .buildSessionFactory(); }
@Test publicvoid testSave() throws Exception { // 模拟数据 User user = new User(); user.setName("Jack"); user.getAddressSet().add("广州"); user.getAddressSet().add("深圳"); user.getAddressList().add("zz"); user.getAddressList().add("aa"); user.getAddressMap().put("gzth", "广州市天河区"); user.getAddressMap().put("szlh", "深圳市罗湖区");
Session session = sf.openSession(); session.beginTransaction(); // 保存 session.save(user);
session.getTransaction().commit(); session.close(); }
@Test publicvoid testGet() throws Exception { Session session = sf.openSession(); session.beginTransaction();
// 主键查询 User user = (User) session.get(User.class, 3);
// 测试set System.out.println(user.getName()); System.out.println(user.getAddressSet()); // 在用到set集合数据的时候,才向数据库发送sql语句!同load方法。
// 测试list数据 System.out.println(user.getAddressList());
// 测试map数据 System.out.println(user.getAddressMap()); session.getTransaction().commit(); session.close(); } } |
7.4 一对一映射
例如,用户信息与身份证信息的关系是一对一。
映射文件书写方式有两种:基于外键的形式;基于主键的形式
用户信息实体 |
publicclass User { privateintid; private String name; private String address; /* * 用户对应的身份证, 一对一关系, 无外键方 */ private IdCard idCard; } |
身份证信息实体(基于外键) |
publicclass IdCard { private String cardNo; private String place; /* * 一对一关系,有外键方 * (映射: user对象,要映射生成外键字段) */ private User user; } |
身份证信息实体(基于主键) |
publicclass IdCard {
private int user_id; // 主键字段, 外键字段 private String cardNo; private String place; /* * 一对一关系,有外键方 * (映射: user对象,要映射生成主键外键约束) */ private User user; } |
用户映射 |
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="cn.itcast.b_one2one_2"> <class name="User" table="t_user"> <id name="id"> <generator class="native"></generator> </id> <property name="name" length="20"></property> <property name="address" length="20"></property> <!-- 一对一,无外键方 --> <one-to-one name="idCard" class="IdCard"></one-to-one> </class> </hibernate-mapping> |
身份证映射(基于外键) |
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <!-- 一对一映射,基于外键的映射实现: --> <hibernate-mapping package="cn.itcast.b_one2one_1"> <class name="IdCard" table="t_IdCard"> <id name="cardNo"> <!—assigned 手动指定主键 --> <generator class="assigned"></generator> </id> <property name="place" type="string" length="20"></property> <!-- 一对一,有外键方(相当于员工与部门的关系中,员工的一方配置) unique="true" 给当前字段(外键),添加唯一约束 --> <many-to-one name="user" class="User" column="user_id" unique="true"></many-to-one> </class> </hibernate-mapping> |
身份证映射(基于主键) |
<class name="IdCard" table="t_IdCard"> <id name="user_id"> <!-- 主键生成: 外键的策略! --> <!-- param 指定参数 name="property" 固定写法,指定引用的对象属性是哪个 <param name="property">user</param> user指应用的对象是 <one-to-one name="user"> 保持一致 --> <generator class="foreign"> <param name="property">user</param> </generator> </id> <property name="cardNo" type="string" length="40"></property> <property name="place" type="string" length="40"></property> <!-- 基于主键的一对一 constrained="true" 指定在主键上,添加外键约束! --> <one-to-one name="user" class="User" constrained="true"></one-to-one> </class> |
测试 |
@Test publicvoid test_1() throws Exception { Session session = sf.openSession(); session.beginTransaction();
IdCard idCard = new IdCard(); idCard.setCardNo("3412XX");
User user = new User(); user.setName("Jet"); user.setAddress("广州天河");
// 关系 idCard.setUser(user);
// -----保存 session.save(user); session.save(idCard);
session.getTransaction().commit(); session.close(); } |
7.5 组件映射与继承映射
类与类之间的关系:组合关系:一个对象中包含另外一个对象,这两个类可以看成组合关系;继承关系:子类继承父类!
7.5.1 组件映射
一个类中包含了另一个类我们称之为组合关系组件映射: 组件类与被包含的组件类共同映射到一张表中,需要使用组件映射。
例如:汽车与车轮
汽车实体 |
publicclass Wheel { privatedoublesize; privateintcount; } |
车轮实体 |
publicclass Car { privateintid; private String name; privatedoubleprice; // 汽车的,轮子 private Wheel wheel; } |
映射文件 |
<class name="Car" table="t_car"> <id name="id"> <generator class="native"></generator> </id> <property name="name" length="50"></property> <property name="price" type="double"></property>
<!-- 组件映射,将车轮(被包含的组件类)的实体映射写在这里 --> <component name="wheel" class="Wheel"> <property name="size" type="double"></property> <property name="count" type="int"></property> </component> </class> |
7.5.2 继承映射
类与类之间也有继承关系,例如动物—猫—狗。写映射文件有四种方式(包含简单继承映射),但在数据库中有三中存储方式。
简单继承映射(映射文件写法同普通的映射文件)
动物类 |
publicclass Animal { privateintid; private String name; } |
狗 |
publicclass Dog extends Animal { // 玩耍 private String play; } |
猫 |
publicclass Cat extends Animal{ // 抓老鼠 private String catching; } |
简单映射文件,以猫为例(与一个普通类的映射文件没有区别) |
<!-- 继承映射,简单继承映射 每一个子类,对应一个映射文件,父类、子类字段写到一起! --> <class name="Cat" table="t_cat"> <!-- 父类 --> <id name="id"> <generator class="native"></generator> </id> <property name="name"></property>
<!-- 子类 --> <property name="catching"></property> </class> |
这种写法与普通映射文件写法没有区别,只不过一些字段是从父类继承过来的。
这种映射配置的写法特点:每一个子类用一个映射文件写;
每一个子类对应一个映射文件,对应一张表!
缺点:重复配置较多!(父类字段的配置)
继承映射其他方式
数据中存储继承映射有三种方式,但无论哪种存储方式,映射文件都只有一个。本例图解。
方式一:父类与子类都在一张表中。
本例的一张表映射写法:
<!-- 复杂继承映射 1. 整个继承结构一张表 --> <class name="Animal" table="t_animal"> <!-- 父类 --> <id name="id"> <generator class="native"></generator> </id> <!-- 指定鉴别器字段: 区分是哪一个字段信息! --> <discriminator column="type_" type="string"></discriminator> <property name="name"></property>
<!-- 子类: 猫 每一个子类用一个subclass节点,最终把所有的子类字段都存储到一张表! discriminator-value 指定鉴别器字段,即上面type_字段的值! 如果不指定,默认为当前子类全名! --> <subclass name="Cat" discriminator-value="cat_b"> <property name="catching"></property> </subclass>
<!-- 子类: 狗 --> <subclass name="Dog" discriminator-value="dog_a"> <property name="play"></property> </subclass> </class> |
这种写法不符合数据库三大范式
方式二:父类一张表,每个子类一张表。
本例的三张表写法
<!-- 复杂继承映射 2. 每个类一张表,2个子类,三张表! --> <class name="Animal" table="t_animal"> <!-- 父类 --> <id name="id"> <generator class="native"></generator> </id> <property name="name"></property>
<!-- 猫:子类 每个子类对应一张表 joined-subclass 用这个映射标签,就表示每个对象都对应表 key : t_cat表的外键 property 对象属性 --> <joined-subclass name="Cat" table="t_cat"> <key column="animal_id"></key> <property name="catching"></property> </joined-subclass>
<joined-subclass name="Dog" table="t_dog"> <key column="animal_id"></key> <property name="play"></property> </joined-subclass>
</class> |
这种写法符合数据库三大范式,且更加面向对象。但是, 表的结构过于复杂, 不方便查找数据,也影响查找效率!
方式三:父类没有表,每个子类有一张表。
本例两张表的写法
<!-- 复杂继承映射 3. 每个子类对应一张表、父类不对应表、所有子类写到一个映射文件 abstract="true" 表示当前映射的对象不对应表,没有默认表! --> <class name="Animal" abstract="true"> <!-- 父类 --> <id name="id"> <!—- 此时主键字段不能设置为自增长 --> <generator class="assigned"></generator> </id> <property name="name"></property>
<!-- 子类, 猫 --> <union-subclass name="Cat" table="t_cat"> <property name="catching"></property> </union-subclass>
<!-- 狗 --> <union-subclass name="Dog" table="t_dog"> <property name="play"></property> </union-subclass> </class> |
注意:主键字段不能设置为自增长,否则会报异常:
MappingException: Cannot use identitycolumn key generation with <union-subclass> mapping
7.6 多对一、一对多映射
例:员工跟部门的关系。员工对部门是多对一,部门对员工一对多。
部门实体类,省略get和set方法。 |
publicclass Dept { privateintid; private String name; // 多个员工 (一对多) private Set<Employee> employees = new HashSet<Employee>(); } |
Dept.hbm.xml |
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="cn.itcast.b_one2many"> <class name="Dept" table="t_dept"> <id name="id"> <generator class="native"></generator> </id> <property name="name" type="string" length="20"></property>
<!-- 一对多配置 name 映射的集合属性 table 集合属性对应的表(集合表) key 集合表的外键字段 (集合表的哪个字段,引用了当前表的主键, 这样就关联上了!即: t_employee.dept_id=t_dept.id ) -->
<!-- inverse 维护关联关系的属性 默认值为false,表示不控制反转,即当前方有控制权,控制表的关联关系(即可以维护关系)。 为true时 表示控制反转,放弃当前表的维护,解除关系。 --> <set name="employees" table="t_employee" inverse="true"> <!—- 该表中被别的表作为外键引用的字段。--> <key column="dept_id"></key> <one-to-many class="Employee"/> </set> </class> </hibernate-mapping> |
员工实体类,省略get和set方法 |
publicclass Employee { privateintempId; private String empName; private Date birth; // 员工对应的部门 (多对一) private Dept dept; // 注意:这里一定不能实例化! } |
Employee.hbm.xml |
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="cn.itcast.b_one2many"> <class name="Employee" table="t_employee"> <id name="empId"> <generator class="native"></generator> </id> <property name="empName" type="string" length="20"></property> <property name="birth" type="date"></property>
<!-- 多对一映射配置 name 映射的对象属性 column 属性对应的外键字段 (指定了当前表t_employee,的外键字段) class 属性的类型 --> <many-to-one name="dept" column="dept_id" class="Dept"></many-to-one> </class> </hibernate-mapping> |
注:两个映射文件中的配置都有列:dept_id。是为了告诉Hibernate两种表是如何关联起来的。(也就是告诉Hibernate两张表是通过dept.dept_id = emp.dept_id关联的) |
7.6.1 两张表如何维护关系效率最高
publicclass App_1_save { privatestatic SessionFactory sf; static { sf = new Configuration()// .configure()// .addClass(Dept.class)// .addClass(Employee.class)// .buildSessionFactory(); } // 1. 一的一方维护关系 @Test publicvoid testSave() throws Exception { Session session = sf.openSession(); session.beginTransaction();
// 保存数据,通过部门方维护关系! Dept dept = new Dept(); dept.setName("开发部");
Employee emp1 = new Employee(); emp1.setEmpName("老张"); emp1.setBirth(new Date());
Employee emp2 = new Employee(); emp2.setEmpName("老李"); emp2.setBirth(new Date());
// 处理关系,----> 1. 部门方 dept.getEmployees().add(emp1); dept.getEmployees().add(emp2);
//------------------------- session.save(emp1); // 在通过一的一方维护关系,先保存哪个对象没有关系;因为都是通过update语句维护的两张表之间的关系(事务并没有提交,由Hibernate对插入数据进行了处理,所以当先插入员工时,即使没有对应的部门也能执行代码,因为并没有先将部门数据插入到数据库的表中。) session.save(emp2); session.save(dept);
session.getTransaction().commit(); session.close(); }
// 2. 【推荐】通过多的一方维护关系 // 建议: 在一对多与多对一的关系的维护上,要通过多的一方维护关系!【在保存的时候,先保存一的一方,可以减少生成的update语句!】 @Test publicvoid testSave2() throws Exception { Session session = sf.openSession(); session.beginTransaction();
// 保存数据,通过部门方维护关系! Dept dept = new Dept(); dept.setName("开发部");
Employee emp1 = new Employee(); emp1.setEmpName("老张"); emp1.setBirth(new Date());
Employee emp2 = new Employee(); emp2.setEmpName("老李"); emp2.setBirth(new Date());
// 处理关系,----> 2. 员工方维护 emp1.setDept(dept); emp2.setDept(dept);
// 保存 session.save(dept); // 通过多的一方维护关系,先保存哪个对运行时有影响的! 当先保存一的方法时候,可以减少生成的update语句,效率提升! session.save(emp1); session.save(emp2);
session.getTransaction().commit(); session.close(); }
// 3. 双向维护 【有可能生成更多的update语句,效率最低 sql】 @Test publicvoid bak() throws Exception { Session session = sf.openSession(); session.beginTransaction();
// 保存数据,通过部门方维护关系! Dept dept = new Dept(); dept.setName("开发部");
Employee emp1 = new Employee(); emp1.setEmpName("老张"); emp1.setBirth(new Date());
Employee emp2 = new Employee(); emp2.setEmpName("老李"); emp2.setBirth(new Date());
// 通过部门维护一次 dept.getEmployees().add(emp1); dept.getEmployees().add(emp2);
// 再通过员工方维护一次 emp1.setDept(dept); emp2.setDept(dept);
//---------------- session.save(dept); session.save(emp1); session.save(emp2);
session.getTransaction().commit(); session.close(); } } |
7.6.2 对获取数据进行测试
/** * 关联关系的维护: * 1. 保存数据 * 2. 通过一方,获取另外一方 */ publicclass App_2_get { privatestatic SessionFactory sf; static { sf = new Configuration()// .configure()// .addClass(Dept.class)// .addClass(Employee.class)// .buildSessionFactory(); }
// 2. 通过一方,获取另外一方 @Test publicvoid get() throws Exception { Session session = sf.openSession(); session.beginTransaction();
// 部门方, Dept dept = (Dept) session.get(Dept.class, 1); System.out.println(dept.getName()); System.out.println(dept.getEmployees()); // 懒加载,使用时才向数据库发送sql
// 员工, // Employee emp = (Employee) session.get(Employee.class, 1); // System.out.println(emp.getEmpName()); // System.out.println(emp.getDept().getName()); // 懒加载
session.getTransaction().commit(); session.close(); }
//3. 解除关系 @Test publicvoid releaseRelation() throws Exception { Session session = sf.openSession(); session.beginTransaction(); // 主键查询 Dept dept = (Dept) session.get(Dept.class, 1); dept.getEmployees().clear();// update t_employee set dept_id=null where dept_id=1
session.getTransaction().commit(); session.close(); }
//4. 删除数据,对关联关系的影响,会先解除关联在进行删除 @Test publicvoid deleteData() throws Exception { Session session = sf.openSession(); session.beginTransaction();
// 先查询 Dept dept = (Dept) session.get(Dept.class, 2); session.delete(dept);
session.getTransaction().commit(); session.close(); } } |
7.6.3 inverse属性对关联关系的维护
// inverse 属性,在维护关联关系的时候起作用, 表示控制权是否转移! // 注意: 这个只在一的一方有效!(因为例如只在部门一方有效,即可以配置是否放弃控制权。多的一方,即员工方的表的字段是自己的(dept_id),当然应该拥有完全的控制权,所以不用配置。) // inverse, 表示控制反转 // ---> false 不控制反转, 当前方不进行控制权转移,有控制权 //----> true 控制反转, 当前方控制反转,当前方没有控制权 publicclass App_3_inverse { privatestatic SessionFactory sf; static { sf = new Configuration()// .configure()// .addClass(Dept.class)// .addClass(Employee.class)// .buildSessionFactory(); } /** * 1. 保存数据 * inverse = false (默认值)有控制权,可以保存, 及处理关联关系! * inverse = true 控制反转,当前方没有控制权,也可以保存,但不能处理关联关系!即员工表中dept_id=null */ @Test publicvoid testSave2() throws Exception { Session session = sf.openSession(); session.beginTransaction();
// 保存数据,通过部门方维护关系! Dept dept = new Dept(); dept.setName("开发部");
Employee emp1 = new Employee(); emp1.setEmpName("老张"); emp1.setBirth(new Date());
Employee emp2 = new Employee(); emp2.setEmpName("老李"); emp2.setBirth(new Date());
dept.getEmployees().add(emp1); dept.getEmployees().add(emp2);
// 保存 session.save(dept); // 通过多的一方维护关系,先保存哪个对运行时有影响的! 当先保存一的方法时候,可以减少生成的update语句,效率提升! session.save(emp1); session.save(emp2);
session.getTransaction().commit(); session.close(); } /** * 2. 获取数据 * 是否设置inverse属性,对获取数据,无影响!(前提员工表dept_id != null) */ @Test publicvoid get() throws Exception { Session session = sf.openSession(); session.beginTransaction();
// 部门方, Dept dept = (Dept) session.get(Dept.class, 3); System.out.println(dept.getName()); System.out.println(dept.getEmployees()); // 懒加载
// 员工, // Employee emp = (Employee) session.get(Employee.class, 1); // System.out.println(emp.getEmpName()); // System.out.println(emp.getDept().getName()); // 懒加载
session.getTransaction().commit(); session.close(); } /** * 3. 解除关系 * inverse=false, 表示有控制权, 可以通过一的一方解除关系:清空所有对当前部门的引用! * inverse=true, 表示没有控制权, 不能解除关系(但不会报错!) * @throws Exception */ @Test publicvoid releaseRelation() throws Exception { Session session = sf.openSession(); session.beginTransaction(); // 主键查询 Dept dept = (Dept) session.get(Dept.class, 4); dept.getEmployees().clear(); // inverse=true, 不执行任何操作
session.getTransaction().commit(); session.close(); } /** * 4. 删除数据,对关联关系的影响 * inverse=false , 有控制权, 先清空对当前主键(dept_id)的所有引用, 再删除自身! * inverse=true, 没有控制权, 如果要删除的记录主键(dept_id)没有被引用,可以删除; * 如果有被外键引用,因为没有控制权不能维护对方,所以会直接生成一条delete 语句, 违反外键约束,报错! * @throws Exception */ @Test publicvoid deleteData() throws Exception { Session session = sf.openSession(); session.beginTransaction();
// 先查询 Dept dept = (Dept) session.get(Dept.class, 3); session.delete(dept);
session.getTransaction().commit(); session.close(); } } |
7.6.4 lazy属性详解
lazy,即hibernate中懒加载!懒加载就是在用到数据的时候,才向数据库发送查询的sql语句!(所谓的懒就是我不得不做的时候才去做)
get/load主键查询的区别:get 及时加载,只要执行get方法,就向数据库发送查询的sql,如果查询的主键不存在,返回null;load 懒加载,当用到数据的时候才向数据库发送sql查询!如果查询的主键不存在,使用会报错!
设置位置:可以在类、字段、一对多、多对一上设置lazy属性,开启/关闭懒加载功能。类级别,默认开启懒加载!字段级别,只对长文本类型有效(longtext),即我们不用这个长文本字段时可以先不查,等用到了在查,可以提高系统的执行效率;集合,默认懒加载;多对一,默认懒加载。
集合懒加载 :Lazy 的属性有三个值。true 默认,表示默认支持懒加载;false 关闭懒加载;extra也是开启懒加载(但会更懒),在真正用到数据的时候才查询;当只是使用集合的size()/isEmpty()方法的时候,只是发送统计sql,而不是查询数据的sql,只有真正用到集合数据的时候才会发送查询sql。
懒加载会出现的异常信息:(当session关闭后,使用懒加载数据,就会有懒加载异常!)org.hibernate.LazyInitializationException:
failedto lazily initialize a collection of role: cn.itcast.c_lazy.Dept.employees, nosession or session was closed
异常的解决方式(并非真正解决)
//懒加载, 异常 @Test publicvoid lazy_exeception() throws Exception { Session session = sf.openSession(); session.beginTransaction(); // 查询, Dept dept = (Dept) session.get(Dept.class, 5);
// 方式1: 先用一下懒加载数据 //System.out.println(dept.getEmployees()); // 已经查询! // 方式2:强迫代理对象初始化 Hibernate.initialize(dept.getEmployees());//相当于先用一下 // 方式3:关闭懒加载, 即设置lazy=false // 总结, 在session关闭前,先使用懒加载数据; 然后session关闭,再获取的数据就不是懒加载的数据了! // 方式4:连接查询 / open session in view 模式
session.getTransaction().commit(); session.close();
// Session关闭后,不能使用懒加载数据! System.out.println(dept.getEmployees());// 执行这里,发送SQL,到数据库服务器端! } |
7.6.5 cascade级联操作
cascade 表示级联
save-update 级联保存或更新【保存的时候,会保存当前对象,以及其关联的对象】
delete 表示级联删除;先删除对当前主键的所有引用,再删除自身
none 不级联(默认)
save-update,delete或all 级联保存更新、删除
例:部门与员工的例子中,如果想保存部门的时候,级联保存部门下的员工,需要在部门的映射文件的set标签中配置cascade=“save-update”
7.7 多对多映射
例如:项目与开发人员是多对多的关系。
7.7.1 实体设计代码:
项目实体 |
publicclass Project { privateintpid; private String pname; // 项目对应的多个开发人员 【一对多】 private Set<Developer> developers = new HashSet<Developer>(); |
开发人员实体 |
publicclass Developer { privateintid; private String name; // 开发人员,参与的多个项目 private Set<Project> projects = new HashSet<Project>(); |
7.7.2 实体映射配置:
Project.hbm.xml |
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="cn.itcast.d_many2many"> <class name="Project" table="t_project"> <id name="pid"> <generator class="native"></generator> </id> <property name="pname" type="string" length="20"></property> <!-- 多对多映射 项目,对应的多个开发人员 --> <set name="developers" table="t_relation"> <key column="pid"></key> <many-to-many class="Developer" column="tid"></many-to-many> </set> </class> </hibernate-mapping> |
Developer.hbm.xml |
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="cn.itcast.d_many2many"> <class name="Developer" table="t_developer"> <id name="id"> <generator class="native"></generator> </id> <property name="name" type="string" length="20"></property> <!-- 多对多映射 name 要映射的集合属性 table 集合属性,对应的中间表 key 指定中间表的外键字段(自动引用当前表的主键t_developer(id) ) many-to-many class 集合元素的类型 column 外键字段,对应的中间表字段
(t_developer.id=t_relation.tid, t_relation.pid=t_project.pid) --> <set name="projects" table="t_relation"> <key column="tid"></key> <many-to-many class="Project" column="pid"></many-to-many> </set> </class> </hibernate-mapping> |
7.7.3 Inverse关联关系的维护
是否设置inverse,对维护关联关系的影响:
1) 保存数据时有影响。
Inverse=false, 有控制权,才可以往中间表中插入数据
Inverse=true, 不会往中间表插入数据!
2) 获取数据没有影响。
注意:a. 多对多映射配置,也可以只配置一方(一方配置,三张表的关系已经体现):这样,就只能通过配置的一方维护对方;
b. 如果多对多双向配置,2个方向都可以维护另外一方;
c. (了解)双方维护时候,对应的中间表可以不是同一个;
单独的一方,维护一个独立的中间表!
3) 解除关系有影响。
Inverse=false, 有控制权,解除关系就是指删除中间表数据!
Inverse=true, 没有控制权, 不能解除关系(不会报错!), 什么操作都不执行
4) 删除数据有影响。
inverse=false, 有控制权, 会先删除中间表数据,再删除自身!
inverse=true, 没有控制权,如果主键被中间表引用,删除报错! 否则,可以删除!
总结:Inverse属性对关联关系的影响:在中间表中就是对外键约束的维护,多对多中就是对中间表的维护。获取数据,根本就不会影响外键和中间的数据,所以不会受影响。保存,删除和解除关系时会外键字段和中间的数据进行置空或删除,故会影响关联关系的维护。
8 Hibernate中对象的三种状态
8.1 临时状态
-
一般是直接new出来的对象
-
不处于Session的缓存中;
-
在数据库中没有对应的记录。
持久化状态
-
在session管理范围内;
-
数据库中有对应的记录;
-
处于持久化状态的对象
-
当对象属性修改的时候,提交事务时,会把修改的结果同步到数据库!只要调用session的save/get/load/update/saveOrUpdate/list()等方法的时候,对象就会变为持久化状态;
8.3 游离状态
-
一般指session关闭后由持久化状态转换来的状态 ,因此在数据库中可能还有对应的记录(前提是记录没有被其它程序删除)
-
不处于session管理中
-
注:只要对象的(对应数据库中主键的)属性在数据库中存在,就可以说对象在数据库中有对应的记录!
状态转化图:
演示代码
publicclass App_1_status { privatestatic SessionFactory sf; static { sf = new Configuration()// .configure()// .addClass(Dept.class)// .addClass(Employee.class)// .buildSessionFactory(); }
// 1. 对象3种状态,之间的转换 @Test publicvoid test_1() throws Exception { Session session = sf.openSession(); session.beginTransaction();
// 对象 Dept dept = new Dept(); //---> 【临时状态】 dept.setName("HR");
// 保存 session.save(dept); //---> 【持久化状态】 insert into t_dept (name) values (?)
// 现在,dept处理session的管理! dept.setName("HR"); //update t_dept set name=? where id=?
session.getTransaction().commit(); session.close();
// 对象 //System.out.println(dept.getName()); // //---> 【游离状态】 }
// 持久化状态 // 只要调用session的save/get/load/update/saveOrUpdate/list() 等方法的时候,对象就会变为持久化状态状态 @Test publicvoid test_2() throws Exception { Session session = sf.openSession(); session.beginTransaction(); // 状态? Dept dept = (Dept) session.get(Dept.class, 1); dept.setName("Developer");
session.getTransaction().commit(); session.close(); } // 思考: 对象状态,哪些状态的对象,在数据库中有对象的记录?持久化/游离 // 游离状态 @Test publicvoid test_3() throws Exception { Session session = sf.openSession(); session.beginTransaction();
// 删除数据两种方式: // 方式1:先获取,再删除 // Dept dept = (Dept) session.get(Dept.class, 6); // if (dept != null) { // session.delete(dept);// 对象在数据库中要有记录 // } //方式2. 手动模拟一个游离状态对象 Dept dept = new Dept(); dept.setId(6); session.delete(dept);
session.getTransaction().commit(); session.close(); // 1. session关闭后对象处于游离状态 } } |
|
9 Hibernate一级缓存和二级缓存
9.1 一级缓存
Hibernate的一级缓存是基于session对象的缓存,用于减少对数据库的访问次数,从而提升程序执行效率!(先从缓存中找,缓存中有数据直接获取,没有数据在向数据库发送sql获取数据。缓存实际上是map<主键, 数据>的形式存储数据)所以一级缓存只是在创建session到session关闭之间有效。
当调用session的get/load/save/update/saveOrUpdate/list/iteraor等方法操作的对象会自动放入一级缓存!如果用户想操作一级缓存的内容,只能通过hibernate提供的相应方法操作!
session.flush(); 让一级缓存的数据与数据库中的数据同步,
相当于执行了一次事务的commit方法
session.clear(); 清空一级缓存中所有的数据
session.evict(object); 清空一级缓存中指定的对象数据
总结:可以在短时间内减少对数据库访问次数;
一级缓存只在session范围有效:缓存时间短、作用范围小
9.1.1 示例代码解析
Session session = HibernateSessionFactory.getSession(); @Test publicvoid testSave(){ session.beginTransaction();
Dept dept = new Dept(); dept.setDeptName("开发一部"); dept.setDeptAddress("广州");
dept = (Dept) session.get(Dept.class, 15); dept.setDeptName("a"); dept.setDeptName("b"); /* * 执行flush方法会将session中的数据与数据库同步 * 即如果session中的数据与数据库中一样,此方法执行跟没执行效果一样 * 但若session中的数据与数据库中的不一样,会生成update语句更新数据库 */ session.flush(); //也会将session中的数据跟数据库同步 session.getTransaction().commit(); HibernateSessionFactory.closeSession(); } |
9.1.2 不同的session对象是否共享一级缓存的数据
每一个session对象都有自己的一个独立的缓存区,他们之间互不影响,所以不共享数据。
9.1.3 session的Query接口的2个查询方法list()/iterator()区别
list和itrator两个方法都会将数据放入一级缓存,但list方法不会从缓存中获取数据,而是直接发送sql从数据库中获取数据。执行itrator方法的时候发送的sql只是获取全部的id,然后会逐个根据id从缓存中获取数据,缓存中若没有数据,才会根据id从数据库中获取。
//3. 区别 /** * 1. iterator会有n+1问题 * 2. 缓存: * list, 会放入缓存,但不会从缓存中取 * iterator, 会放入返回,也会从缓存中取! */ @Test publicvoid test_2_3_list_iterator() throws Exception { Session session = sf.openSession(); session.beginTransaction();
Query q = session.createQuery("from Dept");
/* 两次执行list List<Dept> list = q.list(); // 会放入缓存。
q = session.createQuery("from Dept"); list = q.list(); */ // 从缓存中取? /* * 两次执行iterator
Iterator<Dept> it = q.iterate(); // 主键 while (it.hasNext()) { System.out.println(it.next()); // 先从缓存找,每一找到再根据主键去数据库查询 } System.out.println("----------"); it = q.iterate(); // 主键 while (it.hasNext()) { System.out.println(it.next()); // 从缓存找,找打后就返回,不查找数据库 } /* * 先list,再iteraor,证明list方法查到的数据会放入sesison缓存! */ q.list(); // 有放入缓存
Iterator<Dept> it = q.iterate(); while (it.hasNext()) { System.out.println(it.next()); // 没有查找数据库 } session.getTransaction().commit(); session.close(); } |
|
9.2 二级缓存
一级缓存作用范围小,时间段。Hibernate支持二级缓存,但二级缓存默认是关闭的。二级缓存是基于应用程序或者说基于SessionFactory的缓存!二级缓存的内容可以给不同的session访问!
获取数据的过程其实是先去一级缓存,再去二级缓存(前提是开启),最后才去数据库中找。所以只会把一些经常用来查询,却不怎么修改的数据放入二级缓存。并且一级缓存的内容修改时,数据会与二级缓存同步(前提指定该数据可以放入二级缓存)。
9.2.1 具体配置参考hibernate.properties
## disable the second-level cache
#hibernate.cache.use_second_level_cachefalse 【是否开启二级缓存】
## enable the query cache
#hibernate.cache.use_query_cache true 【是否开启查询缓存】
## choose a cache implementation 【选择二级缓存的实现框架】
#hibernate.cache.provider_classorg.hibernate.cache.EhCacheProvider
#hibernate.cache.provider_classorg.hibernate.cache.EmptyCacheProvider
【默认支持的二级缓存的实现】
hibernate.cache.provider_classorg.hibernate.cache.HashtableCacheProvider
#hibernate.cache.provider_classorg.hibernate.cache.TreeCacheProvider
#hibernate.cache.provider_classorg.hibernate.cache.OSCacheProvider
#hibernate.cache.provider_class org.hibernate.cache.SwarmCacheProvider
9.2.2 二级缓存使用步骤:
1.开启Hibernate二级缓存;2.指定使用哪种二级缓存实现框架;3,指定哪些类可以加入二级缓存。
配置代码:
<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="cn.itcast.b_second_cache.Dept"/> |
测试代码:
Session session1 = HibernateSessionFactory.getSessionFactory().openSession(); session1.beginTransaction(); // 1. session1, 先get Dept dept = (Dept) session1.get(Dept.class, 15); // 发送sql,查询数据库中的原始数据是:开发部
// 2. sessoin1. 再update,修改数据库中的数据 session1.createQuery("update Dept set deptName='new_dept' where id=15").executeUpdate();
session1.getTransaction().commit(); //发送sql,修改数据 // 3. 输出一级缓存取出数据 dept = (Dept) session1.get(Dept.class, 15); //一级缓存中获取的数据 System.out.println(dept.getDeptName()); // 开发部 , 一级缓存数据没有改, 说明修改数据没有通知一级缓存
session1.close(); /******************Session2*******************/ Session session2 = HibernateSessionFactory.getSessionFactory().openSession(); session2.beginTransaction(); // 4. session2, 再get dept = (Dept) session2.get(Dept.class, 15); System.out.println(dept.getDeptName()); // new_dept, 说明,修改数据通知二级缓存了!
session2.getTransaction().commit(); session2.close(); |
控制台打印结果 |
若将session1的修改操作注释后: |
试验显示,因为session1对数据进行了修改后,session1再次获取数据没有发送sql证明是从一级缓存中获取,但是数据仍为修改之前的数据:开发部。这证明修改数据没有通知一级缓存。此时session2同样获取数据,却发送了sql这足以证明session2不是从二级缓存中获取,这证明修改数据通知了二级缓存。
若session1不进行修改操作,则session1第二次获取数据是从一级缓存中获取,session2是从二级缓存中获取。根据输出结果进一步证明了上述观点。
9.2.3 集合缓存
经过以上配置就可以从缓存中获取部门,不能通过部门获取员工数据(例:dept.getEmployees()),需要以下配置:
<class-cache usage="read-write" class="cn.itcast.b_second_cache.Employee"/> <collection-cache usage="read-only" collection="cn.itcast.b_second_cache.Dept.employees"/> |
9.2.4 Query接口使用缓存
query.list()是不使用一级缓存的,同样也不使用二级缓存,若想使用二级缓存,需要以下配置。
<propertyname="hibernate.cache.use_query_cache">true</property>
并且在代码中使用query.list()方法时需要使用setCacheable()方法。例:
Query q = session1.createQuery("fromDept").setCacheable(true);
q = session2.createQuery("fromDept").setCacheable(true);
这个方法表名查询数据放入缓存或者从缓存中取数据。这样session2就是从二级缓存中获取的数据。
9.2.5 Hibernate的缓存策略:
只读缓存策略:放入二级缓存的数据时只读的
<class-cache usage="read-only" />
读取策略,可以读取/修改二级缓存数据
<class-cache usage="read-write"/>
非严格的读写策略, 不会对数据锁定, 效率高于读写策略;可能会有无效数据的存在
<class-cache usage="nonstrict-read-write"/>
基于事务的缓存策略(一般,二级缓存框架,目前不支持)
<class-cache usage="transactional"/>
10 Hibernate提供的查询
10.1 主键查询
// get: 及时加载
Employee emp = (Employee) session.get(Employee.class, 1);
// load: 懒加载
Employee emp = (Employee) session.load(Employee.class, 1);
10.2 对象导航查询
//对象导航查询
Employee emp = (Employee)session.get(Employee.class, 1);
System.out.println(emp.getEmpName());
System.out.println(emp.getDept().getName()); // 懒加载。
员工是多方
10.3 Hql查询(重要)
//1. 使用hql: 查询的是对象、属性! // 注意:需要试着auto-import="true" ,表示自动导入包!如果为false,再使用hql的时候,需要写上类的全名 Query q = session.createQuery("from cn.itcast.a_query.Employee e");
//2. 查询全部列 //q = session.createQuery("select * from Employee e"); // 不能用 * 符号 q = session.createQuery("select e from Employee e");
//3. 查询指定列,把每一行记录返回一个Object[], 再把数组对象添加到list集合 // 查询一列,返回List<字段对应的Java类型> // 查询多列,返回List<Object[]> q = session.createQuery("select e.empId,empName from Employee e");
//查询指定的列,封装为对象。此时需要bean中有对应的构造函数。 q = session.createQuery("select new Employee(e.empId,empName) from Employee e");
/* * 4. 条件查询【一个条件、多个条件(in or and)、模糊、between and 】 */
//4.1 简单条件 q = session.createQuery("from Employee e where e.empName='老张'"); // 设置条件参数,方式1:占位符 q = session.createQuery("from Employee e where e.empName=? or birth=?"); q.setParameter(0, "老张"); // 参数从0开始设置 q.setParameter(1, new Date());
// 设置条件参数,方式2:命名参数查询 :name则是参数名定义为name q = session.createQuery("from Employee e where e.empName = :name or birth = :birthday"); q.setString("name", "老张"); q.setDate("birthday", new Date());
// in 查询 q = session.createQuery("from Employee e where e.empId in(1,2,3)");
// like 模糊查询 String name = "张"; q = session.createQuery("from Employee e where e.empName like ?"); q.setParameter(0, "%"+name+"%");
// between and q = session.createQuery("from Employee e where empId between 1 and 2");
// 测试题:查询部门id为1的记录 q = session.createQuery("from Employee e where e.dept.id=?");//要找到具体的条件 q.setParameter(0, 1);
//5. 聚合函数统计、分组、筛选 q = session.createQuery("select count(*) from Employee e "); Long obj = (Long) q.uniqueResult();
// 需求:统计每个部门的人数 // HQL 分组,方式1: q = session.createQuery("select e.dept.id,count(*) from Employee e group by e.dept.id");
// HQL分组,方式2:hql不用后面的id也可以 //q = session.createQuery("select e.dept,count(*) from Employee e group by e.dept");
// 分组筛选,显示部门人数 > 2 的部门 q = session.createQuery("select e.dept,count(*) from Employee e group by e.dept having count(*)>2"); // 内连接 //Query q = session.createQuery("from Employee e inner join e.dept"); Query q = session.createQuery("from Dept d inner join d.employees"); // 查询返回数据 List<Object[]> list = q.list(); for (int i=0; i<list.size(); i++) { Object[] obj = list.get(i); // 获取第一个元素 Dept dept = (Dept) obj[0]; // 获取第二个元素 Employee e = (Employee) obj[1];
System.out.print(dept.getName()); System.out.println(e.getEmpName());
} /* * 需求1: 查询显示部门信息,以及部门下的员工 * 左外连接 */ //Query q = session.createQuery("from Dept d left join d.employees");
// 改为右外连接, Query q = session.createQuery("from Employee e right join e.dept");
Dept dept = (Dept) session.get(Dept.class, 1); // 没有查询关联的数据 // System.out.println(dept.getName()); // System.out.println(dept.getEmployees());//-->懒加载, select
// 迫切左外连接,fetch关键字 //Query q = session.createQuery("from Dept d left join fetch d.employees");
// 内连接,迫切内连接, 自动封装数据【“右表”数据填充到“左表”中,将员工信息封装到部门的set中,可以像懒加载一样使用。确是即时加载】 Query q = session.createQuery("from Dept d inner join fetch d.employees");
List<Dept> list = q.list(); // 关联的所有数据都有查询出来 System.out.println("--------------------------"); for (Dept d : list){ System.out.println(d); System.out.println(d.getEmployees()); // 及时加载 } |
10.4 Criteria面向对象的查询(不会数据库的可以使用,缺乏灵活性)
// 获取QBC查询接口 Criteria criteria = session.createCriteria(Employee.class); // 设置条件 //criteria.add(Restrictions.idEq(1));// 根据id查询 criteria.add(Restrictions.eq("empId", 1)); criteria.add(Restrictions.eq("empName", "老张"));
// 查询 List<Employee> list = criteria.list(); System.out.println(list); |
10.5 Sql查询(执行原生态sql)
// hibernate也支持对原生态的sql语句的查询,返回的Object[] SQLQuery q = session.createSQLQuery("SELECT * FROM t_dept d"); List<Object[]> list = q.list();
// 也可以封装为对象,但要有映射 SQLQuery q = session.createSQLQuery("SELECT * FROM t_dept d"); q.addEntity(Dept.class); // 查询的结果封装为指定的对象类型
List<Dept> list = q.list();
System.out.println(list); |
11 Hibernate连接池
Hibernate提供c3p0连接池提供支持(本身的连接池值维护一个连接),具体配置可以查看hibernate.properties 。
hibernate.connection.pool_size 1 默认支持的连接池
###########################
### C3P0 Connection Pool### 对c3p0连接池支持
###########################
#hibernate.c3p0.max_size 2 最大连接数
#hibernate.c3p0.min_size 2 最小连接数
#hibernate.c3p0.timeout 5000 超时时间
#hibernate.c3p0.max_statements 100 一次最多执行的sql语句个数
#hibernate.c3p0.idle_test_period 3000 空闲测试时间,单位:秒(因为数据库的连接当一定时间(大概八小时)不使用时,会自动断开连接。)
#hibernate.c3p0.acquire_increment 2 连接不够用时候一次增加的连接数
#hibernate.c3p0.validate false
Hibernate对c3p0连接池支持驱动类(相当于CombopooledDataSource)
#hibernate.connection.provider_classorg.hibernate.connection.C3P0ConnectionProvider
主配置文件中的代码
<!-- 3. 连接池配置 --> <!-- a. 驱动类 --> <property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property> <!-- b.连接池参数配置 --> <property name="hibernate.c3p0.min_size">2</property> <property name="hibernate.c3p0.max_size">4</property> <property name="hibernate.c3p0.max_statements" >100</property> <property name="hibernate.c3p0.acquire_increment">2</property> <property name="hibernate.c3p0.timeout">5000</property> <property name="hibernate.c3p0.idle_test_period">3000</property> |
12 Hibernate两种创建session的方式
方式一:openSession
// 每次创建一个新的session, 必须手动关闭 Session session1 = sf.openSession(); Session session2 = sf.openSession(); System.out.println(session1 == session2); // false session1.close(); session2.close(); |
方式二:getCurrentSession创建或者获取session
* 注意:
* 1, 需要配置session创建方式
<property name="hibernate.current_session_context_class">thread</property>
* 2. 这种方式创建session, 可以不用关闭,线程结束自动关闭!
// 首先,从当前线程上获取session,如果没有获取到就执行openSession,创建新的session。然后,把创建的session,绑定到当前线程上;
// 当再获取session的时候,就从当前线程取出已绑定的session,就不创建新的session 。
Session session3 = sf.getCurrentSession(); Session session4 = sf.getCurrentSession(); System.out.println(session3 == session4); // true session3.close(); //session4.close();
|