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