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

时间:2023-12-24 13:42:49

  

一、  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)-------开发人员自己确保主键唯一。  

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

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();//释放资源
}

数据库结果:

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

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

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.三种状态的状态转换关系

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

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

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

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

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