目录
主键的生成策略
主键类型之自然主键和代理主键
1、创建表的时候
- 自然主键:对象本身的一个属性.创建一个人员表,每个人都有一个身份证号.(唯一的)使用身份证号作为表的主键.自然主键.(开发中不会使用这种方式)
- 代理主键:不是对象本身的一个属性.创建一个人员表,为每个人员单独创建一个字段.用这个字段作为主键.代理主键.(开发中推荐使用这种方式)
2、创建表的时候尽量使用代理主键创建表
主键的生成策略
1. increment:适用于short,int,long作为主键.不是使用的数据库自动增长机制.
- Hibernate中提供的一种增长机制.
- 先进行查询 :select max(id) from user;
- 再进行插入 :获得最大值+1作为新的记录的主键.
- 问题:不能在集群环境下或者有并发访问的情况下使用.
2. identity:适用于short,int,long作为主键。但是这个必须使用在有自动增长数据库中.采用的是数据库底层的自动增长机制(MySQL数据库).
- 底层使用的是数据库的自动增长(auto_increment).像Oracle数据库没有自动增长.
3. sequence:适用于short,int,long作为主键.底层使用的是序列的增长方式(Oracle数据库).
- Oracle数据库底层没有自动增长,想自动增长需要使用序列.
4. uuid:适用于char,varchar类型的作为主键.
- 使用随机的字符串作为主键.
5. native:本地策略.根据底层的数据库不同,自动选择适用于该种数据库的生成策略.(short,int,long)
- 如果底层使用的MySQL数据库:相当于identity.
- 如果底层使用Oracle数据库:相当于sequence.
6. assigned:自然主键的生成不用Hibernate管理了.必须手动设置主键.
increment主键策略
由 hibernate 来生成 OID 和维护的,原理是 select max(id) + 1
如果数据表中没有数据,则初始的时候,hibernate 给值是 1,再次给值 2
适用场景:适用于只有单个Hibernate应用进程访问同一个数据库的场合
问题:可能出现多线程冲突问题,两个线程同时查询 max(id),同时+1 ,insert
1.新建项目(具体参照Java网课基础笔记(40))
编写Person.java
package com.feng_01;
public class Person {
private int id;
private String name;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
编写映射文件Person.hbm.xml
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<!-- javabean与表之间的对应关系 -->
<class name="com.feng_01.Person" table="t_person">
<!-- 主键对应 -->
<id name="id" column="id">
<!-- 主键生成策略(与自增长相关) -->
<!-- hibernate框架产生主键,整型-->
<generator class="increment"></generator>
</id>
<!-- 其他字段 -->
<property name="name" column="name"></property>
<property name="age" column="age"></property>
</class>
</hibernate-mapping>
编写映射文件hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<!-- 配置连接信息 -->
<!-- -->
<session-factory>
<!-- 必选配置 -->
<!-- 数据库驱动 -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<!-- 连接的URL -->
<property name="hibernate.connection.url"> jdbc:mysql:///hibernate</property>
<!-- 用户名和密码 -->
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">12345678</property>
<!-- 数据库方言 使用的数据库类型 MySQL -->
<property
name="hibernate.dialect org.hibernate.dialect.MySQLDialect"></property>
<!-- 可选配置 -->
<!-- c3p0供应商 -->
<property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
<!-- 最小连接 -->
<property name="hibernate.c3p0.min_size">5</property>
<!-- 最大连接数 -->
<property name="hibernate.c3p0.max_size">10</property>
<!-- 每120秒检查空闲连接 -->
<property name="hibernate.c3p0.timeout">500000</property>
<!-- 显示sql -->
<property name="hibernate.show_sql ">true</property>
<!-- 格式化sql -->
<property name="hibernate.format_sql ">true</property>
<!-- 自动注释 -->
<property name="use_sql_comments">true</property>
<!-- 自动建表 -->
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- 映射文件 -->
<mapping resource="com/feng_01/Person.hbm.xml" />
</session-factory>
</hibernate-configuration>
编写测试类
package com.feng.test;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.feng.util.HibernateUtil;
import com.feng_01.Person;
public class TestPerson {
Session session;
Transaction transaction;
@Before
public void init() {
session = HibernateUtil.openSession();
transaction = session.beginTransaction();
}
@Test
public void save() {
Person person = new Person();
person.setName("张艺兴");
person.setAge(28);
session.save(person);
}
@After
public void destory() {
transaction.commit();
session.close();
}
}
HibernateUtil.java
package com.feng.util;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtil {
// 使用一个静态块创建SessionFactory
private static final Configuration CONFIG;
private static final SessionFactory FACTORY;
static {
CONFIG = new Configuration().configure();
FACTORY = CONFIG.buildSessionFactory();
}
// 从连接池获取连接
public static Session openSession() {
return FACTORY.openSession();
}
}
运行结果
再次运行
identity主键策略(SQL)
新建数据库名为hibernate_one
修改Person.hbm.xml
<!-- MySQL数据库产生主键 -->
<generator class="identity"></generator>
修改hibernate.cfg.xml
<property name="hibernate.connection.url"> jdbc:mysql:///hibernate_one</property>
运行测试方法
sequence主键策略(ORACLE)
uuid主键策略
用于 String 类型,生成代理主键,采用 uuid (32 位)作为主键值
Hibernate 会产生不重复的 32 位字符串作为主键
Person.java增加uuid属性
private String uuid;
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
修改Person.hbm.xml
<id name="uuid" column="id">
<!-- uuid主键生成策略,可以在多线程的情况下使用 -->
<generator class="uuid"></generator>
</id>
测试结果
报错
把表删除重新运行
assigned主键策略
唯一的一个自然主键设置方式,手动设置主键的值。
修改Person.hbm.xml
<!-- assigned 唯一的自然主键生成方式,需要手动设置 -->
<generator class="assigned"></generator>
在测试类增加方法
@Test
public void save1() {
Person person = new Person();
person.setName("王一博");
person.setAge(28);
//手动设置id
person.setUuid(UUID.randomUUID().toString());
session.save(person);
}
运行结果
小结:
代理主键:
- increment ——由框架产生 整型
- identity ——由SQL数据库产生 整型
- sequence ——由Oracle数据库产生 整型
- uuid ——由框架产生 字符串类型 适合多线
- native ——identity+sequence 根据不同的数据库产生不同的主键
自然主键:
- assigned 手动添加主键(不推荐)
Hibernate的持久化类概念和操作
什么是持久化类
1.持久化类:就是一个Java类(咱们编写的JavaBean),这个Java类与表建立了映射关系就可以成为是持久化类。
- 持久化类 = JavaBean + xxx.hbm.xml
2.Hibernate框架环境中存在持久化类的概念
持久化类的编写规则
- 提供一个无参数 public访问控制符的构造器——底层需要进行反射.
- 提供一个标识属性,映射数据表主键字段——唯一标识OID.数据库中通过主键.Java对象通过地址确定对象.持久化类通过唯一标识OID确定记录
- 所有属性提供public访问控制符的set和get 方法
- 标识属性应尽量使用基本数据类型的包装类型
- 选择实现序列化接口
持久化类示例
编写User.java
package com.feng_01;
import java.io.Serializable;
//选择实现序列化接口
public class User implements Serializable {
// 一个唯一OID
private Integer id;
private String name;
// 类型尽量使用基本类型的包装类
private Integer age;
// 空的构造方法:用于反射生成对象
public User() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
编写User.hbm.xml
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<!-- javabean与表之间的对应关系 -->
<class name="com.feng_01.User" table="t_user">
<!-- 主键对应 -->
<id name="id" column="id">
<generator class="native"></generator>
</id>
<!-- 其他字段 -->
<property name="name" column="name"></property>
<property name="age" column="age"></property>
</class>
</hibernate-mapping>
编写hibernate.cfg.xml
<mapping resource="com/feng_01/User.hbm.xml" />
编写测试类增加测试方法
package com.feng.test;
import java.util.UUID;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.feng.util.HibernateUtil;
import com.feng_01.Person;
import com.feng_01.User;
public class TestUser1 {
Session session;
Transaction transaction;
@Before
public void init() {
session = HibernateUtil.openSession();
transaction = session.beginTransaction();
}
@Test
public void testSave() {
User user=new User();
user.setName("肖战");
user.setAge(28);
session.save(user);
}
@After
public void destory() {
transaction.commit();
session.close();
}
}
Hibernate持久化对象的状态
持久化对象的状态
1. Hibernate的持久化类
- 持久化类:Java类与数据库的某个表建立了映射关系.这个类就称为是持久化类.
- 持久化类 = Java类 + hbm的配置文件
2. Hibernate的持久化类的状态
Hibernate为了管理持久化类:将持久化类分成了三个状态
- 瞬时态:Transient Object
没有持久化标识OID, 没有被纳入到Session对象的管理.
- 持久态:Persistent Object
有持久化标识OID,已经被纳入到Session对象的管理.
- 脱管态:Detached Object
有持久化标识OID,没有被纳入到Session对象的管理.
Hibernate持久化对象的状态的转换
1. 瞬时态 -- 没有持久化标识OID, 没有被纳入到Session对象的管理
- 获得瞬时态的对象
User user = new User()
- 瞬时态对象转换持久态
save()/saveOrUpdate();
- 瞬时态对象转换成脱管态
user.setId(1)
2. 持久态 -- 有持久化标识OID,已经被纳入到Session对象的管理
- 获得持久态的对象
get()/load();
- 持久态转换成瞬时态对象
delete(); --- 比较有争议的,进入特殊的状态(删除态:Hibernate中不建议使用的)
- 持久态对象转成脱管态对象
session的close()/evict()/clear();
3. 脱管态 -- 有持久化标识OID,没有被纳入到Session对象的管理
- 获得托管态对象:不建议直接获得脱管态的对象.
User user = new User();
user.setId(1);
- 脱管态对象转换成持久态对象
update();/saveOrUpdate()/lock();
- 脱管态对象转换成瞬时态对象
user.setId(null);
注意:持久态对象有自动更新数据库的能力
【示例】
编写测试类
public class TestUser2 {
Session session;
Transaction transaction;
// 保存一条数据,查看3个状态
@Test
public void testSave() {
session = HibernateUtil.openSession();
transaction = session.beginTransaction();
// 1.新建User对象
// 创建的对象处于瞬时态,没有OID,不受session管理,与数据库无关
User user = new User();
user.setName("可爱小宝宝");
user.setAge(3);
// 2.保存对象
// 保存的对象处于持久态,有OID,受session管理,与数据库有关
session.save(user);
transaction.commit();
// 3.关闭session
// session关闭后,有OID,不受session管理,与数据库有关
session.close();
}
}
设置断点
debug模式运行
id=null
执行save后,有id
关闭之后,有id
持久态对象自动更新数据库
通过改变查询出来的PO的属性值,来查看一级缓存的更改,通过提交事务,来使用更新的数据。
在测试类增加测试方法
//持久化对象自动更新数据的能力
@Test
public void testUpdate() {
session = HibernateUtil.openSession();
transaction = session.beginTransaction();
User user=session.get(User.class, 1);
user.setName("新鲜的小战");
//session.update(user);
//把内存中修改的数据刷入数据库,不用进行update操作
transaction.commit();
session.close();
}
测试结果
三者的区别总结
对于三者,在session中存在的,就是持久化对象,不存在的就是瞬时或脱管对象。
对于瞬时和脱管对象,有oid(持久化标识)的就是脱管对象,没有的就是瞬时对象。
OID一定与数据库主键一一对应的
是否有持久化标识OID | session是否存在 | 数据库中是否有 | |
瞬时态对象-临时状态 | N | N | N |
持久态对象 | Y | Y | Y/(N,没有提交) |
脱管态对象-游离 | Y | N | Y |
Hibernate的一级缓存
Session对象的一级缓存
1. 什么是缓存?
- 其实就是一块内存空间,将数据源(数据库或者文件)中的数据存放到缓存中.再次获取的时候 ,直接从缓存中获取.可以提升程序的性能!
2. Hibernate框架提供了两种缓存
- 一级缓存 -- 自带的不可卸载的.一级缓存的生命周期与session一致.一级缓存称为session级别的缓存.
- 二级缓存 -- 默认没有开启,需要手动配置才可以使用的.二级缓存可以在多个session中共享数据,二级缓存称为是sessionFactory级别的缓存.
3. Session对象的缓存概述
- Session接口中,有一系列的java的集合,这些java集合构成了Session级别的缓存(一级缓存).将对象存入到一级缓存中,session没有结束生命周期,那么对象在session中存放着
- 内存中包含Session实例 --> Session的缓存(一些集合) --> 集合中包含的是缓存对象!
4. 证明一级缓存的存在,编写查询的代码即可证明
- 在同一个Session对象中两次查询,可以证明使用了缓存
5. Hibernate框架是如何做到数据发生变化时进行同步操作的呢?
- 使用get方法查询User对象
- 然后设置User对象的一个属性,注意:没有做update操作。发现,数据库中的记录也改变了。
- 利用快照机制来完成的(SnapShot)
【示例一】证明一级缓存的存在
编写测试方法
@Test
public void testQuery() {
Session session = HibernateUtil.openSession();
Transaction transaction = session.beginTransaction();
//第一次查询,从数据库获取,发sql
User user1 = session.get(User.class, 2);
System.out.println(user1);
//第二次查询,从缓存中取数据,不发sql,证明hibernate的一级缓存的存在
User user2 = session.get(User.class, 2);
System.out.println(user2);
transaction.commit();
session.close();
}
测试结果(两个地址一样,并且只有一条sql语句)
【示例二】证明一级缓存的存在
编写测试方法
@Test
public void getUser() {
Session session = HibernateUtil.openSession();
Transaction transaction = session.beginTransaction();
User user1=new User();
user1.setName("卫庄");
user1.setAge(30);
//返回oid
Serializable oid=session.save(user1);
transaction.commit();
//不要关闭session,用同一个session去缓存中取数据
User user2 = session.get(User.class, oid);
System.out.println(user2.getName());
session.close();
}
测试结果
一级缓存中的快照机制
编写测试方法
@Test
public void testUpdate() {
Session session = HibernateUtil.openSession();
Transaction transaction = session.beginTransaction();
User user = session.get(User.class, 1);
user.setName("大叔");
//执行提交操作时,hibernate框架去比较缓存区和内容区的内容,如果一致,不做任何操作
//如果不一致,把缓存区的数据更新到数据库,同时把缓存区的数据更新到快照区
transaction.commit();
session.close();
}
测试结果
Hibernate中的事务
1. 什么是事务
- 事务就是逻辑上的一组操作,组成事务的各个执行单元,操作要么全都成功,要么全都失败.
- 转账的例子:粑粑给我转钱,扣钱,加钱。两个操作组成了一个事情!
2. 事务的特性
- 原子性 -- 事务不可分割.
- 一致性 -- 事务执行的前后数据的完整性保持一致.
- 隔离性 -- 一个事务执行的过程中,不应该受到其他的事务的干扰.
- 持久性 -- 事务一旦提交,数据就永久保持到数据库中.
3. 如果不考虑隔离性:引发一些读的问题
- 脏读 -- 一个事务读到了另一个事务未提交的数据.
- 不可重复读 -- 一个事务读到了另一个事务已经提交的update数据,导致多次查询结果不一致.
- 虚读 -- 一个事务读到了另一个事务已经提交的insert数据,导致多次查询结构不一致.
4. 通过设置数据库的隔离级别来解决上述读的问题
- 未提交读:以上的读的问题都有可能发生.
- 已提交读:避免脏读,但是不可重复读,虚读都有可能发生.
- 可重复读:避免脏读,不可重复读.但是虚读是有可能发生.
- 串行化:以上读的情况都可以避免.
5. 如果想在Hibernate的框架中来设置隔离级别,需要在hibernate.cfg.xml的配置文件中通过标签来配置
通过:hibernate.connection.isolation = 4 来配置
取值
- 1—Read uncommitted isolation
- 2—Read committed isolation
- 4—Repeatable read isolation
- 8—Serializable isolation
绑定本地的Session
1. 之前在讲JavaWEB的事务的时候,需要在service业务层使用Connection来开启事务,
- 一种是通过参数的方式传递下去(麻烦)
- 另一种是把Connection绑定到ThreadLocal对象中,获取到同一session
2. 现在的Hibernate框架中,使用session对象开启事务,所以需要来传递session对象,框架提供了ThreadLocal的方式
- 需要在hibernate.cfg.xml的配置文件中提供配置
<property name="hibernate.current_session_context_class">thread</property>
- 重新HibernateUtil的工具类,使用SessionFactory的getCurrentSession()方法,获取当前的Session对象。并且该Session对象不用手动关闭,线程结束了,会自动关闭。
public static Session getCurrentSession(){
return factory.getCurrentSession();
}
注意:想使用getCurrentSession()方法,必须要先配置才能使用。
【示例】
配置hibernate.cfg.xml
<!-- 把session绑定到当前线程,使用getCurrentSession获取当前session -->
<property name="hibernate.current_session_context_class">thread"></property>
在HibernateUtil.java增加
//session绑定到线程
public static Session getCurrentSession() {
return FACTORY.getCurrentSession();
}
编写UserDao.java
public class UserDao {
public void save1(User user1) {
//获取session对象
Session session=HibernateUtil.getCurrentSession();
//不需要开启事务,事务在service层开启
session.save(user1);
}
public void save2(User user2) {
//获取session对象
Session session=HibernateUtil.getCurrentSession();
//不需要开启事务,事务在service层开启
session.save(user2);
}
}
编写UserService.java
public class UserService {
public void save(User user1, User user2) {
//先获取到session对象,session会创建,会把session绑定到当前线程上
Session session=HibernateUtil.getCurrentSession();
//开启事务
Transaction transaction=session.beginTransaction();
try {
//调用dao层
UserDao dao=new UserDao();
dao.save1(user1);
dao.save2(user2);
//提交事务
transaction.commit();
} catch (Exception e) {
// TODO: handle exception
//回滚事务
transaction.rollback();
e.printStackTrace();
}finally {
//释放资源,强调问题,当前线程结束了,session会自动关闭
}
}
}
测试结果
Hibernate框架的查询方式
Criteria查询接口(做条件查询非常合适)
- QBC:Query By Criteria ——按条件进行查询
- QBC查询方式的使用
- 先创建查询的接口
Criteria c=session.createCriteria(User.class);
- 设置查询的条件
criteria.add(Restrictions.gt("age",10);
- 查询数据
List<User> list=criteria.list();
不带条件的查询
User.java增加方法
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", age=" + age + "]";
}
在测试类增加测试方法
//不带条件的查询
@Test
public void test01() {
//1.创建criteria对象
Session session=HibernateUtil.getCurrentSession();
Transaction transaction=session.beginTransaction();
Criteria criteria=session.createCriteria(User.class);
//查询所有数据
List<User> users=criteria.list();
for (User user:users) {
System.out.println(user);
}
transaction.commit();
//session不用关闭
}
测试结果
条件查询
在测试类增加测试方法——模糊查询
// 模糊查询
@Test
public void test02() {
Session session = HibernateUtil.getCurrentSession();
Transaction transaction = session.beginTransaction();
// 创建QBC查询接口
Criteria criteria = session.createCriteria(User.class);
// 设置条件 查询名字中在有“大”的记录
criteria.add(Restrictions.like("name", "%大%"));
// 查询所有数据
List<User> users = criteria.list();
for (User user : users) {
System.out.println(user);
}
transaction.commit();
// session不用关闭
}
测试结果
在测试类增加测试方法——多条件查询
and条件查询
//and条件查询
@Test
public void test03() {
Session session = HibernateUtil.getCurrentSession();
Transaction transaction = session.beginTransaction();
// 创建QBC查询接口
Criteria criteria = session.createCriteria(User.class);
//设置条件 age=30
//第一个参数是Javabean的属性
//第二个参数是要设置的值
criteria.add(Restrictions.eq("age", 30));
//设置第二个条件
criteria.add(Restrictions.eq("name", "卫庄"));
//设置第n个条件
//查询所有数据
List<User> users=criteria.list();
for (User user : users) {
System.out.println(user);
}
transaction.commit();
// session不用关闭
}
测试结果
or条件查询
// or条件查询
@Test
public void test04() {
Session session = HibernateUtil.getCurrentSession();
Transaction transaction = session.beginTransaction();
// 创建QBC查询接口
Criteria criteria = session.createCriteria(User.class);
// 设置条件 查询名字中带有”宝“或者年龄大于29的记录
criteria.add(Restrictions.or(Restrictions.like("name", "%宝%"), Restrictions.gt("age", 29)));
// 查询所有数据
List<User> users = criteria.list();
for (User user : users) {
System.out.println(user);
}
transaction.commit();
// session不用关闭
}
测试结果
betwwen...and条件查询
// between...and
@Test
public void test05() {
Session session = HibernateUtil.getCurrentSession();
Transaction transaction = session.beginTransaction();
// 创建QBC查询接口
Criteria criteria = session.createCriteria(User.class);
// 设置条件 查询年龄在20到30之间到记录
criteria.add(Restrictions.between("age", 20, 30));
// 查询所有数据
List<User> users = criteria.list();
for (User user : users) {
System.out.println(user);
}
transaction.commit();
// session不用关闭
}
测试结果
分页
//分页
@Test
public void test06() {
Session session = HibernateUtil.getCurrentSession();
Transaction transaction = session.beginTransaction();
// 创建QBC查询接口
Criteria criteria = session.createCriteria(User.class);
// 设置条件
//mysql中分页limit index,size
//size每页条数
criteria.setFirstResult(1);//index 索引从0开始,从第二个开始索引
criteria.setMaxResults(2);//每页两条记录
// 查询所有数据
List<User> users = criteria.list();
for (User user : users) {
System.out.println(user);
}
transaction.commit();
// session不用关闭
}
测试结果