SH

hibernate中的实体类规范
注意:实体类的编写规范:
  1. 尽量使用包装类型。
  2. 建议实现序列化接口。
  3. 一定要有无参构造方法。

hibernate的执行流程
  创建Configuration类的实例,并通过它来读取并解析配置文件hibernate.cfg.xml。然后创建SessionFactory读取解析映射文件信息。接下来,让SessionFactory提供连接,打开Session,并开启一个事务,之后创建对象,向对象中添加数据,通过session.save()方法完成向数据库中保存数据的操作。最后提交事务,并关闭资源。

  create:每次加载hibernate框架时,先删除表,再建表
  create-drop:每次加载hibernate框架时,先删除表,再建表。当sf关闭时再删除表。
  update:每次加载hibernate框架时,会验证实体类与表结构是否一致,如果不一致,就更新表结构
  validate:每次加载hibernate框架时,会验证实体类与表结构是否一致,如果不一致,就报错

核心接口:
SessionFactory
Session
1. 概述
  1. Session是在Hibernate中使用最频繁的接口。也被称之为持久化管理器。它提供了和持久化有关的操作,比如添加、修改、删除、加载和查询实体对象
  2. Session 是应用程序与数据库之间交互操作的一个单线程对象,是 Hibernate 运作的中心
  3. Session是线程不安全的
  4. 所有持久化对象必须在 session 的管理下才可以进行持久化操作
  5. Session 对象有一个一级缓存,显式执行 flush 之前,所有的持久化操作的数据都缓存在 session 对象处
  6. 持久化类与 Session 关联起来后就具有了持久化的能力
2. 特点
  1. 不是线程安全的。应避免多个线程使用同一个Session实例
  2. Session是轻量级的,它的创建和销毁不会消耗太多的资源。应为每次客户请求分配独立的Session实例
  3. Session有一个缓存,被称之为Hibernate的一级缓存。每个Session实例都有自己的缓存

区别:
 1、查询的时机不一样
  get方法任何时候都是立即加载,即只要一调用get马上发起数据库查询
  load方法默认情况下是延迟加载,即真正用到对象的非OID字段数据才发起查询
  load方法可以通过配置的方式改为立即加载。
配置的方式:

2、返回的结果不一样
  get方法永远返回查询的实体类对象。
  load方法当是延迟加载时,返回的是实体类的代理对象。
涉及的概念:
 立即加载:
  是不管用不用马上查询。
 延迟加载:
  等到用的时候才真正发起查询。

持久化类
  其实所谓的持久化类指的是一个Java类与数据库表建立了映射关系,那么这个类称为是持久化类。其实你可以简单的理解为持久化类就是一个Java类有了一个映射文件与数据库的表建立了关系。

  持久化类需要提供无参数的构造方法。因为在Hibernate的底层需要使用反射生成类的实例。
  持久化类的属性需要私有,对私有的属性提供公有的get和set方法。因为在Hibernate底层会将查询到的数据进行封装。
  持久化类的属性要尽量使用包装类的类型。因为包装类和基本数据类型的默认值不同,包装类的类型语义描述更清晰而基本数据类型不容易描述。举个例子:

  假设表中有一列员工工资,如果使用double类型,如果这个员工工资忘记录入到系统中,系统会将默认值0存入到数据库,如果这个员工工资被扣完了,也会向系统中存入0.那么这个0就有了多重含义,而如果使用包装类类型就会避免以上情况,如果使用Double类型,忘记录入工资就会存入null,而这个员工工资被扣完了,就会存入0,不会产生歧义。

  持久化类要有一个唯一标识OID与表的主键对应。因为Hibernate中需要通过这个唯一标识OID区分在内存中是否是同一个持久化类。在Java中通过地址区分是否是同一个对象的,在关系型数据库的表中是通过主键区分是否同一条记录。那么Hibernate就是通过这个OID来进行区分的。Hibernate是不允许在内存中出现两个OID相同的持久化对象的。
  持久化类尽量不要使用final进行修饰。因为Hibernate中有延迟加载的机制,这个机制中会产生代理对象,Hibernate产生代理对象使用的是字节码的增强技术完成的,其实就是产生了当前类的一个子类对象实现的。如果使用了final修饰持久化类。那么就不能产生子类,从而就不会产生代理对象,那么Hibernate的延迟加载策略(是一种优化手段)就会失效。
  一般都实现Serializable接口。

  OID全称是Object Identifier,又叫做对象标识符。
  它是hibernate用于区分两个对象是否是同一个对象的标识。

  自然主键:把具有业务含义的字段作为主键,称之为自然主键
  代理主键:把不具备业务含义的字段作为主键,称之为代理主键

  1. 当主键生成策略为native时当我们创建对象时,不需要为对象指定OID的值,就可以保存,因为可以采用自增或序列的方式来维护主键
  2. 当主键生成策略为identity时,表示采用自增的方式来维护主键。适合MySql、SqlServer数据库。同样的,我们在创建实体类时也不需要指定OID的值
  3. 当主键生成策略为sequence时,表示采用序列的方式来维护主键。适合Oracle数据库。同样的,我们在创建实体类时也不需要指定OID的值
  4. 当主键生成策略为assigned时,表示由我们程序员自己来维护主键。在创建对象时,需要我们手动的为OID设置一个值。适合用于代理主键,例如当主键是订单编号时,订单编号一般都是由我们自己来写算法生成,就可以把订单编号这样的主键生成策略定义为assigned。当没有为id标签指定generator时,默认就是assigned.
  5. 当主键生成策略为increment时,Hibernate会在插入数据之前,先查询表中主键的最大值,然后把主键的最大值+1,作为这一次的主键插入到表中。不适合集群的情况。increment和identity的区别是:increment是由Hibernate框架来为维护主键,每次查询主键的最大值,再加1作为这一次插入的主键;而identity是由数据库自身来维护,mysql自身就具备自增的能力
  6. 当主键生成策略为uuid时,表示采用UUID算法来随机生成一个32位的字符串作为主键。一般不用,因为字符串类型的主键比整数类型的主键占用更多的空间

  Hibernate的一级缓存就是指Session缓存,Session缓存是一块内存空间,用来存放相互管理的java对象,在使用Hibernate查询对象的时候,首先会使用对象属性的OID值在Hibernate的一级缓存中进行查找,如果找到匹配OID值的对象,就直接将该对象从一级缓存中取出使用,不会再查询数据库;如果没有找到相同OID值的对象,则会去数据库中查找相应数据。当从数据库中查询到所需数据时,该数据信息也会放置到一级缓存中。Hibernate的一级缓存的作用就是减少对数据库的访问次数。
  在 Session 接口的实现中包含一系列的 Java 集合, 这些 Java 集合构成了 Session 缓存。只要 Session 实例没有结束生命周期,存放在它缓存中的对象也不会结束生命周期。故一级缓存也被称为是Session级别的缓存。
Hibernate的一级缓存有如下特点:

  当应用程序调用Session接口的save()、update()、saveOrUpdate时,如果Session缓存中没有相应的对象,Hibernate就会自动的把从数据库中查询到的相应对象信息加入到一级缓存中去。
  当调用Session接口的load()、get()方法,会判断缓存中是否存在该对象,有则返回,不会查询数据库,如果缓存中没有要查询对象,再去数据库中查询对应对象,并添加到一级缓存中。
  当调用Session的close()方法时,Session缓存会被清空。

为什么说mybatis的缓存没什么用?

  三个代码块 更好地理解快照机制的作用

  Hibernate 向一级缓存放入数据时,同时复制一份数据放入到Hibernate快照中,当使用commit()方法提交事务时,同时会清理Session的一级缓存,这时会使用OID判断一级缓存中的对象和快照中的对象是否一致,如果两个对象中的属性发生变化,则执行update语句,将缓存的内容同步到数据库,并更新快照;如果一致,则不执行update语句。Hibernate快照的作用就是确保一级缓存中的数据和数据库中的数据一致。

     @Test
     public void test3(){
           Session session = HibernateUtils.openSession();
           Transaction tx = session.beginTransaction();

           Customer customer = new Customer();
           customer.setCustName("dd");
           //这里保存也会先到一级缓存  所以如果将要新增的语句的oid和查询的oid相同则下面的get查询则不会发送查询语句
           session.save(customer);

           session.get(Customer.class, 18L);


           tx.commit();
           session.close();//当session关闭时,一级缓存就会失效
     }

     @Test
     public void test4(){
           Session session = HibernateUtils.openSession();
           Transaction tx = session.beginTransaction();
           //如果一级缓存中的数据和快照区一致 则不发送更新语句 如果不一致 则发送
           //也就是说如果数据库中id=4这条数据的这两个属性的值都是想要set的值 那么事务提交的时候 则不会发送sql语句
           Customer customer = session.get(Customer.class, 4L);
           customer.setCustName("富士康1234");
           customer.setCustIndustry("制造业");



           tx.commit();
           session.close();
     }

     @Test
     public void test5(){
           Session session = HibernateUtils.openSession();
           Transaction tx = session.beginTransaction();

           Customer customer = new Customer();
           customer.setCustId(4L);
           customer.setCustPhone("000000");
           //自己创建的对象则不会创建快照 也就睡说下面的update是必须的
           session.update(customer);
           tx.commit();
           session.close();
     }

三种状态
  Hibernate为了更好的来管理持久化类,特将持久化类分成了三种状态。在Hibernate中持久化的对象可以划分为三种状态,分别是瞬时态、持久态和脱管态,一个持久化类的实例可能处于三种不同状态中的某一种,三种状态的详细介绍如下。

瞬时态(transient)
  瞬时态也称为临时态或者自由态,瞬时态的实例是由new命令创建、开辟内存空间的对象,不存在持久化标识OID(相当于主键值),尚未与Hibernate Session关联,在数据库中也没有记录,失去引用后将被JVM回收。瞬时状态的对象在内存中是孤立存在的,与数据库中的数据无任何关联,仅是一个信息携带的载体。

持久态(persistent)
  持久态的对象存在持久化标识OID ,加入到了Session缓存中,并且相关联的Session没有关闭,在数据库中有对应的记录,每条记录只对应唯一的持久化对象,需要注意的是,持久态对象是在事务还未提交前变成持久态的。

脱管态或游离态(detached)
  脱管态也称离线态或者游离态,当某个持久化状态的实例与Session的关联被关闭时就变成了脱管态。脱管态对象存在持久化标识OID,并且仍然与数据库中的数据存在关联,只是失去了与当前Session的关联,脱管状态对象发生改变时Hibernate不能检测到

    /**
      * 瞬时态------->持久态------->脱管态
      */
     @Test
     public void test1(){
           //瞬时态:没有OID,和Session没有关联
           Customer customer = new Customer();
           customer.setCustName("ee");

           Session session = HibernateUtils.openSession();
           Transaction tx = session.beginTransaction();
           //持久态:有OID,和Session有关联
           session.save(customer);

           tx.commit();
           session.close();
           //脱管态(游离态):有OID,和Session没有关联

     }

     /**
      * n脱管态------->持久态
      */
     @Test
     public void test2(){
           //游离态
           Customer customer = new Customer();
           customer.setCustId(1L);//脱管态
           customer.setCustName("a233");

           Session session = HibernateUtils.openSession();
           Transaction tx = session.beginTransaction();

           //持久态
           session.update(customer);
           tx.commit();
           session.close();
     }

     /**
      * n持久态------>瞬时态
      */
     @Test
     public void test3(){
           Session session = HibernateUtils.openSession();
           Transaction tx = session.beginTransaction();

           Customer customer = session.get(Customer.class, 1L);//持久态
           session.delete(customer);//调用delete方法后 转换为瞬时态

           tx.commit();
           session.close();
           System.out.println(customer);

     }

事务回顾

* 什么是事务:

    * 事务:指的是逻辑上的一组操作,组成这组操作的各个单元要么一起成功要么一起失败。
* 事务的特性

    * 原子性:事务的不可分割
    * 一致性:事务执行的前后,数据完整性保持一致。
    * 隔离性:一个事务的执行,不应该受到其他事务的干扰。
    * 持久性:一旦事务结束,数据就持久到数据库中。
* 如果不考虑隔离性,事务并发引发安全性问题:

    * 读问题:

        * 脏读        :一个事务读到另一个事务未提交的数据。
        * 不可重复读  :一个事务读到另一个事务已经提交update的数据,导致一个事务中多次查询结果不一致。
        * 虚读        :一个事务读到另一个事务已经提交insert的数据,导致一个事务中多次查询结果不一致。
* 解决读问题:设置事务隔离级别

    * read uncommitted    :以上读问题都有可能发生    1
    * read committed  :避免脏读,但是不可重复读和虚读有可能发生。    2     oracle默认
    * repeatable read :避免脏读和不可重复读,但是虚读有可能发生。    4    mysql默认
    * serializable        :以上读问题都能解决。  8

Hibernate中设置session与当前线程绑定
  事务控制不应该是在DAO层实现的,应该在Service层实现,并且在Service中调用多个DAO实现一个业务逻辑的操作。具体操作如下显示
  其实最主要的是如何保证在Service中开启的事务时使用的Session对象和DAO中多个操作使用的是同一个Session对象。

其实有两种办法可以实现:
  * 可以在业务层获取到Session,并将Session作为参数传递给DAO。
  * 可以使用ThreadLocal将业务层获取的Session绑定到当前线程中,然后在DAO中获取Session的时候,都从当前线程中获取。

  其实使用第二种方式肯定是最优方案,那么具体的实现已经不用我们来完成了,Hibernate的内部已经将这个事情做完了。我们只需要完成一段配置即可。
Hibernate5中自身提供了三种管理 Session 对象的方法
  Session 对象的生命周期与本地线程绑定
  Session 对象的生命周期与 JTA 事务绑定
  Hibernate 委托程序管理 Session 对象的生命周期
  在 Hibernate 的配置文件中, hibernate.current_session_context_class 属性用于指定 Session 管理方式, 可选值包括
  thread:Session 对象的生命周期与本地线程绑定
  jta:Session 对象的生命周期与 JTA 事务绑定
  managed:Hibernate 委托程序来管理 Session 对象的生命周期

    /**
      * 每次获取session时,都从当前线程中获取
      * 细节:当把session与当前线程绑定后,当事务提交后,session自动关闭了,不需要我们再手动关闭了
      */
     @Test
     public void test1(){
           //如果当前线程没有绑定session,就创建一个新的session,还会把session和线程绑定
           Session session = HibernateUtils.getCurrentSession();
           Transaction tx = session.beginTransaction();

           session.get(Customer.class, 1L);

           tx.commit();//事务一旦提交,session会自动关闭
           //session.close();
     }

  Query代表面向对象的一个Hibernate查询操作。在Hibernate中,通常使用session.createQuery()方法接受一个HQL语句,然后调用Query的list()或uniqueResult()方法执行查询。所谓的HQL是Hibernate Query Language缩写,其语法很像SQL语法,但它是完全面向对象的。

HQL的投影查询

    public void test6() {
           Session session = HibernateUtils.getCurrentSession();
           Transaction tx = session.beginTransaction();
           Query query = session.createQuery("select new Customer(custId,custName) from Customer");
           // 执行
           List<Customer> list = query.list();
           for (Customer customer : list) {
                System.out.println(customer);
           }
           tx.commit();
     }

Query方法说明

  * list方法:该方法用于查询语句,返回的结果是一个list集合。
  * setter方法:Query接口中提供了一系列的setter方法用于设置查询语句中的参数,针对不同的数据类型,需要用到不同的setter方法。
  * uniqueResult()方法:该方法用于返回唯一的结果,在确保只有一条记录的查询时可以使用该方法。
  * setFirstResult()方法:该方法可以设置获取第一个记录的位置,也就是它表示从第几条记录开始查询,默认从0开始计算。
  * setMaxResult()方法:该方法用于设置结果集的最大记录数,通常与setFirstResult()方法结合使用,用于限制结果集的范围,以实现分页功能。

  Criteria是一个完全面向对象,可扩展的条件查询API,通过它完全不需要考虑数据库底层如何实现,以及SQL语句如何编写,它是Hibernate框架的核心查询对象。Criteria 查询,又称为QBC查询(Query By Criteria),它是Hibernate的另一种对象检索方式。

  离线查询对象一般用于如下场景:页面上有个搜索表单,提交搜索表单后,发送多个搜索参数到servlet,在servlet里用离线查询对象DetachedCriteria拼接sql条件,再把拼接好条件的DetachedCriteria对象依次传递给service、dao,在dao层直接执行DetachedCriteria,不用拼接sql条件了。

     @Test
     public void test7(){

           //获取Criteria对象
           DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Customer.class);

           List<Customer> list = findAllCustomerServlet(detachedCriteria);
           //执行查询
           for(int i = 0;i < list.size();i++){
                System.out.println(list.get(i));
           }

     }
     private  List<Customer>  findAllCustomerServlet(DetachedCriteria detachedCriteria) {
           return findAllCustomerService(detachedCriteria);
     }
     @SuppressWarnings("unchecked")
     private List<Customer> findAllCustomerService(DetachedCriteria detachedCriteria) {
           Session session = HibernateUtils.getCurrentSession();
           Transaction tx = session.beginTransaction();
           Criteria criteria = detachedCriteria.getExecutableCriteria(session);
           List<Customer> list = criteria.list();
           tx.commit();
           return list;
     }

inverse的作用
  作用:在一的一方(主表)设置inverse=true后,表示一的一方(主表)放弃维护外键的权利,由多的一方来维护。

  默认情况下,一方和多方都具备维护外键的权利。,inverse的默认值为false,自己来维护外键。可以在一方设置invsese=true,放弃维护外键的权利。

注意:多方不能设置inverse。

那到底什么时候用inverse??

  建立双向关系后,会产生多余的update语句,如果想解决的话,就要用inverse,让一方放弃外键的维护权。例如,4.1.5中就会出现多余的update语句。
如何解决:在一的一方(主表)的set标签中配置inverse=true.让一的一方放弃外键维护权。

多对多案例
  我们可以在角色方放弃外键维护:在Role.hbm.xml文件中配置inverse=true,表示角色放弃外键维护权。
  在User.hbm.xml中设置cascade=”save-update”,表示保存用户级联保存角色
多对多中最好不要设置级联删除。因为一旦设置级联删除后,用户级联角色,当删除用户时,就会删除把与之关联的其它角色删除,但是这些角色可能被其它用户用到。

  测试发现,会报错,因为删除用户会级联删除角色,但角色放弃了外键维护,不会删除中间表中与角色关联的数据,所以整个删除操作失败。
  解决办法:把角色方的inverse去掉。但是,也没必要,应为在多对多中没有表级联删除。

Customer.hbm.xml

            <set name="linkMans" inverse="true"  cascade="save-update,delete" lazy="true">
                <key column="lkm_customer_id"></key>
                <one-to-many class="cn.itcast.domain.LinkMan"/>
           </set>

LinkMan.hbm.xml

<many-to-one name="customer" class="cn.itcast.domain.Customer" column="lkm_customer_id" lazy="false"></many-to-one>

SysUser.hbm.xml

            <set name="roles" table="user_role" cascade="save-update,delete">
                <key column="user_id"></key>
                <many-to-many class="cn.itcast.domain.SysRole" column="role_id"></many-to-many>
           </set>

SysRole.hbm.xml

            <set name="users" table="user_role">
                <key column="role_id"></key>
                <many-to-many class="cn.itcast.domain.SysUser" column="user_id"></many-to-many>
           </set>

  对象图导航检索方式是根据已经加载的对象,导航到他的关联对象。它利用类与类之间的关系来检索对象。
  例如:我们通过OID查询方式查出一个客户,可以调用Customer类中的getLinkMans()方法来获取该客户的所有联系人。
  对象导航查询的使用要求是:两个对象之间必须存在关联关系。

  什么时候JPA?JPA它是一套Java持久化规范。那么JPA和我们前面三天所学的Hibernate有什么关系呢?
  1. JPA是一套Java持久化规范;而Hibernate实现了JPA规范。
  2. 在Hibernate中有两套操作数据库的方式,一套是Hibernate自己特有的的方式,前面三天已经学过了;还有一套是实现了JPA规范的方式。

  EntityTransaction:JPA规范中的事务接口,用来管理JPA规范中的事务,作用等价于Transaction;
  EntityManager.persist():用来保存对象的;作用等价于session.save();

主键生成策略
  基于annotation的hibernate主键标识为@Id,
  其生成规则由@GeneratedValue设定的.这里的@id和@GeneratedValue都是JPA的标准用法。

  JPA提供的四种标准用法为TABLE,SEQUENCE,IDENTITY,AUTO。具体说明如下:

Customer.java

     //一个客户包含多个联系人
     //mappedBy属性:表示由多方(联系人)来维护主键,值就是多方(联系人)中一方的属性名
     @OneToMany(mappedBy="customer",cascade={CascadeType.PERSIST,CascadeType.REMOVE})
     //@JoinColumn(name="lkm_customer_id")
     private Set<LinkMan> linkMans = new HashSet<LinkMan>();

LinkMan.java

     //一个联系人对应一个客户
     @ManyToOne
     @JoinColumn(name="lkm_customer_id")
     private Customer customer;

SysUser.java

    //一个用户有多个角色
     @ManyToMany(cascade={CascadeType.PERSIST})
     @JoinTable(
           name="user_role",
           joinColumns=@JoinColumn(name="user_id"),
           inverseJoinColumns=@JoinColumn(name="role_id")
     )
     private Set<SysRole> roles = new HashSet<SysRole>();

SysRole.java

//一个角色有多个用户
     @ManyToMany(mappedBy="roles")
//   @JoinTable(
//         name="user_role",
//         joinColumns=@JoinColumn(name="role_id"),
//         inverseJoinColumns=@JoinColumn(name="user_id")
//   )
     private Set<SysUser> users = new HashSet<SysUser>();

JPA常用注解
小结:
@OneToMany
@ManyToOne
@JoinColumn
@ManyToMany
@JoinTable

猜你喜欢

转载自blog.csdn.net/huming1250446609/article/details/82432594
.sh
SH