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