第一节 HQL查询
1.1 HQL简介
- HQL(Hibernate Query Language) 描写对象操作的一种查询语言,Hibernate特有。
- HQL的语法与SQL基本一致,不同的是HQL是面向对象的查询,查询的是对象和对象中的属性。
- HQL的关键字不区分大小写,但类名和属性区分大小写
- 语法示例:
SELECT 别名/属性名/表达式
FROM 实体 AS 别名
WHERE 过滤条件
GROUP BY 分组条件
HAVING 分组后的结果的过滤条件
ORDER BY 排序条件
1.2 案例1:查询所有客户
- 在day03的基础上,进一步使用hql。
//案例1:查询所有客户
@Test
public void test1(){
Session session = HibernateUtils.openSession();
//查询所有客户
Query query = session.createQuery("from Customer");
//查询后的结果会封装到一个list集合中
List<Customer> list = query.list();
//遍历
for (Customer customer : list) {
System.out.println(customer.getCustomerName());
}
session.close();
}
1.3 案例2:选择查询
//案例2:选择查询
@Test
public void test2(){
Session session = HibernateUtils.openSession();
//查询客户带条件
//Query query = session.createQuery("from Customer where id = 1");
Query query = session.createQuery("from Customer where id = ? ");
//起别名
//Query query1 = session.createQuery("from Customer c where c.id = ? ");
Query query1 = session.createQuery("select c from Customer c where c.id = ? ");
//设置参数从0开始
query.setParameter(0,1);
query1.setParameter(0,2);
//查询后的结果会封装到一个list集合中
List<Customer> list = query.list();
//遍历
for (Customer customer : list) {
System.out.println(customer.getCustomerId()+":"+customer.getCustomerName());
}
//只获取一条记录
Customer customer1 = (Customer) query1.uniqueResult();
System.out.println(customer1.getCustomerId()+":"+customer1.getCustomerName());
session.close();
}
1.4 案例3:投影查询
//案例3:投影查询1
@Test
public void test3(){
Session session = HibernateUtils.openSession();
//指定查询字段
Query query = session.createQuery("select c.id,c.customerName from Customer c ");
//这种查询出来的结果是一个数组类型的集合
List<Object[]> list = query.list();
//遍历集合,集合中的每一个元素是一个数组
for (Object[] objects : list) {
for (Object object : objects) {
System.out.println(object);
}
System.out.println("------------");
}
session.close();
}
//案例3:投影查询2
@Test
public void test4(){
Session session = HibernateUtils.openSession();
//指定查询字段,封装到集合对象中,但是Customer必须要提供对应的构造方法
Query query = session.createQuery("select new Customer(c.id,c.customerName) from Customer c ");
//这种查询出来的结果是Customer集合
List<Customer> list = query.list();
//遍历
for (Customer customer : list) {
System.out.println(customer);
}
session.close();
}
1.5 案例4:排序
//案例4:排序
@Test
public void test5(){
Session session = HibernateUtils.openSession();
//asc升序 desc降序
List<Customer> list = session.createQuery("from Customer c order by c.id desc ").list();
//需要Customer提供对应的构造方法,否则报错
//List<Customer> list = session.createQuery("select new Customer(c.id,c.customerName) from Customer c order by c.id desc ").list();
//遍历
for (Customer customer : list) {
System.out.println(customer);
}
session.close();
}
1.6 案例5:分页
//案例5:分页
@Test
public void test6(){
Session session = HibernateUtils.openSession();
//分页 limit ?,?
Query query = session.createQuery("from Customer ");
//当前页(pageCode)
query.setFirstResult(0);//相当于limit 0,2 第一页,每页显示条数2
//每页显示个数(pageSize)
query.setMaxResults(2);
//2,2 第二页,每页显示条数2
//第一页开始0、第二页开始2,计算方法:(页数-1)*每页显示条数
List<Customer> list = query.list();
for (Customer customer : list) {
System.out.println(customer);
}
session.close();
}
1.7 案例6:聚合函数和分组查询
//案例6:聚合函数和分组查询
@Test
public void test7(){
Session session = HibernateUtils.openSession();
//1.聚合函数
//1.1总记录数 count
Query query1 = session.createQuery("select count(*) from Customer ");
//返回的是long类型
long count = (long) query1.uniqueResult();
System.out.println("客户表的总记录数为:"+count);
//1.2平均数 avg
Query query2 = session.createQuery("select avg(c.id) from Customer c ");
//返回的是Double类型
Double avg = (Double) query2.uniqueResult();
System.out.println("客户表id的平均数为:"+avg);
//1.3分组 group by【根据客户分组,查看客户有多少张订单】
Query query3 = session.createQuery("select o.customer,count(o) from Order o group by o.customer having count(o) > 1");
//这种查询出来的结果是Customer集合
List<Object[]> list = query3.list();
//遍历集合,集合中的每一个元素是一个数组
for (Object[] objects : list) {
System.out.println("客户信息:"+objects[0]+" 订单数:"+objects[1]);
}
session.close();
}
1.8 案例7:连接查询
- INNER JOIN:在表中存在至少一个匹配时,INNER JOIN 关键字返回行。
- LEFT OUTER JOIN:关键字会从左表 (table_name1) 那里返回所有的行,即使在右表 (table_name2) 中没有匹配的行
- RIGHT OUTER JOIN:关键字会右表 (table_name2) 那里返回所有的行,即使在左表 (table_name1) 中没有匹配的行。
程序中指定的连接查询类型 | HQL语法 | 适用范围 |
内连接 | inner join | join | 适用于有关联关系的持久化类 |
迫切内连接 | inner join fetch | join fetch | |
隐式内连接 | ||
左外连接 | left outer join | left join | |
迫切左外连接 | left outer join fetch | left join fetch | |
右外连接 | right outer join | outer join | |
交叉连接 | ClassA ClassB | 适用于不存在关联关系的持久化类 |
1.交叉连接 ,等效 sql 笛卡尔积
//交叉连接
@Test
public void test1(){
Session session = HibernateUtils.openSession();
//HQL的交叉连接等效sql的笛卡尔积
List<Object[]> list = session.createQuery("from Customer c ,Order o ").list();
for (Object[] objects : list) {
System.out.println("客户:"+objects[0]+" 订单:"+objects[1]);
}
session.close();
}
左边客户表的每一条记录都与右边订单表的每一条记录匹配,有很多不符合条件的数据,下面会使用隐式内连接过滤掉。
2.隐式内连接,等效 sql 隐式内连接
//隐式内连接【join】
@Test
public void test2(){
Session session = HibernateUtils.openSession();
//隐式内连接相当于在笛卡尔积上加了过滤条件
List<Object[]> list = session.createQuery("from Customer c ,Order o where c = o.customer").list();
for (Object[] objects : list) {
System.out.println("客户:"+objects[0]+" 订单:"+objects[1]);
}
session.close();
}
3.内连接,等效sql内连接
//内连接【inner join】
@Test
public void test3(){
Session session = HibernateUtils.openSession();
//显示内连接(非迫切)
//非迫切:返回的数据将客户和订单对象封装到数组 Object[] objects = {Customer,Order}
List<Object[]> list = session.createQuery("from Customer c inner join c.orders").list();
for (Object[] objects : list) {
System.out.println("客户:"+objects[0]+" 订单:"+objects[1]);
}
session.close();
}
4.迫切内连接,hibernate底层使用内连接
//迫切内连接【inner join fetch】
@Test
public void test4(){
Session session = HibernateUtils.openSession();
//显示内连接(迫切)
//迫切:返回的数据封装到List中的客户对象中 List<Customer>
List<Customer> list = session.createQuery("from Customer c inner join fetch c.orders").list();
for (Customer customer : list) {
System.out.println(customer);
}
session.close();
}
注意:此处Customer中生成的toString可以包含orders,或者可以直接customer.getOrders()获取封装到customer的订单信息。但是Order中就不要生成包含customer的toString,否则死循环报错。
内连接和迫切内连接内部使用的都是inner join sql语句
它们的区别是有没有使用fetch和返回的数据最终封装到数组中还是对象中,当然它们都要先封装到集合中。
5.左外连接,等效sql左外连接
//左外连接【left outer join】
@Test
public void test5(){
Session session = HibernateUtils.openSession();
//左外连接:左表(客户)中如果没有右表(订单)的匹配数据,左表记录也会显示。
List<Object[]> list = session.createQuery("from Customer c left outer join c.orders").list();
for (Object[] objects : list) {
System.out.println("客户:"+objects[0]+" 订单:"+objects[1]);
}
session.close();
}
6.内连接与左外连接的区别
左外连接和内连接的区别:内连接左边与右边一定要相等(有交集),必须要能匹配到否则不显示;左外连接不相等也显示。
- 例如:添加一个客户shu3,而不添加对应的订单信息
- 执行内连接,还是6条记录,因为没有客户shu3匹配的订单
- 执行左外连接,7条记录,没有客户shu3的订单也显示它
7.迫切左外连接,hibernate底层使用左外连接
//迫切左外连接【left outer join fetch】
@Test
public void test6(){
Session session = HibernateUtils.openSession();
//也是封装成一个对象
List<Customer> list = session.createQuery("from Customer c left outer join fetch c.orders").list();
for (Customer customer : list) {
System.out.println("客户:"+customer+" 订单:"+customer.getOrders());
}
session.close();
}
8.右外连接(迫切),等效sql右外连接
//右外连接(迫切)【right outer join (fetch)】
@Test
public void test7(){
Session session = HibernateUtils.openSession();
//右外连接:右表(订单)中如果没有左表(客户)的匹配数据,右表记录也会显示。
List<Object[]> list = session.createQuery("from Customer c right outer join c.orders").list();
for (Object[] objects : list) {
System.out.println("客户:"+objects[0]+" 订单:"+objects[1]);
}
session.close();
}
- 添加一个没有客户的订单信息
- 使用右外连接(迫切)也能查询到
注意:使用迫切右外连接的时候注意防止空指针异常,比如customer为null,使用customer.getOrders()会报空指针异常错误,也可以在Customer中直接把orders加入toString,但是在Order中toString就不能加customer注意toString死循环。
1.9 案例8:命名查询
- HQL语句写在java文件里编译后会变成.class文件无法修改,如果web项目打成war包或项目上线后要修改hql语句,要重新编译项目打包
- 我们可以在hibernate映射文件hbm.xml中命名查询语句,然后使用java从hbm.xml取出hql语句,这样,以后的开发,可以直接在hbm.xml中配置,使得hql灵活使用。
- 通过查看hibernate-mapping-3.0.dtd文件,可以知道query放的位置
//命名查询:把hql写在映射文件中,通过session的getNameQuery来返回一个Query对象
@Test
public void test1(){
Session session = HibernateUtils.openSession();
//把hql写在映射文件中,会比较灵活(web项目打成war包后或项目上线后也可以修改hql)
//第一种:写在类class标签里,必须要写在末尾,这种hql是局部的,(找局部的hql需要提供包名)
Query query1 = session.getNamedQuery("com.it.hibernate.domain.Customer.hql1");
List<Object> list = query1.list();
for (Object o : list) {
System.out.println(o);
}
System.out.println("------------------");
//第二种:写在hibernate-mapping里,最好写在末尾,这种hql是全局的,(找全局的hql不需要提供包名)
Query query2 = session.getNamedQuery("hql2");
query2.setParameter(0,1);
System.out.println(query2.list());
session.close();
}
第二节 QBC查询
2.1 QBC简介
- QBC(query by criteria)条件查询,hibernate提供的纯面向对象查询语言,提供直接使用PO对象进行操作。
- 上面还要写少量的hql,使用criteria将可以完全不使用sql。
一些概念:
PO:persistent object ,用于与数据库交互数据。–dao层 (JavaBean + hbm )
BO:Business object 业务数据对象。–service层
VO:Value Object 值对象。–web层
2.2 QBC查询所有客户
//qbc简单查询(查询所有客户信息)
@Test
public void test1(){
Session session = HibernateUtils.openSession();
Criteria criteria = session.createCriteria(Customer.class);
List<Customer> list = criteria.list();
for (Customer customer : list) {
System.out.println(customer);
}
session.close();
}
2.3 QBC分页
//qbc分页
@Test
public void test2(){
Session session = HibernateUtils.openSession();
Criteria criteria = session.createCriteria(Order.class);
criteria.setFirstResult(0);//第一页0,第二页3,第三页6
criteria.setMaxResults(3);//每页条数3
List<Order> list = criteria.list();
for (Order order : list) {
System.out.println(order);
}
session.close();
}
2.4 QBC排序
//qbc排序
@Test
public void test3(){
Session session = HibernateUtils.openSession();
Criteria criteria = session.createCriteria(Order.class);
//这里addOrder不是添加订单的意思,是排序的意思,
//还要找到addOrder里Order排序的包名是 org.hibernate.criterion
criteria.addOrder(org.hibernate.criterion.Order.desc("id"));//降序 desc
//criteria.addOrder(org.hibernate.criterion.Order.asc("id"));//升序 asc
List<Order> list = criteria.list();
for (Order order : list) {
System.out.println(order);
}
session.close();
}
- 根据id降序展示
2.5 QBC条件查询
//qbc条件查询
@Test
public void test4(){
Session session = HibernateUtils.openSession();
Criteria criteria = session.createCriteria(Order.class);
//1.模糊查询 like
//criteria.add(Restrictions.like("orderName","%8%"));//订单名包含8的
//criteria.add(Restrictions.like("orderName","iphone%"));//订单名以 iphone开头的
//criteria.add(Restrictions.like("orderName","%6"));//订单名以 6结尾的
//criteria.add(Restrictions.like("orderName","_p%"));//订单名第2位是p的
//...
//2.【 eq = 】【 ge >= 】【 gt > 】
//【 lt < 】【 le <= 】 要查看更多可按住 Ctrl 点 Restrictions
criteria.add(Restrictions.gt("id",4));//查看id大于4的订单
//上面的条件可以结合查询,可以criteria.add多条组合
List<Order> list = criteria.list();
for (Order order : list) {
System.out.println(order);
}
session.close();
}
- 查询id值大于4的订单
2.6 离线查询【了解】
- DetachedCriteria离线查询对象,不需要使用Session就可以拼凑查询条件。一般使用在web层或service层拼凑。将此对象传递给dao层,此时将与session进行绑定执行查询。
//qbc离线查询
@Test
public void test5(){
//查询名字shu的客户
//离线查询放在service/web层======================================================
//拼凑查询条件【离线查询对象】
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Customer.class);
detachedCriteria.add(Restrictions.eq("customerName","shu"));
//原来放在dao层======================================================
Session session = HibernateUtils.openSession();
//Criteria criteria = session.createCriteria(Customer.class);
//criteria.add(Restrictions.eq("customerName","shu"));
Criteria criteria = detachedCriteria.getExecutableCriteria(session);//要与session绑定
List<Customer> list = criteria.list();
for (Customer customer : list) {
System.out.println(customer);
}
session.close();
}
第三节 hibernate常见配置
3.1 整合c3p0连接池
- 虽然前面操作数据库是使用session,但是hibernate的本质还是封装jdbc,所以底层还是使用connection,所以要使用连接池。
第一步:导入c3p0的jar包
- hibernate框架中提供了c3p0的jar包
- 将它复制到你为hibernate项目放jar包的文件夹中
- 打开IDEA的Project Structure
- 添加jar
- 导入成功
第二步:配置c3p0
- 找到这个hibernate配置文件中c3p0的配置
- 将以下选项配置到hibernate.cfg.xml文件中
- hibernate.cfg.xml中添加
<!--配置c3p0-->
<!--
#hibernate.connection.provider_class org.hibernate.connection.C3P0ConnectionProvider
//c3p0提供者
#hibernate.c3p0.max_size 2 //最大连接数,同时最多得到的connection数
#hibernate.c3p0.min_size 2 //最小连接数
#hibernate.c3p0.timeout 5000 //连接超时时间5000毫秒(5秒)
#hibernate.c3p0.max_statements 100 //最多可以创建Statements对象的个数,就是可以执行SQL语句的对象的个数
#hibernate.c3p0.idle_test_period 3000 //检查连接池中所有空闲连接的间隔时间,单位为秒
#hibernate.c3p0.acquire_increment 2 //连接池中连接耗尽了一次性增加2个
#hibernate.c3p0.validate false //每次都验证连接是否可用
-->
<property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
<property name="hibernate.c3p0.max_size">2</property>
<property name="hibernate.c3p0.min_size">2</property>
<property name="hibernate.c3p0.timeout">5000</property>
<property name="hibernate.c3p0.max_statements">100</property>
<property name="hibernate.c3p0.idle_test_period">3000</property>
<property name="hibernate.c3p0.acquire_increment">2</property>
<property name="hibernate.c3p0.validate">false</property>
第三步:测试
- 随便执行一个使用到session的程序
- 如下图,初始化c3p0连接池,配置成功
第四节 事务的隔离级别
- 为什么有事务的隔离级别?
- 解决多线程访问数据库的问题。
- 一组业务操作,要么全部成功,要么全部不成功。
特性:ACID
- 原子性:整体 【原子性是指事务包含的所有操作要么全部成功,要么全部失败】
- 一致性:数据 【一个事务执行之前和执行之后都必须处于一致性状态】
- 隔离性:并发 【对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。】
- 持久性:结果 【持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的】
隔离问题
-
脏读:一个事务读到另一个事务未提交的内容【读取未提交内容】
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其它级别好多少。 -
不可重复读:一个事务读到另一个事务已提交的内容(insert)【读取提交内容】
这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。 -
虚读(幻读):一个事务读到另一个事务已提交的内容(update)
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。 -
Serializable(可串行化)
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
隔离级别–解决问题
- read uncommittd,读未提交。存在3个问题。
- read committed,读已提交。解决:脏读。存在2个问题。
- repeatable read ,可重复读。解决:脏读、不可重复读。存在1个问题。
- serializable,串行化。单事务。没有问题。
Hibernate中设置隔离级别
- 在hibernate.cfg.xml 配置hibernate.connection.isolation