Hibernate

1、引入依赖:image-20220225140049060

2、创建Hibernate配置文件

对于java项目必须放在根目录下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- 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>

<!-- 配置dateSource -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/for_test</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password"></property>

<!-- 是否展示sql语句 -->
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>

<!-- 配置映射 -->
<mapping resource="com/bean/Customer.xml"/>

</session-factory>
</hibernate-configuration>

3、创建HibernateUtil工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//HibernateUtil
package com.util;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;

public class HibernateUtil {
private static SessionFactory sessionFactory;
static {
StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build();

try {
sessionFactory = new MetadataSources(registry).buildMetadata().buildSessionFactory();
}catch (Exception e){
e.printStackTrace();
StandardServiceRegistryBuilder.destroy(registry);
}
}
public static Session openSession(){
return sessionFactory.openSession();
}

public static void closeSessionFactory(){
sessionFactory.close();
}
}

HelloWorld

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 实体类映射文件 -->
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.bean">
<!-- name:对应类名 table:对应表名 -->
<class name="Customer" table="customer">
<!-- name:指定类中定义的属性名 column:指定表中主键名 -->
<id name="id" column="id"/>
<property name="name" column="name"/>
<!-- id和property元素位置不能颠倒 -->
</class>
</hibernate-mapping>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//实体类
package com.bean;

public class Customer {

private int id;

private String name;

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 Customer(int id, String name) {
this.id = id;
this.name = name;
}

public Customer() {
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import com.bean.Customer;
import com.util.HibernateUtil;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;

public class HibernateTest {
@Test
public static void main(String[] args) {
Customer customer = new Customer(3,"jjw2");
saveCustomer(customer);
}
public static void saveCustomer(Customer c){
//获取数据库会话
Session session = HibernateUtil.openSession();

//开启事务
Transaction tran = session.beginTransaction();

//插入数据
session.save(c);

//提交事务
tran.commit();

//关闭session
session.close();
}
}

单实体映射

对象-关系映射文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<hibernate-mapping package="com.bean">
<class>
<id/>
<property/>
</class>
</hibernate-mapping>


<class></class> 用于指定类和表之间的映射
<!-- name 设定类名
table 设定表名,默认以类名作为表名
包含一个<id>以及多个<property> -->

<id></id> 设定持久化类的 OID(对象标识符) 和表的主键的映射关系。
<!-- name 指定类的属性
column 指定字段的名称
generator 元素指定OID (设置自增之类的)-->

<property></property> 设定类的其它属性和表的字段的映射关系。
<!-- name 对应类的属性名
type 指定属性的类型
column 指定表字段的名称
not-null 指定属性是否允许为空 -->

单实体的属性映射

property的属性

在对象-关系映射文件中元素的 access 属性用于指定Hibernate访问持久化类属性的方式

  • property : 默认值,通过getter(增删改)和setter(查)方法访问属性值;
  • field : 通过Java反射机制直接访问属性值。

场景:当持久化类属性与数据库字段不对称时,可以利用access属性调用对应的getter、setter方法

image-20220227194456144

1
2
3
4
5
6
7
8
9
public String getUserName() {
return firstName + "" +lastName;
}
public void setUserName(String name) {
String[] strName = userName.split("");this.firstName = strName[0];
this.lastName = strName[1];
)

//映射文件中映射的是通过 getUserName 方法名中的 Username 字段查找到这个set方法的,而不是通过 username 属性!!!

场景:User 需要订单总额属性,但数据库表中没有这个字段。可在元素的 formula 属性设置查询语句(formula属性仅在查询操作中起作用)

1
2
3
<property name="totalPrice"                  
formula="(select sum(o.price) from
orders as o where o.userId=id)"/>

控制持久化类的insert和update

image-20220227200456769

这些属性用于控制insert和update,例如update=”false”时,不更新这个字段 ,dynamic-update=”true”时,当执行更新操作时,如果设置的属性为null则不改变之前的值,但是使用dynamic-update=”true”的前提是先获取一次这个数据到缓存中,才能用新的数据对比缓存中的数据,进而修改不需要改变的数据

单实体的对象标识符映射

Hibernate 采用对象标识符(OID)区分对象。

  • OID 是关系数据库中主键(通常是代理主键)在 Java 对象模型中的等价物;

  • Hibernate 采用 OID 来维持java对象和数据库表中对应关系。

Hibernate 自带了很多种标识符生成器

  • increment 采用 Hibernate 数值递增的方式;
  • identity 采用数据库提供的自增长方式;
  • assigned 主键由应用逻辑产生;
  • sequence 采用数据库提供的序列方式;
  • hilo 通过hi/lo算法 // Hibernate 5.0 以后不支持;
  • seqhilo 通过hi/lo算法;
  • native 自动选择合适的标识符生成器;
  • uuid.hex 通过uuid算法。

使用注解映射单实体

  • @Entity:声明一个实体

  • @Table(name=”table_name”):为实体类指定对应的数据库表。

  • @Id:声明实体类的OID属性。

  • @GeneratedValue(generator=”increment_generator”):声明OID的生成策略。(JPA通用注解)

  • @GenericGenerator(name=”increment_generator”, strategy=”increment”):使用Hibernate提供的生成策略

  • @Column(name=”columnName”) :将属性映射到列。

    • 这个类只标明了他是一个注解类,并且主键是自增长的,然而其他的一些属性并没有配置注解,那么他一样会在数据库的表中存在,因为这些属性默认就有@Column注解。因此如果没有添加注解,则hibernate会自动在属性前面添加@Column注解。

    • name=”columnName” 字段名称;

    • unique=false 是否在该字段上设置唯一约束;

    • nullable=true 字段是否能为空;

    • insertable=true 控制 insert语句;

    • updatable=true 控制 update语句;

    • length=255 指定字段长度。

  • @Access(AccessType.PROPERTY):

    • 通过 getter 和 setter 方法访问实体类的属性;
    • 需要在 getter 方法上定义字段的属性
  • @Access(AccessType.FIELD):

    • 直接访问实体类的属性,可以不定义 getter 和 setter 方法;
    • 需要在变量上定义字段的属性。
  • @Formula:将属性映射到SQL语句。

  • @DynamicInsert:动态生成 INSERT 语句。

  • @DynamicUpdate:动态生成 UPDATE 语句。

注意:

对于属性字段和表的字段关系对应的注解属性的位置,一般我们采用以下两种方式

第一种:是把注解@Column(name =”xx”)放在field上

第二种:是把注解@Column(name= “xx”)放在get方法上

但是第一种方式这样做实际上破坏了java面向对象的封装性,原因是一般我们写javaBean,成员变量通常定义为private,目的就是不让别人来直接访问的私有属性,而我们把注解放在私有成员的变量上,就是默认hibernate可以直接访问我们的私有的成员变量,所以我们定义属性为private,就实际没有多大意义,至于hibernate为什么能访问,hibernate采用java的反射机制完全可以访问私有成员变量!所以应该放在get方法上,第二种方式这个时候就显得更加合理。


Hibernate继承关系映射

场景:现要为某公司开发一个员工信息管理系统,已经了解到该公司的员工中有按小时计薪和按月计薪两种方式,这种情况下系统中该如何维护员工的基本信息呢?

三种不同的解决方案:

一方案:

  • 关系数据模型不支持域模型中的继承关系和多态。
  • 每个子类所对应的表中同时存在从父类继承的属性和自己特有的属性。
  • 如果父类不是抽象类并且也需要被持久化,还需要为父类创建对应的表。

image-20220228185931662

image-20220228192235782

映射文件形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- HourlyEmployee.xml -->
<class name="HourlyEmployee" table="HOURLYEMPLOYEE" >
<id name="id"> <!-- 父类属性 -->
<generator class="increment"/>
</id>
<property name="name"/>
<property name="rate"/>
</class>

<!-- SalariedEmployee.xml -->
<class name="SalariedEmployee" table="SALARIEDEMPLOYEE" >
<id name="id"> <!-- 父类属性 -->
<generator class="increment"/>
</id>
<property name="name"/>
<property name="salary"/>
</class>

注解形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Entity 
@Table(name="HourlyEmployee")
public class HourlyEmployee extends Employee {
private String name;
private String rate;
...
}

@Entity
@Table(name="SalariedEmployee")
public class SalariedEmployee extends Employee {
private String name;
private String salary;
...
}

Employee基类

1.标注为@MappedSuperclass的类将不是一个完整的实体类,他将不会映射到数据库表,但是他的属性都将映射到其子类的数据库字段中。

2.标注为@MappedSuperclass的类不能再标注@Entity或@Table注解,也无需实现序列化接口。

1
2
3
4
5
6
7
8
9
10
11
@MappedSuperclass //表明这是一个父类
public class Employee{
@Id
@GeneratedValue
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
}

方案二:

  • 关系数据模型支持继承关系和多态。
  • 在表中加入额外的字段区分子类的类型,表中包含父类和所有子类的属性对应的字段。
  • 支持多态查询,就是从数据库中检索父类对象时,同时包含所有子类的对象。

image-20220228192309890

image-20220228192257132

在数据库表中会有这样的一个字段用来区别记录的属性,如:在客户表中有一个字段表示客户级别,当这个记录为A时是一级客户,为B时是二级客户;然后,在程序中手动控制flag的值,但是这样当每个级的客户有不同的属性时Customer类将包含所有级别的属性,这样不是很好。

hibernate提供一个Discriminator映射的方法,就是把一个表映射成不同的类,有不同的属性

xml文件方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- Employee.xml -->

<!-- 如果 Employees 本身也需要被持久化,可以在<class>元素中设置 discriminator-value 属性的值。-->
<class name="Employee" table="EMPLOYEE">
<id name="id">
<generator class="increment"/>
</id>
<discriminator column="EMPLOYEETYPE" /> <!-- 公共属性的映射,用来标明不同的子类,必须紧跟id元素 -->
<property name="name"/>

<!-- discriminator-value 属性:子类中区分类型字段的取值 -->
<subclass name="HourlyEmployees" discriminator-value="HE"> <!-- HourlyEmployees特有属性的映射
-->
<property name="rate"/>
</subclass>

<!-- discriminator-value 属性:子类中区分类型字段的取值 -->
<subclass name="SalariedEmployees" discriminator-value="SE"> <!-- SalariedEmployees特有属性的映射 -->
<property name="salary"/>
</subclass>
</class>

注解形式

Employee基类

指定继承关系的生成策略

@Inheritance(strategy=InheritanceType.SINGLE_TABLE)

指定区分子类类型的字段。

@DiscriminatorColumn(name=”EMPLOYEETYPE”)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Entity
@Table(name="employee")
//继承关系的生成策略
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
//映射额外字段
@DiscriminatorColumn(name="employee_type")
public class Employee {

private Integer id;
private String name;

@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)//自增
public Integer getId() {
return id;
}
...
}

各个子类使用注解:

指定各个子类区分字段的值。

@DiscriminatorValue(value = “”)

1
2
3
4
5
6
7
8
@Entity
@DiscriminatorValue(value="HE")
public class HourlyEmployee extends Employee{

private double rate;
...
}

方案三:

  • 在关系数据模型中,用外键参照关系来表示继承关系,子类对应的表中存在外键参照父类对应表的主键。
  • 继承关系中的每个类及接口都对应一个表。
  • 支持多态查询。
image-20220301194313368 image-20220301194405032

xml文件形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<class name="Employee" table="EMPLOYEE">
<id name="id">
<generator class="increment"/>
</id>
<property name="name"/>

<joined-subclass name="HourlyEmployee" table="HOURLYEMPLOYEE">
<!-- name 属性:子类类名;
table 属性:子类对应的数据库表;
<key> 子元素:指定子类对应表的主键列;
<property> 子元素:映射子类的属性。
-->
<key column="EMPLOYEEID"/> <!-- 代表一个外键 -->
<property name="rate"/>
</joined-subclass>

<joined-subclass name="SalariedEmployee" table="SALARIEDEMPLOYEE">
<key column="EMPLOYEEID"/> <!-- 代表一个外键 -->
<property name="salary"/>
</joined-subclass>
</class>

注解形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//映射基类
@Entity
@Table(name="employee")
//继承关系的生成策略
@Inheritance(strategy=InheritanceType.JOINED)
public class Employee {

private Integer id;
private String name;

@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)//自增
public Integer getId() {
return id;
}
...
}


@Entity
@Table(name="hourly_employee")
//映射主键字段(既是主键又是外键)
@PrimaryKeyJoinColumn(name="employee_id")
public class HourlyEmployee extends Employee{

...

}

Hibernate一对一关联映射

参考:https://www.cnblogs.com/whgk/p/6128395.html

一对一关系映射

  • 定义

    如果对于实体集A中的每一个实体,实体集B中至多有一个(也可以没有)实体与之联系,反之亦然,则称实体集A与实体集B具有一对一联系,记为1:1 。

  • 实例
    一个班级只有一个正班长。
    一个正班长只在一个班中任职

image-20220301200035389

实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class User {
private Integer id;
private String userName;
private String password;
private Person person;
……
}

public class Person {
private Integer id;
private String name;
private String idNumber;
private User user;
……
}

Hibernate提供两种映射一对一关联关系的方式:

  • 主键关联映射
image-20220301200225127
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<!---------- Person ----------->
<hibernate-mapping package="com.hibernate.entity">
<!-- name指定类名 table指定表名 -->
<class name="Person" table="person">
<!-- 必须的,映射id属性 -->
<id name="id" column="person_id">
<!-- 生成策略 foreign:主键也是外键-->
<generator class="foreign">
<!-- 参数指定参照的主键字段,表明参照哪个类的主键 -->
<param name="property">user</param>
</generator>
</id>

<!-- 映射其他属性 字段名和属性名不一致是不能省略column-->
<!-- <property name="userName" column="user_name"></property> -->
<!-- access="field"不访问属性的getter、setter方法 -->
<property name="name" />
<!-- not-null="true"表示不能为空 -->
<property name="idNumber"/>

<!-- user属性的映射:Person与User之间的一对一关联关系
constrained="true"表示表中存在约束关系,?主键映射外键 -->
<one-to-one name="user" class="User" constrained="true"/>
</class>
</hibernate-mapping>


<!---------- User ----------->
<hibernate-mapping package="com.hibernate.entity">
<!-- name指定类名 table指定表名 -->
<class name="User" table="user">
<!-- 必须的,映射id属性 -->
<id name="id" column="id">
<!-- 生成策略 identity:使用底层数据库的自动递增 -->
<generator class="identity"/>
</id>

<!-- 映射其他属性 字段名和属性名不一致是不能省略column-->
<!-- <property name="userName" column="user_name"></property> -->
<!-- access="field"不访问属性的getter、setter方法 -->
<property name="userName" >
<column name="user_name"></column>
</property>
<!-- not-null="true"表示不能为空 -->
<property name="password"/>

<!-- person属性的映射:User与Person之间的一对一关联关系 -->
<!-- 关联映射中cascade属性:级联操作:在操作用户对象的同时,操作关联的Person对象
none:所有操作都不级联
save-update:插入、更新级联
delete:删除级联
all:所有操作级联 -->
<one-to-one name="person" class="Person" cascade="all"></one-to-one>
</class>
</hibernate-mapping>
1
2
3
4
5
6
// 查询出已经存在的用户
User u = session.get(User.class, id);
//给用户绑定实名信息
u.setPerson(p); //给user绑定person
p.setUser(u); //person在插入时就能依照user这个外键进行插入了
session.save(p);

级联操作(保存为例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
      User u = new User("李四", "8888888");
Person p = new Person();
p.setName("Tom");
p.setIdNumber("1111111");
// 建立关联关系
u.setPerson(p);
p.setUser(u);

// 获取Session对象
Session session = HibernateUtil.openSession();
// 开启数据库事务
Transaction tran = session.beginTransaction();

// 删除
session.save(u); //同时会保存person

// 提交数据库事务
tran.commit();

// 关闭Session
session.close();
  • 唯一外键关联映射
    • 外键必须设定 unique 约束
image-20220305162102873
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<!---------- Person ----------->
<hibernate-mapping package="com.hibernate.entity">
<!-- name指定类名 table指定表名 -->
<class name="Person" table="person">
<!-- 必须的,映射id属性 -->
<id name="id" column="person_id">
<!-- 生成策略 foreign:主键也是外键-->
<generator class="foreign">
<!-- 参数指定参照的主键字段 -->
<param name="property">user</param>
</generator>
</id>

<!-- 映射其他属性 字段名和属性名不一致是不能省略column-->
<!-- <property name="userName" column="user_name"></property> -->
<!-- access="field"不访问属性的getter、setter方法 -->
<property name="name" />
<!-- not-null="true"表示不能为空 -->
<property name="idNumber"/>

<!-- user属性的映射:Person与User之间的一对一关联关系
property-ref="person"表示建立双向一对一 ,表示user里的person就是当前这个person-->
<one-to-one name="user" class="User" property-ref="person"/>
</class>
</hibernate-mapping>


<!---------- User ----------->
<hibernate-mapping package="com.hibernate.entity">
<!-- name指定类名 table指定表名 -->
<class name="User" table="user">
<!-- 必须的,映射id属性 -->
<id name="id" column="id">
<!-- 生成策略 identity:使用底层数据库的自动递增 -->
<generator class="identity"/>
</id>

<!-- 映射其他属性 字段名和属性名不一致是不能省略column-->
<!-- <property name="userName" column="user_name"></property> -->
<!-- access="field"不访问属性的getter、setter方法 -->
<property name="userName" >
<column name="user_name"></column>
</property>
<!-- not-null="true"表示不能为空 -->
<property name="password"/>

<!-- person属性的映射:User与Person之间的一对一关联关系 -->
<!-- 唯一外键关联是多对一关联的一种特殊情况:在外键字段上加唯一约束 -->
<!-- 关联映射中column属性指定外键字段
cascade属性:级联操作:在操作用户对象的同时,操作关联的Person对象
none:所有操作都不级联
save-update:插入、更新级联
delete:删除级联
all:所有操作级联 -->
<!-- 表示这个person与user有一对一关系,person的主键是user的外键 -->
<one-to-one name="person" column="per_id" class="Person" unique="true"/>
</class>
</hibernate-mapping>

级联操作(保存为例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 保存
public static void save(User u, Person p) {
// 获取Session对象
Session session = HibernateUtil.openSession();
// 开启数据库事务
Transaction tran = session.beginTransaction();

//建立关联关系
u.setPerson(p);
p.setUser(u);

// 保存
session.save(u);
session.save(p);

// 提交数据库事务
tran.commit();

// 关闭Session
session.close();
}

使用注解映射主键关联的一对一

  • 主键关联映射

@OneToOne(cascade=CascadeType.ALL) //指定一对一关系,并设置级联属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Entity
@Table(name="user")
public class User {

private Integer id;
private String userName;
private String password;
private Person person;

@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
public Integer getId() {
return id;
}

...

//targetEntity可以省略
//CascadeType.PERSIST级联保存
//CascadeType.MERGE更新
//CascadeType.REMOVE删除
//cascade=CascadeType.ALL所有操作
@OneToOne(targetEntity=Person.class, cascade=CascadeType.REMOVE)
@PrimaryKeyJoinColumn(name="person_id")//person表中既是主键又是外键的字段
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}

...

//与数据库表不对应时必须加上
@Column(name="user_name")
public String getUserName() {
return userName;
}

...

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Entity
@Table(name="person")//如果表名与实体类类名一致该注解可省略
public class Person {

private Integer id;
private String name;
private String idNumber;
private User user;

@Id
//在JPA通用注解中使用我们在Hibernate中自定的Hibernate生成策略
@GeneratedValue(generator="my_gen")
//name 起别名
//strategy Hibernate中的生成策略
@GenericGenerator(name="my_gen", strategy="foreign",
/* 设置参数 */
parameters= {@Parameter(value="user",name="property")})
public Integer getId() {
return id;
}

...

//mappedBy="person"表示关联关系已经在User类的person属性上进行了映射
//不需要再用@PrimaryKeyJoinColumn注解进行映射
//相对应的,如果在person类中使用了@PrimaryKeyJoinColumn,在user类中也不需要
@OneToOne(mappedBy="person",targetEntity=User.class)
public User getUser() {
return user;
}

...

}
  • 唯一外键关联映射

@OneToOne(cascade=CascadeType.ALL)

@JoinColumn(name=”PERSONID”):指明USER表中的外键列名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Entity
@Table(name="user")
public class User {

private Integer id;
private String userName;
private String password;
private Person person;

@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
public Integer getId() {
return id;
}

...

@OneToOne
@JoinColumn(name="per_id")//映射外键字段
public Person getPerson() {
return person;
}

...

@Column(name="user_name")
public String getUserName() {
return userName;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Entity
@Table(name="person")//如果表名与实体类类名一致该注解可省略
public class Person {

private Integer id;
private String name;
private String idNumber;
private User user;

@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
public Integer getId() {
return id;
}

...

@OneToOne(mappedBy="person")//不需要重复映射外键字段
public User getUser() {
return user;
}
}

组合关系映射

image-20220305202114172 image-20220305202439841

xml映射文件方式

Address类不能持久化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<hibernate-mapping package="com.hibernate.entity">
<!-- 组合关系中的整体类的映射 -->
<class name="Contact" table="contact">
<id name="id">
<generator class="identity"/>
</id>
<property name="phoneNum"/>
<!-- 映射组合关系 -->
<component name="homeAddress" class="Address">
<!-- 映射部分类中定义的属性 -->
<property name="province" column="home_pro"/>
<property name="city" column="home_city"/>
</component>
<component name="workAddress" class="Address">
<!-- 映射部分类中定义的属性 -->
<property name="province" column="work_pro"/>
<property name="city" column="work_city"/>
</component>
</class>
</hibernate-mapping>

Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class Test {

public static void main(String[] args) {

Address home = new Address();
home.setProvince("河北省");
home.setCity("邯郸市");

Address work = new Address();
work.setProvince("河北省");
work.setCity("石家庄市");

Contact c = new Contact();
c.setPhoneNum("15000001222");
c.setHomeAddress(home);
c.setWorkAddress(work);

saveContact(c);

HibernateUtil.closeSessionFactory();
}

public static void saveContact(Contact c) {
// 获取Session对象
Session session = HibernateUtil.openSession();
// 开启数据库事务
Transaction tran = session.beginTransaction();

// 保存
session.save(c);

// 提交数据库事务
tran.commit();

// 关闭Session
session.close();
}
}

注解方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Embeddable
public class Address {
......
}

@Embedded
@AttributeOverrides(value={
@AttributeOverride(
name = "province",
column = @Column(name=“HOMEPROVINCE")),
@AttributeOverride(
name = "city",
column = @Column(name=“HOMECITY")),
......
})
public class Contact {
......
}

Hibernate一对多关联映射

  • 单向一对多

xml映射文件

实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class User {
private Integer id;
private String userName;
private String password;
private Set orderSet = new HashSet<Order>();
......
}

public class Order {
private Integer id;
private Double price; // 价格
......
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<!------- user -------->
<hibernate-mapping package="com.hibernate.entity">
<!-- name指定类名 table指定表名 -->
<class name="User" table="user">
<!-- 必须的,映射id属性 -->
<id name="id" column="id">
<!-- 生成策略 identity:使用底层数据库的自动递增 -->
<generator class="identity"/>
</id>

<property name="userName" column="user_name"/>
<!-- not-null="true"表示不能为空 -->
<property name="password" not-null="true" length="11"/>

<!-- 使用set元素映射Set类型的属性;无序,不可重复 -->
<set name="orders" cascade="all">
<!-- 映射外键字段 -->
<key column="user_id"></key> <!-- 订单表的外键字段 -->
<one-to-many class="Order"/>
</set>

<!-- 使用list元素映射list类型的属性;有序,可重复 -->
<!-- <list name="orders" >
映射外键字段
<key column="user_id"></key> 订单表的外键字段
映射保存订单顺序的额外字段
<index column="order_index"/>
<one-to-many class="Order"/>
</list> -->

<!-- 使用map元素映射list类型的属性;无序,不可重复,key/value存储 -->
<!-- <map name="orders" >
映射外键字段
<key column="user_id"></key> 订单表的外键字段
映射保存订单key的额外字段,type="string"不能省略
<index column="order_key" type="string"/>
<one-to-many class="Order"/>
</map> -->
</class>
</hibernate-mapping>


<!------- order -------->
<hibernate-mapping package="com.hibernate.entity">
<class name="Order" table="orders">
<id name="id">
<generator class="identity"></generator>
</id>
<property name="orderNum"/>
<property name="price"/>
</class>
</hibernate-mapping>

Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class Test {

public static void main(String[] args) {

Order o = new Order();
o.setOrderNum("QW121212121212121");
o.setPrice(100.0);

saveOrder(111, o);


HibernateUtil.closeSessionFactory();
}

// 已经注册的用户添加订单
public static void saveOrder(Integer userId,Order o) {
// 获取Session对象
Session session = HibernateUtil.openSession();
// 开启数据库事务
Transaction tran = session.beginTransaction();

//查询用户
User u = session.get(User.class, userId);

//给用户添加订单

u.getOrders().add(o);

// 保存
session.save(o);

// 提交数据库事务
tran.commit();

// 关闭Session
session.close();
}
}
  • 双向一对多

xml文件形式

1
2
3
4
5
6
7
8
9
10
11
12
<!------- order -------->
<hibernate-mapping package="com.hibernate.entity">
<class name="Order" table="orders">
<id name="id">
<generator class="identity"></generator>
</id>
<property name="orderNum"/>
<property name="price"/>
<many-to-one name="user" class="User" column="user_id"/>
</class>
</hibernate-mapping>

注解形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Entity
@Table(name="orders")
public class Order {

private Integer id;
private String orderNum;
private double price;
private User user;

@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
public Integer getId() {
return id;
}

...

//映射多对一关联
@ManyToOne(targetEntity=User.class)
//映射外键字段
@JoinColumn(name="user_id")
public User getUser() {
return user;
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Entity
@Table(name="user")
public class User {

private Integer id;
private String userName;
private String password;
private Set<Order> orders = new HashSet<Order>();

@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
public Integer getId() {
return id;
}
...

//映射一对多关系:targetEntity需要添加
//mappedBy="user"表示外键已经映射在Order类定义的user属性
//作为User一方,不再去维护关联关系:不关注外键字段的赋值
@OneToMany(targetEntity=Order.class,
cascade=CascadeType.REMOVE,
mappedBy="user" /*表示在另一头的user属性中已经映射了*/)
// @JoinColumn(name="user_id")//映射外键字段
public Set<Order> getOrders() {
return orders;
}
public void setOrders(Set<Order> orders) {
this.orders = orders;
}

}

Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class Test {

public static void main(String[] args) {

Order o = new Order();
o.setOrderNum("QW121212121212121");
o.setPrice(100.0);

saveOrder(111, o);


HibernateUtil.closeSessionFactory();
}

// 已经注册的用户添加订单
public static void saveOrder(Integer userId,Order o) {
// 获取Session对象
Session session = HibernateUtil.openSession();
// 开启数据库事务
Transaction tran = session.beginTransaction();

//查询用户
User u = session.get(User.class, userId);

//给用户添加订单
u.getOrders().add(o);
o.setUser(u);

// 保存
session.save(o);

// 提交数据库事务
tran.commit();

// 关闭Session
session.close();
}
}

Hibernate多对多关联映射

image-20220309210655839 image-20220309210718991
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Student {
private int id;
private String name;
private String studentNo;
private Set<Course> courseSet
= new HashSet<Course>();
......
}

public class Course {
private Integer id;
private String name; // 课程名称
private int credit; // 学分
private Set<Student> studentSet
= new HashSet<Student>();
......
}

xml文件形式

inverse 是 Hibernate 中双向关联关系中的基本概念,用来设置关系由哪一方来维护

  • inverse=true 表示被控方,=false 表示主控方
  • 在多对多关系中需要设置哪一方为被控方,即设置inverse=true
  • 只能在双向一对多,多对多中使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- Student.hbm.xml -->
<!-- table映射的是外键表 -->
<set name="courseSet" table="student_course"
inverse="false">
<!-- 顺序不能改变 -->
<key column="student_id"/>
<many-to-many class="Course" column="course_id"/>
</set>

<!-- Course.hbm.xml -->
<!-- table映射的是外键表 -->
<!-- inverse="true"表示放弃了关联关系的维护 -->
<set name="studentSet" table="student_course"
inverse="true">
<!-- 顺序不能改变 -->
<key column="course_id"/>
<many-to-many class="Student" column="student_id"/>
</set>

注解形式

image-20220310205831875

Hibernate操作持久化对象

session缓存

缓存介于应用程序和永久性存储源之间,其作用是降低应用程序直接读写永久性存储源的频率,从而提高应用的运行效率。

缓存内的数据是永久性存储源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件同步缓存和永久性存储源的数据。

image-20220311082156384

当 Session 执行查询方法时,先从 Session 缓存中读取据,如果缓存中有则直接读取,如果缓存中没有,从数据库中查询并加载到 Session 缓存中,再从缓存中读取。

当 Session 执行 save()、update() 方法时,将对象持久化到数据库中并将对象加载到 Session 缓存中。

session清理缓存

Session在某一时间点按照缓存中对象的属性变化来同步更新数据库的这一过程被称为 Session 清理缓存。

缓存清理的时间点:

  • 当调用 transaction.commit() 方法时,会先清理缓存,再向数据库提交事务;
  • 当显式调用 Session.flush() 方法时,会清理缓存;
  • 当调用 Session 的查询(不包括 load() 和 get() )方法时,如果缓存中对象的属性有变化则清理缓存。

Session清理缓存的模式

setHibernateFlushMode() 用于设定 Session 清理缓存的模式。

image-20220311084043414

Hibernate实体对象生命周期

  • Transient(临时状态) : 刚刚被 new 关键字创建,还没有被持久化,不在Session的缓存中。
  • Persistent(持久化状态) : 已经被持久化,并加入到 Session 缓存中。
  • Detached(游离状态) : 已经被持久化,但不再处于 Session 缓存中(给id赋值了)。
  • Removed(删除状态) : Session 已经计划将其从数据库删除,并且不再处于 Session 缓存中。

image-20220311085824284

image-20220311092226118 image-20220311092241541 image-20220311092339814 image-20220311092405770

Hibernate检索方式

HQL检索方式

  • HQL语句中关键字大小写无关,但习惯将关键字小写。
  • from 关键字后面是类名不是数据库表名,类名需区分大小写。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
public static void testHQL() {
Session session = HibernateUtil.openSession();

// 1.查询所有记录的所有字段
//String hql = "from User"; //== select * from user
String hql = "select u from User u"; //== select * from user,不能使用* 要用别名
Query<User> query = session.createQuery(hql);
List<User> users = query.list();
System.out.println(users);



// 2.查询所有记录的某个字段(list返回值中集合的元素类型同字段类型一致)
String hql = "select u.userName from User u";
Query query = session.createQuery(hql);
List<String> users = query.list();
System.out.println(users);



// 3.查询所有记录的多个字段(默认list方法返回集合中元素是Object数组,可手动调用构造方法)
String hql = "select u.userName, u.password from User u";
//String hql = "select new User(u.userName, u.password) from User u"; 如果想要每次将结果封装起来而不是成为一个数组的话 需要自己构造一个(需要有相应构造器)
Query query = session.createQuery(hql);
List<User> users = query.list();

List<Object[]> users = query.list();
for(Object[] o : users) {
System.out.println("用户名:" + o[0]);
System.out.println("密码:" + o[1]);
}



// 4.where子句加查询条件
String hql = "select u from User u where u.userName='张三'";
//String hql = "select u from User as u where u.userName like '%张%'";
Query<User> query = session.createQuery(hql);
List<User> users = query.list();
System.out.println(users);



// 5.查询结果中只包含一条记录
String hql = "select u from User u where u.id=104";
Query<User> query = session.createQuery(hql);
User u = query.uniqueResult();
System.out.println(u);



// 6.使用order by 实现排序
String hql = "select u from User u order by u.userName, u.id desc";
Query<User> query = session.createQuery(hql);
List<User> users = query.list();
System.out.println(users);



// 7.使用group by实现分组+having条件(某个用户名同名的用户个数)
String hql = "select u.userName, count(u) from User u group by u.userName having u.userName != '张三'";
Query query = session.createQuery(hql);
List<Object[]> users = query.list();
for(Object[] o : users) {
System.out.println("用户名:" + o[0]);
System.out.println("个数:" + o[1]);
}



// 8.参数的绑定
String hql = "select u from User u where u.userName=?";
Query query = session.createQuery(hql);

//绑定参数,通过位置
query.setParameter(0, "张三");
List<User> users = query.list();
System.out.println(users);

//命名参数
String hql = "select u from User u where u.userName=:name";
Query query = session.createQuery(hql);
//绑定参数,通过参数名称
query.setParameter("name", "张三");
List<User> users = query.list();
System.out.println(users);

// 参数名称同对象的属性名称一致
String hql = "select u from User u where u.userName=:userName";
Query query = session.createQuery(hql);
//绑定参数
User u = new User();
u.setUserName("张三");
query.setProperties(u);
List<User> users = query.list();
System.out.println(users);

// 绑定参数,使用Map对象
String hql = "select u from User u where u.userName=:userName";
Query query = session.createQuery(hql);
Map<String, Object> map = new HashMap<>();
// map的key值同参数的名称一致
map.put("userName", "张三");
query.setProperties(map);
List<User> users = query.list();
System.out.println(users);


//子查询
// SQL:SELECT * FROM user u WHERE (SELECT COUNT(o.id) FROM orders o WHERE o.user_id = u.id) > 0
// String hql = "select u from User u where (select count(o) from Order o where o.user = u) > 0";
String hql = "select u from User u where (select count(o) from u.orders o) > 0";
Query query = session.createQuery(hql);
List<User> users = query.list();
System.out.println(users);

session.close();
}

public static void hqlUpdate() {
// 获取Session对象
Session session = HibernateUtil.openSession();
// 开启数据库事务
Transaction tran = session.beginTransaction();

// String hql = "update User u set u.password=:pass where u.userName=:name";
// Query<User> query = session.createQuery(hql);
// query.setParameter("pass", "000000");
// query.setParameter("name", "张三");

String hql = "delete from User u where u.userName=?";
Query<User> query = session.createQuery(hql);
query.setParameter(0, "张三");
int row = query.executeUpdate();

System.out.println("影响的条数:" + row);

tran.commit();
session.close();
}

HQL子查询说明以下几点:

  • 子查询分为相关子查询和无关子查询;

    • 相关子查询:子查询语句引用了外层查询语句定义的别名。
    • 无关子查询:子查询语句没有引用外层查询语句定义的别名。
  • HQL子查询功能依赖于底层数据库对子查询的支持;

  • HQL子查询返回的是多条记录,使用以下关键字量化。

    • all、any、some、in、exists。
  • 如果HQL子查询的是集合,HQL提供了一组操作集合的函数。

    • size(),获得集合中元素的个数;
    • maxIndex(),对于建立索引的集合,获得最大索引值;
    • minIndex(),对于建立索引的集合,获得最小索引值;
    • elements(),获得集合中所有元素。
  • 做批量查询时,如果数据量很大就需要分页功能,HQL提供了用于分页查询的方法:

    • setFirstResult(int firstResult)
      设定从哪个对象开始检索。

    • setMaxResults (int maxResult)
      设定一次检索对象的数目。

      1
      2
      3
      Query<User> query = session.createQuery("from User");
      query.setFirstResult((pageNum-1) * maxCount);
      query.setMaxResult(maxCount);
  • 引用查询指在映射文件中定义查询语句。

    • 在O/R映射xml文件中,用与元素同级的元素定义一个HQL查询语句

    • ```xml
      from User

      1
      2
      3
      4
      5
      6

      - 程序中通过session.getNamedQuery("XXX")调用对应的HQL。

      - ```java
      Query query = session.createNamedQuery("findUser",User.class);
      List userList = query.list();

QBC检索方式

Query By Criteria(QBC) 可以看作是传统SQL的对象化表示。它主要由Criteria接口,Criterion接口,Restrictions类组成。

image-20220315111739151

image-20220315111756016

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//例如:检索姓名为张三的所有用户

//创建查询容器
Criteria criteria=session.createCriteria(User.class);
//创建查询条件
Criterion c1= Restrictions.eq("userName", "张三");
//将查询条件添加到查询容器中
criteria.add(c1);
List result = criteria.list();

/**步骤:
调用 Session 的 createCriteria() 创建 Criteria 实例;
通过 Restrictions 设定查询条件;
调用 Criteria 实例的 list() 方法执行查询。
*/

本地SQL检索方式

1
2
3
4
5
6
7
8
String sql = "select * from  user";
NativeQuery query = session.createNativeQuery(sql, User.class);
List list = query.list();

/**步骤:
调用session.createNativeQuery()创建NativeQuery实例,并指定查询的实体类型;
调用NativeQuery实例的list() 方法执行查询(如果查询单个对象,调用uniqueResult())
*/

Hibernate检索策略

image-20220317145359965

image-20220317150313456

类级别检索策略:

立即检索:立即加载检索方法指定的对象。

加载多于需要的对象白白浪费内存空间

select 语句数量多,频繁访问数据库,影响系统性能

1
2
映射配置文件中<class>元素的 lazy 属性设置为 false。
<class name="User" table="USER" lazy="false">

延迟检索:延迟加载检索方法指定的对象。

避免多加载应用程序不需要访问的数据对象

1
2
3
映射配置文件中<class>元素的 lazy 属性设置为 true。
<class name="User" table="USER" lazy="true">
<!-- 注:访问id属性时不会触发查询,因为是以id为条件进行查询的,查询之前就已经将id赋值给返回的类了 -->

关联级别检索策略:

在映射文件中用 元素 来配置 一对多 和 多对多 关联关系。

image-20220317152311703

1
2
3
4
5
一对多和多对多
修改User 一对多关联的Order对象的检索策略,User.hbm.xml 中<set>元素 lazy 和 outer-join 属性

多对一
修改Order 多对一关联的 User 对象检索策略,Order.hbm.xml 中 <many-to-one>元素 outer-join 属性和 User.hbm.xml 中<class>元素的 lazy 属性。

迫切左外连接检索:利用SQL外连接查询功能加载检索方法指定对象。

减少执行select语句的数量,减少数据库访问,提高系统性能。


Hibernate二级缓存机制

SessionFactory有一个内置缓存(实现机制跟Session缓存类似)和一个可以配置的缓存插件被称为外置缓存

  • Session缓存是内置的不能被卸载的,被称为Hibernate的一级缓存

  • SessionFactory的外置缓存被称为Hibernate的二级缓存

image-20220317154654073

Session缓存是事务范围的缓存。

SessionFactory缓存是进程范围或者集群范围的缓存。

进程范围和集群范围的缓存可能被进程内的多个事务并发访问,因此需要采取必要的并发访问策略。

持久化层的二级缓存,在查询时先在事务缓存中查找,如果没有查询到相应数据,再到进程范围或集群范围缓存中查找,如果还没找到再数据库中查找。

image-20220317155140250


Hibernate二级缓存配置

Hibernate的第二级缓存是可配置的缓存插件,允许选用以下类型的缓存插件,表中给出了各个缓存插件支持的并发访问策略。

image-20220317155525782

选择EHCache缓存插件的配置步骤如下

1、在Hibernate.cfg.xml配置文件中配置

1
2
3
4
5
6
<!-- cache.use_second_level_cache设为true,打开二级缓存 -->
<property name="cache.use_second_level_cache">true</property>

<!-- cache.region.factory_class配置缓存插件提供商 -->
<property name="cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>

2、设置实体映射配置文件中元素的子元素的usage属性

1
2
3
4
5
6
7
8
9
<class name="User">
<cache usage="read-write"/>
……
</class>

transactional:一般缓存插件没有此策略(除jboss-cache)
nonstrict-read-write:不严格的读写(如:帖子的回帖量)
read-write:严格的读写(如:银行系统数据)
read-only : 只读策略(对象不能修改,修改会报异常),效率最高