标签:构建 obj 详细介绍 组件 utils 延迟加载 而且 内存 api
Hibernate是持久层的ORM映射框架,专注于数据的持久化工作。所谓的持久化,就是将内存中的数据永久存储到关系型数据库中。那么知道了什么是持久化,什么又是持久化类呢?其实所谓的持久化类指的是一个Java类与数据库表建立了映射关系,那么这个类称为是持久化类。其实你可以简单的理解为持久化类就是一个Java类有了一个映射文件与数据库的表建立了关系。那么我们在编写持久化类的时候有哪些要求呢?接下来我们来看一下:
我们在编写持久化类的时候需要有以下几点需要注意:
举个例子:
假设表中有一列员工工资,如果使用double类型,如果这个员工工资忘记录入到系统中,系统会将默认值0存入到数据库,如果这个员工工资被扣完了,也会向系统中存入0.那么这个0就有了多重含义,而如果使用包装类类型就会避免以上情况,如果使用Double类型,忘记录入工资就会存入null,而这个员工工资被扣完了,就会存入0,不会产生歧义。
l 持久化类要有一个唯一标识OID与表的主键对应。因为Hibernate中需要通过这个唯一标识OID区分在内存中是否是同一个持久化类。在Java中通过地址区分是否是同一个对象的,在关系型数据库的表中是通过主键区分是否同一条记录。那么Hibernate就是通过这个OID来进行区分的。Hibernate是不允许在内存中出现两个OID相同的持久化对象的。
l 持久化类尽量不要使用final进行修饰。因为Hibernate中有延迟加载的机制,这个机制中会产生代理对象,Hibernate产生代理对象使用的是字节码的增强技术完成的,其实就是产生了当前类的一个子类对象实现的。如果使用了final修饰持久化类。那么就不能产生子类,从而就不会产生代理对象,那么Hibernate的延迟加载策略(是一种优化手段)就会失效。.
持久化类我们已经可以正常编写了,但是在持久化类中需要有一个唯一标识OID与表的主键去建立映射关系。而且主键一般我们是不会让客户手动录入的,一般我们是由程序生成主键。那么Hibernate中也提供了相应的主键生成的方式,那么我们来看下Hibernate的主键生成策略。
l 简单的说:
我们的实体类都需要遵从JavaBean的编写规范。
什么是JavaBean:
Bean:在软件开发领域,Bean表示可重用组件。
JavaBean就是用java语言开发的可重用组件。
JavaBean的编写规范是什么:
类都是public的
都有默认无参构造函数
成员变量都是私有的
都有公有的get/set方法
一般都实现Serializable接口。
基本类型和包装类的选择问题:
由于包装类可以有null值。所以实际开发中都是用包装类。
OID全称是Object Identifier,又叫做对象标识符。
它是hibernate用于区分两个对象是否是同一个对象的标识。
我们都知道,虚拟机内存区分两个对象看的是内存的地址是否一致。数据库区分两个对象,靠的是表的主键。hibernate负责把内存中的对象持久化到数据库表中,靠的就是对象标识符来区分两个对象是否是同一个。实体类中映射主键的字段就是OID,如下图所示:
在讲解Hibernate的主键生成策略之前,先来了解两个概念,即自然主键和代理主键,具体如下:
Hibernate的一级缓存就是指Session缓存,Session缓存是一块内存空间,用来存放相互管理的java对象,在使用Hibernate查询对象的时候,首先会使用对象属性的OID值在Hibernate的一级缓存中进行查找,如果找到匹配OID值的对象,就直接将该对象从一级缓存中取出使用,不会再查询数据库;如果没有找到相同OID值的对象,则会去数据库中查找相应数据。当从数据库中查询到所需数据时,该数据信息也会放置到一级缓存中。Hibernate的一级缓存的作用就是减少对数据库的访问次数。
在 Session 接口的实现中包含一系列的 Java 集合, 这些 Java 集合构成了 Session 缓存。只要 Session 实例没有结束生命周期,存放在它缓存中的对象也不会结束生命周期。固一级缓存也被称为是Session基本的缓存。
Hibernate的一级缓存有如下特点:
@Test
// 证明Hibernate的一级缓存的存在:
public void demo1(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
Customer customer1 = session.get(Customer.class, 1l);// 马上发生一条sql查询1号客户.并将数据存入了一级缓存
System.out.println(customer1);
Customer customer2 = session.get(Customer.class, 1l);// 没有发生SQL语句从一级缓存中获取数据.
System.out.println(customer2);
System.out.println(customer1 == customer2);// true 一级缓存缓存的是对象的地址.
tx.commit();
session.close();
}
Hibernate 向一级缓存放入数据时,同时复制一份数据放入到Hibernate快照中,当使用commit()方法提交事务时,同时会清理Session的一级缓存,这时会使用OID判断一级缓存中的对象和快照中的对象是否一致,如果两个对象中的属性发生变化,则执行update语句,将缓存的内容同步到数据库,并更新快照;如果一致,则不执行update语句。Hibernate快照的作用就是确保一级缓存中的数据和数据库中的数据一致。
数据库中的数据:
程序代码:
@Test
public void test2(){
Customer c2 = null;//内存中的一个对象
//1.获取Session对象
Session s = HibernateUtil.openSession();
//2.使用Session对象开启事务
Transaction tx = s.beginTransaction();
//3.执行查询:查询ID为2的客户对象
c2 = s.get(Customer.class, 2L);
System.out.println(c2);//custName:黑马训练营
//修改客户名称为:修正大厦
c2.setCustName("修正大厦");
System.out.println(c2);//custName:修正大厦
//4.提交事务
tx.commit();
//5.关闭Session
s.close();
//当程序走到此处时,一级缓存就没了,但是并不影响我继续使用客户对象
System.out.println(c2);//custName到底是什么
}
问题:
custName输出的到底是什么?
分析:
如果是黑马训练营,则表示我们修改的代码没启任何作用,废代码一行。
如果输出是修正大厦,则表示我们程序内存的数据可能和数据库表中的不一致了,那就是脏数据。
思考:
有没有可能输出的是修正大厦,并且数据库的数据也变成了修正大厦呢?
如果真的发生了这种情况,是如何做到的呢?
答案:
hibernate的快照机制。原理如下图:
了解了主键的生成策略之后,我们可以进一步来了解持久化类了。Hibernate为了更好的来管理持久化类,特将持久化类分成了三种状态。在Hibernate中持久化的对象可以划分为三种状态,分别是瞬时态、持久态和脱管态,一个持久化类的实例可能处于三种不同状态中的某一种,三种状态的详细介绍如下。
1、 瞬时态(transient)
瞬时态也称为临时态或者自由态,瞬时态的实例是由new命令创建、开辟内存空间的对象,不存在持久化标识OID(相当于主键值),尚未与Hibernate Session关联,在数据库中也没有记录,失去引用后将被JVM回收。瞬时状态的对象在内存中是孤立存在的,与数据库中的数据无任何关联,仅是一个信息携带的载体。
2、 持久态(persistent)
持久态的对象存在持久化标识OID ,加入到了Session缓存中,并且相关联的Session没有关闭,在数据库中有对应的记录,每条记录只对应唯一的持久化对象,需要注意的是,持久态对象是在事务还未提交前变成持久态的。
3、 脱管态(detached)
脱管态也称离线态或者游离态,当某个持久化状态的实例与Session的关联被关闭时就变成了脱管态。脱管态对象存在持久化标识OID,并且仍然与数据库中的数据存在关联,只是失去了与当前Session的关联,脱管状态对象发生改变时Hibernate不能检测到。
a、是为了更好的掌握hibernate中操作的方法。
b、区分状态只有两个标识
一是否有OID
二是否和Session建立的关系
临时状态:
没有OID,和Session没有关系。
持久化状态:
有OID,和Session有关系。
脱管状态:
有OID,和Session没有关系。
在Hibernate中,可以通过代码来操作管理事务,如通过“Transaction tx = session.beginTransaction();”开启一个事务;持久化操作后,通过“tx.commit();”提交事务;如果事务出现异常,又通过“tx.rollback();”操作来撤销事务(事务回滚)。
除了在代码中对事务开启,提交和回滚操作外,还可以在Hibernate的配置文件中对事务进行配置。配置文件中,可以设置事务的隔离级别。其具体的配置方法是在hibernate.cfg.xml文件中的<session-factory>标签元素中进行的。配置方法如下所示。
<!—
事务隔离级别
hibernate.connection.isolation = 4
1—Read uncommitted isolation
2—Read committed isolation
4—Repeatable read isolation
8—Serializable isolation
-->
<property name="hibernate.connection.isolation">4</property>
到这我们已经设置了事务的隔离级别,那么我们在真正进行事务管理的时候,需要考虑事务的应用的场景,也就是说我们的事务控制不应该是在DAO层实现的,应该在Service层实现,并且在Service中调用多个DAO实现一个业务逻辑的操作。具体操作如下显示:
其实最主要的是如何保证在Service中开启的事务时使用的Session对象和DAO中多个操作使用的是同一个Session对象。
其实有两种办法可以实现:
其实使用第二种方式肯定是最优方案,那么具体的实现已经不用我们来完成了,Hibernate的内部已经将这个事情做完了。我们只需要完成一段配置即可。
Hibernate5中自身提供了三种管理 Session 对象的方法
在 Hibernate 的配置文件中, hibernate.current_session_context_class 属性用于指定 Session 管理方式, 可选值包括
配置步骤:
1、在hibernate.cfg.xml文件中配置
<!-- 把session绑定到当前线程上 -->
<property name="hibernate.current_session_context_class">thread</property>
2、获取Session时使用的方法:
/**
* 每次都是从当前线程上获取Session
* @return
*/
public static Session getCurrentSession(){
return factory.getCurrentSession();
}
细节:
当我们把Session绑定到当前线程之后,关闭session就是hibernate来做的,我们就不用关了。
到这里我们已经对Hibernate的事务管理有了基本的了解,但是之前我们所做的CRUD的操作其实还没有查询多条记录。那如果我们需要查询多条记录要如何完成呢,我们接下来去学习一下Hibernate的其他的相关的API。
Query代表面向对象的一个Hibernate查询操作。在Hibernate中,通常使用session.createQuery()方法接受一个HQL语句,然后调用Query的list()或uniqueResult()方法执行查询。所谓的HQL是Hibernate Query Language缩写,其语法很像SQL语法,但它是完全面向对象的。
在Hibernate中使用Query对象的步骤,具体所示:
(1)获得Hibernate的Session对象。
(2)编写HQL语句。
(3)调用session.createQuery 创建查询对象。
(4)如果HQL语句包含参数,则调用Query的setXxx设置参数。
(5)调用Query对象的方法执行查询。
HQL的说明:
把表的名称换成实体类名称。把表字段名称换成实体类属性名称。
例如:
SQL:select * from cst_customer where cust_name like ?
HQL:select * from Customer where custName = ?
其中select * 可以省略,写为:from Customer where custName = ?
了解了使用Query对象的步骤后,接下来,通过具体示例来演示Query对象的查询操作。
/**
* 查询所有
*/
@Test
public void test1(){
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//1.获取Query对象
Query query = s.createQuery("from Customer");
//2.执行对象的方法,获取结果集
List list = query.list();
for(Object o : list){
System.out.println(o);
}
tx.commit();
}
/**
* 条件查询
* hibernate的参数占位符索引是从0开始的
*/
@Test
public void test2(){
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//1.获取Query对象
Query query = s.createQuery("from Customer where custName like ? and custLevel = ?");
//2.给参数占位符赋值
query.setString(0, "%集%");
query.setString(1, "普通客户");
//3.执行对象的方法,获取结果集
List list = query.list();
for(Object o : list){
System.out.println(o);
}
tx.commit();
}
/**
* 条件查询
* 给参数占位符提供一个具体的名称
* 参数占位符的写法:
* :名称
* 赋值的时候不需要写冒号,直接写名称
*/
@Test
public void test3(){
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//1.获取Query对象
Query query = s.createQuery("from Customer where custName like :custName and custLevel = :custLevel");
//2.给参数占位符赋值
query.setString("custName", "%集%");
query.setString("custLevel", "普通客户");
//3.执行对象的方法,获取结果集
List list = query.list();
for(Object o : list){
System.out.println(o);
}
tx.commit();
}
/**
* mysql的分页关键字
* limit
* limit的参数含义
* 第一个:查询的开始记录索引
* 第二个:每次查询的条数
* hibernate中针对分页提供了两个方法
* setFirstResult(int firstResult);设置开始记录索引
* setMaxResults(int maxResults);设置每次查询的记录条数
*/
@Test
public void test4(){
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//1.获取Query对象
Query query = s.createQuery("from Customer");
//2.设置分页的方法
query.setFirstResult(2);
query.setMaxResults(2);
//3.执行对象的方法,获取结果集
List list = query.list();
for(Object o : list){
System.out.println(o);
}
tx.commit();
}
/**
* 排序查询
* 使用的关键字:
* order by
* 升序:
* asc 默认值
* 降序:
* desc
*/
@Test
public void test2(){
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
Query query = s.createQuery("from LinkMan order by lkmId desc ");
List list = query.list();
for(Object o : list){;
System.out.println(o);
}
tx.commit();
}
/**
* HQL使用聚合函数:
* 统计查询
* 聚合函数:
* count sum max min avg
*
* sql语句使用聚合函数时,在不使用group by子句的情况下,返回的结果,永远只有一行一列的情况。
*
* 在SQL语句时:
* select count(*) from table 它是统计所有字段,效率没有只统计主键字段高
* select count(主键) from table 它和第一个的结果是一样的,但是效率更高
* select count(非主键) from table 只统计不为null的字段
*/
@Test
public void test1(){
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
Query query = s.createQuery("select count(lkmId) from LinkMan");//它最终仍然换转成SQL语句
// List list = query.list();
// for(Object o : list){;
// System.out.println(o);
// }
Long total = (Long)query.uniqueResult();//返回的是一个唯一的结果集。 只有确定结果集唯一时,才能使用
System.out.println(total);
tx.commit();
}
/**
* 投影查询:
* 投影:使用一个实体的部分字段信息,来构建实体类对象,叫做对象的投影(在hibernate中的叫法)
* 使用HQL的方式查询实体类的部分字段信息,并且封装到实体类中。(QBC也能实现投影查询,但是不如hql的好用,所以使用投影查询,一般都用HQL)
* HQL语句的写法:
* select new Customer() from Customer
* 如果工程只有一个唯一的类,可以不写全限定类名,否则必须写全限定类名。
* 实体类要求:
* 必须提供一个相同参数列表的构造函数
*/
@Test
public void test3(){
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
Query query = s.createQuery("select new Customer(custId,custName) from Customer ");
List<Object[]> list = query.list();
for(Object o : list){
System.out.println(o);
}
tx.commit();
}
/**
* 客户的实体类
*/
public class Customer implements Serializable {
private Long custId;
private String custName;
private String custSource;
private String custIndustry;
private String custLevel;
private String custAddress;
private String custPhone;
public Customer(){
}
//提供对应参数列表的构造函数
public Customer(Long custId, String custName) {
this.custId = custId;
this.custName = custName;
}
public Long getCustId() {
return custId;
}
public void setCustId(Long custId) {
this.custId = custId;
}
public String getCustName() {
return custName;
}
public void setCustName(String custName) {
this.custName = custName;
}
public String getCustSource() {
return custSource;
}
public void setCustSource(String custSource) {
this.custSource = custSource;
}
public String getCustIndustry() {
return custIndustry;
}
public void setCustIndustry(String custIndustry) {
this.custIndustry = custIndustry;
}
public String getCustLevel() {
return custLevel;
}
public void setCustLevel(String custLevel) {
this.custLevel = custLevel;
}
public String getCustAddress() {
return custAddress;
}
public void setCustAddress(String custAddress) {
this.custAddress = custAddress;
}
public String getCustPhone() {
return custPhone;
}
public void setCustPhone(String custPhone) {
this.custPhone = custPhone;
}
public Set<LinkMan> getLinkmans() {
return linkmans;
}
public void setLinkmans(Set<LinkMan> linkmans) {
this.linkmans = linkmans;
}
@Override
public String toString() {
return "Customer [custId=" + custId + ", custName=" + custName + ", custSource=" + custSource
+ ", custIndustry=" + custIndustry + ", custLevel=" + custLevel + ", custAddress=" + custAddress
+ ", custPhone=" + custPhone + "]";
}
}
Criteria是一个完全面向对象,可扩展的条件查询API,通过它完全不需要考虑数据库底层如何实现,以及SQL语句如何编写,它是Hibernate框架的核心查询对象。Criteria 查询,又称为QBC查询(Query By Criteria),它是Hibernate的另一种对象检索方式。
org.hibernate.criterion.Criterion是Hibernate提供的一个面向对象查询条件接口,一个单独的查询就是Criterion接口的一个实例,用于限制Criteria对象的查询,在Hibernate中Criterion对象的创建通常是通过Restrictions 工厂类完成的,它提供了条件查询方法。
通常,使用Criteria对象查询数据的主要步骤,具体如下:
(1)获得Hibernate的Session对象。
(2)通过Session获得Criteria对象。
(3)使用Restrictions的静态方法创建Criterion条件对象。Restrictions类中提供了一系列用于设定查询条件的静态方法,这些静态方法都返回Criterion实例,每个Criterion实例代表一个查询条件。
(4)向Criteria对象中添加Criterion 查询条件。Criteria的add()方法用于加入查询条件。
(5)执行Criterita的 list() 或uniqueResult() 获得结果。
细节:
HQL能查的,QBC都能查,反之亦然。
了解了Criteria对象的使用步骤后,接下来,通过具体示例来演示Criteria对象的查询操作。
/**
* 查询所有
*/
@Test
public void test1(){
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//1.获取Criteria对象
Criteria c = s.createCriteria(Customer.class);//它就相当于HQL的from Customer
//2.执行对象的方法获取结果集
List list = c.list();
for(Object o : list){
System.out.println(o);
}
tx.commit();
}
/**
* 条件查询
*/
@Test
public void test2(){
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//1.获取Criteria对象
Criteria c = s.createCriteria(Customer.class);//它就相当于HQL的from Customer
//2.设置查询条件
c.add(Restrictions.like("custName", "%集%"));//from Customer where custName like "%集%"
c.add(Restrictions.eq("custLevel", "普通客户"));
//3.执行对象的方法获取结果集
List list = c.list();
for(Object o : list){
System.out.println(o);
}
tx.commit();
}
/**
* 分页查询
* 和HQL是一模一样的
*/
@Test
public void test3(){
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//1.获取Criteria对象
Criteria c = s.createCriteria(Customer.class);//它就相当于HQL的from Customer
//2.设置分页
c.setFirstResult(2);
c.setMaxResults(2);
//3.执行对象的方法获取结果集
List list = c.list();
for(Object o : list){
System.out.println(o);
}
tx.commit();
}
/**
* 排序查询
*/
@Test
public void test1(){
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//1.获取对象
Criteria c = s.createCriteria(Customer.class);
//2.设置排序
c.addOrder(Order.desc("custId"));
//3.获取结果集
List list = c.list();
for(Object o : list){
System.out.println(o);
}
tx.commit();
}
/**
* QBC使用聚合函数
* 统计查询
* 涉及的对象:
* Criteria
* 涉及的方法:
* setProjection(Projection p);
* 参数的含义
* Projection:要添加的查询投影
*/
@Test
public void test2(){
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//1.获取对象
Criteria c = s.createCriteria(Customer.class);//from Customer | select * from cst_customer
//2.想办法把select * 变成 select count(*)
// c.setProjection(Projections.rowCount());//select count(*)
c.setProjection(Projections.count("custId"));//select count(cust_id)
//3.获取结果集
// List list = c.list();
// for(Object o : list){
// System.out.println(o);
// }
Long total = (Long)c.uniqueResult();
System.out.println(total);
tx.commit();
}
/**
* 离线条件查询
* 离线:
* 它是和在线对应的。
* Criteria对象是一个在线对象,它是由一个可用的(活动的)Session对象获取的出来的。
* 当session失效时,就无法再获取该对象了。
* 有一个对象,它也可以用于设置条件,但是获取的时候并不需要Session对象。
* 该对象就叫做离线对象:
* DetachedCriteria对象
* 使用该对象进行的查询就叫做:离线查询
*
* 如何获取该对象
* DetachedCriteria dCriteria = DetachedCriteria.forClass(要查询的实体类字节码);
*
*/
@Test
public void test3(){
//模拟一次web操作: 浏览器发送请求——调用servlet——调用service——调用dao——拿到结果到jsp上展示
List list = servletFindAllCustomer();
for(Object o : list){
System.out.println(o);
}
}
//模拟servlet
public List<Customer> servletFindAllCustomer(){
//离线对象
DetachedCriteria dCriteria = DetachedCriteria.forClass(Customer.class);
//设置条件:和Criteria是一样的
dCriteria.add(Restrictions.like("custName","%集%"));
return serviceFindAllCustomer(dCriteria);
}
public List<Customer> serviceFindAllCustomer(DetachedCriteria dCriteria) {
return daoFindAllCustomer(dCriteria);
}
public List<Customer> daoFindAllCustomer(DetachedCriteria dCriteria) {
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//把离线对象使用可用Session激活
Criteria c = dCriteria.getExecutableCriteria(s);
List<Customer> list = c.list();
tx.commit();
return list;
}
短语 |
含义 |
Restrictions.eq |
等于= |
Restrictions.allEq |
使用Map,使用key/value进行多个等于的判断 |
Restrictions.gt |
大于> |
Restrictions.ge |
大于等于>= |
Restrictions.lt |
小于< |
Restrictions.le |
小于等于<= |
Restrictions.between |
对应sql的between子句 |
Restrictions.like |
对应sql的like子句 |
Restrictions.in |
对应sql的in子句 |
Restrictions.and |
and 关系 |
Restrictions.or |
or关系 |
Restrictions.sqlRestriction |
Sql限定查询 |
Restrictions.asc() |
根据传入的字段进行升序排序 |
Restrictions.desc() |
根据传入的字段进行降序排序 |
运算类型 |
HQL运算符 |
QBC运算方法 |
比较运算 |
= |
Restrictions.eq() |
<> |
Restrictions.not(Restrictions.eq()) |
|
>= |
Restrictions.ge() |
|
< |
Restrictions.lt() |
|
<= |
Restrictions.le() |
|
is null |
Restrictions.isNull() |
|
is not null |
Restrictions.isNotNull() |
|
范围运算符 |
in |
Restrictions.in() |
not in |
Restrictions.not(Restrictions.in()) |
|
between |
Restrictions.between() |
|
not between |
Restrictions.not(Restrictions.between()) |
运算类型 |
HQL运算符 |
QBC运算方法 |
字符串模式匹配 |
like |
Restrictions.like() |
逻辑 |
and |
Restrictions.and()| Restrictions.conjunction() |
or |
Restrictions.or()| Restrictions.disjunction() |
|
not |
Restrictions.not() |
|
|
|
标签:构建 obj 详细介绍 组件 utils 延迟加载 而且 内存 api
原文地址:https://www.cnblogs.com/zyk2019/p/11259119.html