①Hibernate是什么
Hibernate就是一个持久层的ORM框架,对JDBC进行了轻量级的封装。
ORM : Object Relational Mapping 对象关系映射,描述对象和数据库之间映射的元数据,自动把Java引用程序中的对象,持久化到关系型数据库的表中。通过操作Java对象可以,可以实现对数据库表的操作。可以理解为中间的一个纽带
②Hibernate环境的搭建
Hibernate快速入门
下载:https://sourceforge.net/projects/hibernate/files/latest/download
目录结构:
documentation:Hibernate相关文档
lib:Hibernate编译运行所需要的jar包,required下包含的是运行Hibernate5所必须的jar包
project:存放Hibernate各种相关的源代码
创建数据库表
导入jar包:mysql+required文件夹下的jar包。 1+9
创建实体:这里指类的对象能够持久化到数据库中,Hibernate使用的普通Java对象。
创建映射文件:数据库→ ←对象 Customer.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">
<!-- 配置表与实体对象的关系 -->
<!-- package属性:填写一个包名.在元素内部凡是需要书写完整类名的属性,可以直接写简答类名了. -->
<hibernate-mapping package="cn.pb.domain" >
<!--
class元素: 配置实体与表的对应关系的
name: 完整类名
table:数据库表名
-->
<class name="Customer" table="cst_customer" >
<!-- id元素:配置主键映射的属性
name: 填写主键对应属性名
column(可选): 填写表中的主键列名.默认值:列名会默认使用属性名
type(可选):填写列(属性)的类型.hibernate会自动检测实体的属性类型.
每个类型有三种填法: java类型|hibernate类型|数据库类型
not-null(可选):配置该属性(列)是否不能为空. 默认值:false
length(可选):配置数据库中列的长度. 默认值:使用数据库类型的最大长度
-->
<id name="cust_id" >
<!-- generator:主键生成策略(明天讲) -->
<generator class="native"></generator>
</id>
<!-- property元素:除id之外的普通属性映射
name: 填写属性名
column(可选): 填写列名
type(可选):填写列(属性)的类型.hibernate会自动检测实体的属性类型.
每个类型有三种填法: java类型|hibernate类型|数据库类型
not-null(可选):配置该属性(列)是否不能为空. 默认值:false
length(可选):配置数据库中列的长度. 默认值:使用数据库类型的最大长度
-->
<property name="cust_name" column="cust_name" >
<!-- <column name="cust_name" sql-type="varchar" ></column> -->
</property>
<property name="cust_source" column="cust_source" ></property>
<property name="cust_industry" column="cust_industry" ></property>
<property name="cust_level" column="cust_level" ></property>
<property name="cust_linkman" column="cust_linkman" ></property>
<property name="cust_phone" column="cust_phone" ></property>
<property name="cust_mobile" column="cust_mobile" ></property>
</class>
</hibernate-mapping>
创建核心配置文件 hibernate.cfg.xml
<?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>
<!--
#hibernate.dialect org.hibernate.dialect.MySQLDialect
#hibernate.dialect org.hibernate.dialect.MySQLInnoDBDialect
#hibernate.dialect org.hibernate.dialect.MySQLMyISAMDialect
#hibernate.connection.driver_class com.mysql.jdbc.Driver
#hibernate.connection.url jdbc:mysql:///test
#hibernate.connection.username gavin
#hibernate.connection.password
-->
<!-- 数据库驱动 -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<!-- 数据库url -->
<property name="hibernate.connection.url">jdbc:mysql:///hibernate_32</property>
<!-- 数据库连接用户名 -->
<property name="hibernate.connection.username">root</property>
<!-- 数据库连接密码 -->
<property name="hibernate.connection.password">1234</property>
<!-- 数据库方言
不同的数据库中,sql语法略有区别. 指定方言可以让hibernate框架在生成sql语句时.针对数据库的方言生成.
sql99标准: DDL 定义语言 库表的增删改查
DCL 控制语言 事务 权限
DML 操纵语言 增删改查
注意: MYSQL在选择方言时,请选择最短的方言.
-->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- #hibernate.show_sql true
#hibernate.format_sql true
-->
<!-- 将hibernate生成的sql语句打印到控制台 -->
<property name="hibernate.show_sql">true</property>
<!-- 将hibernate生成的sql语句格式化(语法缩进) -->
<property name="hibernate.format_sql">true</property>
<!--
## auto schema export 自动导出表结构. 自动建表
#hibernate.hbm2ddl.auto create 自动建表.每次框架运行都会创建新的表.以前表将会被覆盖,表数据会丢失.(开发环境中测试使用)
#hibernate.hbm2ddl.auto create-drop 自动建表.每次框架运行结束都会将所有表删除.(开发环境中测试使用)
#hibernate.hbm2ddl.auto update(推荐使用) 自动生成表.如果已经存在不会再生成.如果表有变动.自动更新表(不会删除任何数据).
#hibernate.hbm2ddl.auto validate 校验.不自动生成表.每次启动会校验数据库中表是否正确.校验失败.
-->
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- 引入orm元数据
路径书写: 填写src下的路径
-->
<mapping resource="cn/pb/domain/Customer.hbm.xml" />
</session-factory>
</hibernate-configuration>
编写测试类:
package cn.pb.a_hello;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.Test;
import cn.pb.domain.Customer;
//测试Hibernate框架
public class Demo {
@Test
//保存客户
public void fun1(){
//加载配置文件中的信息,默认是src下的hibernate.cfg.xml,当然也可以有带参构造,完成指定路径的初始化
Configuration conf = new Configuration().configure();
//SessionFactory 相当于一个缓冲区,负责初始化,建立session,他是线程安全的,内部还维护了一个连接池
SessionFactory sessionFactory = conf.buildSessionFactory();
//session是应用程序和数据库交互的一个单线程对象,获取方法除了↓getCurrentSession()获得的session会与线程绑定,他是线程不安全的
Session session = sessionFactory.openSession();
//Transaction 主要用来管理事务 commit rollback
Transaction tx = session.beginTransaction();
Customer c = new Customer();
c.setCust_name("google公司");
session.save(c);//执行保存
tx.commit();
session.close();
sessionFactory.close();
}
}
③Hibernate中的实体规则
持久化类:Hibernate是持久层的 ORM映射框架,专注于数据持久化的工作,所谓的持久化就是将没存中数据永久的存储在关系型数据库中
持久化类的编写原则:
①必须提供无参构造
②属性私有,对私有的属性提供共有的get和set方法
③持久化类的属性要尽量使用包装类类型
④持久化类要有一个唯一表示OID与表的主键对应
⑤持久化类尽量不要使用final修饰:Hiernate有延迟加载机制,在这个机制中会产生代理对象,Hibernate中的代理对象是使用字节码增强技术来实现,其中的方法是需要具体实现的。如果使用了final修饰持久化类,那么就不能产生子类。
主键生成策略:
自然主键:将具有业务含义的字段作为主键
代理主键:把不具有业务含义的字段所谓主键
1)increment:用于long、short、int类型,由Hibernate自动以递增的方式生成唯一标识,每次增量为1。只有当没有其它进程向同一张表中插入数据时才可以使用,不能再集群环境下使用。适用于代理主键
2)identity:采用底层数据库本身提供的主键成成标识符,前提是数据库支持自动增长的数据类型,适用于代理主键
3)sequence:Hibernate根据底层数据库序列生成标识符。前提数据库支持序列,如Oracle,适用于代理主键
4)native:根据底层数据库对自动生成标识符能力来选择identity、sequence、hilo三种生成方式中的一种,适合跨数据平台开发,适用于代理主键
5)uuid:Hibernate使用128位的UUID算法生成标识符。概算可以在网络环境下生成一个32位十六进制的字符串唯一的字符串标识,但因为字符串比整数类型占用太多的数据库空间,适用于代理主键
6)由Java程序来生成标识符,不指定id元素的generator属性,则默认使用该主键生成策略。适用于自然主键。
④Hibernate中的对象状态
Hibernate持久化对象的三种状态
1)瞬时态(transient):
也被称为临时态或者自由态→使用new创建,开辟内存空间。但是不存在持久化标识OID(相当于主键值),尚未与Hibernate Session关联,在数据库中没有记录,失去引用后被JVM回收。 信息携带的载体
2)持久态(persistent):
持久态的对象存在持久化标识OID,加入到了session缓存中,并且相关联的session没有关闭,在数据库中没有对应的记录。持久态对象是事务还未提交前变为持久态的。
3)托管态(detached)
也称为离线态或游离态。当某个持久化状态的实例与session的关联被关闭就变成了托管态。托管态存在持久化标识OID,并且任与数据库中的数据存在关联,只是失去当前的session关联。托管状态发生改变时,Hibernate不能检测到
⑤Hibernate的一级缓存
缓存:提高效率.hibernate中的一级缓存也是为了提高操作数据库的效率.
提高效率手段1:提高查询效率
提高效率手段2:减少不必要的修改语句发送
⑥Hibernate中的事务
事务的特性:a(原子性) c(一致性) i(隔离性) d(持久性)
事务的并发问题: 1、脏读 2、不可重复读(update) 3、幻读/虚读(insert)
事务的隔离级别:读未提交、读已提交(Oracle默认)、可重复读(MySQL默认)、串行化
如何在hibernate中指定数据库的隔离级别
在项目中如何管理事务
1)业务开始之前打开事务,业务执行之后提交事务. 执行过程中出现异常.回滚事务.
2)在dao层操作数据库需要用到session对象.在service控制事务也是使用session对象完成. 我们要确保dao层和service层使用的使用同一个session对象,用sf.getCurrentSession()方法即可获得与当前线程绑定的session对象
注:调用getCurrentSession方法必须配合主配置中的一段配置。其实就是将session和线程进行了绑定
⑦Hibernate中的批量查询
1)HQL查询-hibernate Query Language(不复杂时使用)
基本查询
条件查询
?号占位符
命名占位符
分页查询
2)Criteria查询(单表条件查询):Hibernate自创的无语句面向对象查询
基本查询
条件查询
分页查询
查询总记录数
3)原生SQL查询(复杂的业务查询)
基本查询
返回数组List
返回对象List
条件查询
分页查询
自己补充的一个查询方法
hibernate命名查询的配置
①在对应的实体如user.hbm.xml中进行配置
*class标签外,</hibernate-mapping>标签内
<query name="user.editPassword">
UPDATE TUser SET password=? WHERE id=?
</query>
②方法设置
public void executeUpdate(String queryName,Object... objects){
Session session = this.getSessionFactory().openSession();
Query query = session.getNamedQuery("user.editPassword");
int i=0;
for (Object object : objects) {
query.setParameter(i++, object);
}
query.executeUpdate();
}
③方法调用
userDao.executeUpdate("user.editPassword",password,id);
⑧Hibernate中的关联关系
1)一对多 | 多对一
关系表达
表中的表达:通过外键的形式来表达
实体中的表达
orm元数据中的表达
操作
@Test
//保存客户 以及客户 下的联系人
public void fun1(){
//1 获得session
Session session = HibernateUtils.openSession();
//2 开启事务
Transaction tx = session.beginTransaction();
//3操作
Customer c = new Customer();
c.setCust_name("刘强东");
LinkMan lm1 = new LinkMan();
lm1.setLkm_name("张三");
LinkMan lm2 = new LinkMan();
lm2.setLkm_name("李四");
//表达一对多,客户下有多个联系人
c.getLinkMens().add(lm1);
c.getLinkMens().add(lm2);
//表达对对对,联系人属于哪个客户
lm1.setCustomer(c);
lm2.setCustomer(c);
session.save(c);
session.save(lm1);
session.save(lm2);
//4提交事务
tx.commit();
//5关闭资源
session.close();
}
级联操作:最好用save-update,不建议使用delete.
关系维护:在保存时.两方都会维护外键关系.关系维护两次,冗余了. 多余的维护关系语句,显然是客户这一端在维护关系
2)多对多
关系表达
表中的表达:通过第三张表的外键引用另外两张表的主键
对象中的表达:
orm源数据的表达
操作
@Test
//保存员工以及角色
public void fun1(){
//1 获得session
Session session = HibernateUtils.openSession();
//2 开启事务
Transaction tx = session.beginTransaction();
//-------------------------------------------------
//3操作
//1> 创建两个 User
User u1 = new User();
u1.setUser_name("张三");
User u2 = new User();
u2.setUser_name("李四");
//2> 创建两个 Role
Role r1 = new Role();
r1.setRole_name("保洁");
Role r2 = new Role();
r2.setRole_name("保安");
//3> 用户表达关系
u1.getRoles().add(r1);
u1.getRoles().add(r2);
u2.getRoles().add(r1);
u2.getRoles().add(r2);
//4> 角色表达关系
r1.getUsers().add(u1);
r1.getUsers().add(u2);
r2.getUsers().add(u1);
r2.getUsers().add(u2);
//5> 调用Save方法一次保存
session.save(u1);
session.save(u2);
session.save(r1);
session.save(r2);
//-------------------------------------------------
//4提交事务
tx.commit();
//5关闭资源
session.close();
}
级联操作
关系维护
或者以面向对象进行属性关系设置的时候,减少一方的设置
⑨查询补充
HQL语法
1)基础语法
String hql = " from cn.pb.Customer ";//完整写法
String hql2 = " from Customer "; //简单写法
String hql3 = " from java.lang.Object ";
2)排序
String hql1 = " from com.pb.Customer order by cust_id asc ";//完整写法
String hql2 = " from com.pb.Customer order by cust_id desc ";//完整写法
3)条件
String hql1 = " from cn.pb.domain.Customer where cust_id =? ";//完整写法
String hql2 = " from cn.pb.domain.Customer where cust_id = :id ";//完整写法
Query query = session.createQuery(hql2);
query.setParameter("id", 2l);
4)分页
String hql1 = " from cn.pb.domain.Customer ";//完整写法
Query query = session.createQuery(hql1);
// (当前页数-1)*每页条数
query.setFirstResult(2);
query.setMaxResults(2);
5)聚合
String hql1 = " select count(*) from cn.pb.domain.Customer ";//完整写法
String hql2 = " select sum(cust_id) from cn.pb.domain.Customer ";//完整写法
String hql3 = " select avg(cust_id) from cn.pb.domain.Customer ";//完整写法
String hql4 = " select max(cust_id) from cn.pb.domain.Customer ";//完整写法
String hql5 = " select min(cust_id) from cn.pb.domain.Customer ";//完整写法
Query query = session.createQuery(hql5);
Number number = (Number) query.uniqueResult();
6)投影
String hql1 = " select cust_name from cn.pb.domain.Customer ";
String hql2 = " select cust_name,cust_id from cn.pb.domain.Customer ";
String hql3 = " select new Customer(cust_id,cust_name) from cn.pb.domain.Customer ";
7)多表查询
//HQL 内连接 => 将连接的两端对象分别返回.放到数组中.
String hql = " from Customer c inner join c.linkMens ";
Query query = session.createQuery(hql);
List<Object[]> list = query.list();
//HQL 迫切内连接 => 帮我们进行封装.返回值就是一个对象
String hql = " from Customer c inner join fetch c.linkMens ";
Query query = session.createQuery(hql);
List<Customer> list = query.list();
//HQL 左外连接 => 将连接的两端对象分别返回.放到数组中.
String hql = " from Customer c left join c.linkMens ";
Query query = session.createQuery(hql);
List<Object[]> list = query.list();
//HQL 右外连接 => 将连接的两端对象分别返回.放到数组中
String hql = " from Customer c right join c.linkMens ";
Criteria语法
1)基本
Criteria c = session.createCriteria(Customer.class);
List<Customer> list = c.list();
2)条件
Criteria c = session.createCriteria(Customer.class);
// c.add(Restrictions.idEq(2l));
c.add(Restrictions.eq("cust_id",2l));
3)分页
Criteria c = session.createCriteria(Customer.class);
//limit ?,?
c.setFirstResult(0);
c.setMaxResults(2);
4)排序
Criteria c = session.createCriteria(Customer.class);
c.addOrder(Order.asc("cust_id"));
//c.addOrder(Order.desc("cust_id"));
5)统计
Criteria c = session.createCriteria(Customer.class);
//设置查询目标
c.setProjection(Projections.rowCount());
离线查询
@Test
public void fun1(){
//Service/web层
DetachedCriteria dc = DetachedCriteria.forClass(Customer.class);
dc.add(Restrictions.idEq(6l));//拼装条件(全部与普通Criteria一致)
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
Criteria c = dc.getExecutableCriteria(session);
List list = c.list();
System.out.println(list);
tx.commit();
session.close();
}
查询优化
抓取策略:应用程序需要在关联关系之间进行导航的时候,hibernate如何获取对象的策略,适当的选择抓取策略可提高性能
懒加载:关联关系对象的默认加载方式
类级别延迟加载 (class)
get方法:没有任何策略.调用即立即查询数据库加载数据.
load方法: 应用类级别的加载策略
关联级别延迟加载
注:如果fetch设置为join,lazy就失效了。
结论:为了提高效率.fetch的选择上应选择select. lazy的取值应选择 true. 全部使用默认值.
session失效问题:问题的引入是由于fetch和lazy默认属性致使查询性能低下,在必要时可以通过servlet的session来获取查询结果
批量抓取:减少查询语句,每次将一定数量的结果放入缓存中
一个客户有两个联系人,不加下面这句,查询的时候出现三条查询语句。加了之后之后出现两条查询语句,联系人直接使用limit查询出来了
补充:Hibernate离线查询
场景:区域(进行划分形成了分区) 分区 定区(中间表定义分区和取派员之间的联系)
现在要对分区进行条件查询。条件有省市区,这些数据在区域中的,这里要进行类似别名的离线查询。
web层的封装
public String pageQuery() throws IOException{
pageBean.setPage(page);
pageBean.setPageSize(rows);
String addresskey = model.getAddresskey();
if(StringUtils.isNotBlank(addresskey)){
detachedCriteria.add(Restrictions.like("addresskey", "%"+addresskey+"%"));
}
Region region = model.getRegion();
if(region!=null){
String province = region.getProvince();
String city = region.getCity();
String district = region.getDistrict();
detachedCriteria.createAlias("region", "r");
if(StringUtils.isNotBlank(province)){
detachedCriteria.add(Restrictions.like("r.province", "%"+province+"%"));
}
if(StringUtils.isNotBlank(city)){
detachedCriteria.add(Restrictions.like("r.city", "%"+city+"%"));
}
if(StringUtils.isNotBlank(district)){
detachedCriteria.add(Restrictions.like("r.district", "%"+district+"%"));
}
}
subareaService.pageQuery(pageBean);
ServletActionContext.getResponse().setContentType("text/html;charset=utf-8");
ServletActionContext.getResponse().getWriter().print(this.objectToJson(pageBean, excludesItem));
return NONE;
}
dao层的改变
@Override
public void findByPageBean(PageBean pageBean) {
Integer currentPage = pageBean.getPage();
Integer pageSize = pageBean.getPageSize();
DetachedCriteria criteria = pageBean.getDetachedCriteria();
criteria.setProjection(Projections.rowCount());
List<Long> countList = (List<Long>) this.getHibernateTemplate().findByCriteria(criteria);
pageBean.setTotal(countList.get(0));
criteria.setProjection(null);
criteria.setResultTransformer(DetachedCriteria.ROOT_ENTITY);
List<Staff> rows = (List<Staff>) this.getHibernateTemplate().findByCriteria(criteria,(currentPage-1)*pageSize, pageSize);
pageBean.setRows(rows);
}
这样就可以实现关联条件查询。
补充:关联查询补充:根据外键查,criteria的语法
DetachedCriteria criteria = DetachedCriteria.forClass(Subarea.class);
criteria.add(Restrictions.eq("decidedzone.id", decidedzoneid));
补充:hql实现通过用户来查询权限信息
select distinct f from Function f left outer join f.roles r
left outer join r.users u where u.id=?