hibernate(三)多对多、加载策略、懒加载、HQL查询、整合c3p0、乐观锁悲观锁
1、多对多关系
2、加载策略(优化查询)
2.1、类级别加载策略
2.2、关联级别加载策略--一对多(查询集合时间时使用,比如查询Customer下面的Set<Order>)
2.3、关联级别加载策略--多对一(查询外键所在的对象,即查询Order时使用)
2.4、批量查询加载策略
2.5、加载策略-总结
3、查询总结
4、HQL详解
5、HQL查询--表连接
6、HQL的命名查询
7、QBC查询(了解)
8、整合c3p0连接池
9、事务&Hibernate中指定隔离级别
10、悲观锁
11、乐观锁
1、多对多关系 <--返回目录
创建实体类Student.java
public class Student{ private Integer id; private String name; private Set<Course> courses = new HashSet<>(); }
创建实体类Course.java
public class Course{ private Integer id; private String name; private Set<Student> students = new HashSet<>(); }
映射文件Student.hbm.xml
<class name="com.oy.domain.Student" table="t_student"> <id name="id" column="id"> <generator class="native"></generator> </id> <property name="name" column="name"></property> <set name="courses" table="t_student_course"> <key column="sid" /> <many-to-many class="com.oy.domain.Course" column="cid" /> </set> </class>
映射文件Course.hbm.xml
<class name="com.oy.domain.Course" table="t_course" > <id name="id" column="id" > <generator class="native"></generator> </id> <property name="name" column="name" ></property> <set name="students" table="t_student_course" inverse="true" > <key column="cid" ></key> <many-to-many class="Student" column="sid" ></many-to-many> </set> </class>
测试代码
@Test public void fun1() { Session session = HibernateUtils.openSession(); Transaction ts = session.beginTransaction(); // ============================================== Student s = new Student(); s.setName("张无忌"); Course c1 = new Course(); c1.setName("乾坤大挪移"); Course c2 = new Course(); c2.setName("九阳神功"); s.getCourses().add(c1); s.getCourses().add(c2); session.save(c1); session.save(c2); session.save(s); // ============================================== ts.commit(); // 事务提交,这里使用insert into语句 session.close(); }
控制台打印
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
Hibernate: insert into t_course (name) values (?) Hibernate: insert into t_course (name) values (?) Hibernate: insert into t_student (name) values (?) Hibernate: insert into t_student_course (sid, cid) values (?, ?) Hibernate: insert into t_student_course (sid, cid) values (?, ?) 虚拟机关闭!释放资源
数据库记录
t_student 表
t_course 表
t_student_course 表
操作:
- inverse: 是否要放弃维护外键关系
- cascade: 是否需要级联操作 (5个)
- 注意: 配置级联删除时,要小心,双方都配置级联删除, 任意删除一条记录, 整个关系链数据都会被删除
2、加载策略(优化查询) <--返回目录
策略种类:
延迟加载:等到使用的时候才会加载数据
立即加载:不管使用不使用,都会立刻将数据加载
策略的应用:
类级别的加载策略
关联级别的加载策略
2.1、类级别加载策略 <--返回目录
类级别加载策略:
get() 方法,立即查询数据库,将数据初始化
load() 方法,hbm 文件中,class 元素的 lazy 属性决定类级别 load 方法的加载策略,lazy 默认为 true
1) lazy=true(默认): 先返回一个代理对象,使用代理对象的属性时,才去查询数据库
2) lazy=false: 与 get() 一致,会立即加载数据
类级别懒加载测试
- lazy:<class>元素属性,默认为 true,即使用懒加载
- 映射文件 Customer.hbm.xml:<class name="Customer" table="t_customer"> lazy 没有设置,默认为 true
Customer c = (Customer)session.load(Customer.class, 1);//断点发现,运行此行代码后,不打印sql语句 System.out.println(c.getName());//运行此行代码,打印sql语句
- 映射文件Customer.hbm.xml:<class name="Customer" table="t_customer" lazy="false">
Customer c = (Customer)session.load(Customer.class, 1);//断点发现,运行此行代码后,打印sql语句
2.2、关联级别加载策略--一对多(查询集合时间时使用,比如查询Customer下面的Set<Order>) <--返回目录
在查询有关联关系的数据时,加载一方的数据是否需要将另一方立即查询出。
默认: 关联的数据,在使用时才会加载。
lazy:<set>元素属性,默认为true,即使用懒加载
映射文件Customer.hbm.xml:<set name="orders" lazy="true"> lazy没有设置,默认为true
Customer c = (Customer)session.get(Customer.class, 1);//get()方法立即加载,查询Customer,但是不会查询Customer下面的Order for(Order o: c.getOrders){ //c.getOrders执行后,会查询Customer下面的所有Order System.out.println(o.getName()); }
映射文件 Customer.hbm.xml:<set name="orders" lazy="false">
Customer c = (Customer)session.get(Customer.class, 1);//查询Customer,并且查询Customer下面的所有Order
<set lazy="">: 是否对set数据使用懒加载
- true:(默认值) 对集合使用才加载
- false: 集合将会被立即加载
- extra: 极其懒惰,如果使用集合时,之调用size方法查询数量, Hibernate会发送count语句,只查询数量.不加载集合内数据.
<set fetch="">:<set>元素的属性,决定加载集合使用的sql语句种类
- select: (默认值) 普通select查询
- join: 表链接语句查询集合数据
- subselect: 使用子查询 一次加载多个Customer的订单数据
fetch lazy 结论
------------------------------------------------------------------------------------------
select true 默认值, 会在使用集合时加载,普通select语句
select false 立刻使用select语句加载集合数据
select extra 会在使用集合时加载,普通select语句,如果只是获得集合的长度,会发送Count语句查询长度.
join true 查询集合时使用表链接查询,会立刻加载集合数据
join false 查询集合时使用表链接查询,会立刻加载集合数据
join extra 查询集合时使用表链接查询,会立刻加载集合数据
subselect true 会在使用集合时加载,子查询语句
subselect false 会在查询用户时,立即使用子查询加载客户的订单数据
subselect extra 会在使用集合时加载,子查询语句,如果只是获得集合的长度,会发送Count语句查询长度
2.3、关联级别加载策略--多对一(查询外键所在的对象,即查询Order时使用) <--返回目录
<many-to-one lazy="">
- false: 关闭懒加载;加载订单时,会立即加载客户
- proxy: 看客户对象的类加载策略来决定
- no-proxy: 不做研究
<many-to-one fetch="">
- select:(默认值)使用普通select加载
- join:使用表链接加载数据
fetch lazy 结果
---------------------------------------------------------------------------------------
select false 加载订单时,立即加载客户数据.普通select语句加载客户.
select proxy 类加载策略为:lazy=false 同上
lazy=true 加载订单时,先不加载客户数据.使用客户数据时才加载
join false 使用表链接查询订单以及对应客户信息.lazy属性无效
join proxy 使用表链接查询订单以及对应客户信息.lazy属性无效
---------------------------------------------------------------------------------------
映射文件 Order.hbm.xml <many-to-one lazy="false" fetch="select">
Order o = (Order)session.get(Order.class, 1);//关闭懒加载;加载订单时,立即加载客户数据 System.out.println(o.getCustomer().getName());
2.4、批量查询加载策略 <--返回目录
查询所有客户,遍历客户,打印客户下的订单
List<Customer> list = session.createQuery("from Customer").list(); for(Customer c: list){ (System.out.println(c.getOrders().size());//每循环一次,发送一条sql语句,加载一个客户的order的集合 }
<set name="orders" batch-size="2">
List<Customer> list = session.createQuery("from Customer").list(); for(Customer c: list){ (System.out.println(c.getOrders().size());//一次加载两个客户的order集合 }
总结batch-size:决定一次加载几个对象的集合数据. in 条件加载多个用户的订单
2.5、加载策略-总结 <--返回目录
* 表连接件检索用到比较少
* 延迟检索和立即检索的优缺点
* 如何解决延迟加载的缺点:
缺点描述:在dao层有一个方法Customer getCustomer(int id),
return session.load(Customer.class, id);return的是查询出的Customer对象代理对象
中间没有使用查询出的Customer对象的属性,此时由于使用延迟加载,该return结果没有初始化。
* 解决方案1:设置lazy=false关闭懒加载
* 解决方案2:在Service层获得在页面要用到的属性=>在Service层中确保数据已经加载
3、查询总结 <--返回目录
HIbernate查询分类:
1)根据OID检索:get/load()
2)对象视图检索:customer.getOrders()
3)sql语句:createSqlQuery
4)HQL语句:createQuery
5)criteria查询:createcriteria
4、HQL详解 <--返回目录
要用到的表t_order 表t_customer
------------------- ------------------
oid oname cid cid cname
------------------- ------------------
1 香皂 1 1 zs
2 肥皂 1 2 ls
3 蜡烛 2 3 ww
4 手电筒 2
5 胶带 2
-------------------
HQL查询所有
Query query = session.createQuery("from Customer"); //Query query = session.createQuery("from Customer c");//指定别名为c //Query query = session.createQuery("select c from Customer c");//其他写法 List<Customer> list = query.list();
选择查询(查询对象的某几个属性)
Query query = session.createQuery("select c.name from Customer c");//c.name为对象属性名, List list = query.list(); System.out.println(list);//[tom,jerry] Query query = session.createQuery("select c.id, c.name from Customer c"); List<Object[]> list = query.list(); for(Object[] objs: list){ System.out.println(Arrays.toString(objs));//[1, tom] [2,jerry] }
投影查询:选择查询的基础上,把查询结果封装到对象中
Query query = session.createQuery("select new Customer(c.id, c.name) from Customer c");//Customer对象要有含参构造方法 List<Customer> list = query.list();
排序:asc升序,desc降序
Query query = session.createQuery("from Customer c order by c.id desc");
分页
Query query = session.createQuery("from Customer c"); query.setFirstResult(0);//从哪个索引开始取数据,firstResutl=(当前页数-1)*每页记录数 query.setMaxResults(10);//查询多少条数据 List<Customer> list = query.list();
绑定参数
Query query = session.createQuery("from Customer c where c.id=?"); query.setInteger(0,2);//0表示第一个? Customer c = (Customer)query.uniqueResult(); 另一种方式: Query query = session.createQuery("from Customer c where c.id=haha"); query.setInteger("haha",2);
聚合函数
Query query = session.createQuery("select count(*) from Customer c"); Object count = query.uniqueResult(); select avg(c.id) from Customer c select sum(c.id) from Customer c select max(c.id) from Customer c select min(c.id) from Customer c
分组
Query query = session.createQuery("select o.customer, count(o) from Order o group by o.customer having count(o)>2"); List<Object[]> list = query.list(); for(Object[] objs: list){ System.out.println(Arrays.toString(objs));//[Customer对象, 3] }
5、HQL查询--表连接 <--返回目录
表连接:交叉连接(笛卡尔积)、内连接(去笛卡尔积)、左外连接、右外连接
隐式内连接(mysql方言)--在笛卡尔积基础上过滤无效数据。
显示内连接(标准,建议使用)-- inner join
//Query query = session.createQuery("from Customer c, Order o where o.customer=c");//隐式内连接(mysql方言) Query query = session.createQuery("from Customer c inner join c.orders");//显示内连接 List<Object[]> list = query.list(); System.out.println(Arrays.toString(list.get(1)));//[Customer [cid=1, cname=zs, orders=[Order [oid=2, oname=肥皂], //Order [oid=1, oname=香皂]]], Order [oid=2, oname=肥皂]] for(Object[] objs: list){ //遍历打印 System.out.println(Arrays.toString(objs)); }
打印
[Customer [cid=1, cname=zs, orders=[Order [oid=2, oname=肥皂], Order [oid=1, oname=香皂]]], Order [oid=1, oname=香皂]] [Customer [cid=1, cname=zs, orders=[Order [oid=2, oname=肥皂], Order [oid=1, oname=香皂]]], Order [oid=2, oname=肥皂]] [Customer [cid=2, cname=ls, orders=[Order [oid=5, oname=胶带], Order [oid=4, oname=手电筒], Order [oid=3, oname=蜡烛]]], Order [oid=3, oname=蜡烛]] [Customer [cid=2, cname=ls, orders=[Order [oid=5, oname=胶带], Order [oid=4, oname=手电筒], Order [oid=3, oname=蜡烛]]], Order [oid=4, oname=手电筒]] [Customer [cid=2, cname=ls, orders=[Order [oid=5, oname=胶带], Order [oid=4, oname=手电筒], Order [oid=3, oname=蜡烛]]], Order [oid=5, oname=胶带]]
内连接(迫切):查询结果List<Customer>;二非迫切查询List<Object[]>
Query query = session.createQuery("from Customer c inner join fetch c.orders"); List<Customer> list = query.list(); for(Customer c: list){ System.out.println(c); }
打印
Customer [cid=1, cname=zs, orders=[Order [oid=1, oname=香皂], Order [oid=2, oname=肥皂]]] Customer [cid=1, cname=zs, orders=[Order [oid=1, oname=香皂], Order [oid=2, oname=肥皂]]] Customer [cid=2, cname=ls, orders=[Order [oid=4, oname=手电筒], Order [oid=5, oname=胶带], Order [oid=3, oname=蜡烛]]] Customer [cid=2, cname=ls, orders=[Order [oid=4, oname=手电筒], Order [oid=5, oname=胶带], Order [oid=3, oname=蜡烛]]] Customer [cid=2, cname=ls, orders=[Order [oid=4, oname=手电筒], Order [oid=5, oname=胶带], Order [oid=3, oname=蜡烛]]]
注意:以后实体类中重写toString()方法:包含外键的类toString()中不要包含代表外键的属性!!!
否则,在使用内连接查询后,打印结果抛异常:java.lang.StackOverflowError
左外连接:from Customer c left outer join fetch c.orders //会打印出[Customer [cid=3, cname=ww, orders=[]], null]
右外连接: from Customer c right outer join fetch c.orders
6、HQL的命名查询 <--返回目录
映射文件Customer.hbm.xml
<hibernate-mapping> <class> <!--局部配置--> <query name="bcd"><![CDATA[from Order]]</query> </class> <!--全局配置--> <query name="abc"><![CDATA[from Customer]]</query> </hibernate-mapping>
找到局部配置的HQL语句
Query query = session.getNamedQuery("com.oy.domain.Cumtomer.bcd");//表示从Cumtomer.hbm.xml找名称为bcd的HQL语句
找全局配置的HQL语句
Query query = session.getNamedQuery("Cumtomer.abc");
7、QBC查询(了解) <--返回目录
QBC查询结束criteria查询
只能单表查询(不能表连接查询)
8、整合c3p0连接池 <--返回目录
1) 导包
2) 在hibernate.cfg.xml中配置
<property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property> <property name="hibernate.c3p0.max_size">2</property>
设置参数:不设置的化,有默认值
#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
如果连接池参数设置不好,可能会降低系统效率
9、事务&Hibernate中指定隔离级别 <--返回目录
事务的特性ACID:
- 原子性:整体
- 一致性:数据
- 隔离性:并发
- 持久性:结果
改变Hibernate连接数据库的事务隔离级别
<property name="hibernate.connection.isolation">4</>
1: 读未提交
2:读已提交
4:可重复读
8:串行化
10、悲观锁 <--返回目录
悲观锁:数据库提供实现。防止别人跟我抢着修改数据,分为读锁和写锁
读锁/共享锁:在读取过程中,不希望别人修改,并且自己也不会修改,我们可以给读取的数据加上读锁
select * from t_user lock in share mode
写锁/排他锁:我不仅要读还要对数据进行修改,我就可以为读取的数据加上写锁
select * from t_user for update
hibernate 中使用悲观锁
@Test public void fun1() { Session session = HibernateUtils.openSession(); Transaction ts = session.beginTransaction(); // ============================================== Student s = (Student) session.get(Student.class, 1, LockOptions.UPGRADE); System.out.println(s); // ============================================== ts.commit(); // 事务提交,这里使用insert into语句 session.close(); }
打印
Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_ from t_student student0_ where student0_.id=? for update
演示写锁
debug 模式调试
另一事务提交,写锁被释放,该事务可以读取到数据了
11、乐观锁 <--返回目录
乐观锁:人为来控制的锁,需要自己实现
乐观锁实现:通过字段 version实现,修改前判断 version,只有大于数据库表的version 才能修改成功
hibernate 中 乐观锁的实现
实体类中添加 version 字段
private Integer version; public Integer getVersion() { return version; } public void setVersion(Integer version) { this.version = version; }
实体类对应的映射文件添加
测试
@Test public void fun1() { Session session = HibernateUtils.openSession(); Transaction ts = session.beginTransaction(); // ============================================== Student s = (Student) session.get(Student.class, 1); s.setName("jack1"); System.out.println(s); // ============================================== ts.commit(); // 事务提交,这里使用insert into语句 session.close(); }
数据库实体类对应的表自动添加了字段 version。每次修改成功后 version 增1
断点测试乐观锁的生效
程序中查出version=2,如果程序update成功,会将 version 加1。为了演示乐观锁的生效,手动修改数据库表中该记录version+1
手动将 version 改为 3
程序往下执行
报错(乐观锁起作用了)
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction
(or unsaved-value mapping was incorrect): [com.oy.domain.Student#1]
---
posted on 2020-06-14 01:19 wenbin_ouyang 阅读(255) 评论(0) 编辑 收藏 举报