入门教程
Hibernate简介
(度娘解释)
-
Hibernate是一个开放源代码的对象关系映射(ORM)框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。
-
什么意思呢?其实,直白点说就是,hibernate框架会把你创建的Java实体类对象自动映射成数据库表,该框架可以自动建表,进行crud操作,程序员不必亲自操作数据库。
如下:将一个User实体类映射成t_user数据库表。
什么是ORM?
- 对象关系映射(Object Relational Mapping,简称ORM)是通过使用描述对象和数据库之间映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据库中。直白说就是一种将实体类映射成对应的数据库表格的设计思想。
Hibernate怎么开始呢?
一、进入官网
二、点击orm
三、点击Documentation,选择版本
四、Getting Started Guide
五、慢慢看吧。。。
Hibernate“三步走”
我使用的环境是:JDK11 + MySQL8.0 + Hibernate5.4.18
导入jar包
这里可以选择将releases of Hibernate下载到本地再导入jar包,也可以使用maven项目直接引入。为了学习方便,我选择下载!
- 解压后的lib目录下,有一个required文件夹
- 创建一个普通的Java项目,将该目录下的jar包全部导入
- 此外,还需要导入MySQL的jdbc驱动包
- 最后,Bulid Path一下就哦了!
创建实体类
-
创建实体类时,需要注意:实体类中必须有一个唯一标识该实体的字段,比如:id,会作为数据库表的id主键约束。
public class User {
private int id;
private String username;
private String password;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
编写配置文件
①编写配置实体类和数据库表的映射关系的文件
- 配置文件要求
名称 | 位置 | 后缀 | 建议 |
---|---|---|---|
无要求 | 无要求 | .hbm.xml | 位置:实体类所在包;名称:实体类名.hbm.xml |
- 配置文件写法
步骤 | 目标 | 备注 | |
---|---|---|---|
1 | 引入xml约束 | 在hibernate-core-5.4.18.Final.jar 包下的org.hibernate 包中,给出了相应的xml配置文件,从中可以找到对应的xml约束,找mapping 类型的即可,我这里使用的时dtd 约束。如果引入后没有提示,可以下载下来引入本地dtd! | |
2 | 配置类和表的映射 | 使用<class> 标签,name :实体类全限定名称,table :数据库表明 | |
3 | 配置实体类id和表id字段对应 | 主键生成策略:<generator class="native"></generator> ,native: 根据使用的数据库自动选择主键生成策略 | <id> column属性可以省略,省略时默认和name一致 |
4 | 配置其他属性和表的字段对应 | <property/> 标签中,column属性可以省略,省略时默认和name一致 |
- 完成后的配置文件
<?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">
<hibernate-mapping>
<!-- 配置类和表的映射
name: 实体类的全限定名称
table: 数据库表名
-->
<class name="demo.test.pojo.User" table="t_user">
<!-- 配置实体类id和数据库表id对应
name: 实体类属性名
column: 数据库表id名
-->
<id name="id" column="id">
<!-- 设置数据库id增长策略
native: 根据使用的数据库自动选择主键生成策略
-->
<generator class="native"></generator>
</id>
<!-- 配置其他属性和数据库列名映射关系 -->
<property name="username" column="username"></property>
<property name="password" column="password"></property>
</class>
</hibernate-mapping>
②编写hibernate核心配置文件
- 配置文件要求
名称 | 位置 | 后缀 |
---|---|---|
必须为hibernate.cfg.xml | 必须在src 下 | .cfg.xml |
- 配置文件写法
这个配置文件中所有要配置的内容,在
hibernate-release-5.4.18.Final\project\etc
中的hibernate.properties
可以找到。
- 要注意,需要提前创建好数据库,hibernate可以自动创建表,但是不能自动创建数据库!!!而且配置内容要在
<session-factory>
标签内!!!
步骤 | 目标 | 备注 |
---|---|---|
1 | 引入xml约束 | 在hibernate-core-5.4.18.Final.jar 包下的org.hibernate 包中,给出了相应的xml配置文件,从中可以找到对应的xml约束,找configuration 类型的即可,我这里使用的时dtd 约束。如果引入后没有提示,可以下载下来引入本地dtd! |
2 | 配置数据库信息 | 注意MySQL8.0和之前版本的Driver路径不一样哦! |
3 | 配置hibernate信息 | 可选,应当开启hibernate的自动创建表功能hibernate.hbm2ddl.auto ,值为update :无表创建有表更新!不开启是不能自动创建表的哦! |
4 | 配置映射文件路径 | hibernate只会加载核心配置文件,不会加载其他映射文件,所以需要配置。友情提示:配置路径时,eclipse中按住Ctrl点击路径,如果正常跳转,说明配置成功! |
- 完成后的配置文件
<?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.cj.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">leidada</property>
<!-- 第二部分:配置hibernate信息 -->
<!-- 打印sql语句 -->
<property name="hibernate.show_sql">true</property>
<!-- 对sql语句进行格式化 -->
<property name="hibernate.format_sql">true</property>
<!-- 开启表的自动更新
update: 无表创建,有表更新
-->
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- 如果使用分页,需要配置数据库方言,使其能够识别各自的语言 -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- 第三部分:配置映射文件 -->
<mapping resource="demo/test/pojo/User.hbm.xml"/>
</session-factory>
</hibernate-configuration>
- 如果报错
WARN: GenerationTarget encountered exception accepting command : Error executing DDL "(。。。)
- 修改为以下内容
这里主要是因为数据库版本问题!!!
<!-- 如果使用分页,需要配置数据库方言,使其能够识别各自的语言
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>-->
③至此,配置完毕,测试添加操作
- 步骤
步骤 |
---|
1:加载核心配置文件 |
2:创建SessionFactory对象,这一步会创建表 |
3:创建Session对象,类似数据库连接中的Connection对象 |
4:开启事务 |
5:具体逻辑,crud操作 |
6:提交事务 |
7:关闭资源 |
- 测试代码(注意别导错包哦!)
public class DemoTest {
public static void main(String[] args) {
// 加载核心配置文件
Configuration cfg = new Configuration();
cfg.configure();
// 创建SessionFactory对象
SessionFactory factory = cfg.buildSessionFactory();
// 创建Session对象
Session session = factory.openSession();
// 开启事务
Transaction tx = session.beginTransaction();
// 具体逻辑,添加操作
User user = new User();
user.setUsername("张三");
user.setPassword("123456");
session.save(user);
// 提交事务
tx.commit();
// 关闭资源
session.close();
factory.close();
}
}
- 效果
Ⅰ、数据库被创建
Ⅱ、数据被添加
Ⅲ、目录结构(别在意小红×,注释标签引起的。。。)
Hibernate主键生成策略
生成策略 | 作用 |
---|---|
increment | 用于long、short、int类型,由Hibernate自动以递增的方式生成唯一标识符,每次增量为1。只有当没有其它进程向同一张表中插入数据时才可以使用,不能在集群环境下使用。适用于代理主键。 |
identity | 采用底层数据库本身提供的主键生成标识符,条件是数据库支持自动增长类型数据。在DB2、MySQL、MS SQL Server、Sybase和HypersionicSQL数据库中可以是使用该生成器,适用于代理主键。 |
sequence | Hibernate根据底层数据库序列生成标识符,条件是数据库支持序列。适用于代理主键。 |
native | 根据底层数据库对自动生成标识符的能力来选择identity、sequence、hilo三种生成器中的一种,适合跨数据库平台开发。适用于代理主键。 |
uuid | Hibernate采用128位的UUID算法来生成标识符。能够在网络环境中生成唯一的字符串标识符,其UUID被编码为一个长度为32位的十六进制字符串。占用空间大,要求id类型位String。适用于代理主键。 |
assigned | 由Java程序负责生成标识符,如果不指定id元素的gencrator属性,则默认使用该主键生成策略。适用于自然主键 |
hilo | 高低位方式(high low),需要一张额外的表保存hi的值,且至少由一条记录。 |
实体类操作
crud操作
操作 | 代码 |
---|---|
create | save(Object) |
Retrieve | get(Class, int id) |
update | 1、根据id查询出来;2、重新设置查询出来的对象;3、调用:update(Object) |
delete | 1、根据id查询;2、delete(Object) ;或者:1、创建实体对象,设置id;2、delete(Object) |
实体类对象的三种状态
状态 | 描述 |
---|---|
瞬时态 | 对象里面没有id值,且和session也没有关系(不是查询出来的) |
持久态 | 对象里面设置了id,且和session建立了关系(是session查询出来的),也就是在数据库中存储的数据 |
托管态 | 对象设置了id,但和session没有关系 |
saveOrUpdate方法
- 如果实体类是瞬时态,该方法执行新增;
- 如果实体类是托管态,该方法执行修改;
- 如果实体类是持久态,该方法执行修改;
hibernate缓存
什么是缓存?
- 缓存就是将访问过的数据库数据等保存在系统的内存中,不需要使用流的方式就可以直接获取到,提高了读取效率。也减少了对数据库的访问。
hibernate的一种优化方式。
一级缓存
-
默认是打开的
-
使用范围:
session的范围,从session创建到关闭。
- 存储的数据必须是持久态数据
验证一级缓存
- 根据id查询,返回对象
第一次查询会访问数据库,控制台会打印sql语句
- 再次根据id查询,返回对象
第二次查询会查询缓存,控制台不会打印sql语句
- 代码
public class DemoTest {
public static void main(String[] args) {
// 加载核心配置文件
Configuration cfg = new Configuration();
cfg.configure();
// 创建SessionFactory对象
SessionFactory factory = cfg.buildSessionFactory();
// 创建Session对象
Session session = factory.openSession();
// 开启事务
Transaction tx = session.beginTransaction();
// 查询操作
System.out.println("第一次查询");
User user = session.get(User.class, 1);
System.out.println(user.getUsername());
System.out.println("第二次查询");
User user1 = session.get(User.class, 1);
System.out.println(user1.getUsername());
// 提交事务
tx.commit();
// 关闭资源
session.close();
factory.close();
}
}
- 结果
一级缓存特性
- 持久态的数据会自动更新,就算不调用update方法或者save方法;
过程:一级缓存会有一个快照区(副本),当我们修改持久态对象的值时,会同时更新一级缓存中的内容,但是不修改快照区内容。最后,提交事务时,会比较一级缓存和快照区的内容,如果不同,会把一级缓存中的数据更新到数据库中,相同则不会更新到数据库。
- 代码
User user = session.get(User.class, 1);
user.setUsername("李四");
// 提交事务
tx.commit();
- 结果
二级缓存
现在使用redis替代了
- 默认不打开,需要配置
- 使用范围:sessionFactory范围
hibernate的事务操作
事务特性:
- 原子性
- 一致性
- 隔离性
- 持久性
事务不考虑隔离性产生问题:
- 脏读 - Read uncommitted isolation
- 不可重复读 - Read committed isolation
- 幻读 - Repeatable read isolation
hibernate配置MySQL隔离级别
- 在核心配置文件中:
1 -> Read uncommitted isolation
2 -> Read committed isolation
4 -> Repeatable read isolation(mysql默认)
8 -> Serializable isolation
<property name="hibernate.connection.isolation">4</property>
事务代码规则写法
try {
// 开启事务
// 提交事务
} catch () {
// 回滚事务
} finally {
// 关闭事务
}
hibernate绑定session
底层是threadLocal实现的
实现本地线程绑定session
- 获取本地线程绑定session
步骤 | 代码 |
---|---|
在hibernate核心配置文件中配置 | <property name="hibernate.current_session_context_class">thread</property> |
调用sessionFactory的方法 | factory.getCurrentSession(); |
- 这时就不需要进行手动关闭session
hibernate查询API
Query对象
(1)不需要写sql语句,但是要写hql(hibernate query language)语句
(2)hql和sql区别:
sql操作数据库表和字段
hql操作实体类和属性
查询所有
- hql语句:
from 实体类名称
- 步骤
步骤 |
---|
创建Query对象 |
调用方法获取结果 |
- 代码
// 创建Query对象
Query query = session.createQuery("from User");
// 查询所有
List<User> list = query.list();
for(User u : list) {
System.out.println(u.getUsername());
}
Criteria对象
不需要写任何语句,直接调用方法即可
- 步骤
步骤 |
---|
创建Criteria对象 |
调用方法获取结果 |
- 代码
// 创建Criteria
Criteria c = session.createCriteria(User.class);
// 查询全部
List<User> list = c.list();
SQLQuery对象
调用底层sql进行查询,语句是原来的sql查询语句
select * from 表名
- 步骤
步骤 |
---|
创建SQLQuery对象 |
设置返回结果的形式 |
调用方法获取结果 |
- 代码
// 创建SQLQuery对象
SQLQuery sqlQuery = session.createSQLQuery("select * from t_user");
// 调用方法实现,返回结果为一个个数组结果
List<Object[]> list = sqlQuery.list();
for(Object[] o : list) {
System.out.println(Arrays.toString(o));
}
或者:
// 创建SQLQuery对象
SQLQuery sqlQuery = session.createSQLQuery("select * from t_user");
// 设置返回结果中每部分的形式
sqlQuery.addEntity(User.class);
// 调用方法获取结果
List<User> list = sqlQuery.list();
hibernate多表操作
多表关系
一对多(联系人和客户)
客户是一,联系人是多
一个客户对应多个联系人,一个联系人属于一个客户
- 一对多建表:外键
一的一方为主表,多的一方为副表。
多对多(用户和角色)
一个用户对应多个角色,一个角色对应多个用户
- 创建第三张表维护多对多关系
一对多操作
步骤 | 备注 |
---|---|
1、导jar包 | |
2、新建客户、联系人实体类 | 新建实体类时,如果要重写toString方法,不要把第3步创建的属性也包括进去哦,否则会栈溢出!!! |
3、让两个实体类之间互相表示 | 在实体类中添加属性,表示:一个客户有多个联系人,一个联系人属于一个客户。 hibernate要求使用set 表示多的数据。 |
4、配置映射关系(详见代码) | Ⅰ、每个实体类都要有对应的映射文件;Ⅱ、完成基本配置;Ⅲ、配置对应的一对多关系 |
5、创建核心配置文件(详见代码) | Ⅰ、配置数据库连接;Ⅱ、配置hibernate相关;Ⅲ、引入映射配置文件 |
6、测试 |
一对多映射配置
- 客户实体类
使用
Set
集合表示多个联系人
public class Customer {
// 客户id
private Integer cid;
// 客户名称
private String cName;
// 客户性别
private String cSex;
// 客户级别
private String cLevel;
// 联系电话
private String cPhone;
// 多个联系人(重点)
private Set<LiaisonMan> lmans = new HashSet<>();
// 这里需要生成所有属性的get、set方法
}
- 客户映射文件
文件名为:
Customer.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">
<hibernate-mapping>
<class name="demo.test.pojo.Customer" table="t_customer">
<id name="cid" column="cid">
<generator class="native"></generator>
</id>
<property name="cName" column="cName"></property>
<property name="cSex" column="cSex"></property>
<property name="cLevel" column="cLevel"></property>
<property name="cPhone" column="cPhone"></property>
<!-- 多个联系人
name: 联系人集合属性名
key->column:外键名称
class: 联系人所在的类
-->
<set name="lmans">
<!-- hibernate中双向维护外键-->
<key column="clid"></key>
<one-to-many class="demo.test.pojo.LiaisonMan"/>
</set>
</class>
</hibernate-mapping>
- 联系人实体类
使用客户实体类的实例表示一个客户;
public class LiaisonMan {
// 联系人id
private Integer lid;
// 联系人姓名
private String lname;
// 联系人性别
private String lsex;
// 联系人电话
private String lphone;
// 属于一个客户(重点)
private Customer customer;
// 这里需要生成所有属性的get、set方法
}
- 联系人映射文件
文件名为:
LiaisonMan.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">
<hibernate-mapping>
<class name="demo.test.pojo.LiaisonMan" table="t_liaisonMan">
<id name="lid" column="lid">
<generator class="native"></generator>
</id>
<property name="lname" column="lname"></property>
<property name="lsex" column="lsex"></property>
<property name="lphone" column="lphone"></property>
<!-- 所属客户
name: 客户属性名、
class: 客户类
column: 外键名称
-->
<many-to-one name="customer" class="demo.test.pojo.Customer" column="clid"></many-to-one>
</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.cj.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate?serverTimezone=UTC</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">leidada</property>
<!-- 第二部分:配置hibernate信息 -->
<!-- 打印sql语句 -->
<property name="hibernate.show_sql">true</property>
<!-- 对sql语句进行格式化 -->
<property name="hibernate.format_sql">true</property>
<!-- 开启表的自动更新
update: 无表创建,有表更新
-->
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- 如果使用分页,需要配置数据库方言,使其能够识别各自的语言 -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
<!-- 第三部分:配置映射文件 -->
<mapping resource="demo/test/pojo/Customer.hbm.xml"/>
<mapping resource="demo/test/pojo/LiaisonMan.hbm.xml"/>
</session-factory>
</hibernate-configuration>
- 测试代码
static中的代码在类最初被加载时就会运行,而SessionFactory的创建代表着数据库表的创建。
public class HibernateTest {
private static Configuration cfg = null;
private static SessionFactory factory = null;
static {
// 读取配置文件
cfg = new Configuration();
cfg.configure();
// 获取SessionFactory
factory = cfg.buildSessionFactory();
}
public static void main(String[] args) {
}
}
- 测试效果
成功创建两张数据库表,说明配置是正确的!!!
一对多级联保存
- 添加一个客户,为这个客户添加多个联系人
- 复杂实现方式
public class OneToManySaveTest {
private static Configuration cfg = null;
private static SessionFactory factory = null;
static {
// 读取配置文件
cfg = new Configuration();
cfg.configure();
// 获取SessionFactory
factory = cfg.buildSessionFactory();
}
public static void main(String[] args) {
Session session = null;
Transaction tx = null;
try {
session = factory.openSession();
// 开启事务
tx = session.beginTransaction();
// 级联保存
// 1、添加一个客户,为这个客户添加两个联系人
Customer c = new Customer();
c.setcName("小李");
c.setcSex("男");
c.setcLevel("vip");
c.setcPhone("123456");
LiaisonMan l_1 = new LiaisonMan();
l_1.setLname("A");
l_1.setLsex("女");
l_1.setLphone("123789");
LiaisonMan l_2 = new LiaisonMan();
l_2.setLname("B");
l_2.setLsex("男");
l_2.setLphone("789789");
// 2、建立 客户-联系人 的关系
c.getLmans().add(l_1);
c.getLmans().add(l_2);
l_1.setCustomer(c);
l_2.setCustomer(c);
// 3、保存
session.save(c);
session.save(l_1);
session.save(l_2);
// 提交事务
tx.commit();
}catch(Exception e) {
e.printStackTrace();
// 事务回滚
tx.rollback();
}finally {
// 关闭资源
session.close();
factory.close();
}
}
}
-
效果
-
简便方式(-----------------)
步骤 |
---|
需求:根据客户添加联系人 |
1、在客户的映射文件中的<set> 标签中进行配置,添加save-update |
2、将联系人放入到客户中即可,不需要将客户再放入到联系人中,且最后只需要保存客户即可 |
和上面代码的不同在于:代码中的2、和3、 |
配置内容:
代码:
- 效果
一对多级联删除
- 删除一个客户,同时删除这个客户的所有联系人
步骤 |
---|
1、在客户的映射文件中的<set> 标签中进行配置,添加delete |
2、先查出要删除的用户,再调用方法删除 |
配置如下:
代码:
- 效果
一对多级联修改
- 修改前
步骤 |
---|
需求:将联系人A的所属客户修改为小苏,也就是clid修改为2 |
1、查询出需要被修改的联系人A |
2、查询出被修改为的客户小苏,(把小李修改为小苏,所以查询出小苏来) |
3、将A和小苏关联 |
- 代码
// 级联修改 - 将A的客户由小李修改为小苏
// 1、根据id查询 客户-小苏
Customer customer = session.get(Customer.class, 2);
// 2、根据id查询联系人A
LiaisonMan A = session.get(LiaisonMan.class, 5);
// 3、设置持久态对象值
// 把联系人放入客户中
customer.getLmans().add(A);
// 把客户放到联系人中
A.setCustomer(customer);
- 修改后效果
-
inverse
属性
存在问题:
由于hibernate中的外键是由双方共同维护的,所以,在下面过程中可以看到,对外键进行了两次修改,但我们在这个过程中只需要修改一次联系人和外键即可,第二次对外键的修改可以不进行。考虑到效率,要进行优化。
解决方式:
让其中的一方不去维护外键。一对多中,让“一”的这方放弃维护外键。所以,我们让customer放弃对外键的维护!!!
步骤 |
---|
在customer的映射配置文件中的<set> 标签中,添加inverse="true" |
效果:
只维护外键一次
多对多操作
步骤 | 备注 |
---|---|
1、导jar包 | |
2、新建用户、角色 实体类 | |
3、让两个实体类之间互相表示 | Ⅰ、用户中使用set集合表示所有角色;Ⅱ、角色中使用set集合表示所有用户; |
4、配置映射关系(详见代码) | Ⅰ、基本配置;Ⅱ、配置多对多关系,使用<set> 标签------(配置的内容是:①当前实体类在第三张表中的外键名称<key> ;②要关联多个关系的实体类的类名+在第三张表中的外键名称<many-to-many );注意要对应上哈!!! |
5、创建核心配置文件(详见代码) | |
6、测试 |
多对多映射配置
- 用户实体类
public class User {
// 用户id
private Integer uid;
// 用户名
private String username;
// 密 码
private String password;
// 多个角色
private Set<Role> roles = new HashSet<>();
// 下面请给出所有属性的get和set方法
}
- 用户映射文件
文件名为:
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>
<class name="demo.test.pojo.User" table="t_user">
<id name="uid" column="uid">
<generator class="native"></generator>
</id>
<property name="username" column="username"></property>
<property name="password" column="password"></property>
<!-- 多个角色
name: 角色集合名称
table: 第三张表的名称
-->
<set name="roles" table="t_user_role">
<!-- 配置当前user在第三张表中的外键名称 -->
<key column="user_id"></key>
<!-- 配置多个角色所在的类,以及角色在第三张表中的外键名称 -->
<many-to-many class="demo.test.pojo.Role" column="role_id"></many-to-many>
</set>
</class>
</hibernate-mapping>
- 角色实体类
public class Role {
// 角色id
private Integer rid;
// 角色名
private String rolename;
// 多个用户
private Set<User> users = new HashSet<>();
// 下面请给出所有属性的get和set方法
}
- 角色映射文件
文件名为:
Role.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">
<hibernate-mapping>
<class name="demo.test.pojo.Role" table="t_role">
<id name="rid" column="rid">
<generator class="native"></generator>
</id>
<property name="rolename" column="rolename"></property>
<!-- 多个用户
name: 用户集合名称
table: 第三张表的名称 -->
<set name="users" table="t_user_role">
<!-- 配置当前角色在第三张表中的外键名称 -->
<key column="role_id"></key>
<!-- 配置多个用户所在的类,以及用户在第三张表中的外键名称 -->
<many-to-many class="demo.test.pojo.User" column="user_id"></many-to-many>
</set>
</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.cj.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate?serverTimezone=UTC</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">leidada</property>
<!-- 第二部分:配置hibernate信息 -->
<!-- 打印sql语句 -->
<property name="hibernate.show_sql">true</property>
<!-- 对sql语句进行格式化 -->
<property name="hibernate.format_sql">true</property>
<!-- 开启表的自动更新
update: 无表创建,有表更新
-->
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- 如果使用分页,需要配置数据库方言,使其能够识别各自的语言 -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
<!-- 第三部分:配置映射文件 -->
<mapping resource="demo/test/pojo/Role.hbm.xml"/>
<mapping resource="demo/test/pojo/User.hbm.xml"/>
</session-factory>
</hibernate-configuration>
- 测试代码
SessionFactory
创建意味着数据库表的创建。
public class HibernateTest {
private static Configuration cfg = null;
private static SessionFactory factory = null;
static {
// 读取配置文件
cfg = new Configuration();
cfg.configure();
// 获取SessionFactory
factory = cfg.buildSessionFactory();
}
public static void main(String[] args) {
}
}
- 测试效果
共创建了三张表,说明配置成功!!!
多对多级联保存
- 添加用户,同时添加角色
- 直接使用简便方式,在用户配置文件中的
<set>
标签中添加save-update
属性
- 编写测试代码
1、两个用户对象
2、三个角色对象
public class HibernateTest {
private static Configuration cfg = null;
private static SessionFactory factory = null;
static {
// 读取配置文件
cfg = new Configuration();
cfg.configure();
// 获取SessionFactory
factory = cfg.buildSessionFactory();
}
public static void main(String[] args) {
Session session = null;
Transaction tx = null;
try {
// 创建session
session = factory.openSession();
// 开启事务
tx = session.beginTransaction();
// 级联保存
// 1、创建用户
User u1 = new User();
u1.setUsername("小A");
u1.setPassword("123456");
User u2 = new User();
u2.setUsername("小B");
u2.setPassword("123456");
// 2、创建角色
Role r1 = new Role();
r1.setRolename("老板");
Role r2 = new Role();
r2.setRolename("经理");
Role r3 = new Role();
r3.setRolename("员工");
// 3、建立联系:
// u1 -> r1/r2
// r2 -> r2/r3
u1.getRoles().add(r1);
u1.getRoles().add(r2);
u2.getRoles().add(r2);
u2.getRoles().add(r3);
// 4、保存用户
session.save(u1);
session.save(u2);
// 提交事务
tx.commit();
}catch(Exception e) {
e.printStackTrace();
// 事务回滚
tx.rollback();
}finally {
// 关闭资源
session.close();
factory.close();
}
}
}
- 测似效果
多对多级联删除
步骤 |
---|
1、在客户的映射文件中的<set> 标签中进行配置,添加delete |
2、先查出要删除的用户,再调用方法删除 |
- 代码
- 测试效果
问题:可以看到,再删除第一个用户时,将两个用户共同的角色也给删除掉了。所以,多对多情况下一般不进行级联删除。
维护第三张表关系
- 用户和角色的多对多关系,是通过维护第三张表来进行的;
关系图
小A:老板、经理
小B:经理、员工
- 对持久态数据进行修改后,可以不调用save或update方法也会进行数据库修改,因为hibernate一级缓存的特性!!!
- 操作:让某个用户有某个角色
步骤 |
---|
1、根据id查询用户和角色 |
2、把角色放到用户的角色set集合中即可 |
目的:小B也要当老板!!! |
代码
效果:
- 操作:让某个用户没有某个角色
步骤 |
---|
1、根据id查询用户和角色 |
2、把该角色从用户的角色set集合中去掉即可 |
目的:小B不要当经理!!! |
代码:
效果:
Hibernate查询方式
衔接上面***多对多查询***的案例进行总结!
对象导航查询
- 根据id查询用户,再查询出该用户的所有角色信息
查询小A以及小A的所有角色信息
OID查询
- 根据id查询某一条记录,返回对象
根据id查询小A信息
hql查询
hql操作的是对象及其属性;sql操作的是数据库表以及其字段;
使用:Query对象及其方法。
- 查询所有
语句:
from 实体类名
- 条件查询
语句:
from 实体类名 as 别名 where 别名.实体类属性名=? and 别名.实体类属性名=?
,注意:?
代表占位符,下标从0开始!
// 条件查询
Query query = session.createQuery("from User as u where u.uid=? and u.username=?");
// 设置占位符的值
query.setParameter(0, 1);
query.setParameter(1, "小A");
// 调用方法
List<User> list = query.list();
- 模糊查询
语句:
from 实体类名 where 实体类属性名 like ?
// 模糊查询
Query query = session.createQuery("from User where username=?");
// 设置占位符的值
query.setParameter(0, "%A%");
// 调用方法
List<User> list = query.list();
- 排序查询
语句:
from 实体类名 order by 实体类属性名称 asc/desc
// 排序查询
Query query = session.createQuery("from User order by uid asc");
// 调用方法
List<User> list = query.list();
- 分页查询
不能写
limit
,而是使用Query对象封装的两个方法实现:
// 分页查询
Query query = session.createQuery("from User");
// 设置分页数据
// 1、设置开始位置
query.setFirstResult(0);
// 2、设置每页记录数
query.setMaxResults(2);
// 调用方法
List<User> list = query.list();
- 投影查询
什么是投影查询?不查询全部,而是查询数据库中的部分字段。
语句:select 实体类属性名,... from 实体类名
,注意,select后不能写*
;
// 投影查询
Query query = session.createQuery("select username from User");
// 调用方法:泛型不再是实体类哦!
List<Object> list = query.list();
- 聚合函数的使用
查询表记录数量:
语句:select count(*) from User
// 聚集函数的使用:sum、count、avg、max、min等
Query query = session.createQuery("select count(*) from User");
// 调用方法:返回值不再是list
Object result = query.uniqueResult();
System.out.println(result);
QBC查询
使用:
Criteria
对象及其方法,不用写语句
- 查询全部
// 查询所有
Criteria c = session.createCriteria(User.class);
// 调用方法
List<User> list = c.list();
- 条件查询
Restrictions
类的使用
// 条件查询
Criteria c = session.createCriteria(User.class);
// 设置条件
c.add(Restrictions.eq("username", "小A"));
//调用方法
List<User> list = c.list();
- 排序查询
Order
类的使用
// 排序查询
Criteria c = session.createCriteria(User.class);
// 设置排序规则
c.addOrder(Order.asc("uid"));
//调用方法
List<User> list = c.list();
- 分页查询
开始位置计算公式:(当前页-1)* 每页记录数
// 分页查询
Criteria c = session.createCriteria(User.class);
// 设置分页信息
c.setFirstResult(0);//开始位置
c.setMaxResults(2);//每页记录数
//调用方法
List<User> list = c.list();
- 统计查询
统计表中记录数,
Projections
的使用
//统计查询
Criteria c = session.createCriteria(User.class);
//设置统计条件
c.setProjection(Projections.rowCount());
//调用方法得到结果
Object obj = c.uniqueResult();
long l = (long) obj; //不能直接转int
int i = (int) l;
System.out.println(i);
- 离线查询
离线查询:不使用session对象也能进行查询;
DetachedCriteria
类的使用
// 离线查询
DetachedCriteria dc = DetachedCriteria.forClass(User.class);
// 最终执行的时候才需要得到session
Criteria c = dc.getExecutableCriteria(session);
//调用方法
List<User> list = c.list();
System.out.println(list.get(0).getUsername());
本地sql查询
- SQLQuery对象,使用普通sql实现查询
HQL多表查询
在一对多条件下,假装:User对Role是一对多关系,一个User对应多个Role,Role中有外键uid,关联User的uid。
内连接
普通sql:
select * from User u, Role r where u.uid = r.uid
---------或者:select * from User u inner join Role r on u.uid = r.uid
使用
inner join
HQL:from 实体类名 inner join (实体类中多的哪一方set集合的属性名)
返回结果的list中,每部分是数组形式!!!
// 内连接
Query query = session.createQuery("from User u inner join u.roles");
//调用方法
List<User> list = query.list();
左外连接
左外连接查询的是:左表全部,右表符合条件的
普通sql:select * from User u left join Role r on u.uid = r.uid
使用
left outer join
HQL:from 实体类名 left outer join(实体类中多的哪一方set集合的属性名)
返回结果的list中的每部分是数组形式!!!
// 左外连接
Query query = session.createQuery("from User u left outer join u.roles");
//调用方法
List<User> list = query.list();
右外连接
右外连接查询的是:右表全部,左表符合条件的
普通sql:select * from User u right join Role r on u.uid = r.uid
使用
right outer join
HQL:from 实体类名 right outer join (实体类中多的哪一方set集合的属性名)
返回结果的list中的每部分是数组形式!!!
// 左外连接
Query query = session.createQuery("from User u right outer join u.roles");
//调用方法
List<User> list = query.list();
迫切内连接(独有)
迫切内连接和内连接的底层实现是一样的;
区别:
1、普通内连接返回list的每部分是数组;
2、迫切内连接返回list的每部分是对象类型;
使用
inner join fetch
HQL:from 实体类名 inner join fetch(实体类中多的哪一方set集合的属性名)
返回结果的list中,每部分是对象形式!!!
// 内连接
Query query = session.createQuery("from User u inner join fetch u.roles");
//调用方法
List<User> list = query.list();
迫切左外连接(独有)
和左外连接区别:返回结果是数组形式还是对象形式
使用
left outer join fetch
HQL:from 实体类名 left outer join fetch(实体类中多的哪一方set集合的属性名)
返回结果的list中每部分是对象形式!!!
// 左外连接
Query query = session.createQuery("from User u left outer join fetch u.roles");
//调用方法
List<User> list = query.list();
Hibernate检索策略
检索策略的概念
立即检索(查询)
调用get方法,马上发送语句查询;
// 根据id查询 - 立即查询
User user = session.get(User.class, 1);
System.out.println(user.getUsername());
延迟检索(查询)
- 类级别延迟
调用load方法,不会马上发送语句查询,只有等到想获取对象中,除id属性的其他属性时,才会发送语句查询。也就是说,只有用到对象中除id的其他属性时才会查询。
//类级别延迟
//根据id查询 - 延迟查询(可用debug模式查看)
// 返回的结果只有id值
User user = session.load(User.class, 1);
System.out.println(user.getUid());
System.out.println("开始发送语句,请查看控制台输出");
// 只有想得到除id的值外的其他数据时,这时才发送查询语句
System.out.println(user.getUsername());
- 关联级别延迟
只有用到关联类的数据时,才查询其关联类的数据;
比如:查询用户数据后,用到其角色数据时,才查询其关联的所有角色的信息。
get
方式默认使用了关联级别延迟
//关联级别延迟
//根据id查询 - 延迟查询(可用debug模式查看)
//返回的结果只有id值
User user = session.get(User.class, 1);
System.out.println(user.getUid());
System.out.println("开始发送语句,请查看控制台输出");
//只有想得到除id的值外的其他数据时,这时才发送查询语句
System.out.println(user.getRoles());
检索策略<set>
使用配置选择是否进行关联级别延迟
fetch | lazy | 策略 |
---|---|---|
join | false | 采用迫切左外连接检索 |
join | true | 采用迫切左外连接检索 |
join | extra | 采用迫切左外连接检索 |
select | false | 采用立即检索 |
select | true | 采用延迟检索 |
select | extra | 采用极其延迟检索,要什么查什么,绝不多查,比如:需要得到list的size时,hibernate会查询count()值 |
subselect | false/true/extra | 嵌套子查询 |
批量抓取
- 使用场景:
批量获取关联查询的数据时,可以进行优化。
比如:遍历用户,获取所有用户的所有角色信息时,因为默认使用延迟加载,在获取角色信息时会频繁发送sql查询语句。使用批量抓取,可以减少查询语句发送的次数,提高效率。
在
<set>
中配置batch-size
属性,其值越大效率越高
步骤 |
---|
配置<set> |
// 查询所有
Criteria c = session.createCriteria(User.class);
// 调用方法
List<User> list = c.list();
// 遍历所有用户,获取角色信息,用批量抓取优化
for(User u : list) {
// 获取每个用户里面所有的角色信息
Set<Role> roles = u.getRoles();
for(Role r : roles) {
System.out.println(r.getRolename());
}
}
—
做笔记太累了!