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的原则.就是将对象转换为持久化状态.

原文地址:https://www.cnblogs.com/qlqwjy/p/9347739.html