Hibernate的多对多关联关系
多对多关联关系在java对象中可以通过定义集合类型来实现关联关系。
在关系数据模型中,无法直接表达表和表之间的多对多关联关系,而是需要创建一个中间表包含了两边的主键,来表达两张表的多对多关联关系。
- 1
- 2
- 3
- 4
我们用一个Student和Course(学生和课程)的例子来演示多对多关联关系。
第一步:创建Student和Course类
public class Student {
private Integer id;
private String name;
//用一个集合包含该学生所选的课程对象
private Set<Course> courses=new HashSet<Course>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Course> getCourses() {
return courses;
}
public void setCourses(Set<Course> courses) {
this.courses = courses;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
public class Course {
private Integer id;
private String name;
//用一个集合包含所有选择该课程的学生
private Set<Student> students=new HashSet<Student>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Student> getStudents() {
return students;
}
public void setStudents(Set<Student> students) {
this.students = students;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
第二步:编写我们的映射文件
编写Course.hbm.xml
<hibernate-mapping >
<class name="com.cad.domain.Course" table="course">
<id name="id" column="id">
<generator class="native"></generator>
</id>
<property name="name" column="name"></property>
<!--table属性用来指定生成的中间表的表名称 inverse指定关联关系由Student维护-->
<set name="students" table="student_course" inverse="true">
<key column="cid"></key>
<!--<many-to-many>元素中的column属性指定本表通过中间表中的sid外键关联到Student对象-->
<many-to-many class="com.cad.domain.Student" column="sid"></many-to-many>
</set>
</class>
</hibernate-mapping>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
编写Student.hbm.xml
<hibernate-mapping >
<class name="com.cad.domain.Student" table="student">
<id name="id" column="id">
<generator class="native"></generator>
</id>
<property name="name" column="name"></property>
<!-进行级联保存和更新操作-->
<set name="courses" table="student_course" cascade="save-update">
<key column="sid"></key>
<many-to-many class="com.cad.domain.Course" column="cid"></many-to-many>
</set>
</class>
</hibernate-mapping>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
我们来测试一下
public class Demo {
@Test
public void fun(){
//读取配置文件
Configuration conf=new Configuration().configure();
//根据配置创建factory
SessionFactory sessionfactory=conf.buildSessionFactory();
Session session = sessionfactory.openSession();
Transaction ts=session.beginTransaction();
//创建两个Student
Student s1=new Student();
s1.setName("tom");
Student s2=new Student();
s2.setName("jack");
//创建三个Course
Course c1=new Course();
c1.setName("语文");
Course c2=new Course();
c2.setName("数学");
Course c3=new Course();
c3.setName("英语");
//因为设置了关联关系由Student维护,所以不需要课程再来关联Student
s1.getCourses().add(c1);
s1.getCourses().add(c2);
s1.getCourses().add(c3);
s2.getCourses().add(c1);
s2.getCourses().add(c2);
s2.getCourses().add(c3);
//由于设置了级联保存,所以只保存Student即可
session.save(s1);
session.save(s2);
ts.commit();
session.close();
sessionfactory.close();
}
}
结果,数据库中生成了三张表。
student和course
还有中间表student_course
里面的数据也相互对应。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
Hibernate的检索策略
简介
Session的缓存中可以存放相互关联的对象。当Hibernate从数据库中加载对象时,如果同时自动加载与之关联的所有对象,那么这些关联的对象就浪费了很多的内存空间。而我们可以设置检索策略,来优化检索性能。
Hibernate提供了三种检索策略
-立即检索策略:检索对象时立即加载对象以及关联的对象。
-延迟检索策略:使用时才会加载对象以及关联的对象。能避免多余加载不需要的对象。
-迫切左外连接检索策略 :利用SQL的外连接查询功能,能够减少select语句的数目。
- 1
- 2
- 3
- 4
- 5
- 6
类级别检索策略
定义:只加载某个类对象
类级别的检索策略包括立即检索和延迟检索。默认为延迟检索。
Hibernate中允许在映射文件中配置检索策略。
类级别检索策略,可以配置<class>元素中的lazy属性。属性可选值:true(延迟检索) false(立即检索)。默认值为true。
类级别检索策略会影响Session的load方法。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
我们来做一个小例子,在load方法加断点
public class Demo {
private Session session;
@Test
public void test() {
//读取配置文件
Configuration conf=new Configuration().configure();
//根据配置创建factory
SessionFactory sessionfactory=conf.buildSessionFactory();
session = sessionfactory.openSession();
Transaction ts=session.beginTransaction();
Customer c=session.load(Customer.class, 7);//此处加断点来观察效果
System.out.println(c.getName());
ts.commit();
session.close();
sessionfactory.close();
}
}
当lazy属性为true时,调用load方法什么都不输出,使用对象时才输出select语句。
当lazy属性为false时,调用load方法会直接输出select语句。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
原理:
当为延迟检索策略时,执行load方法时,是返回当前对象的代理对象,这个代理对象由Hibernate动态生成,代理对象继承了当前对象,仅仅初始化了OID属性,其他属性都为null,当程序访问代理类属性时,Hibernate才会初始化代理对象,初始化过程执行select语句,从数据库加载所有数据。
一对多关联级别的策略策略
关联级别检索策略:查询有关联关系的对象时,加载对象时是否需要将关联的对象加载。
在映射文件中,使用< set >元素来配置一对多或者多对多的关联关系。
< set >元素中有lazy和fetch属性决定加载策略。
lazy属性:和类级别加载策略一致。
默认值为true。即使用延迟检索。
当lazy为false时,使用立即检索。
当lazy为extra时,采用加强延迟检索策略,它尽可能的延迟集合被初始化的时机。例如程序访问集合的一些属性时,如访问size()、contains()、isEmpty()这些方法时,如果指定了extra,就不会加载集合,仅通过特定的语句查询必要的信息。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
例子 :
public class Demo {
private Session session;
@Test
public void test() {
//读取配置文件
Configuration conf=new Configuration().configure();
//根据配置创建factory
SessionFactory sessionfactory=conf.buildSessionFactory();
session = sessionfactory.openSession();
Transaction ts=session.beginTransaction();
//加载Customer对象
Customer c=(Customer)session.get(Customer.class, 7);
//加载Customer关联的Order对象
for(Order o:c.getOrders()){
System.out.println(o.getName());
}
ts.commit();
session.close();
sessionfactory.close();
}
}
当我们lazy属性为false时,在执行get方法时,会使用立即检索策略,会执行两个select语句,加载Customer和关联的Orders集合。
当我们lazy属性为true时,执行get方法,只会执行一句select语句,此时Customer的Orders属性引用一个没有被初始化的集合代理类,此时Orders集合中不存在任何Order对象,当我们使用order时,才会初始化集合代理类,才会到数据库中检索所有关联的Order对象
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
fetch属性:决定加载集合时查询语句的格式。取值为:select 、subselect、join
select:(默认值)使用普通的select语句进行查询。
subselect:使用子查询加载集合数据。当查找多个对象时才有用,例如查找多个用户,每个用户有多个订单
join:采用迫切左外连接检索策略进行查询。fetch属性设为join,lazy属性将被忽略,在查询对象时一定会加载与之关联的对象。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
例子:
public class Demo {
private Session session;
@Test
public void test() {
//读取配置文件
Configuration conf=new Configuration().configure();
//根据配置创建factory
SessionFactory sessionfactory=conf.buildSessionFactory();
session = sessionfactory.openSession();
Transaction ts=session.beginTransaction();
//加载Customer对象
Customer c=(Customer)session.get(Customer.class, 7);
//加载关联的Order对象
for(Order o:c.getOrders()){
System.out.println(o.getName());
}
ts.commit();
session.close();
sessionfactory.close();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
当fetch属性为默认值(select)时,打印的sql语句
Hibernate:
select
customer0_.id as id1_0_0_,
customer0_.name as name2_0_0_
from
customer customer0_
where
customer0_.id=?
Hibernate:
select
orders0_.cid as cid3_1_0_,
orders0_.id as id1_1_0_,
orders0_.id as id1_1_1_,
orders0_.name as name2_1_1_,
orders0_.cid as cid3_1_1_
from
orders orders0_
where
orders0_.cid=?
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
当fetch属性为join时,打印select语句,采用左外连接的查询语句,减少select语句
Hibernate:
select
customer0_.id as id1_0_0_,
customer0_.name as name2_0_0_,
orders1_.cid as cid3_1_1_,
orders1_.id as id1_1_1_,
orders1_.id as id1_1_2_,
orders1_.name as name2_1_2_,
orders1_.cid as cid3_1_2_
from
customer customer0_
left outer join
orders orders1_
on customer0_.id=orders1_.cid
where
customer0_.id=?
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
演示fetch属性为subselect时,我们查询所有Customer,然后使用每一个Customer的Orders集合
public class Demo {
private Session session;
@Test
public void test() {
//读取配置文件
Configuration conf=new Configuration().configure();
//根据配置创建factory
SessionFactory sessionfactory=conf.buildSessionFactory();
session = sessionfactory.openSession();
Transaction ts=session.beginTransaction();
//查找所有Customer
List<Customer> customerlist=session.createQuery("from Customer").list();
//加载每个Customer关联的Orders集合
for(Customer c:customerlist){
System.out.println(c.getOrders().size());
}
ts.commit();
session.close();
sessionfactory.close();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
打印的语句,使用了子查询语句
当fetch属性为subselect时,Hibernate能够通过带有子查询的select语句,来整批量的初始化多个对象的集合中的实例。
Hibernate:
select
customer0_.id as id1_0_,
customer0_.name as name2_0_
from
customer customer0_
Hibernate:
select
orders0_.cid as cid3_1_1_,
orders0_.id as id1_1_1_,
orders0_.id as id1_1_0_,
orders0_.name as name2_1_0_,
orders0_.cid as cid3_1_0_
from
orders orders0_
where
orders0_.cid in (
select
customer0_.id
from
customer customer0_
)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
fetch和lazy结合
多对一关联级别的策略策略
和<set>元素一样,<many-to-one>元素中也有lazy和fetch属性。
lazy的可选值有false(立即检索),proxy(延迟检索),no-proxy(无代理延迟检索)
fetch属性可选值为select 和 join
- 1
- 2
- 3
- 4
- 5
例子:设置订单配置文件中fetch属性为join
public class Demo {
private Session session;
@Test
public void test() {
//读取配置文件
Configuration conf=new Configuration().configure();
//根据配置创建factory
SessionFactory sessionfactory=conf.buildSessionFactory();
session = sessionfactory.openSession();
Transaction ts=session.beginTransaction();
//加载订单对象
Order c=(Order)session.get(Order.class, 6);
//加载关联的Customer对象
System.out.println(c.getCustomer().getName());
ts.commit();
session.close();
sessionfactory.close();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
则在执行get方法时,会直接使用外连接查询。忽略lazy。
Hibernate:
select
order0_.id as id1_1_0_,
order0_.name as name2_1_0_,
order0_.cid as cid3_1_0_,
customer1_.id as id1_0_1_,
customer1_.name as name2_0_1_
from
orders order0_
left outer join
customer customer1_
on order0_.cid=customer1_.id
where
order0_.id=?
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
批量检索
< class >和< set >元素中包含batch-size属性。设定批量检索的数量。可选值为正整数,默认为1。
仅用于关联级别的立即检索和批量检索。
我们在Customer配置文件中设置批量延迟检索
<set name="orders" lazy="true" batch-size="3">
public class Demo {
private Session session;
@Test
public void test() {
//读取配置文件
Configuration conf=new Configuration().configure();
//根据配置创建factory
SessionFactory sessionfactory=conf.buildSessionFactory();
session = sessionfactory.openSession();
Transaction ts=session.beginTransaction();
//加载所有Customer对象
List<Customer> customerlist=session.createQuery("from Customer").list();
for(Customer c:customerlist){
System.out.println(c.getOrders().size());
}
ts.commit();
session.close();
sessionfactory.close();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
我们使用延迟检索,当使用到每个Customer的orders时,才加载Orders,每执行一次循环,就打印一个select语句。
如果有很多Customer对象,那么就会打印很多的select语句。
所以我们可以使用批量检索。只需要将<set>的batch-size属性设置为3.
所以会批量初始化三个Orders集合,使用in语句。这样当用到第一个第二个第三个对象的集合时,就不会再发送语句。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
Hibernate的查询方式
(1)OID检索方式
按照对象的OID来查询对象。Session的get/load方法提供了这种功能。
如果应用程序中事先知道了OID,就可以使用这种查询对象的方式。
- 1
- 2
- 3
- 4
(2)导航对象图检索方式
根据已经加载的对象,导航到其他对象.
例如,对于已经加载的Customer对象,可以导航到所有关联的Order对象
- 1
- 2
- 3
- 4
(3)HQL检索方式
简介:
使用面向对象的HQL查询语言。Hibernate提供了Query接口,它是专门的HQL查询接口,能执行各种复杂的HQL语句。
HQL是面向对象的查询语言,和SQL查询语言有些类似,Hibernate提供的各种检索方式中,HQL是使用最广的一种检索方式。HQL封装了JDBC的细节
HQL和SQL本质是不一样的:
-HQL查询语言面向对象,Hibernate负责解析HQL语句,然后根据映射文件,把HQL语句翻译成SQL语句。HQL查询语句中主体是类和类的属性。
-SQL查询语言是和关系数据库绑定在一起的,SQL查询语句中主体是数据库表和表的字段。
- 1
- 2
- 3
- 4
- 5
- 6
HQL查询的步骤
-通过session的createQuery(HQL语句)方法创建一个Query对象
-调用Query的list()方法执行查询语句。该方法返回List集合
(1)================= 查找所有对象 ====================
public class Demo {
private Session session;
@Test
public void test() {
//读取配置文件
Configuration conf=new Configuration().configure();
//根据配置创建factory
SessionFactory sessionfactory=conf.buildSessionFactory();
session = sessionfactory.openSession();
Transaction ts=session.beginTransaction();
//HQL语句是面向对象的,from 类名
List<Customer> customerlist=session.createQuery("from Customer").list();
System.out.println(customerlist);
ts.commit();
session.close();
sessionfactory.close();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
(2)================= HQL语句使用别名 ====================
例如:List< Customer > customerlist=session.createQuery(“from Customer as c”).list();
as关键字用来设置别名,as也可以省略。 from Customer c即可。
(3)================= HQL语句选择查询 ====================
选择查询是指仅查找某些属性。
这时list()返回的集合中包括的是一个一个数组,每个数组中是一条记录。
List <Object[]> customerlist=session.createQuery("select id,name from Customer").list();
for(Object[] obj:customerlist){
System.out.println(Arrays.toString(obj));
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
(4)================= HQL语句投影查询 ====================
基于选择查询的基础上,把查询到的属性封装到对象中,该对象的其他属性为null。
该对象必须有对应的构造方法。
List <Customer> customerlist=session.createQuery("select new Customer(id,name) from Customer").list();
for(Customer c:customerlist){
System.out.println(c.toString());
}
Customer类中必须有如下构造方法
public Customer(Integer id, String name) {
this.id = id;
this.name = name;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
(5)================= HQL对查询结果排序 ====================
HQL语言使用order by关键字对结果进行排序。
asc为升序,desc为降序。
List <Customer> customerlist=session.createQuery("select new Customer(id,name) from Customer order by id desc").list();
- 1
- 2
- 3
- 4
- 5
(6)================= HQL分页查询 ====================
-setFirstResult(int index):设定从第几个对象开始检索,起始值为0。
-setMaxResult(int count):设定一次检索的对象数目。默认情况下检索所有的对象。
Query query=session.createQuery("select id,name from Customer");
//从第一个对象开始
query.setFirstResult(0);
//查找两个对象
query.setMaxResults(2);
List result=query.list();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
(7)================= HQL语句中绑定参数 ====================
对于实际应用中,经常需要用户输入一些查询条件,我们返回符合查询条件的数据。
我们可以使用from Customer where name='"+name+"';来实现
但是这种方式是非常不安全的,会受到SQL注入等非法攻击。
- 1
- 2
- 3
- 4
Hibernate中使用参数绑定来避免以上问题。有两种方式
(1)使用?占位符绑定
占位符的索引从0开始。
Query query=session.createQuery("from Customer where id=?");
query.setInteger(0, 7);
Customer c=(Customer) query.uniqueResult();
System.out.println(c.toString());
Query提供了各种绑定数据类型的参数的方法setXxx(),如果参数为字符串类型,就调用setString(),如果参数为整数类型,就调用setInteger()等等。
这些setXxx()方法第一个参数代表占位符的索引,第二个代表参数值。
(2) 使用参数名字绑定
在HQL中使用命名参数,命名参数以":"开头。
Query query=session.createQuery("from Customer where id=:a");
query.setInteger("a", 7);
Customer c=(Customer) query.uniqueResult();
System.out.println(c.toString());
这些setXxx()方法第一个参数是命名参数的名字,第二个参数是值。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
(8)================= HQL查询单个对象 ====================
-uniqueResult()方法:返回单个对象
Query query=session.createQuery("from Customer where id=1");
Customer c=(Customer) query.uniqueResult();
System.out.println(c.toString());
- 1
- 2
- 3
- 4
- 5
- 6
(9)================= HQL聚合函数和分组查询 ====================
(1)HQL调用聚合函数
-查询customer表所有记录数目
Query query=session.createQuery("select count(*) from Customer");
//该语句返回long类型
Long c=(Long) query.uniqueResult();
System.out.println(c.intValue());
-查询最大ID
Query query=session.createQuery("select Max(id) from Customer");
Integer c=(Integer) query.uniqueResult();
System.out.println(c.intValue());
(2)分组查询
group by子句用来分组查询。
-根据id分组,统计相同id的数目
Query query=session.createQuery("select id,count(*) from Customer group by id");
List<Object[]> list=query.list();
for(Object[] obj:list){
System.out.println(Arrays.toString(obj));
}
-having子句用于为分组查询加上条件
Query query=session.createQuery("select id,count(*) from Customer group by id having id>8");
List<Object[]> list=query.list();
for(Object[] obj:list){
System.out.println(Arrays.toString(obj));
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
(10)================= HQL连接查询 ====================
-交叉连接:返回被连接的两个表所有数据行的笛卡儿积
如果A表有5行记录,B表有7行记录,返回的结果就有35行记录
显然,会产生很多没有意义的数据。
Query query=session.createQuery("from Customer,Order");
List<Object[]> list=query.list();
for(Object[] obj:list){
System.out.println(Arrays.toString(obj));
}
-隐式内连接
在交叉连接基础上,通过条件来过滤一些无意义的数据,达到内连接的效果。
Query query=session.createQuery("from Customer c,Order o where o.customer=c");
List<Object[]> list=query.list();
for(Object[] obj:list){
System.out.println(Arrays.toString(obj));
}
-显式内连接
使用inner join关键字表示内连接。inner可以省略,只使用join。
调用list()方法返回的集合中存放的是每个元素对应的记录,每个元素都是数组类型。
Query query=session.createQuery("from Customer c inner join c.orders");
List<Object[]> list=query.list();
for(Object[] obj:list){
System.out.println(Arrays.toString(obj));
}
-迫切内连接
使用inner join fetch关键字表示迫切内连接。
调用list()方法返回的集合中存放的是Customer对象的引用,每个Customer对象的Orders集合都被初始化,存放所有关联的Order对象。
Query query=session.createQuery("from Customer c inner join c.orders");
List <Object> list=query.list();
for(Object obj:list){
System.out.println(obj);
}
-左外连接
在连接查询中,连接左端的表中的所有的行全部显示,并且能在右端的表中找到匹配的行,如果右端表中没能找到左端匹配的行,则对应NULL.
使用left join关键字表示左外连接。
返回的list集合中存放的是多个对象数组。
Query query=session.createQuery("from Customer c left join c.orders");
List<Object[]> list=query.list();
for(Object[] obj:list){
System.out.println(Arrays.toString(obj));
}
-迫切左外连接
使用left join fetch关键字表示迫切左外连接。
list()方法返回的集合中存放的是Customer对象的引用,每个Customer的Orders集合都被初始化,存放关联的Order对象。
Query query=session.createQuery("from Customer c left join fetch c.orders");
List<Object> list=query.list();
for(Object obj:list){
System.out.println(obj);
}
-右外连接
和左外连接一样,连接右端表中的行全部显示,连接左端找到匹配的行,如果未能找到匹配的行,则用NULL代替
使用right join关键字表示右外连接。
Query query=session.createQuery("from Customer c right join c.orders");
List<Object[]> list=query.list();
for(Object[] obj:list){
System.out.println(Arrays.toString(obj));
}
-迫切右外连接
Query query=session.createQuery("from Customer c right join fetch c.orders");
List<Object> list=query.list();
for(Object obj:list){
System.out.println(obj);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
(11)================= 在映射文件中定义命名查询语句 ====================
前面的例子中,HQL查询语句都写在程序代码中。Hibernate允许在映射文件中定义字符串形式的查询语句。
<class name="com.cad.domain.Customer" table="customer" >
<id name="id" column="id">
<generator class="native"></generator>
</id>
<property name="name" column="name"></property>
<set name="orders" batch-size="3" fetch="subselect">
<key column="cid"/>
<one-to-many class="com.cad.domain.Order" />
</set>
<!--定义局部命名查询语句-->
<query name="bcd"><![CDATA[from Order]]></query>
</class>
<!--定义全局命名查询语句-->
<query name="abc"><![CDATA[from Customer]]></query>
//调用全局的命名查询语句
Query query=session.getNamedQuery("abc");
//调用局部的命名查询语句
Query query=session.getNamedQuery("com.cad.domain.Customer.bcd");
List<Object> list=query.list();
for(Object obj:list){
System.out.println(obj);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
(4)QBC检索方式
使用QBC(Query By Criteria)API检索对象。封装了基于字符串形式的查询语句,提供了更加面向对象的查询接口。
QBC检索方式的步骤
-调用Session的createCriteria()方法创建一个Criteria对象
-设定查询条件
-调用Criteria接口的list()方法执行查询语句。返回List集合。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
(1)================= 查询所有对象 ====================
//传递类名.class即可
Criteria criteria=session.createCriteria(Customer.class);
List<Customer> l=criteria.list();
for(Customer c:l){
System.out.println(c);
}
- 1
- 2
- 3
- 4
- 5
- 6
(2)================= QBC对查询结果进行排序 ====================
QBC使用org.hibernate.criterion.order类对查询结果排序。
asc为升序,desc为降序。
Criteria criteria=session.createCriteria(Customer.class);
criteria.addOrder(org.hibernate.criterion.Order.desc("id"));
List<Customer> l=criteria.list();
for(Customer c:l){
System.out.println(c);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
(3)================= QBC分页查询 ====================
QBC的分页查询和HQL类似。Criteria接口也提供了方法。
-setFirstResult(int index):设定从第几个对象开始检索,起始值为0。
-setMaxResult(int count):设定一次检索的对象数目。默认情况下检索所有的对象。
Criteria criteria=session.createCriteria(Customer.class);
criteria.setFirstResult(0);
criteria.setMaxResults(2);
List<Customer> l=criteria.list();
for(Customer c:l){
System.out.println(c);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
(4)================= QBC设定查询条件 ====================
必须创建一个Criterion对象来设定查询条件。Restrictions类提供了创建Criterion的方法。
-Restrictions.eq() 等于
-Restrictions.ne() 不等于
-Restrictions.gt() 大于
..........等等很多方法
Criteria criteria=session.createCriteria(Customer.class);
//查询id为7的Customer
Criterion c1=Restrictions.eq("id", 7);
criteria.add(c1);
Customer c=(Customer) criteria.uniqueResult();
System.out.println(c);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
(5)================= QBC检索单个对象 ====================
uniqueResult():返回单个对象
- 1
(5)本地SQL检索方式
可以使用原生的SQL语句来进行查询。
本地SQL检索也是使用Query接口,通过Session的createSQLQuery(SQL语句)方法来创建Query。
例子:
Query query =session.createSQLQuery("select * from customer");
List<Object[]> list=query.list();
for(Object[] o:list){
System.out.println(Arrays.toString(o));
}
默认情况下,SQLQuery返回的list集合中存放的是关系数据。每个元素都是Object[]数组。
-addEntity()方法能把查询结果中每一行的数据封装成对象
Query query =session.createSQLQuery("select * from customer").addEntity(Customer.class);
List<Customer> list=query.list();
for(Customer o:list){
System.out.println(o.toString());
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
Hibernate高级配置
配置数据库连接池
配置C3P0连接池。
先导入c3p0包。
然后在hibernate.cfg.xml文件中 ,使用下面代码配置连接池
<property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</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
例如: <property name="hibernate.c3p0.max_size">5</property>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
处理并发问题
Hibernate处理事务并发问题
事务并发问题以前都有讲到过,可以翻阅以前的笔记。
在Hibernate中设置事务的隔离级别。
<property name="hibernate.connection.isolation">2</property>
隔离级别代号。
1:Read Uncommitted
2: Read Committed
4: Repeatable Read
8: Serializable
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
数据库系统锁的基本原理
为了避免各种并发问题,以保证数据的完整性和一致性,数据库系统采用锁来实现事务的隔离性。
锁的类型
-共享锁:用于读数据操作。允许其他事务同时读取资源,但不允许其它事务更新。
-独占锁:用于修改数据的场合。它锁定的资源,其他事务不能读取也不能修改。
锁的基本原理如下
-当一个事务访问某种数据库资源时,如果执行select语句,必须先获得共享锁。
如果执行update、insert、delete语句,必须先获得独占锁。
-当第二个事务也要访问相同的资源时,如果执行select语句,也必须获得共享锁。
如果执行update、insert、delete语句,也必须先获得独占锁。
此时根据锁的类型。来决定第二个事务是应该等待第一个事务完成,还是可以立即获得锁。
许多数据库系统都有自动管理锁的功能,能够根据事务执行的sql语句,自动为资源加上适当的锁。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
悲观锁和乐观锁
悲观锁 :
指在应用程序显式的为数据资源加锁。悲观锁假定当前事务操纵数据资源时,一定会有其他事务同时访问资源。为了避免当前事务受到干扰,先锁定资源。悲观锁能够防止丢失更新和不可重复读等并发问题,但是会影响并发性能。需要谨慎使用。 悲观锁对事务处理很悲观,总是认为其他事务会占用资源。
当事务执行select语句时,默认使用共享锁来锁定查询的语句。也可以使用 select ..... lock in share mode来指定使用共享锁。
使用select .... for update来指定采用独占锁来锁定查询的记录
例如取款:select * from bank where id=1 for update
那么这条记录就被锁定,其他事务如果要处理这条数据,必须停下来等待。直到我们的取款事务结束。
Hibernate应用中,当通过Session的get()和load()方法来加载一个对象时,可以采用以下方式使用悲观锁。
Session.get(Clsss,OID,锁的类型);
锁的类型
LockMode.UPGRADE:使用悲观锁,也就是独占锁。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
乐观锁 :
乐观锁假定当前事务操纵数据资源时,不会有其他事务同时访问该数据资源。
因此完全由数据库的隔离级别来控制,或者我们自己来实现。乐观锁对事务的态度总是很乐观。
- 1
- 2
- 3
实现的原理
在数据表中添加Version字段,初始化为0,事务提交的前提是我的数据Version字段必须比数据库中的Version字段小1。
例如同时两个付款单,A付款事务的时候会先取出金额,然后付款,修改余额,然后将version字段加一
B付款事务执行同样的操作,然后事务提交。
如果A先提交事务,version变为1.B再提交的时候version也变为1,然后就会发现Version的值是一样的,这时就会抛出异常
Hibernate帮我们实现了这种操作。
我们需要在我们的实体类中添加一个version属性,并添加get/set方法。
然后在实体类的映射文件中配置这个属性,紧跟在<id>元素后面有一个version元素,name值为version属性名。
<version name="version"></version>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
<link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/markdown_views-ea0013b516.css">
</div>
Hibernate的多对多关联关系
多对多关联关系在java对象中可以通过定义集合类型来实现关联关系。
在关系数据模型中,无法直接表达表和表之间的多对多关联关系,而是需要创建一个中间表包含了两边的主键,来表达两张表的多对多关联关系。
- 1
- 2
- 3
- 4
我们用一个Student和Course(学生和课程)的例子来演示多对多关联关系。
第一步:创建Student和Course类
public class Student {
private Integer id;
private String name;
//用一个集合包含该学生所选的课程对象
private Set<Course> courses=new HashSet<Course>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Course> getCourses() {
return courses;
}
public void setCourses(Set<Course> courses) {
this.courses = courses;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
public class Course {
private Integer id;
private String name;
//用一个集合包含所有选择该课程的学生
private Set<Student> students=new HashSet<Student>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Student> getStudents() {
return students;
}
public void setStudents(Set<Student> students) {
this.students = students;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
第二步:编写我们的映射文件
编写Course.hbm.xml
<hibernate-mapping >
<class name="com.cad.domain.Course" table="course">
<id name="id" column="id">
<generator class="native"></generator>
</id>
<property name="name" column="name"></property>
<!--table属性用来指定生成的中间表的表名称 inverse指定关联关系由Student维护-->
<set name="students" table="student_course" inverse="true">
<key column="cid"></key>
<!--<many-to-many>元素中的column属性指定本表通过中间表中的sid外键关联到Student对象-->
<many-to-many class="com.cad.domain.Student" column="sid"></many-to-many>
</set>
</class>
</hibernate-mapping>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
编写Student.hbm.xml
<hibernate-mapping >
<class name="com.cad.domain.Student" table="student">
<id name="id" column="id">
<generator class="native"></generator>
</id>
<property name="name" column="name"></property>
<!-进行级联保存和更新操作-->
<set name="courses" table="student_course" cascade="save-update">
<key column="sid"></key>
<many-to-many class="com.cad.domain.Course" column="cid"></many-to-many>
</set>
</class>
</hibernate-mapping>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
我们来测试一下
public class Demo {
@Test
public void fun(){
//读取配置文件
Configuration conf=new Configuration().configure();
//根据配置创建factory
SessionFactory sessionfactory=conf.buildSessionFactory();
Session session = sessionfactory.openSession();
Transaction ts=session.beginTransaction();
//创建两个Student
Student s1=new Student();
s1.setName("tom");
Student s2=new Student();
s2.setName("jack");
//创建三个Course
Course c1=new Course();
c1.setName("语文");
Course c2=new Course();
c2.setName("数学");
Course c3=new Course();
c3.setName("英语");
//因为设置了关联关系由Student维护,所以不需要课程再来关联Student
s1.getCourses().add(c1);
s1.getCourses().add(c2);
s1.getCourses().add(c3);
s2.getCourses().add(c1);
s2.getCourses().add(c2);
s2.getCourses().add(c3);
//由于设置了级联保存,所以只保存Student即可
session.save(s1);
session.save(s2);
ts.commit();
session.close();
sessionfactory.close();
}
}
结果,数据库中生成了三张表。
student和course
还有中间表student_course
里面的数据也相互对应。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
Hibernate的检索策略
简介
Session的缓存中可以存放相互关联的对象。当Hibernate从数据库中加载对象时,如果同时自动加载与之关联的所有对象,那么这些关联的对象就浪费了很多的内存空间。而我们可以设置检索策略,来优化检索性能。
Hibernate提供了三种检索策略
-立即检索策略:检索对象时立即加载对象以及关联的对象。
-延迟检索策略:使用时才会加载对象以及关联的对象。能避免多余加载不需要的对象。
-迫切左外连接检索策略 :利用SQL的外连接查询功能,能够减少select语句的数目。
- 1
- 2
- 3
- 4
- 5
- 6
类级别检索策略
定义:只加载某个类对象
类级别的检索策略包括立即检索和延迟检索。默认为延迟检索。
Hibernate中允许在映射文件中配置检索策略。
类级别检索策略,可以配置<class>元素中的lazy属性。属性可选值:true(延迟检索) false(立即检索)。默认值为true。
类级别检索策略会影响Session的load方法。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
我们来做一个小例子,在load方法加断点
public class Demo {
private Session session;
@Test
public void test() {
//读取配置文件
Configuration conf=new Configuration().configure();
//根据配置创建factory
SessionFactory sessionfactory=conf.buildSessionFactory();
session = sessionfactory.openSession();
Transaction ts=session.beginTransaction();
Customer c=session.load(Customer.class, 7);//此处加断点来观察效果
System.out.println(c.getName());
ts.commit();
session.close();
sessionfactory.close();
}
}
当lazy属性为true时,调用load方法什么都不输出,使用对象时才输出select语句。
当lazy属性为false时,调用load方法会直接输出select语句。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
原理:
当为延迟检索策略时,执行load方法时,是返回当前对象的代理对象,这个代理对象由Hibernate动态生成,代理对象继承了当前对象,仅仅初始化了OID属性,其他属性都为null,当程序访问代理类属性时,Hibernate才会初始化代理对象,初始化过程执行select语句,从数据库加载所有数据。
一对多关联级别的策略策略
关联级别检索策略:查询有关联关系的对象时,加载对象时是否需要将关联的对象加载。
在映射文件中,使用< set >元素来配置一对多或者多对多的关联关系。
< set >元素中有lazy和fetch属性决定加载策略。
lazy属性:和类级别加载策略一致。
默认值为true。即使用延迟检索。
当lazy为false时,使用立即检索。
当lazy为extra时,采用加强延迟检索策略,它尽可能的延迟集合被初始化的时机。例如程序访问集合的一些属性时,如访问size()、contains()、isEmpty()这些方法时,如果指定了extra,就不会加载集合,仅通过特定的语句查询必要的信息。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
例子 :
public class Demo {
private Session session;
@Test
public void test() {
//读取配置文件
Configuration conf=new Configuration().configure();
//根据配置创建factory
SessionFactory sessionfactory=conf.buildSessionFactory();
session = sessionfactory.openSession();
Transaction ts=session.beginTransaction();
//加载Customer对象
Customer c=(Customer)session.get(Customer.class, 7);
//加载Customer关联的Order对象
for(Order o:c.getOrders()){
System.out.println(o.getName());
}
ts.commit();
session.close();
sessionfactory.close();
}
}
当我们lazy属性为false时,在执行get方法时,会使用立即检索策略,会执行两个select语句,加载Customer和关联的Orders集合。
当我们lazy属性为true时,执行get方法,只会执行一句select语句,此时Customer的Orders属性引用一个没有被初始化的集合代理类,此时Orders集合中不存在任何Order对象,当我们使用order时,才会初始化集合代理类,才会到数据库中检索所有关联的Order对象
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
fetch属性:决定加载集合时查询语句的格式。取值为:select 、subselect、join
select:(默认值)使用普通的select语句进行查询。
subselect:使用子查询加载集合数据。当查找多个对象时才有用,例如查找多个用户,每个用户有多个订单
join:采用迫切左外连接检索策略进行查询。fetch属性设为join,lazy属性将被忽略,在查询对象时一定会加载与之关联的对象。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
例子:
public class Demo {
private Session session;
@Test
public void test() {
//读取配置文件
Configuration conf=new Configuration().configure();
//根据配置创建factory
SessionFactory sessionfactory=conf.buildSessionFactory();
session = sessionfactory.openSession();
Transaction ts=session.beginTransaction();
//加载Customer对象
Customer c=(Customer)session.get(Customer.class, 7);
//加载关联的Order对象
for(Order o:c.getOrders()){
System.out.println(o.getName());
}
ts.commit();
session.close();
sessionfactory.close();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
当fetch属性为默认值(select)时,打印的sql语句
Hibernate:
select
customer0_.id as id1_0_0_,
customer0_.name as name2_0_0_
from
customer customer0_
where
customer0_.id=?
Hibernate:
select
orders0_.cid as cid3_1_0_,
orders0_.id as id1_1_0_,
orders0_.id as id1_1_1_,
orders0_.name as name2_1_1_,
orders0_.cid as cid3_1_1_
from
orders orders0_
where
orders0_.cid=?
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
当fetch属性为join时,打印select语句,采用左外连接的查询语句,减少select语句
Hibernate:
select
customer0_.id as id1_0_0_,
customer0_.name as name2_0_0_,
orders1_.cid as cid3_1_1_,
orders1_.id as id1_1_1_,
orders1_.id as id1_1_2_,
orders1_.name as name2_1_2_,
orders1_.cid as cid3_1_2_
from
customer customer0_
left outer join
orders orders1_
on customer0_.id=orders1_.cid
where
customer0_.id=?
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
演示fetch属性为subselect时,我们查询所有Customer,然后使用每一个Customer的Orders集合
public class Demo {
private Session session;
@Test
public void test() {
//读取配置文件
Configuration conf=new Configuration().configure();
//根据配置创建factory
SessionFactory sessionfactory=conf.buildSessionFactory();
session = sessionfactory.openSession();
Transaction ts=session.beginTransaction();
//查找所有Customer
List<Customer> customerlist=session.createQuery("from Customer").list();
//加载每个Customer关联的Orders集合
for(Customer c:customerlist){
System.out.println(c.getOrders().size());
}
ts.commit();
session.close();
sessionfactory.close();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
打印的语句,使用了子查询语句
当fetch属性为subselect时,Hibernate能够通过带有子查询的select语句,来整批量的初始化多个对象的集合中的实例。
Hibernate:
select
customer0_.id as id1_0_,
customer0_.name as name2_0_
from
customer customer0_
Hibernate:
select
orders0_.cid as cid3_1_1_,
orders0_.id as id1_1_1_,
orders0_.id as id1_1_0_,
orders0_.name as name2_1_0_,
orders0_.cid as cid3_1_0_
from
orders orders0_
where
orders0_.cid in (
select
customer0_.id
from
customer customer0_
)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
fetch和lazy结合
多对一关联级别的策略策略
和<set>元素一样,<many-to-one>元素中也有lazy和fetch属性。
lazy的可选值有false(立即检索),proxy(延迟检索),no-proxy(无代理延迟检索)
fetch属性可选值为select 和 join
- 1
- 2
- 3
- 4
- 5
例子:设置订单配置文件中fetch属性为join
public class Demo {
private Session session;
@Test
public void test() {
//读取配置文件
Configuration conf=new Configuration().configure();
//根据配置创建factory
SessionFactory sessionfactory=conf.buildSessionFactory();
session = sessionfactory.openSession();
Transaction ts=session.beginTransaction();
//加载订单对象
Order c=(Order)session.get(Order.class, 6);
//加载关联的Customer对象
System.out.println(c.getCustomer().getName());
ts.commit();
session.close();
sessionfactory.close();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
则在执行get方法时,会直接使用外连接查询。忽略lazy。
Hibernate:
select
order0_.id as id1_1_0_,
order0_.name as name2_1_0_,
order0_.cid as cid3_1_0_,
customer1_.id as id1_0_1_,
customer1_.name as name2_0_1_
from
orders order0_
left outer join
customer customer1_
on order0_.cid=customer1_.id
where
order0_.id=?
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
批量检索
< class >和< set >元素中包含batch-size属性。设定批量检索的数量。可选值为正整数,默认为1。
仅用于关联级别的立即检索和批量检索。
我们在Customer配置文件中设置批量延迟检索
<set name="orders" lazy="true" batch-size="3">
public class Demo {
private Session session;
@Test
public void test() {
//读取配置文件
Configuration conf=new Configuration().configure();
//根据配置创建factory
SessionFactory sessionfactory=conf.buildSessionFactory();
session = sessionfactory.openSession();
Transaction ts=session.beginTransaction();
//加载所有Customer对象
List<Customer> customerlist=session.createQuery("from Customer").list();
for(Customer c:customerlist){
System.out.println(c.getOrders().size());
}
ts.commit();
session.close();
sessionfactory.close();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
我们使用延迟检索,当使用到每个Customer的orders时,才加载Orders,每执行一次循环,就打印一个select语句。
如果有很多Customer对象,那么就会打印很多的select语句。
所以我们可以使用批量检索。只需要将<set>的batch-size属性设置为3.
所以会批量初始化三个Orders集合,使用in语句。这样当用到第一个第二个第三个对象的集合时,就不会再发送语句。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
Hibernate的查询方式
(1)OID检索方式
按照对象的OID来查询对象。Session的get/load方法提供了这种功能。
如果应用程序中事先知道了OID,就可以使用这种查询对象的方式。
- 1
- 2
- 3
- 4
(2)导航对象图检索方式
根据已经加载的对象,导航到其他对象.
例如,对于已经加载的Customer对象,可以导航到所有关联的Order对象
- 1
- 2
- 3
- 4
(3)HQL检索方式
简介:
使用面向对象的HQL查询语言。Hibernate提供了Query接口,它是专门的HQL查询接口,能执行各种复杂的HQL语句。
HQL是面向对象的查询语言,和SQL查询语言有些类似,Hibernate提供的各种检索方式中,HQL是使用最广的一种检索方式。HQL封装了JDBC的细节
HQL和SQL本质是不一样的:
-HQL查询语言面向对象,Hibernate负责解析HQL语句,然后根据映射文件,把HQL语句翻译成SQL语句。HQL查询语句中主体是类和类的属性。
-SQL查询语言是和关系数据库绑定在一起的,SQL查询语句中主体是数据库表和表的字段。
- 1
- 2
- 3
- 4
- 5
- 6
HQL查询的步骤
-通过session的createQuery(HQL语句)方法创建一个Query对象
-调用Query的list()方法执行查询语句。该方法返回List集合
(1)================= 查找所有对象 ====================
public class Demo {
private Session session;
@Test
public void test() {
//读取配置文件
Configuration conf=new Configuration().configure();
//根据配置创建factory
SessionFactory sessionfactory=conf.buildSessionFactory();
session = sessionfactory.openSession();
Transaction ts=session.beginTransaction();
//HQL语句是面向对象的,from 类名
List<Customer> customerlist=session.createQuery("from Customer").list();
System.out.println(customerlist);
ts.commit();
session.close();
sessionfactory.close();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
(2)================= HQL语句使用别名 ====================
例如:List< Customer > customerlist=session.createQuery(“from Customer as c”).list();
as关键字用来设置别名,as也可以省略。 from Customer c即可。
(3)================= HQL语句选择查询 ====================
选择查询是指仅查找某些属性。
这时list()返回的集合中包括的是一个一个数组,每个数组中是一条记录。
List <Object[]> customerlist=session.createQuery("select id,name from Customer").list();
for(Object[] obj:customerlist){
System.out.println(Arrays.toString(obj));
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
(4)================= HQL语句投影查询 ====================
基于选择查询的基础上,把查询到的属性封装到对象中,该对象的其他属性为null。
该对象必须有对应的构造方法。
List <Customer> customerlist=session.createQuery("select new Customer(id,name) from Customer").list();
for(Customer c:customerlist){
System.out.println(c.toString());
}
Customer类中必须有如下构造方法
public Customer(Integer id, String name) {
this.id = id;
this.name = name;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
(5)================= HQL对查询结果排序 ====================
HQL语言使用order by关键字对结果进行排序。
asc为升序,desc为降序。
List <Customer> customerlist=session.createQuery("select new Customer(id,name) from Customer order by id desc").list();
- 1
- 2
- 3
- 4
- 5
(6)================= HQL分页查询 ====================
-setFirstResult(int index):设定从第几个对象开始检索,起始值为0。
-setMaxResult(int count):设定一次检索的对象数目。默认情况下检索所有的对象。
Query query=session.createQuery("select id,name from Customer");
//从第一个对象开始
query.setFirstResult(0);
//查找两个对象
query.setMaxResults(2);
List result=query.list();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
(7)================= HQL语句中绑定参数 ====================
对于实际应用中,经常需要用户输入一些查询条件,我们返回符合查询条件的数据。
我们可以使用from Customer where name='"+name+"';来实现
但是这种方式是非常不安全的,会受到SQL注入等非法攻击。
- 1
- 2
- 3
- 4
Hibernate中使用参数绑定来避免以上问题。有两种方式
(1)使用?占位符绑定
占位符的索引从0开始。
Query query=session.createQuery("from Customer where id=?");
query.setInteger(0, 7);
Customer c=(Customer) query.uniqueResult();
System.out.println(c.toString());
Query提供了各种绑定数据类型的参数的方法setXxx(),如果参数为字符串类型,就调用setString(),如果参数为整数类型,就调用setInteger()等等。
这些setXxx()方法第一个参数代表占位符的索引,第二个代表参数值。
(2) 使用参数名字绑定
在HQL中使用命名参数,命名参数以":"开头。
Query query=session.createQuery("from Customer where id=:a");
query.setInteger("a", 7);
Customer c=(Customer) query.uniqueResult();
System.out.println(c.toString());
这些setXxx()方法第一个参数是命名参数的名字,第二个参数是值。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
(8)================= HQL查询单个对象 ====================
-uniqueResult()方法:返回单个对象
Query query=session.createQuery("from Customer where id=1");
Customer c=(Customer) query.uniqueResult();
System.out.println(c.toString());
- 1
- 2
- 3
- 4
- 5
- 6
(9)================= HQL聚合函数和分组查询 ====================
(1)HQL调用聚合函数
-查询customer表所有记录数目
Query query=session.createQuery("select count(*) from Customer");
//该语句返回long类型
Long c=(Long) query.uniqueResult();
System.out.println(c.intValue());
-查询最大ID
Query query=session.createQuery("select Max(id) from Customer");
Integer c=(Integer) query.uniqueResult();
System.out.println(c.intValue());
(2)分组查询
group by子句用来分组查询。
-根据id分组,统计相同id的数目
Query query=session.createQuery("select id,count(*) from Customer group by id");
List<Object[]> list=query.list();
for(Object[] obj:list){
System.out.println(Arrays.toString(obj));
}
-having子句用于为分组查询加上条件
Query query=session.createQuery("select id,count(*) from Customer group by id having id>8");
List<Object[]> list=query.list();
for(Object[] obj:list){
System.out.println(Arrays.toString(obj));
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
(10)================= HQL连接查询 ====================
-交叉连接:返回被连接的两个表所有数据行的笛卡儿积
如果A表有5行记录,B表有7行记录,返回的结果就有35行记录
显然,会产生很多没有意义的数据。
Query query=session.createQuery("from Customer,Order");
List<Object[]> list=query.list();
for(Object[] obj:list){
System.out.println(Arrays.toString(obj));
}
-隐式内连接
在交叉连接基础上,通过条件来过滤一些无意义的数据,达到内连接的效果。
Query query=session.createQuery("from Customer c,Order o where o.customer=c");
List<Object[]> list=query.list();
for(Object[] obj:list){
System.out.println(Arrays.toString(obj));
}
-显式内连接
使用inner join关键字表示内连接。inner可以省略,只使用join。
调用list()方法返回的集合中存放的是每个元素对应的记录,每个元素都是数组类型。
Query query=session.createQuery("from Customer c inner join c.orders");
List<Object[]> list=query.list();
for(Object[] obj:list){
System.out.println(Arrays.toString(obj));
}
-迫切内连接
使用inner join fetch关键字表示迫切内连接。
调用list()方法返回的集合中存放的是Customer对象的引用,每个Customer对象的Orders集合都被初始化,存放所有关联的Order对象。
Query query=session.createQuery("from Customer c inner join c.orders");
List <Object> list=query.list();
for(Object obj:list){
System.out.println(obj);
}
-左外连接
在连接查询中,连接左端的表中的所有的行全部显示,并且能在右端的表中找到匹配的行,如果右端表中没能找到左端匹配的行,则对应NULL.
使用left join关键字表示左外连接。
返回的list集合中存放的是多个对象数组。
Query query=session.createQuery("from Customer c left join c.orders");
List<Object[]> list=query.list();
for(Object[] obj:list){
System.out.println(Arrays.toString(obj));
}
-迫切左外连接
使用left join fetch关键字表示迫切左外连接。
list()方法返回的集合中存放的是Customer对象的引用,每个Customer的Orders集合都被初始化,存放关联的Order对象。
Query query=session.createQuery("from Customer c left join fetch c.orders");
List<Object> list=query.list();
for(Object obj:list){
System.out.println(obj);
}
-右外连接
和左外连接一样,连接右端表中的行全部显示,连接左端找到匹配的行,如果未能找到匹配的行,则用NULL代替
使用right join关键字表示右外连接。
Query query=session.createQuery("from Customer c right join c.orders");
List<Object[]> list=query.list();
for(Object[] obj:list){
System.out.println(Arrays.toString(obj));
}
-迫切右外连接
Query query=session.createQuery("from Customer c right join fetch c.orders");
List<Object> list=query.list();
for(Object obj:list){
System.out.println(obj);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
(11)================= 在映射文件中定义命名查询语句 ====================
前面的例子中,HQL查询语句都写在程序代码中。Hibernate允许在映射文件中定义字符串形式的查询语句。
<class name="com.cad.domain.Customer" table="customer" >
<id name="id" column="id">
<generator class="native"></generator>
</id>
<property name="name" column="name"></property>
<set name="orders" batch-size="3" fetch="subselect">
<key column="cid"/>
<one-to-many class="com.cad.domain.Order" />
</set>
<!--定义局部命名查询语句-->
<query name="bcd"><![CDATA[from Order]]></query>
</class>
<!--定义全局命名查询语句-->
<query name="abc"><![CDATA[from Customer]]></query>
//调用全局的命名查询语句
Query query=session.getNamedQuery("abc");
//调用局部的命名查询语句
Query query=session.getNamedQuery("com.cad.domain.Customer.bcd");
List<Object> list=query.list();
for(Object obj:list){
System.out.println(obj);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
(4)QBC检索方式
使用QBC(Query By Criteria)API检索对象。封装了基于字符串形式的查询语句,提供了更加面向对象的查询接口。
QBC检索方式的步骤
-调用Session的createCriteria()方法创建一个Criteria对象
-设定查询条件
-调用Criteria接口的list()方法执行查询语句。返回List集合。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
(1)================= 查询所有对象 ====================
//传递类名.class即可
Criteria criteria=session.createCriteria(Customer.class);
List<Customer> l=criteria.list();
for(Customer c:l){
System.out.println(c);
}
- 1
- 2
- 3
- 4
- 5
- 6
(2)================= QBC对查询结果进行排序 ====================
QBC使用org.hibernate.criterion.order类对查询结果排序。
asc为升序,desc为降序。
Criteria criteria=session.createCriteria(Customer.class);
criteria.addOrder(org.hibernate.criterion.Order.desc("id"));
List<Customer> l=criteria.list();
for(Customer c:l){
System.out.println(c);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
(3)================= QBC分页查询 ====================
QBC的分页查询和HQL类似。Criteria接口也提供了方法。
-setFirstResult(int index):设定从第几个对象开始检索,起始值为0。
-setMaxResult(int count):设定一次检索的对象数目。默认情况下检索所有的对象。
Criteria criteria=session.createCriteria(Customer.class);
criteria.setFirstResult(0);
criteria.setMaxResults(2);
List<Customer> l=criteria.list();
for(Customer c:l){
System.out.println(c);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
(4)================= QBC设定查询条件 ====================
必须创建一个Criterion对象来设定查询条件。Restrictions类提供了创建Criterion的方法。
-Restrictions.eq() 等于
-Restrictions.ne() 不等于
-Restrictions.gt() 大于
..........等等很多方法
Criteria criteria=session.createCriteria(Customer.class);
//查询id为7的Customer
Criterion c1=Restrictions.eq("id", 7);
criteria.add(c1);
Customer c=(Customer) criteria.uniqueResult();
System.out.println(c);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
(5)================= QBC检索单个对象 ====================
uniqueResult():返回单个对象
- 1
(5)本地SQL检索方式
可以使用原生的SQL语句来进行查询。
本地SQL检索也是使用Query接口,通过Session的createSQLQuery(SQL语句)方法来创建Query。
例子:
Query query =session.createSQLQuery("select * from customer");
List<Object[]> list=query.list();
for(Object[] o:list){
System.out.println(Arrays.toString(o));
}
默认情况下,SQLQuery返回的list集合中存放的是关系数据。每个元素都是Object[]数组。
-addEntity()方法能把查询结果中每一行的数据封装成对象
Query query =session.createSQLQuery("select * from customer").addEntity(Customer.class);
List<Customer> list=query.list();
for(Customer o:list){
System.out.println(o.toString());
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
Hibernate高级配置
配置数据库连接池
配置C3P0连接池。
先导入c3p0包。
然后在hibernate.cfg.xml文件中 ,使用下面代码配置连接池
<property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</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
例如: <property name="hibernate.c3p0.max_size">5</property>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
处理并发问题
Hibernate处理事务并发问题
事务并发问题以前都有讲到过,可以翻阅以前的笔记。
在Hibernate中设置事务的隔离级别。
<property name="hibernate.connection.isolation">2</property>
隔离级别代号。
1:Read Uncommitted
2: Read Committed
4: Repeatable Read
8: Serializable
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
数据库系统锁的基本原理
为了避免各种并发问题,以保证数据的完整性和一致性,数据库系统采用锁来实现事务的隔离性。
锁的类型
-共享锁:用于读数据操作。允许其他事务同时读取资源,但不允许其它事务更新。
-独占锁:用于修改数据的场合。它锁定的资源,其他事务不能读取也不能修改。
锁的基本原理如下
-当一个事务访问某种数据库资源时,如果执行select语句,必须先获得共享锁。
如果执行update、insert、delete语句,必须先获得独占锁。
-当第二个事务也要访问相同的资源时,如果执行select语句,也必须获得共享锁。
如果执行update、insert、delete语句,也必须先获得独占锁。
此时根据锁的类型。来决定第二个事务是应该等待第一个事务完成,还是可以立即获得锁。
许多数据库系统都有自动管理锁的功能,能够根据事务执行的sql语句,自动为资源加上适当的锁。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
悲观锁和乐观锁
悲观锁 :
指在应用程序显式的为数据资源加锁。悲观锁假定当前事务操纵数据资源时,一定会有其他事务同时访问资源。为了避免当前事务受到干扰,先锁定资源。悲观锁能够防止丢失更新和不可重复读等并发问题,但是会影响并发性能。需要谨慎使用。 悲观锁对事务处理很悲观,总是认为其他事务会占用资源。
当事务执行select语句时,默认使用共享锁来锁定查询的语句。也可以使用 select ..... lock in share mode来指定使用共享锁。
使用select .... for update来指定采用独占锁来锁定查询的记录
例如取款:select * from bank where id=1 for update
那么这条记录就被锁定,其他事务如果要处理这条数据,必须停下来等待。直到我们的取款事务结束。
Hibernate应用中,当通过Session的get()和load()方法来加载一个对象时,可以采用以下方式使用悲观锁。
Session.get(Clsss,OID,锁的类型);
锁的类型
LockMode.UPGRADE:使用悲观锁,也就是独占锁。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
乐观锁 :
乐观锁假定当前事务操纵数据资源时,不会有其他事务同时访问该数据资源。
因此完全由数据库的隔离级别来控制,或者我们自己来实现。乐观锁对事务的态度总是很乐观。
- 1
- 2
- 3
实现的原理
在数据表中添加Version字段,初始化为0,事务提交的前提是我的数据Version字段必须比数据库中的Version字段小1。
例如同时两个付款单,A付款事务的时候会先取出金额,然后付款,修改余额,然后将version字段加一
B付款事务执行同样的操作,然后事务提交。
如果A先提交事务,version变为1.B再提交的时候version也变为1,然后就会发现Version的值是一样的,这时就会抛出异常
Hibernate帮我们实现了这种操作。
我们需要在我们的实体类中添加一个version属性,并添加get/set方法。
然后在实体类的映射文件中配置这个属性,紧跟在<id>元素后面有一个version元素,name值为version属性名。
<version name="version"></version>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
<link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/markdown_views-ea0013b516.css">
</div>