• Hibernate的实体规则、主键生成策略、对象状态


      

    一、  hibernate的实体类有一定的规则,类似于mybatis的逆向工程导出的实体类。具体的规则以及原因如下:

    1.持久化类需要提供无参的构造方法。

      因为hibernate底层采用反射机制创建对象,采用class.newInstance()创建对象,此方法默认调用无参构造方法创建对象。如果我们只写一个带参数的构造函数在查询的时候会报错误 org.hibernate.InstantiationException

    例如:我们重写一个类的无参构造方法:

    public class User {
        private Long user_id;
        private String user_name;
        private Character user_state;
        private Set<Role> roles = new HashSet<>();
    
        
        
        public User() {
            System.out.println("user创建对象");
        }
    ...

    测试:

        public static void main(String[] args) {
            Session session = HibernateUtils.openSession();
            Transaction tx = session.beginTransaction();
            
            User user = session.get(User.class, 1l);
            System.out.println(user);
        }

    结果:

    user创建对象
    Hibernate:
    select
    user0_.user_id as user_id1_3_0_,
    user0_.user_name as user_nam2_3_0_,
    user0_.user_state as user_sta3_3_0_
    from
    sys_user user0_
    where
    user0_.user_id=?
    user创建对象
    cn.qlq.domain.User@2ddc7eba

    2.成员变量私有,提供共有get/set方法访问.需提供属性

      hibernate底层将查询到的数据采用set方法进行设置。

      此处声明一个常识,一个类下声明的变量是成员变量不能说是属性,而属性说的是get/setter建造器,一个类有几个属性看这个类有几个getter/setter建造器。

    3.持久化类中的属性,应尽量使用包装类型

      例如int采用Integer,long采用Long。原因如下:

      (1)有可能我们对一些值不需要设值,比如null,但是我们如果用int这样的基本数据类型将不能存储null,比如我们想用成绩为null表示缺考,成绩为0表示考试了但是获得 的成绩为0.

      (2)数据库中的null如果映射到基本数据类型会报错误,采用包装类型可以避免这些问题。

    4.持久化类需要提供oid(Object Identifier,对象标识符).与数据库中的主键列对应

      注意如果没有主键的表hibernate是不能进行操作的。

      数据库中的表采用ID进行区分,每条唯一的是一条记录;Java中通过内存地址区分两个对象是否是相同对象,而hibernate通过 OID进行区分,hibernate不允许在内存中出现两个相同OID的对象。

    5.不要用final修饰class

     hibernate使用cglib代理生成代理对象.代理对象是继承被代理对象.如果被final修饰.将无法生成代理。JDK代理采用实现接口的方式,而cgib采用继承的方式实现代理。

    二、hibernate主键类型

    1.  自然主键(少见)

       表的业务列中,有某业务列符合,必须有,并且不重复的特征时,该列可以作为主键使用.比如用户表有身份证号字段,身份证号不能为空而且能唯一区分一条记录,则可以将身份证号作为主键。

    2.代理主键(常见)

      表的业务列中,没有某业务列符合,必须有,并且不重复的特征时,创建一个没有业务意义的列作为主键。(这也是设计数据库的一条常用规则,每个表都有一个与业务无关的主键)

    三、hibernate主键生成策略

      由hibernate维护的数据库主键生成策略开发时不用,了解即可。increment(自增)和hilo(高低位算法)。开发一般用native或者UUID,对于int型(对应java的Integer)或者bigint(对应Java的Long型)型的数据可以用

    native就会ui自动支持mysql或者oracle,如果开发采用的主键是varchar(对应Java的String)型的可以采用uuid生成32位唯一的16进制字符串。

      自然主键有6中,代理主键就一种(assigned)-------开发人员自己确保主键唯一。   

    1.  数据库自己托管   identity 

      identity : 主键自增.由数据库来维护主键值.录入时不需要指定主键.

    测试:

            <id name="cust_id"  >
                <!-- generator:主键生成策略 -->
                <!--identity : 主键自增.由数据库来维护主键值.录入时不需要指定主键.  -->
                <generator class="identity"></generator>
            </id>

    执行保存的时候查看SQL语句:(未插入主键)

    Hibernate: 
        insert 
        into
            cst_customer
            (cust_name, cust_source, cust_industry, cust_level, cust_linkman, cust_phone, cust_mobile) 
        values
            (?, ?, ?, ?, ?, ?, ?)

    2.自增主键  increment  (hibernate维护,并发访问会有问题)----开发不用

      increment:自然主键生成策略. hibernate不会管理主键值.由开发人员自己录入.每次插入的时候先查询当前最大值,下次加一。

    Hibernate: 
        select
            max(cust_id) 
        from
            cst_customer
    Hibernate: 
        insert 
        into
            cst_customer
            (cust_name, cust_source, cust_industry, cust_level, cust_linkman, cust_phone, cust_mobile, cust_id) 
        values
            (?, ?, ?, ?, ?, ?, ?, ?)

     3.sequence   用于oracle数据库的序列主键生成策略

    4.hilo:高低位算法---一种算法名称  (hibernate维护,开发时不用)

    5.native  hibernate根据方言(数据库类型)三(hilo+sequence+identify)选一策略  -----------一般用这个

      如果是mysql就采用identify,如果是oracle采用sequence类型。

    6.  uuid  生成一个32位不重复的UUID,注意主键必须是String类型(也常用)

            <id name="cust_id"  >
                <!-- generator:主键生成策略 -->
                <generator class="uuid"></generator>
            </id>

    测试如下:

    User.java

    package cn.qlq.domain;
    
    /**
     * 
     * @author Administrator
     *
     */
    public class User {
        private String userId;
        private String userName;
        private Integer age;
    
        public String getUserId() {
            return userId;
        }
    
        public void setUserId(String userId) {
            this.userId = userId;
        }
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName) {
            this.userName = userName;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    }

    User.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.qlq.domain" >
        <!-- 
            class元素: 配置实体与表的对应关系的
                name: 完整类名
                table:数据库表名
         -->
        <class name="User" table="t_user">
            <!-- id元素:配置主键映射的属性
                    name: 填写主键对应属性名
                    column(可选): 填写表中的主键列名.默认值:列名会默认使用属性名
                    type(可选):填写列(属性)的类型.hibernate会自动检测实体的属性类型.
                            每个类型有三种填法: java类型|hibernate类型|数据库类型
                    not-null(可选):配置该属性(列)是否不能为空. 默认值:false
                    length(可选):配置数据库中列的长度. 默认值:使用数据库类型的最大长度
             -->
            <id name="userId" column="userid" >
                <!-- generator:主键生成策略 -->
                <generator class="uuid"></generator>
            </id>
            <!-- property元素:除id之外的普通属性映射
                    name: 填写属性名
                    column(可选): 填写列名
                    type(可选):填写列(属性)的类型.hibernate会自动检测实体的属性类型.
                            每个类型有三种填法: java类型|hibernate类型|数据库类型
                    not-null(可选):配置该属性(列)是否不能为空. 默认值:false
                    length(可选):配置数据库中列的长度. 默认值:使用数据库类型的最大长度
             -->
            <property name="userName" column="username"></property>
            <property name="age" column="age"></property>
        </class>
    </hibernate-mapping>

    测试类:

        @Test
        //session的新增
        public void fun2(){
            //1 创建,调用空参构造
            Configuration conf = new Configuration().configure();
            //2 根据配置信息,创建 SessionFactory对象
            SessionFactory sf = conf.buildSessionFactory();
            //3 获得session
            Session session = sf.openSession();
            //4 session获得操作事务的Transaction对象
            //获得操作事务的tx对象
            //Transaction tx = session.getTransaction();
            //开启事务并获得操作事务的tx对象(建议使用)
            Transaction tx2 = session.beginTransaction();
            //----------------------------------------------
            User user = new User();
            user.setUserName("张三");
            
            session.save(user);
            //----------------------------------------------
            tx2.commit();//提交事务
            session.close();//释放资源
            sf.close();//释放资源
        }

    数据库结果:

    查看表结构发现采用的是数据库默认的最大长度,太浪费内存,我们可以在映射的时候指定长度:

    mysql> desc t_user;
    +----------+--------------+------+-----+---------+-------+
    | Field    | Type         | Null | Key | Default | Extra |
    +----------+--------------+------+-----+---------+-------+
    | userid   | varchar(255) | NO   | PRI | NULL    |       |
    | username | varchar(255) | YES  |     | NULL    |       |
    | age      | int(11)      | YES  |     | NULL    |       |
    +----------+--------------+------+-----+---------+-------+
    3 rows in set (0.00 sec)

    解决办法:修改xml映射文件同时将数据库表名和列名都设位缺省值,也就是与Java种类名属性名相同。数据库表名列名使用缺省值,指定varchar长度,设置不为空

    <?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.qlq.domain" >
        <!-- 
            class元素: 配置实体与表的对应关系的
                name: 完整类名
                table:数据库表名
         -->
        <class name="User">
            <!-- id元素:配置主键映射的属性
                    name: 填写主键对应属性名
                    column(可选): 填写表中的主键列名.默认值:列名会默认使用属性名
                    type(可选):填写列(属性)的类型.hibernate会自动检测实体的属性类型.
                            每个类型有三种填法: java类型|hibernate类型|数据库类型
                    not-null(可选):配置该属性(列)是否不能为空. 默认值:false
                    length(可选):配置数据库中列的长度. 默认值:使用数据库类型的最大长度
             -->
            <id name="userId" length="32">
                <!-- generator:主键生成策略 -->
                <generator class="uuid"></generator>
            </id>
            <!-- property元素:除id之外的普通属性映射
                    name: 填写属性名
                    column(可选): 填写列名
                    type(可选):填写列(属性)的类型.hibernate会自动检测实体的属性类型.
                            每个类型有三种填法: java类型|hibernate类型|数据库类型
                    not-null(可选):配置该属性(列)是否不能为空. 默认值:false
                    length(可选):配置数据库中列的长度. 默认值:使用数据库类型的最大长度
             -->
            <property name="userName" length="10" not-null="true"></property>
            <property name="age"></property>
        </class>
    </hibernate-mapping>

    结果:

    mysql> desc user;
    +----------+-------------+------+-----+---------+-------+
    | Field    | Type        | Null | Key | Default | Extra |
    +----------+-------------+------+-----+---------+-------+
    | userId   | varchar(32) | NO   | PRI | NULL    |       |
    | userName | varchar(10) | NO   |     | NULL    |       |
    | age      | int(11)     | YES  |     | NULL    |       |
    +----------+-------------+------+-----+---------+-------+
    3 rows in set (0.00 sec)

    7. assigned:     自然主键生成策略. hibernate不会管理主键值.由开发人员自己录入.

          也就是主键必须自己设置唯一值,由开发人员维护。

    四、 Hibernate的对象状态

    1.三种状态的解释

    对象分为三种状态:瞬时状态、持久化状态、游离状态.其实我们调用session.save或者session.update或者session.saveOrUpdate只是为了将对象的状态改变为持久态。

    (1)瞬时状态  没有在session缓存中

      也称为临时态或自由态,只是new 出来的在开辟内存空间的对象,不存在OID(对象标识)、也没有与hibernate的session建立关联,在JVM中没有与之对应的数据库记录,失去引用后将会被JVM垃圾回收。瞬时状态的对象在内存中是孤立存在的,在数据库没有与之关联的记录。只是作为信息的载体。

    (2)持久化状态    有id,在session缓存(hibernate的一级缓存)中.(处于持久态的对象的变化会影响到数据库)

      持久态的对象存在OID,也存在session中(session没有close),在数据库中有对应的记录。每条记录只对应一条唯一的持久态对象,需要注意的是持久态的对象是在事务还未提交前变成持久态的,而且处于持久态的对象的变化hinernate会检测到且同步到数据库中。

    (3)游离|托管状态   有id,没有在session缓存中。有ID指的是数据库种有与之对应的ID,如果简单的new 了一个对象且设置了一个ID,但是数据库种没有与之对应的ID还是处于瞬时状态。

      游离状态的对象存在OID,只是没有与之关联的session或者说与之关联的Session已经关闭,处于游离态的对象的变化hibernate监测不到。

    2.区分对象的三种状态

        @Test
        //三种状态特点
        //save方法: 其实不能理解成保存.理解成将瞬时状态转换成持久状态的方法
        //主键自增 : 执行save方法时,为了将对象转换为持久化状态.必须生成id值.所以需要执行insert语句生成.
        //increment: 执行save方法,为了生成id.会执行查询id最大值的sql语句.
        public void fun2(){
            //1 获得session
            Session session = HibernateUtil.openSession();
            //2 控制事务
            Transaction tx = session.beginTransaction();
            //3执行操作
            Customer c = new Customer(); // 没有id, 没有与session关联 => 瞬时状态
            
            c.setCust_name("联想"); // 瞬时状态
            
            session.save(c); // 持久化状态, 有id,有关联
            
            //4提交事务.关闭资源
            tx.commit();
            session.close();// 游离|托管 状态, 有id , 没有关联
            
            
        }

    3.三种状态的状态转换关系

    下面是对三种状态的转换图(两个图均可以理解):

       其实我们调用session.save或者session.update或者session.saveOrUpdate只是为了将对象的状态改变为持久态。

    4.持久态对象的变化能够自动更新到数据库

        @Test
        //三种状态特点
        // 持久化状态特点: 持久化状态对象的任何变化都会自动同步到数据库中.
        public void fun3(){
            //1 获得session
            Session session = HibernateUtil.openSession();
            //2 控制事务
            Transaction tx = session.beginTransaction();
            //3执行操作
            
            Customer c = session.get(Customer.class, 1l);//持久化状态对象
            
            c.setCust_name("XXXXXXXXXX");
            
            //4提交事务.关闭资源
            tx.commit();
            session.close();// 游离|托管 状态, 有id , 没有关联
            
        }

     测试日志:

    Hibernate: 
        select
            customer0_.cust_id as cust_id1_0_0_,
            customer0_.cust_name as cust_nam2_0_0_,
            customer0_.cust_source as cust_sou3_0_0_,
            customer0_.cust_industry as cust_ind4_0_0_,
            customer0_.cust_level as cust_lev5_0_0_,
            customer0_.cust_linkman as cust_lin6_0_0_,
            customer0_.cust_phone as cust_pho7_0_0_,
            customer0_.cust_mobile as cust_mob8_0_0_ 
        from
            cst_customer customer0_ 
        where
            customer0_.cust_id=?
    Hibernate: 
        update
            cst_customer 
        set
            cust_name=?,
            cust_source=?,
            cust_industry=?,
            cust_level=?,
            cust_linkman=?,
            cust_phone=?,
            cust_mobile=? 
        where
            cust_id=?

       结论:我们在上面的代码中并没有手动调用update方法,Hibernate就可以将数据自动更新了。我们调用get方法查询到的对象处于持久态,我们调用set方法改变属性的实施hibernate会自动检测其变化并更新数据

    库,持久化状态的对象有这一特性,其实这一特性依赖于hibernate的一级缓存。

        

    总结:

    一.hibernate中的实体创建规则
      1>对象必须有oid.
      2>对象中的属性,尽量使用包装类型
      3>不使用final修饰类
      4>提供get/set方法....
    二.hibernate主键生成策略(7种)
      increment: 查询最大值.再加1
      identity: 主键自增.
      sequence:Oracle使用的
      hilo: hibernate自己实现自增算法
      native: 根据所选数据库三选一
      uuid: 随机字符串
      assigned: 自然主键.
    三.对象的三种状态
      瞬时状态
        没有id,没有在session缓存中.
      持久化状态
        有id,再session缓存中。
      托管|游离状态
        有id,不在session缓存中.

    持久化: 持久化状态的对象,会在事务提交时,自动同步到数据库中.
    我们使用hibernate的原则.就是将对象转换为持久化状态.

  • 相关阅读:
    架构原则
    基于DDD的Lean Framework
    Javascript 内核Bug
    Back
    Exercise:函数应用于排序
    Lesson5:函数简单应用(二)
    lesson4: 函数简单应用
    lesson3: While 语句简单应用
    range 和len的并用
    lesson2: Python:for语句简单应用
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/9347739.html
Copyright © 2020-2023  润新知