Hibernate学习
第一天:hibernate的基础入门
第一章 hibernate和ORM的概念
1.1 hibernate 概述
hibernate 框架是当今主流的Java持久层框架之一,由于它具有简单易学、灵活性强、扩展性强等特点,能够大大地简化程序的代码量,提高工作效率,因此受到了广大开发人员的喜爱。
hibernate 是一个开放源代码的ORM框架,它对JDBC进行了轻量级的对象封装,使得Java开发人员可以使用面向对象的编程思想来操作数据库。
1.2 ORM 概述
Object Relation Mapping 对象关系映射。
对象-关系映射(Object Relation Mapping,简称 ORM),是随着面向对象的软件开发发展而产生的。用来把对象模型表示的对象映射到基于SQL的关系模型数据结构中去。这样,我们在具体的操作实体对象的时候,就不需要再去和复杂的SQL语句打交道,只需要简单的操作实体对象的属性和方法。ORM技术是在对象和关系之间提供了一条桥梁,前台的对象型数据和数据库中的关系型数据通过这个桥梁来相互转化。
简单的说就是把我们程序中的实体类和数据表建立起来对应关系。
1.2.1 为什么要学习 Hibernate
使用传统的JDBC开发应用系统时,如果是小型应用系统,并不觉得有什么麻烦,但是对于大型应用系统的开发,使用JDBC就会显得力不从心。例如对几十、几百张包含几十个字段的表进行插入操作时,编写的SQL语句不但很长,而且繁琐,容易出错;在读取数据时,需要写多条getXxx语句从结果集中取出各个字段的信息,不但枯燥重复,并且工作量非常大。为了提高数据访问层的编程效率,Gavin King 开发出了一个当今最流行的ORM框架,它就是Hibernate框架。
所谓的ORM就是利用描述对象和数据库表之间映射的元数据,自动把Java应用程序中的对象持久化到关系型数据库中的表中。通过操作Java对象,就可以完成对数据库表的操作。可以把ORM理解为关系型数据和对象的一个纽带,开发人员只需要关注纽带一端映射的对象即可。ORM原理如图所示:
与其它操作数据库的技术相比,Hibernate具有以下几点优势:
-
Hibernate对JDBC访问数据库的代码做了轻量级封装,大大简化了数据访问层繁琐的重复性代码,并且减少了内存消耗,加快了运行效率。
-
Hibernate是一个基于JDBC的主流持久化框架,是一个优秀的ORM实现,它很大程度的简化了DAO(Data Access Object,数据访问对象)层编码工作。
-
Hibernate的性能非常好,映射的灵活性很出色。它支持很多关系型数据库,从一对一到多对多的各种复杂关系。
-
可扩展性强,由于源代码的开源以及API的开放,当本身功能不够用时,可以自行编码进行扩展。
明确:
操作实体类就相当于操作数据库表
第二章 Hibernate 快速入门
2.1 需求介绍
本章节我们是实现的功能是保存一个客户到数据库的客户表中。
2.2 开发包和版本介绍
下载网址:http://sourceforge.net/projects/hibernate/files/hibernate-orm/3.6.10.Final/
页面显示如下图:
开发包目录,如下图所示:
从图可以看出,hibernate5.0.7的解压s目录中包含一系列的子目录,这些子目录分别用于存放不同功能的文件,接下来针对这些子目录进行简单介绍,具体如下:
-
documentation文件夹:存放Hibernate的相关文档,包括参考文档的API文档。
-
lib文件夹:存放Hibernate编译和运行所依赖的JAR包。其中required子目录下包含了运行Hibernate5项目必须的JAR包。
-
project文件夹:存放Hibernate各种相关的源代码。
2.3 搭建hibernate 开发环境(重点内容)
2.3.1 第一步:导入jar包
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>3.6.10.Final</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.20.0-GA</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>3.6.10.Final</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.0.7</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.2</version>
<scope>test</scope>
</dependency>
</dependencies>
2.3.2 第二步:创建数据库和实体类
持久化类是应用程序中的业务实体类,这里的持久化是指类的对象能够被持久化保存到数据库中。Hibernate使用普通Java对象(Plain Old Java Object),即POJO的编程模式来进行持久化。POJO类中包含的是与数据库表相对应的各个属性,这些属性通过getter和setter方法来访问,对外部隐藏了内部的实现细节。下面就来编写Customer持久化类。
在项目src目录下,创建com.ordinov.domain包,并在包中创建实体类Customer(对应数据库表cst_customer),Customer类包含与cst_customer数据表字段对应的属性,以及相应的getXxx ()和setXxx ()方法。
/创建客户表/
CREATE TABLE `cst_customer` (
`cust_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '客户编号(主键)',
`cust_name` varchar(32) NOT NULL COMMENT '客户名称(公司名称)',
`cust_source` varchar(32) DEFAULT NULL COMMENT '客户信息来源',
`cust_industry` varchar(32) DEFAULT NULL COMMENT '客户所属行业',
`cust_level` varchar(32) DEFAULT NULL COMMENT '客户级别',
`cust_address` varchar(128) DEFAULT NULL COMMENT '客户联系地址',
`cust_phone` varchar(64) DEFAULT NULL COMMENT '客户联系电话',
PRIMARY KEY (`cust_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
/客户的实体类/
public class Customer implements Serializable {
private Long custId;
private String custName;
private String custSource;
private String custIndustry;
private String custLevel;
private String custAddress;
private String custPhone;
public Long getCustId() {
return custId;
}
public void setCustId(Long custId) {
this.custId = custId;
}
public String getCustName() {
return custName;
}
public void setCustName(String custName) {
this.custName = custName;
}
public String getCustSource() {
return custSource;
}
public void setCustSource(String custSource) {
this.custSource = custSource;
}
public String getCustIndustry() {
return custIndustry;
}
public void setCustIndustry(String custIndustry) {
this.custIndustry = custIndustry;
}
public String getCustLevel() {
return custLevel;
}
public void setCustLevel(String custLevel) {
this.custLevel = custLevel;
}
public String getCustAddress() {
return custAddress;
}
public void setCustAddress(String custAddress) {
this.custAddress = custAddress;
}
public String getCustPhone() {
return custPhone;
}
public void setCustPhone(String custPhone) {
this.custPhone = custPhone;
}
@Override
public String toString() {
return "Customer [custId=" + custId + ", custName=" + custName + ", custSource=" + custSource
+ ", custIndustry=" + custIndustry + ", custLevel=" + custLevel + ", custAddress=" + custAddress
+ ", custPhone=" + custPhone + "]";
}
}
2.3.3 第三步:编写映射配置文件(xml)
实体类Customer目前还不具备持久化操作的能力,而Hibernate需要知道实体类Customer映射到数据库Hibernate中的哪个表,以及类中的哪个属性对应数据库表中的哪个字段,这些都需要在映射文件中配置。
在实体类Customer所在的包中,创建一个名称为Customer.hbm.xml的映射文件,在该文件中定义了实体类Customer的属性是如何映射到cst_customer表的列上的。
<?xml version="1.0" encoding="UTF-8"?>
<!-- 导入约束:dtd约束
位置:在Hibernate的核心jar包中名称为hibernate-mapping-3.0.dtd
明确该文件中的内容:
实体类和表的对应关系
实体类中属性和表的字段的对应关系
-->
<!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="com.ordinov.domain">
<!-- class标签
作用:建立实体类和表的对应关系
属性:
name:指定实体类的名称
table:指定数据库表的名称
-->
<class name="Customer" table="cst_customer">
<!-- id标签
作用:用于映射主键
属性:
name:指定的是属性名称。也就是get/set方法后面的部分,并且首字母要转小写。
column:指定的是数据库表的字段名称
-->
<id name="custId" column="cust_id">
<!-- generator标签:
作用:配置主键的生成策略。
属性:
class:指定生成方式的取值。
取值之一:native。使用本地数据库的自动增长能力。
mysql数据库的自动增长能力是让某一列自动+1。但是不是所有数据库都支持这种方式。
-->
<generator class="native"></generator>
</id>
<!-- property标签:
作用:映射其他字段
属性:
name:指定属性的名称。和id标签的name属性含义一致
column:指定数据库表的字段名称
-->
<property name="custName" column="cust_name"></property>
<property name="custLevel" column="cust_level"></property>
<property name="custSource" column="cust_source"></property>
<property name="custIndustry" column="cust_industry"></property>
<property name="custAddress" column="cust_address"></property>
<property name="custPhone" column="cust_phone"></property>
</class>
</hibernate-mapping>
2.3.4 第四步:编写主配置文件(hibernate.cfg.xml)
Hibernate的映射文件反映了持久化类和数据库表的映射信息,而Hibernate的配置文件则主要用来配置数据库连接以及Hibernate运行时所需要的各个属性的值。在项目的src下创建一个名称为hibernate.cfg.xml的文件
<?xml version="1.0" encoding="UTF-8"?>
<!-- 导入dtd约束:
位置:在核心jar包中的名称为hibernate-configuration-3.0.dtd中
-->
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<!-- 配置SessionFactory
SessionFactory就是一个工厂,用于生产Session对象的。
Session就是我们使用hibernate操作数据库的核心对象了。
明确:
它和Web中的HttpSession没一点关系。
此配置文件中的内容不需要背,很多配置都是可以在开发包中找到的。
但是要求必须知道:
创建SessionFactory由三部分组成,缺一不可。要知道是哪三部分
1、连接数据库的基本信息
2、hibernate的基本配置
3、映射文件的位置
找配置文件的key都是在hibernate的开发包中project文件夹下的etc目录中的hibernate.properties
-->
<session-factory>
<!-- 1、连接数据库的基本信息 -->
<property name="hibernate.connection.driver_class">com.mysql.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">root</property>
<!-- 2、hibernate的基本配置 -->
<!-- 数据库的方言 -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- 是否显示SQL语句 -->
<property name="hibernate.show_sql">true</property>
<!-- 是否格式化SQL语句 -->
<property name="hibernate.format_sql">true</property>
<!-- 是否让hibernate根据表结构的变化来生成DDL语句
DDL:数据定义语言
hibernate可以根据映射文件来为我们生成数据库的表结构。
但是他不能生成数据库。
hbm2ddl.auto的取值
* none:不用Hibernate自动生成表.
* create:每次都会创建一个新的表.(测试)
* create-drop:每次都会创建一个新的表,执行程序结束后删除这个表.(测试)
* update:如果数据库中有表,使用原来的表,如果没有表,创建一个新表.可以更新表结构。
* validate:只会使用原有的表.对映射关系进行校验.
-->
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- 3、映射文件的位置 -->
<mapping resource="com/ordinov/domain/Customer.hbm.xml"/>
</session-factory>
</hibernate-configuration>
2.4 实现保存操作
在项目中新建一个名称为com.ordinov.test的包,然后在包中建立一个名为HibernateDemo1.java的文件,该文件是用来测试的类文件。
/**
* hibernate的入门案例:
* 需求:把一个客户保存到数据库中
*/
public class HibernateDemo1 {
/**
* 步骤分析:
* 1、加载主配置文件
* 2、根据主配置文件中的配置构建SessionFactory
* 3、使用工厂生产一个Session对象
* 4、使用Session对象开启事务
* 5、执行保存客户操作
* 6、提交事务
* 7、释放资源
*/
@Test
public void test1(){
Customer c = new Customer();
c.setCustName("customer01");
//1.加载主配置文件
Configuration cfg = new Configuration();
cfg.configure();
//2.构建SessionFactory
SessionFactory factory = cfg.buildSessionFactory();
//3.使用SessionFactory生产一个Session
Session session = factory.openSession();//打开一个新的Session
//4.开启事务
Transaction tx = session.beginTransaction();
//5.保存客户
session.save(c);//根据映射配置文件,生成SQL语句,实现保存。
//6.提交事务
tx.commit();
//7.释放资源
session.close();
factory.close();
}
}
2.5 入门案例的执行过程
首先创建Configuration类的实例,并通过它来读取并解析配置文件hibernate.cfg.xml。然后创建SessionFactory读取解析映射文件信息,并将Configuration对象中的所有配置信息拷贝到SessionFactory内存中。接下来,打开Session,让SessionFactory提供连接,并开启一个事务,之后创建对象,向对象中添加数据,通过session.save()方法完成向数据库中保存数据的操作。最后提交事务,并关闭资源。
2.6 Hibernate 的常见配置
在案例中,已经接触过Hibernate的映射文件和配置文件。接下来,将对这些文件进行详细的讲解。
2.6.1 映射文件的配置
该文件用于向Hibernate提供持久化类到关系型数据库的映射,每个映射文件的的结构基本都是相同的,其普遍的代码形式如下所示。
<?xml version="1.0" encoding="UTF-8"?>
<!—映射文件的dtd信息 -->
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<!-- name代表的是实体类名 talbe代表的是表名 -->
<class name="XXX" table="xxx">
<!-- name=id 代表的是XXX类中属性 column=id 代表的是xxx表中的字段 -->
<id name="id" column="id">
<generator class="native"/><!-- 主键生成策略 -->
</id>
<!-- 其它属性使用property标签来映射 -->
<property name="XXX" column="xxx" type="string" />
</class>
</hibernate-mapping>
2.6.2 核心配置
Hibernate的配置文件,包含了连接持久层与映射文件所需的基本信息,其配置文件有两种格式,具体如下:
-
一种是properties属性文件格式的配置文件,它使用键值对的形式存放信息,默认文件名称为hibernate.properties;
-
另一种是XML格式的配置文件,XML配置文件的默认名称为hibernate.cfg.xml。
上述两种格式的配置文件是等价的,具体使用哪个可以自由选择。XML格式的配置文件更易于修改,配置能力更强,当改变底层应用配置时不需要改变和重新编译代码,只修改配置文件的相应属性即可,而properties格式的文件则不具有此优势,因此,在实际开发项目中,大多数情况会使用XML格式的配置文件。下面将对XML格式的配置文件进行详细介绍。
hibernate.cfg.xml配置文件一般在开发时会放置在src的源文件夹下,发布后,该文件会在项目的WEB-INF/classes路径下。配置文件的常用配置信息如下所示。
<?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>
<property name="hibernate.connection.url">jdbc:mysql:///hibernate_day01</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">1234</property>
<!-- Hibernate的属性 -->
<!-- Hibernate的方言:作用,根据配置的方言生成相应的SQL语句 -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- Hibernate显示SQL语句: -->
<property name="hibernate.show_sql">true</property>
<!-- Hibernate格式化SQL语句: -->
<property name="hibernate.format_sql">true</property>
<!-- Hibernate的hbm2ddl(数据定义语言:create drop alter ...)属性 -->
<!--
hbm2ddl.auto的取值
* none :不用Hibernate自动生成表.
* create :每次都会创建一个新的表.(测试)
* create-drop :每次都会创建一个新的表,执行程序结束后删除这个表.(测试)
* update :如果数据库中有表,使用原来的表,如果没有表,创建一个新表.可以更新表结构。
* validate :只会使用原有的表.对映射关系进行校验.
-->
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- Hibernate加载映射 -->
<mapping resource="com/ordinov/domain/Customer.hbm.xml"/>
</session-factory>
</hibernate-configuration>
在上述代码中,首先进行了xml声明,然后是配置文件的dtd信息,该信息同样可以在核心包hibernate-core-3.6.10.Final.jar下的org.hibernate包中的hibernate-configuration-3.0.dtd文件中找到,读者只需要复制过来用即可,不需要刻意记忆。
Hibernate配置文件的根元素是hibernate-configuration,该元素包含子元素session-factory,在session-factory元素中又包含多个property元素,这些property元素用来对Hibernate连接数据库的一些重要信息进行配置。例如,上面的配置文件中,使用了property元素配置了数据库的方言、驱动、URL、用户名、密码等信息。最后通过mapping元素的配置,加载出映射文件的信息。
Hibernate配置文件的一些常用属性名称及用途,如表所示。
名称 | 用途 |
---|---|
hibernate.dialect | 操作数据库方言 |
hibernate.connection.driver_class | 连接数据库驱动程序 |
hibernate.connection.url | 连接数据库URL |
hibernate.connection.username | 数据库用户名 |
hibernate.connection.password | 数据库密码 |
hibernate.show_sql | 在控制台上输出SQL语句 |
hibernate.format_sql | 格式化控制台输出的SQL语句 |
hibernate.hbm2ddl.auto | 当SessionFactory创建时是否根据映射文件自动验证表结构或自动创建、自动更新数据库表结构。该参数的取值为:validate、update、create和create-drop。 |
hibernate.connection.autocommit | 事务是否自动提交 |
第三章 Hibernate 中 API介绍
3.1 Configuration 对象
3.1.1 作用
在使用Hibernate时,首先要创建Configuration实例,Configuration实例主要用于启动、加载、管理hibernate的配置文件信息。在启动Hibernate的过程中,Configuration实例首先确定Hibernate配置文件的位置,然后读取相关配置,最后创建一个唯一的SessionFactory实例。Configuration对象只存在于系统的初始化阶段,它将SessionFactory创建完成后,就完成了自己的使命。
Hibernate通常使用Configuration config = new Configuration().configure();的方式创建实例,此种方式默认会去src下读取 hibernate.cfg.xml配置文件。如果不想使用默认的hibernate.cfg.xml配置文件,而是使用指定目录下(或自定义)的配置文件,则需要向configure()方法中传递一个文件路径的参数,其代码写法如下:
Configuration config = new Configuration().configure("xml文件位置");
此种写法hibernate会去指定位置查找配置文件,例如,想要使用src下config包中的 hibernate.cfg.xml文件,只需将文件位置加入configure()中即可,其代码如下所示:
Configuration config = new Configuration().configure("/config/hibernate.cfg.xml");
【加载映射文件】
Hibernate除了可以使用Configuration对象加载核心配置文件以外,还可以利用该对象加载映射文件。因为如何使用properties文件作为Hibernate的核心配置文件,其他的属性可以使用key=value的格式来设置,但是映射没有办法加载。这时这个对象就有了用武之地。可以在手动编写代码的时候去加载映射文件。
Configuration configuration = new Configuration().configure("xml文件位置");
configuration.addResource("com/ordinov/domain/Customer.hbm.xml");
3.1.2 常用方法
默认构造函数:
它只能加载类的根路径下,名称为hibernate.properties的配置文件。不能加载xml
configure():
它用于加载类的根路径下,名称为hibernate.cfg.xml的配置文件。
buildSessionFactory():
根据配置文件,构建SessionFactory
addResource(String url);
指定映射文件的位置
addClass(Class clazz);
指定实体类的字节码
3.2 SessionFactory
3.2.1 作用
SessionFactory接口负责Hibernate的初始化和建立Session对象。它在Hibernate中起到一个缓冲区的作用,Hibernate可以将自动生成的SQL语句、映射数据以及某些可重复利用的数据放在这个缓冲区中。同时它还保存了对数据库配置的所有映射关系,维护了当前的二级缓存。
SessionFactory实例是通过Configuration对象获取的,其获取方法如下所示:
SessionFactory sessionfactory = config.buildSessionFactory();
3.2.2 常用方法
openSession():每次都是生成一个新的Session
3.2.3 细节
该对象维护了很多信息系:
- 连接数据库的信息
- hibernate的基本配置
- 映射文件的位置,以及映射文件中的配置
- 一些预定义的SQL语句(这些语句都是通用的)比如:全字段保存,根据id的全字段查询,根据id的删除等等。
- hibernate的二级缓存(了解)
同时,它是一个线程安全的对象,所有由该工厂产生的Session都共享工厂中维护的数据。
3.2.4 使用原则
由于SessionFactory维护了很多信息同时又是线程安全的,一般情况下,一个项目中只需要一个SessionFactory,只有当应用中存在多个数据源时,才为每个数据源建立一个SessionFactory实例。因此,不应该反复的创建和销毁。
原则:一个应用应该只有一个SessionFactory。在应用加载时创建,应用卸载时销毁。
3.2.5 在hibernate中使用数据源(连接池)
SessionFactory内部还维护了一个连接池,如果我们需要使用第三方的连接池如C3P0,那么需要我们自己手动进行配置。
配置C3P0步骤如下:
1、导入连接池的jar包
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>3.6.10.Final</version>
</dependency>
2、在hibernate主配置文件中配置
<!-- 配置数据源的提供商 -->
<property name="hibernate.connection.provider_class">
org.hibernate.connection.C3P0ConnectionProvider
</property>
3.3 Session
3.3.1 作用
Session 是应用程序与数据库之间交互操作的一个单线程对象,是 Hibernate 运作的中心,它的主要功能是为持久化对象提供创建、读取和删除的能力,所有持久化对象必须在session的管理下才可以进行持久化操作。
创建SessionFactory实例后,就可以通过它获取Session实例。获取Session实例有两种方式,一种是通过openSession()方法,另一种是通过getCurrentSession()方法。两种方法获取session的代码如下所示:
//采用openSession方法创建session
Session session = sessionFactory.openSession();
//采用getCurrentSession()方法创建session
Session session = sessionFactory.getCurrentSession();
以上两种获取session实例方式的主要区别是,采用openSession方法获取Session实例时,SessionFactory直接创建一个新的Session实例,并且在使用完成后需要调用close方法进行手动关闭。而getCurrentSession方法创建的Session实例会被绑定到当前线程中,它在提交或回滚操作时会自动关闭。
在没有配置把Session绑定当前线程之前,getCurrentSession方法无法使用,所以今天我们只使用openSession方法。配置的方式,我们明天来讲解。
3.3.2 常用方法
save(Object entity);
保存一个实体到数据库
update(Object entity);
更新一个实体
delete(Object entity);
删除一个实体
get(Class clazz,Serializable id);
根据id查询一个实体。参数的含义:Class表示要查询的实体类字节码。Serializable就是查询的条件。
beginTransaction();
开启事务,并返回事务对象
3.3.3 细节
- 由于SessionFactory已经维护了很多数据,所以Session就维护较少的内容。
- 它是一个轻量级对象。并且:它不是线程安全的!!!!!!!
- 它维护了hibernate的一级缓存。
- 它的反复创建销毁不会消耗太多资源。
3.3.4 使用原则
每个线程都只有一个Session对象。
3.4 Transaction
3.4.1 作用
Transaction接口主要用于管理事务,它是Hibernate的数据库事务接口,且对底层的事务接口进行了封装。Transaction接口的事务对象是通过Session对象开启的,其开启方式如下所示。
Transaction transaction = session.beginTransaction();
3.4.2 常用方法
commit(); // 提交事务
rollback(); // 回滚事务
Session执行完数据库操作后,要使用Transaction接口的commit()方法进行事务提交,才能真正的将数据操作同步到数据库中。发生异常时,需要使用rollback()方法进行事务回滚,以避免数据发生错误。因此,在持久化操作后,必须调用Transaction接口的commit()方法和rollback()方法。如果没有开启事务,那么每个Session的操作,都相当于一个独立的操作。
第四章 抽取HibernateUtil工具类
在上一章节中介绍了SessionFactory的这些特点,一般情况下,在实际项目使用中,通常会抽取出一个HibernateUtils的工具类,用来提供Session对象。
Hibernate的工具类:
/**
* hibernate的工具类
* 用于生产一个Session对象
*/
public class HibernateUtil {
private static SessionFactory factory;
static{
try {
Configuration cfg = new Configuration();
cfg.configure();
factory = cfg.buildSessionFactory();
} catch (Exception e) {
//e.printStackTrace();
throw new ExceptionInInitializerError("初始化SessionFactory失败");
}
}
/**
* 使用工厂生产一个Session对象,
* 每次都是一个新的
* 此时Session还不符合自己的使用原则,调整符合放到hibernate的第二天
* @return
*/
public static Session openSession(){
return factory.openSession();
}
}
第五章 使用Hibernate实现增删改查
5.1 保存操作
/**
* hibernate的增删改查(查一个)
*/
public class HibernateDemo4 {
/**
* 保存
*/
@Test
public void testAdd(){
Customer c = new Customer();
c.setCustName("Customer test");
c.setCustLevel("VIP客户");
c.setCustSource("网络");
c.setCustIndustry("IT");
c.setCustAddress("昌平区");
c.setCustPhone("010-84389340");
//1.使用工具类获取一个Session
Session session = HibernateUtil.openSession();
//2.开启事务
//Transaction tx = session.beginTransaction();
//3.保存客户
session.save(c);
//4.提交事务
//tx.commit();
//5.释放资源
session.close();
}
5.2 查询一个实体
/**
* 根据id查询一个实体
*/
@Test
public void testFindOne(){
//1.使用工具类获取一个Session
Session session = HibernateUtil.openSession();
//2.开启事务
Transaction tx = session.beginTransaction();
//3.根据id查询
Customer c = session.get(Customer.class, 2L);
System.out.println(c);
//4.提交事务
tx.commit();
//5.释放资源
session.close();
}
5.3 修改操作
/**
* 修改一个实体
*/
@Test
public void testUpdate(){
//1.使用工具类获取一个Session
Session session = HibernateUtil.openSession();
//2.开启事务
Transaction tx = session.beginTransaction();
//3.根据id查询
Customer c = session.get(Customer.class, 1L);
c.setCustName("TBD云集中心");
//修改实体
session.update(c);
//4.提交事务
tx.commit();
//5.释放资源
session.close();
}
5.4 删除操作
/**
* 删除一个实体
*/
@Test
public void testDelete(){
//1.使用工具类获取一个Session
Session session = HibernateUtil.openSession();
//2.开启事务
Transaction tx = session.beginTransaction();
//3.根据id查询
Customer c = session.get(Customer.class, 1L);
//删除实体
session.delete(c);//delete from cst_customer where cust_id = ?
//4.提交事务
tx.commit();
//5.释放资源
session.close();
}
5.5 实体查询的另一个方法load
5.5.1 实体查询的概念
所谓实体查询即OID查询,就是使用主键作为条件来查询一个实体。其中涉及的方法是Session对象get方法和load方法。
在本章节都是使用客户查询示例。
5.5.2 方法的说明
get方法:
/**
* 根据id查询一个实体
* @param entityType 指的是要查询的实体类字节码对象
* @param id 查询的条件,即主键的值。
* @return 返回的是实体类对象
*/
<T> T get(Class<T> entityType, Serializable id);
get方法的代码演示:
/**
* 需求: 使用get方法查询id为1的客户
*/
@Test
public void test1(){
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
Customer c = s.get(Customer.class, 1L);
System.out.println(c);
tx.commit();
}
load方法:
/**
* 根据id查询一个实体
* @param theClass 指的是要查询的实体类字节码
* @param id查询的条件,即主键的值。
* @return 返回的是实体类对象或者是实体类对象的代理对象
*/
<T> T load(Class<T> theClass, Serializable id);
load方法的代码演示:
/**
* 需求: 使用load方法查询id为1的客户
*/
@Test
public void test2(){
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
Customer c = s.load(Customer.class, 1L);
System.out.println(c.toString());
tx.commit();
}
问题:既然两个方法都是根据ID查询一个实体,他们有什么区别呢?
5.5.3 get和load的区别
区别:
1、查询的时机不一样
get方法任何时候都是立即加载,即只要一调用get马上发起数据库查询
load方法默认情况下是延迟加载,即真正用到对象的非OID字段数据才发起查询
load方法可以通过配置的方式改为立即加载。
配置的方式:
由于load方法是hibernate的方法所以只有XML的方式:
<class name="Customer" table="cst_customer" lazy="false">
2、返回的结果不一样
get方法永远返回查询的实体类对象。
load方法当是延迟加载时,返回的是实体类的代理对象。
涉及的概念:
- 立即加载:
是不管用不用马上查询。
- 延迟加载:
等到用的时候才真正发起查询。
第二天:hibernate的细节说明
第一章 hibernate的持久化类和对象标识符
1.1 持久化类的编写规范
1.1.1 什么是持久化类:
Hibernate是持久层的ORM映射框架,专注于数据的持久化工作。所谓的持久化,就是将内存中的数据永久存储到关系型数据库中。那么知道了什么是持久化,什么又是持久化类呢?其实所谓的持久化类指的是一个Java类与数据库表建立了映射关系,那么这个类称为是持久化类。其实你可以简单的理解为持久化类就是一个Java类有了一个映射文件与数据库的表建立了关系。那么我们在编写持久化类的时候有哪些要求呢?接下来我们来看一下:
1.1.2 持久化类的编写规则:
我们在编写持久化类的时候需要有以下几点需要注意:
-
持久化类需要提供无参数的构造方法。因为在Hibernate的底层需要使用反射生成类的实例。
-
持久化类的属性需要私有,对私有的属性提供公有的get和set方法。因为在Hibernate底层会将查询到的数据进行封装。
-
持久化类的属性要尽量使用包装类的类型。因为包装类和基本数据类型的默认值不同,包装类的类型语义描述更清晰而基本数据类型不容易描述。举个例子:
假设表中有一列员工工资,如果使用double类型,如果这个员工工资忘记录入到系统中,系统会将默认值0存入到数据库,如果这个员工工资被扣完了,也会向系统中存入0.那么这个0就有了多重含义,而如果使用包装类类型就会避免以上情况,如果使用Double类型,忘记录入工资就会存入null,而这个员工工资被扣完了,就会存入0,不会产生歧义。
-
持久化类要有一个唯一标识OID与表的主键对应。因为Hibernate中需要通过这个唯一标识OID区分在内存中是否是同一个持久化类。在Java中通过地址区分是否是同一个对象的,在关系型数据库的表中是通过主键区分是否同一条记录。那么Hibernate就是通过这个OID来进行区分的。Hibernate是不允许在内存中出现两个OID相同的持久化对象的。
-
持久化类尽量不要使用final进行修饰。因为Hibernate中有延迟加载的机制,这个机制中会产生代理对象,Hibernate产生代理对象使用的是字节码的增强技术完成的,其实就是产生了当前类的一个子类对象实现的。如果使用了final修饰持久化类。那么就不能产生子类,从而就不会产生代理对象,那么Hibernate的延迟加载策略(是一种优化手段)就会失效。
持久化类我们已经可以正常编写了,但是在持久化类中需要有一个唯一标识OID与表的主键去建立映射关系。而且主键一般我们是不会让客户手动录入的,一般我们是由程序生成主键。那么Hibernate中也提供了相应的主键生成的方式,那么我们来看下Hibernate的主键生成策略。
简单的说:
我们的实体类都需要遵从JavaBean的编写规范。
什么是JavaBean:
Bean:在软件开发领域,Bean表示可重用组件。
JavaBean就是用java语言开发的可重用组件。
JavaBean的编写规范是什么:
类都是public的
都有默认无参构造函数
成员变量都是私有的
都有公有的get/set方法
一般都实现Serializable接口。
基本类型和包装类的选择问题:
由于包装类可以有null值。所以实际开发中都是用包装类。
1.2 hibernate中对象标识符(OID)
OID全称是Object Identifier,又叫做对象标识符。
它是hibernate用于区分两个对象是否是同一个对象的标识。
我们都知道,虚拟机内存区分两个对象看的是内存的地址是否一致。数据库区分两个对象,靠的是表的主键。hibernate负责把内存中的对象持久化到数据库表中,靠的就是对象标识符来区分两个对象是否是同一个。实体类中映射主键的字段就是OID,如下图所示:
1.3 hibernate的主键生成策略
在讲解Hibernate的主键生成策略之前,先来了解两个概念,即自然主键和代理主键,具体如下:
-
自然主键:把具有业务含义的字段作为主键,称之为自然主键。例如在customer表中,如果把name字段作为主键,其前提条件必须是:每一个客户的姓名不允许为null,不允许客户重名,并且不允许修改客户姓名。尽管这也是可行的,但是不能满足不断变化的业务需求,一旦出现了允许客户重名的业务需求,就必须修改数据模型,重新定义表的主键,这给数据库的维护增加了难度。
-
代理主键:把不具备业务含义的字段作为主键,称之为代理主键。该字段一般取名为“ID”,通常为整数类型,因为整数类型比字符串类型要节省更多的数据库空间。在上面例子中,显然更合理的方式是使用代理主键。
名称 | 描述 |
---|---|
increment | 用于 long、short、或 int 类型。由Hibernate自动以递增的方式生成唯一标识符,每次增量为1。只有当没有其他进程向同一张表中插入数据时才可以使用,不能在集群环境下使用。适用于代理主键。 |
identity | 采用底层数据库本身提供的主键生成标识符,条件是数据库支持自动增长数据类型。在DB2、MySQL、MS SQL Server、Sybase和HypersonicSQL数据库中可以使用该生成器,该生成器要求在数据库中把主键定义成为自增长型。适用于代理主键。 |
sequence | Hibernate根据底层数据库序列生成标识符。条件是数据库支持序列。适用于代理主键。 |
native | 根据底层数据库对自动生成表示符的能力来选择 identity、sequence、hilo 三种生成器中的一种,适合跨数据库平台开发。适用于代理主键。 |
uuid | Hibernate采用128位的UUID算法来生成标识符。该算法能够在网络环境中生成唯一的字符串标识符。其UUID被编码为一个长度为32位的十六进制字符串。这种策略并不流行,因为字符串类型的主键比整数类型的主键占用更多的数据库空间。适用与代理主键。 |
assigned | 由java程序负责生成标识符。如果不指定id元素的generator属性,则默认使用该主键生成策略。适用于自然主键。 |
第二章 Hibernate的缓存和对象状态
Hibernate缓存包括两大类:一级缓存和二级缓存
- 什么样的数据适合被放到缓存中?
- 很少被修改的数据
- 不是很重要的数据
- 不会被并发访问的数据
- 什么样的数据不适合被放到缓存中?
- 经常被修改的数据
- 绝对不允许出现并发访问的数据
- 与其他应用共享的数据
2.1 hibernate的一级缓存
2.1.1 hibernate中的一级缓存:
Hibernate的一级缓存就是指Session缓存,Session缓存是一块内存空间,用来存放相互管理的java对象,在使用Hibernate查询对象的时候,首先会使用对象属性的OID值在Hibernate的一级缓存中进行查找,如果找到匹配OID值的对象,就直接将该对象从一级缓存中取出使用,不会再查询数据库;如果没有找到相同OID值的对象,则会去数据库中查找相应数据。当从数据库中查询到所需数据时,该数据信息也会放置到一级缓存中。Hibernate的一级缓存的作用就是减少对数据库的访问次数。
在 Session 接口的实现中包含一系列的 Java 集合, 这些 Java 集合构成了 Session 缓存。只要 Session 实例没有结束生命周期,存放在它缓存中的对象也不会结束生命周期。固一级缓存也被称为是Session基本的缓存。
Hibernate的一级缓存有如下特点:
-
当应用程序调用Session接口的save()、update()、saveOrUpdate时,如果Session缓存中没有相应的对象,Hibernate就会自动的把从数据库中查询到的相应对象信息加入到一级缓存中去。
-
当调用Session接口的load()、get()方法,以及Query接口的list()、iterator()方法时,会判断缓存中是否存在该对象,有则返回,不会查询数据库,如果缓存中没有要查询的对象,再去数据库中查询对应对象,并添加到一级缓存中。
-
当调用Session的close()方法时,Session缓存会被清空。
2.1.2 测试一级缓存
@Test
// 证明Hibernate的一级缓存的存在:
public void test1(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
Customer customer1 = session.get(Customer.class, 1l);// 马上发生一条sql查询1号客户.并将数据存入了一级缓存
System.out.println(customer1);
Customer customer2 = session.get(Customer.class, 1l);// 没有发生SQL语句从一级缓存中获取数据.
System.out.println(customer2);
System.out.println(customer1 == customer2);// true 一级缓存缓存的是对象的地址.
tx.commit();
session.close();
}
2.1.3 快照机制:
Hibernate 向一级缓存放入数据时,同时复制一份数据放入到Hibernate快照中,当使用commit()方法提交事务时,同时会清理Session的一级缓存,这时会使用OID判断一级缓存中的对象和快照中的对象是否一致,如果两个对象中的属性发生变化,则执行update语句,将缓存的内容同步到数据库,并更新快照;如果一致,则不执行update语句。Hibernate快照的作用就是确保一级缓存中的数据和数据库中的数据一致。
数据库中的数据:
程序代码:
@Test
public void test2() {
Session s = HibernateUtil.openSession();
Transaction tx = s.beginTransaction();
//1.根据id查询客户
Customer c1 = s.get(Customer.class, 2L);
System.out.println(c1.getCustAddress()); //输出客户的地址:深圳
//2.修改客户的地址为 北京海淀区
c1.setCustAddress("北京市海淀区");
//输出客户的地址
System.out.println(c1.getCustAddress());
//没有写update语句
tx.commit();
s.close();
// 打印c1的address,这个c1能不能用
System.out.println(c1.getCustAddress()); // 此时输出是什么
}
最后id为2的这条数据地址修改成了“北京市海淀区”。
当通过id查询到客户c1时,对象信息保存到了一级缓存和快照区,
调用set方法将地址改变,一级缓存中的对象相应改变。而快照区的对象不变,
当事务提交时,会依据OID,比较一级缓存和快照,当发现不一致时,使用一级缓存修改数据库,并修改快照。
2.2 二级缓存
Hibernate二级缓存又被称为“SessionFactory的缓存”。由于SessionFactory对象的生命周期和应用程序的整个过程对应,因此Hibernate二级缓存是进程范围或者集群范围的缓存,有可能出现并发问题,因此需要采用适当的并发访问策略,该策略为被缓存的数据提供了事务隔离级别。第二级缓存是可选的,是一个可配置的插件,默认下SessionFactory不会启用这个插件。
Hibernate中没有自己去实现二级缓存,而是利用第三方的。
导入pom
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>3.6.10.Final</version>
</dependency>
配置hibernate.cfg.xml
<!-- 启用二级缓存 -->
<property name="cache.use_second_level_cache">true</property>
<!-- 配置使用的二级缓存的产品 -->
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
配置ehcache.xml
<ehcache>
<!--
指定一个目录:当 EHCache 把数据写到硬盘上时, 将把数据写到这个目录下.
-->
<diskStore path="d:\\tempDirectory"/>
<!--
设置缓存的默认数据过期策略
-->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
<!--
设定具体的命名缓存的数据过期策略。每个命名缓存代表一个缓存区域
缓存区域(region):一个具有名称的缓存块,可以给每一个缓存块设置不同的缓存策略。
如果没有设置任何的缓存区域,则所有被缓存的对象,都将使用默认的缓存策略。即:<defaultCache.../>
Hibernate 在不同的缓存区域保存不同的类/集合。
对于类而言,区域的名称是类名。如:com.atguigu.domain.Customer
对于集合而言,区域的名称是类名加属性名。如com.atguigu.domain.Customer.orders
-->
<!--
name: 设置缓存的名字,它的取值为类的全限定名或类的集合的名字
maxElementsInMemory: 设置基于内存的缓存中可存放的对象最大数目
eternal: 设置对象是否为永久的, true表示永不过期,
此时将忽略timeToIdleSeconds 和 timeToLiveSeconds属性; 默认值是false
timeToIdleSeconds:设置对象空闲最长时间,以秒为单位, 超过这个时间,对象过期。
当对象过期时,EHCache会把它从缓存中清除。如果此值为0,表示对象可以无限期地处于空闲状态。
timeToLiveSeconds:设置对象生存最长时间,超过这个时间,对象过期。
如果此值为0,表示对象可以无限期地存在于缓存中. 该属性值必须大于或等于 timeToIdleSeconds 属性值
overflowToDisk:设置基于内存的缓存中的对象数目达到上限后,是否把溢出的对象写到基于硬盘的缓存中
-->
<cache name="com.ordinov.domain.Customer"
maxElementsInMemory="1"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="true"
/>
</ehcache>
开启二级缓存。我们在这里使用的xml的配置方式,所以要在Customer.hbm.xml文件加一点配置信息:
<cache usage="read-only"/>
如果是使用注解的方法,在要在Customer这个类中,加入@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)
这个注解。
测试代码
@Test
public void test1() {
Session session1 = HibernateUtil.openSession();
Transaction tx = session1.beginTransaction();
Customer customer = (Customer) session1.get(Customer.class, 1L);
System.out.println(customer);
tx.commit();
session1.close();
Session session2 = HibernateUtil.openSession();
tx = session2.beginTransaction();
Customer customer2 = (Customer) session2.get(Customer.class, 1L);
System.out.println(customer2);
tx.commit();
session2.close();
}
2.3 对象的三种状态
2.3.1 对象的状态说明:
了解了主键的生成策略之后,我们可以进一步来了解持久化类了。Hibernate为了更好的来管理持久化类,特将持久化类分成了三种状态。在Hibernate中持久化的对象可以划分为三种状态,分别是瞬时态、持久态和脱管态,一个持久化类的实例可能处于三种不同状态中的某一种,三种状态的详细介绍如下。
1、 瞬时态(transient)
瞬时态也称为临时态或者自由态,瞬时态的实例是由new命令创建、开辟内存空间的对象,不存在持久化标识OID(相当于主键值),尚未与Hibernate Session关联,在数据库中也没有记录,失去引用后将被JVM回收。瞬时状态的对象在内存中是孤立存在的,与数据库中的数据无任何关联,仅是一个信息携带的载体。
2、 持久态(persistent)
持久态的对象存在持久化标识OID ,加入到了Session缓存中,并且相关联的Session没有关闭,在数据库中有对应的记录,每条记录只对应唯一的持久化对象,需要注意的是,持久态对象是在事务还未提交前变成持久态的。
3、 脱管态(detached)
脱管态也称离线态或者游离态,当某个持久化状态的实例与Session的关联被关闭时就变成了脱管态。脱管态对象存在持久化标识OID,并且仍然与数据库中的数据存在关联,只是失去了与当前Session的关联,脱管状态对象发生改变时Hibernate不能检测到。
2.3.2 明确:
a、是为了更好的掌握hibernate中操作的方法。
b、区分状态只有两个标识
一是否有OID
二是否和Session建立的关系
临时状态:
没有OID,和Session没有关系。
持久化状态:
有OID,和Session有关系。
脱管状态:
有OID,和Session没有关系。
第三章 Hibernate的事务控制
3.0 事务的相关概念
3.0.1 事务的四个特性(ACID):
a:原子性--事务中包含的一系列操作,要完成一起完成,要么全不执行
c:一致性--事务完成时,所有的数据要保持一致的状态。
i:隔离性--一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能相互干扰。
d:持久性--指的是一个事务一旦提交,它对数据库中数据的改变是永久性的,提交后的其他操作或故障不会对其有任何影响。
3.0.2 事务并发问题
- 脏读:读到了某个事务还未提交的数据。
- 不可重复读:一个事务读到了另一个事务已经提交的更改的数据
- 幻读/虚读:一个事务读到了另一个事务已经提交的新增的数据
3.0.3 事务的隔离级别
- 1读未提交:一个事务在执行过程中,可以读取其他事务未提交的数据。如果一个事务已经开始写数据,则另外一个事务不允许同时进行写操作,但允许其他事务读此行数据。
- 2读已提交:一个事务在执行过程中,可以读取其他事务已经提交的数据,可以有效防止脏读。
- 4可重复读:一个事务在执行过程中,可以访问其他事务成功提交的新插入的数据,但是不可以访问其他事务成功修改的事务。读取数据的事务将会禁止写事务但是允许读事务,写事务则禁止任何其他事务。此隔离级别可有效的防止不可重复读和脏读。
- 8序列化/串行化:提供严格的事务隔离,它要求事务序列化执行,不能并发执行。可以有效的防止脏读、不可重复读和幻读。
3.1 配置Session和线程绑定
在Hibernate中,可以通过代码来操作管理事务,如通过“Transaction tx = session.beginTransaction();”开启一个事务;持久化操作后,通过“tx.commit();”提交事务;如果事务出现异常,又通过“tx.rollback();”操作来撤销事务(事务回滚)。
除了在代码中对事务开启,提交和回滚操作外,还可以在Hibernate的配置文件中对事务进行配置。配置文件中,可以设置事务的隔离级别。其具体的配置方法是在hibernate.cfg.xml文件中的<session-factory>
标签元素中进行的。配置方法如下所示。
<property name="hibernate.connection.isolation">4</property>
到这我们已经设置了事务的隔离级别,那么我们在真正进行事务管理的时候,需要考虑事务的应用的场景,也就是说我们的事务控制不应该是在DAO层实现的,应该在Service层实现,并且在Service中调用多个DAO实现一个业务逻辑的操作。具体操作如下显示:
其实最主要的是如何保证在Service中开启的事务时使用的Session对象和DAO中多个操作使用的是同一个Session对象。
其实有两种办法可以实现:
-
可以在业务层获取到Session,并将Session作为参数传递给DAO。
-
可以使用ThreadLocal将业务层获取的Session绑定到当前线程中,然后在DAO中获取Session的时候,都从当前线程中获取。
其实使用第二种方式肯定是最优方案,那么具体的实现已经不用我们来完成了,Hibernate的内部已经将这个事情做完了。我们只需要完成一段配置即可。
Hibernate中自身提供了三种管理 Session 对象的方法
Session 对象的生命周期与本地线程绑定
Session 对象的生命周期与 JTA 事务绑定
Hibernate 委托程序管理 Session 对象的生命周期
在 Hibernate 的配置文件中, hibernate.current_session_context_class 属性用于指定 Session 管理方式, 可选值包括
- thread:Session 对象的生命周期与本地线程绑定
-
jta:Session 对象的生命周期与 JTA 事务绑定
-
managed:Hibernate 委托程序来管理 Session 对象的生命周期
配置步骤:
- 在hibernate.cfg.xml文件中配置
<!-- 把session绑定到当前线程上 -->
<property name="hibernate.current_session_context_class">thread</property>
- 获取Session时使用的方法:
/**
* 每次都是从当前线程上获取Session
* @return
*/
public static Session getCurrentSession(){
return factory.getCurrentSession();
}
细节:
当我们把Session绑定到当前线程之后,关闭session就是hibernate来做的,我们就不用关了。
到这里我们已经对Hibernate的事务管理有了基本的了解,但是之前我们所做的CRUD的操作其实还没有查询多条记录。那如果我们需要查询多条记录要如何完成呢,我们接下来去学习一下Hibernate的其他的相关的API。
第四章 Hibernate查询对象的API
4.1 Query:(掌握)
4.1.1 概述
Query代表面向对象的一个Hibernate查询操作。在Hibernate中,通常使用session.createQuery()方法接受一个HQL语句,然后调用Query的list()或uniqueResult()方法执行查询。所谓的HQL是Hibernate Query Language缩写,其语法很像SQL语法,但它是完全面向对象的。
在Hibernate中使用Query对象的步骤,具体所示:
(1)获得Hibernate的Session对象。
(2)编写HQL语句。
(3)调用session.createQuery 创建查询对象。
(4)如果HQL语句包含参数,则调用Query的setXxx设置参数。
(5)调用Query对象的方法执行查询。
HQL的说明:
把表的名称换成实体类名称。把表字段名称换成实体类属性名称。
例如:
SQL:select * from cst_customer where cust_name like ?
HQL:select * from Customer where custName = ?
其中select * 可以省略,写为:from Customer where custName = ?
了解了使用Query对象的步骤后,接下来,通过具体示例来演示Query对象的查询操作。
4.1.2 常用查询:
4.1.2.1 基本查询
//基本查询
@Test
public void test1() {
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//1.获取Query对象 // 省略“ select * ”
Query query = s.createQuery("from Customer");
//2.获取结果集
List list = query.list();
for(Object o : list) {
System.out.println(o);
}
tx.commit();
}
4.1.2.2 条件查询
//条件查询
@Test
public void test2() {
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//1.获取Query对象
Query query = s.createQuery("from Customer where custLevel = ? and custName like ? "); // 这种查询方式,容易出现参数顺序的问题
//给参数占位符赋值
query.setString(0, "23"); // hibernate的参数占位符从0开始
query.setString(1, "%集%");
//2.获取结果集
List list = query.list();
for(Object o : list) {
System.out.println(o);
}
tx.commit();
}
@Test
public void test3() {
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//1.获取Query对象 // 省略“select * ”
Query query = s.createQuery("from Customer where custName like :custName and custLevel = :custLevel "); // 给参数起名需要使用“:参数名称”
//给参数占位符赋值
// query.setString("custLevel", "23");
// query.setString("custName", "%集%");
query.setParameter("custLevel", "23");
query.setParameter("custName", "%集%");
//2.获取结果集
List list = query.list();
for(Object o : list) {
System.out.println(o);
}
tx.commit();
}
4.1.2.3 分页查询
@Test
public void test5() {
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//1.获取Query对象 // 省略“select * ”
Query query = s.createQuery("from Customer");
//2.获取结果集
query.setFirstResult(2);
query.setMaxResults(2);
List list = query.list();
for(Object o : list) {
System.out.println(o);
}
tx.commit();
}
4.1.2.4 排序查询
//排序查询
@Test
public void test4() {
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//1.获取Query对象 // 省略“select * ”
Query query = s.createQuery("from Customer order by custId desc");
//2.获取结果集
List list = query.list();
for(Object o : list) {
System.out.println(o);
}
tx.commit();
}
4.1.2.5 统计查询
@Test
public void test6() {
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//1.获取Query对象 // 省略“select * ”
Query query = s.createQuery("select count(*) from Customer");
//2.获取结果
Long count = (Long) query.uniqueResult(); // 当返回的结果唯一时,可以使用此方法,如果返回结果不唯一,使用此方法会抛异常
System.out.println(count);
tx.commit();
}
4.1.2.6 投影查询
//投影查询
/**
* 当我们在查询实体时,只需要部分字段,而不是全部。并且希望它的返回结果使用实体类封装,而不是Object[]
* 这个时候我们称之为创建实体类的投影
*
* 投影查询的用法:
* 1.查询语句需要使用new关键字
* 2.在实体类中添加对应参数列表的构造函数
*/
@Test
public void test8() {
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//1.获取Query对象 // 省略“select * ”
Query query = s.createQuery("select new com.ordinov.domain.Customer(custId,custName) from Customer");
//2.获取结果集
List list = query.list();
for(Object o: list) {
System.out.println(o);
}
tx.commit();
}
@Test
public void test7() {
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//1.获取Query对象 // 省略“select * ”
Query query = s.createQuery("select custId,custName from Customer");
//2.获取结果集
List<Object[]> list = query.list();
for(Object[] os : list) {
System.out.println("-------数组中的内容-------");
for(Object o: os) {
System.out.println(o);
}
}
tx.commit();
}
4.1.3 Query中的方法说明:
-
list方法:该方法用于查询语句,返回的结果是一个list集合。
-
uniqueResult方法:该方法用于查询,返回的结果是一个Object对象。
-
setter方法:Query接口中提供了一系列的setter方法用于设置查询语句中的参数,针对不同的数据类型,需要用到不同的setter方法。
-
uniqueResult()方法:该方法用于返回唯一的结果,在确保只有一条记录的查询时可以使用该方法。
-
setFirstResult()方法:该方法可以设置获取第一个记录的位置,也就是它表示从第几条记录开始查询,默认从0开始计算。
-
setMaxResult()方法:该方法用于设置结果集的最大记录数,通常与setFirstResult()方法结合使用,用于限制结果集的范围,以实现分页功能。
4.2 Criteria:
4.2.1 概述
Criteria是一个完全面向对象,可扩展的条件查询API,通过它完全不需要考虑数据库底层如何实现,以及SQL语句如何编写,它是Hibernate框架的核心查询对象。Criteria 查询,又称为QBC查询(Query By Criteria),它是Hibernate的另一种对象检索方式。
org.hibernate.criterion.Criterion是Hibernate提供的一个面向对象查询条件接口,一个单独的查询就是Criterion接口的一个实例,用于限制Criteria对象的查询,在Hibernate中Criterion对象的创建通常是通过Restrictions 工厂类完成的,它提供了条件查询方法。
通常,使用Criteria对象查询数据的主要步骤,具体如下:
(1)获得Hibernate的Session对象。
(2)通过Session获得Criteria对象。
(3)使用Restrictions的静态方法创建Criterion条件对象。Restrictions类中提供了一系列用于设定查询条件的静态方法,这些静态方法都返回Criterion实例,每个Criterion实例代表一个查询条件。
(4)向Criteria对象中添加Criterion 查询条件。Criteria的add()方法用于加入查询条件。
(5)执行Criterita的 list() 或uniqueResult() 获得结果。
细节:
HQL能查的,QBC都能查,反之亦然。
了解了Criteria对象的使用步骤后,接下来,通过具体示例来演示Criteria对象的查询操作。
4.2.2 常用查询:
4.2.2.1 基本查询
//基本查询
@Test
public void test1() {
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//获取Criteria对象
Criteria c = s.createCriteria(Customer.class);
//获取结果集
List list = c.list();
for(Object o : list) {
System.out.println(o);
}
tx.commit();
}
4.2.2.2 条件查询
//条件查询
@Test
public void test2() {
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//获取Criteria对象
Criteria c = s.createCriteria(Customer.class);
//使用Criteria对象的add方法来添加条件
c.add(Restrictions.eq("custLevel", "23"));
c.add(Restrictions.like("custName", "%集%"));
//获取结果集
List list = c.list();
for(Object o : list) {
System.out.println(o);
}
tx.commit();
}
4.2.2.3 分页查询
//分页查询
/**
* QBC的分页查询和HQL的分页查询方法一样
*/
@Test
public void test4() {
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//获取Criteria对象
Criteria c = s.createCriteria(Customer.class);
c.setFirstResult(0);
c.setMaxResults(2);
//获取结果集
List list = c.list();
for(Object o : list) {
System.out.println(o);
}
tx.commit();
}
4.2.2.4 排序查询
//排序查询
@Test
public void test3() {
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//获取Criteria对象
Criteria c = s.createCriteria(Customer.class);
//添加排序
c.addOrder(Order.desc("custId"));
//获取结果集
List list = c.list();
for(Object o : list) {
System.out.println(o);
}
tx.commit();
}
4.2.2.5 统计查询
//统计(投影查询)查询
@Test
public void test5() {
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//获取Criteria对象
Criteria c = s.createCriteria(Customer.class);
//设置使用聚合函数
c.setProjection(Projections.count("custId"));
Long count = (Long) c.uniqueResult();
System.out.println(count);
tx.commit();
}
4.2.2.6 离线查询
//离线查询
/**
* DetachedCriteria对象,该对象的获取不需要session,可以直接得到
* 我们使用此对象实现的查询,称之为离线查询
*/
@Test
public void test6() {
//1.获取离线对象,不需要Session
DetachedCriteria dc = DetachedCriteria.forClass(Customer.class);
//2.封装查询条件
dc.add(Restrictions.eq("custLevel", "23"));
dc.add(Restrictions.like("custName", "%集%"));
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//把离线对象转成在线对象
Criteria c = dc.getExecutableCriteria(s);
List list = c.list();
for(Object o : list) {
System.out.println(o);
}
}
第五章 QBC常用查询条件说明
短语 | 含义 |
---|---|
Restrictions.eq | 等于= |
Restrictions.allEq | 使用Map,使用key/value进行多个等于的判断 |
Restrictions.gt | 大于> |
Restrictions.ge | 大于等于>= |
Restrictions.lt | 小于< |
Restrictions.le | 小于等于<= |
Restrictions.between | 对应sql的between子句 |
Restrictions.like | 对应sql的like子句 |
Restrictions.in | 对应sql的in子句 |
Restrictions.and | and 关系 |
Restrictions.or | or关系 |
Restrictions.sqlRestriction | Sql限定查询 |
Restrictions.asc() | 根据传入的字段进行升序排序 |
Restrictions.desc() | 根据传入的字段进行降序排序 |
运算类型 | HQL运算符 | QBC运算方法 |
---|---|---|
比较运算 | = | Restrictions.eq() |
<> | Restrictions.not(Restrictions.eq()) | |
>= | Restrictions.ge() | |
< | Restrictions.lt() | |
<= | Restrictions.le() | |
is null | Restrictions.isNull() | |
is not null | Restrictions.isNotNull() | |
范围运算符 | in | Restrictions.in() |
not in | Restrictions.not(Restrictions.in()) | |
between | Restrictions.between() | |
not between | Restrictions.not(Restrictions.between()) |
运算类型 | HQL运算符 | QBC运算方法 |
---|---|---|
字符串模式匹配 | like | Restrictions.like() |
逻辑 | and | Restrictions.and()| Restrictions.conjunction() |
or | Restrictions.or() Restrictions.disjunction() | |
not | Restrictions.not() |
第三天:hibernate的多表映射和操作
第一章 多表映射
1.1 一对多XML关系映射
1.1.1 客户配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 在实体类所在的包下,创建一个xml文件。该文件建议名称为:实体类名称+.hbm+.xml
导入约束:dtd约束
-->
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.ordinov.domain">
<class name="Customer" table="cst_customer" lazy="true">
<id name="custId" column="cust_id">
<generator class="identity"></generator>
</id>
<property name="custName" column="cust_name"></property>
<property name="custSource" column="cust_source"></property>
<property name="custIndustry" column="cust_industry"></property>
<property name="custLevel" column="cust_level"></property>
<property name="custAddress" column="cust_address"></property>
<property name="custPhone" column="cust_phone"></property>
<!-- 一对多关系映射:主表实体的映射配置
涉及的标签:
set:
作用:用于配置set集合属性。
属性:
name:指定实体类中set集合的属性名称。
table:指定从表的名称。在一对多配置时可以不写。
key:
作用:用于映射外键字段。
属性:
column:指定外键字段名称
one-to-many:
作用:用于建立一对多的映射配置
属性:
class:用于指定从表实体的名称
-->
<set name="linkmans" table="cst_linkman" lazy="true">
<key column="lkm_cust_id"></key>
<one-to-many class="LinkMan"/>
</set>
</class>
</hibernate-mapping>
1.1.2 联系人配置文件:
<?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 package="com.ordinov.domain">
<class name="LinkMan" table="cst_linkman">
<id name="lkmId" column="lkm_id">
<generator class="identity"></generator>
</id>
<property name="lkmName" column="lkm_name"></property>
<property name="lkmGender" column="lkm_gender"></property>
<property name="lkmPhone" column="lkm_phone"></property>
<property name="lkmMobile" column="lkm_mobile"></property>
<property name="lkmEmail" column="lkm_email"></property>
<property name="lkmPosition" column="lkm_position"></property>
<property name="lkmMemo" column="lkm_memo"></property>
<!-- 一对多关系映射:从表实体的映射配置
涉及的标签:
many-to-one:
作用:建立多对一的映射配置
属性:
name:从表实体中引用主表实体对象引用的名称
class:指定属性所对应的实体类名称
column:指定从表中外键字段的名称
-->
<many-to-one name="customer" class="Customer" column="lkm_cust_id" lazy="false"/>
</class>
</hibernate-mapping>
关于单向与双向的问题
在上面的配置中,客户与联系人配置了双向的一对多的关系,即一的这一端配置了<one-to-many>
,在多的这一端配置了<many-to-one>
,这样的配置,在查询时,只需要查询客户,就可以通过客户中的get方法获取到联系人,也可以只查询联系人就可以获取到客户。那么客户与联系人就构成了一个你中有我,我中有你的双向的关系。
并不是所有的一对多都需要这样配置,比如,商品和订单的关系,查询商品并不关注商品有哪些订单,而查询订单时需要知道是哪个商品的订单。这种情况就可以建立单向多对一的关系
以客户和联系人为例需要这么配置
在客户的映射文件中去除
<set name="linkmans" table="cst_linkman" lazy="true">
<key column="lkm_cust_id"></key>
<one-to-many class="LinkMan"/>
</set>
并且在实体类中去掉联系人集合属性。而联系人的映射配置不需要改变。
这样当查询客户时,只能获取客户,而查询联系人时,还可以根据联系人的get方法,获取到客户。
1.2 多对多关系映射
1.2.1 用户配置文件:
<?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 package="com.ordinov.domain">
<class name="SysUser" table="sys_user" >
<id name="userId" column="user_id">
<generator class="identity"></generator>
</id>
<property name="userName" column="user_name"></property>
<property name="userPassword" column="user_password"></property>
<property name="userState" column="user_state"></property>
<!-- 多对多关系映射
涉及的标签:
set:
作用:用于映射set集合属性
属性:
name:指定集合的名称
table:指定的是中间表的名称
key:
作用:用于映射外键字段
属性:
column:指定的是当前实体在中间表的外键字段名称
many-to-many
作用:用于映射多对多的关系
属性:
class:对方的实体类名称
column:对方在中间表的外键字段名称
-->
<set name="roles" table="user_role_ref" cascade="delete">
<key column="user_id"></key>
<many-to-many class="SysRole" column="role_id"/>
</set>
</class>
</hibernate-mapping>
1.2.2 角色配置文件:
<?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 package="com.ordinov.domain">
<class name="SysRole" table="sys_role" >
<id name="roleId" column="role_id">
<generator class="identity"></generator>
</id>
<property name="roleName" column="role_name"></property>
<property name="roleMemo" column="role_memo"></property>
<!-- 多对多关系映射 -->
<set name="users" table="user_role_ref" inverse="true" cascade="delete">
<key column="role_id"></key>
<many-to-many class="SysUser" column="user_id"/>
</set>
</class>
</hibernate-mapping>
第二章 多表增删改操作
2.1 一对多关系的操作
2.1.1 保存操作
保存原则:先保存主表,再保存从表。
/**
* 保存操作
* 需求:
* 保存一个客户和一个联系人
* 要求:
* 创建一个客户对象和一个联系人对象
* 建立客户和联系人之间关联关系(双向一对多的关联关系)
* 先保存客户,再保存联系人
* 问题:
* 在使用xml配置的情况下:
当我们建立了双向的关联关系之后,先保存主表,再保存从表时:
会产生2条insert和1条update.
* 而实际开发中我们只需要2条insert。
*
*/
@Test
public void test1(){
//创建客户和联系人对象
Customer c = new Customer();//瞬时态
c.setCustName("TBD云集中心");
c.setCustLevel("VIP客户");
c.setCustSource("网络");
c.setCustIndustry("商业办公");
c.setCustAddress("昌平区北七家镇");
c.setCustPhone("010-84389340");
LinkMan l = new LinkMan();//瞬时态
l.setLkmName("TBD联系人");
l.setLkmGender("male");
l.setLkmMobile("13811111111");
l.setLkmPhone("010-34785348");
l.setLkmEmail("98354834@qq.com");
l.setLkmPosition("老师");
l.setLkmMemo("还行吧");
//建立他们的双向一对多关联关系
//l.setCustomer(c);
c.getLinkmans().add(l);
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//按照要求:先保存客户,再保存联系人(此时符合保存原则:先保存主表,再保存从表)
s.save(c);//如果在把客户对象转成持久态时,不考虑联系人的信息。就不会有联系人的快照产生
s.save(l);
tx.commit();//默认此时会执行快照机制,当发现一级缓存和快照不一致了,使用一级缓存更新数据库。
}
2.1.1.1 保存时遇到的问题
我们已经分析过了,因为双向维护了关系,而且持久态对象可以自动更新数据库,更新客户的时候会修改一次外键,更新联系人的时候同样也会修改一次外键。这样就会产生了多余的SQL,那么问题产生了,我们又该如何解决呢?
其实解决的办法很简单,只需要将一方放弃外键维护权即可。也就是说关系不是双方维护的,只需要交给某一方去维护就可以了。通常我们都是交给多的一方去维护的。为什么呢?因为多的一方才是维护关系的最好的地方,举个例子,一个老师对应多个学生,一个学生对应一个老师,这是典型的一对多。那么一个老师如果要记住所有学生的名字很难的,但如果让每个学生记住老师的名字应该不难。其实就是这个道理。
所以在一对多中,一的一方都会放弃外键的维护权(关系的维护)。这个时候如果想让一的一方放弃外键的维护权,只需要进行如下的配置即可。
inverse的默认值是false,代表不放弃外键维护权,配置值为true,代表放弃了外键的维护权。代码如下:
解决多一条update语句的问题:
<set name="linkmans" table="cst_linkman" inverse="true">
<key column="lkm_cust_id"></key>
<one-to-many class="LinkMan"/>
</set>
2.1.2 修改操作
/**
* 更新操作
* 需求:
* 更新客户
* 要求:
* 创建一个新的联系人对象
* 查询id为1的客户
* 建立联系人和客户的双向一对多关联关系
* 更新客户
* 问题:
* 当更新一个持久态对象时,它关联了一个瞬时态的对象。执行更新
* 如果是注解配置:什么都不会做
* 如果是XML配置:会报错
* 解决办法:
* 配置级联保存更新
*/
@Test
public void test2(){
//创建联系人对象
LinkMan l = new LinkMan();//瞬时态
l.setLkmName("TBD联系人test");
l.setLkmGender("male");
l.setLkmMobile("13811111111");
l.setLkmPhone("010-34785348");
l.setLkmEmail("98354834@qq.com");
l.setLkmPosition("老师");
l.setLkmMemo("还行吧");
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//查询id为1的客户
Customer c1 = s.get(Customer.class, 1L);
//建立双向关联关系
c1.getLinkmans().add(l);
//l.setCustomer(c1);
//更新客户
s.update(c1);
tx.commit();
}
2.1.2.1 修改中遇到的问题
什么是级联操作:
级联操作是指当主控方执行保存、更新或者删除操作时,其关联对象(被控方)也执行相同的操作。在映射文件中通过对cascade属性的设置来控制是否对关联对象采用级联操作,级联操作对各种关联关系都是有效的。
级联操作的方向性:
级联是有方向性的,所谓的方向性指的是,在保存一的一方级联多的一方和在保存多的一方级联一的一方。
【保存客户级联联系人】
首先要确定我们要保存的主控方是那一方,我们要保存客户,所以客户是主控方,那么需要在客户的映射文件中进行如下的配置。
代码如下:
解决报错的问题,配置级联保存更新:
<set name="linkmans" table="cst_linkman" cascade="save-update" inverse="true">
<key column="lkm_cust_id"></key>
<one-to-many class="LinkMan"/>
</set>
2.1.3 删除操作
/**
* 删除操作
* 删除从表数据:可以随时任意删除。
* 删除主表数据:
* 有从表数据引用
* 1、在默认情况下,它会把外键字段置为null,然后删除主表数据。
* 如果在数据库的表结构上,外键字段有非空约束,默认情况就会报错了。
* 2、如果配置了放弃维护关联关系的权利,则不能删除(与外键字段是否允许为null,没有关系)
* 因为在删除时,它根本不会去更新从表的外键字段了。
* 3、如果还想删除,使用级联删除
* 没有从表数据引用:随便删
*
* 在实际开发中,级联删除请慎用!(在一对多的情况下)
*/
@Test
public void test3(){
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
//查询id为1的客户
Customer c1 = s.get(Customer.class, 2L);
//删除id为1的客户
s.delete(c1);
tx.commit();
}
2.1.3.1 删除中遇到的问题
我们之前学习过级联保存或更新,那么再来看级联删除也就不难理解了,级联删除也是有方向性的,删除客户同时级联删除联系人,也可以删除联系人同时级联删除客户(这种需求很少)。
原来JDBC中删除客户和联系人的时候,如果有外键的关系是不可以删除的,但是现在我们使用了Hibernate,其实Hibernate可以实现这样的功能,但是不会删除客户同时删除联系人,默认的情况下如果客户下面还有联系人,Hibernate会将联系人的外键置为null,然后去删除客户。那么其实有的时候我们需要删除客户的时候,同时将客户关联的联系人一并删除。这个时候我们就需要使用Hibernate的级联删除操作了。
【删除客户的时候同时删除客户的联系人】
确定删除的主控方式客户,所以需要在客户端配置:
如果还想有之前的级联保存或更新,同时还想有级联删除,那么我们可以进行如下的配置:
代码如下:
级联删除的配置:
<set name="linkmans" table="cst_linkman" cascade="delete" inverse="true">
<key column="lkm_cust_id"></key>
<one-to-many class="LinkMan"/>
</set>
【删除联系人的时候同时删除客户】
同样我们删除的是联系人,那么联系人是主控方,需要在联系人端配置:
<many-to-one name="customer" cascade="delete" class="Customer" column="lkm_cust_id" lazy="false"/>
如果需要既做保存或更新有有级联删除的功能,也可以如下配置:
<many-to-one name="customer" cascade="delete, save-update" class="Customer" column="lkm_cust_id" lazy="false"/>
2.2 多对多关系的操作
2.2.1 保存操作
/**
* 需求:
* 保存用户和角色
* 要求:
* 创建2个用户和3个角色
* 让1号用户具有1号和2号角色(双向的)
* 让2号用户具有2号和3号角色(双向的)
* 保存用户和角色
* 问题:
* 在保存时,会出现主键重复的错误,因为都是要往中间表中保存数据造成的。
* 解决办法:
* 让任意一方放弃维护关联关系的权利
*/
@Test
public void test1(){
//创建对象
SysUser u1 = new SysUser();
u1.setUserName("用户1");
SysUser u2 = new SysUser();
u2.setUserName("用户2");
SysRole r1 = new SysRole();
r1.setRoleName("角色1");
SysRole r2 = new SysRole();
r2.setRoleName("角色2");
SysRole r3 = new SysRole();
r3.setRoleName("角色3");
//建立关联关系
u1.getRoles().add(r1);
u1.getRoles().add(r2);
r1.getUsers().add(u1);
r2.getUsers().add(u1);
u2.getRoles().add(r2);
u2.getRoles().add(r3);
r2.getUsers().add(u2);
r3.getUsers().add(u2);
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
s.save(u1);
s.save(u2);
s.save(r1);
s.save(r2);
s.save(r3);
tx.commit();
}
解决保存失败的问题:
<set name="users" table="user_role_rel" inverse="true">
<key column="role_id"></key>
<many-to-many class="SysUser" column="user_id"></many-to-many>
</set>
2.2.2 删除操作
/**
* 删除操作
* 在多对多的删除时,双向级联删除根本不能配置
* 禁用
* 如果配了的话,如果数据之间有相互引用关系,可能会清空所有数据
*/
@Test
public void test2(){
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
SysUser u1 = s.get(SysUser.class,3L);
s.delete(u1);
tx.commit();
}
在映射配置中不能出现:双向级联删除的配置
第三章 hibernate中的多表查询
3.1 对象导航查询
3.1.1 概述
对象图导航检索方式是根据已经加载的对象,导航到他的关联对象。它利用类与类之间的关系来检索对象。
例如:我们通过OID查询方式查出一个客户,可以调用Customer类中的getLinkMans()方法来获取该客户的所有联系人。
对象导航查询的使用要求是:两个对象之间必须存在关联关系。
3.1.2 对象导航检索示例
3.1.2.1 查询一个客户,获取该客户下的所有联系人
@Test
public void test1(){
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
Customer c = s.get(Customer.class, 1L);
Set<LinkMan> linkmans = c.getLinkmans();//此处就是对象导航查询
for(Object o : linkmans){
System.out.println(o);
}
tx.commit();
}
3.1.2.2 查询一个联系人,获取该联系人的所有客户
@Test
public void test2(){
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
LinkMan l = s.get(LinkMan.class, 1L);
System.out.println(l);
Customer c = l.getCustomer();
System.out.println(c);
tx.commit();
}
3.2 对象导航查询的问题分析
问题1:我们查询客户时,要不要把联系人查询出来?
分析:
如果我们不查的话,在用的时候还要自己写代码,调用方法去查询。
如果我们查出来的,不使用时又会白白的浪费了服务器内存。
解决:
采用延迟加载的思想。通过配置的方式来设定当我们在需要使用时,发起真正的查询。
配置的方式:
在Customer.hbm.xml配置文件中的set标签上使用lazy属性。取值为true(默认值)|fasle
<set name="linkmans" table="cst_linkman" inverse="true" lazy="true">
<key column="lkm_cust_id"></key>
<one-to-many class="LinkMan"/>
</set>
问题2:我们查询联系人时,要不要把客户查询出来?
分析:
如果我们不查的话,在用的时候还要自己写代码,调用方法去查询。
如果我们查出来的话,一个对象不会消耗太多的内存。而且多数情况下我们都是要使用的。
例如:查询联系人详情时,肯定会看看该联系人的所属客户。
解决:
采用立即加载的思想。通过配置的方式来设定,只要查询从表实体,就把主表实体对象同时查出来。
配置的方式:
在LinkMan.hbm.xml配置文件中的many-to-one标签上使用lazy属性。取值为proxy|fasle
false:立即加载
proxy:看客户的映射文件class标签的lazy属性取值,如果客户的class标签lazy属性是true
那么proxy表示延迟加载,如果是false就表示立即加载。
<many-to-one name="customer" class="Customer" column="lkm_cust_id" lazy="false"/>
第四天:JPA概述和使用hibernate中JPA的实现
第一章 JPA相关概念
1.1 JPA概述
全称是:Java Persistence API。是SUN公司推出的一套基于ORM的规范。hibernate框架中提供了JPA的实现。
JPA通过JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。
1.2 JPA的优势
标准化
JPA 是 JCP 组织发布的 Java EE 标准之一,因此任何声称符合 JPA 标准的框架都遵循同样的架构,提供相同的访问API,这保证了基于JPA开发的企业应用能够经过少量的修改就能够在不同的JPA框架下运行。
容器级特性的支持
JPA框架中支持大数据集、事务、并发等容器级事务,这使得 JPA 超越了简单持久化框架的局限,在企业应用发挥更大的作用。
简单方便
JPA的主要目标之一就是提供更加简单的编程模型:在JPA框架下创建实体和创建Java 类一样简单,没有任何的约束和限制,只需要使用 javax.persistence.Entity进行注释,JPA的框架和接口也都非常简单,没有太多特别的规则和设计模式的要求,开发者可以很容易的掌握。JPA基于非侵入式原则设计,因此可以很容易的和其它框架或者容器集成。
查询能力
JPA的查询语言是面向对象而非面向数据库的,它以面向对象的自然语法构造查询语句,可以看成是Hibernate HQL的等价物。JPA定义了独特的JPQL(Java Persistence Query Language),JPQL是EJB QL的一种扩展,它是针对实体的一种查询语言,操作对象是实体,而不是关系数据库的表,而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。
高级特性
JPA 中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系,这样的支持能够让开发者最大限度的使用面向对象的模型设计企业应用,而不需要自行处理这些特性在关系数据库的持久化。
1.3 明确
a、JPA是一套ORM规范,hibernate实现了JPA规范
b、hibernate中有自己的独立ORM操作数据库方式,也有JPA规范实现的操作数据库方式。
c、在数据库增删改查操作中,我们hibernate和JPA的操作都要会。
第二章 JPA入门
2.1 介绍
本章节我们实现基于JPA注解的对象关系映射,配置实体类和数据库表的对应关系。
并且使用JPA规范中的方法实现CRUD操作。
2.2 JPA环境搭建
2.2.1 第一步:导如相关Jar包
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>3.6.10.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>3.6.10.Final</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.0.7</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>3.6.10.Final</version>
</dependency>
</dependencies>
2.2.2 第二步:创建配置文件
要求:在src下面的META-INF文件夹下面创建一个名称为persistence.xml的文件。
配置文件的内容:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<!-- 配置持久化单元,可以配置多个,但是名称不能重复
name:用于指定持久化单元名称
transaction-type:指定事务的类型。
JTA:Java Transaction API
RESOURCE_LOCAL:指的是本地代码事务。(我们用这个)
-->
<persistence-unit name="myJPAUnit" transaction-type="RESOURCE_LOCAL">
<!-- JPA规范的提供商 可以不写。-->
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<!-- 指定由Jpa注解的实体类位置 可以不写。-->
<class>com.ordinov.domain.Customer</class>
<!-- 连接库相关的一些配置,都是用hibernate的,所以只需要把之前hibernate主配置文件中的部分内容拷贝过来即可 -->
<properties>
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
<property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/hibernate_jpa"/>
<property name="hibernate.connection.username" value="root"/>
<property name="hibernate.connection.password" value="root"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/><!-- 数据库的方言 -->
<!-- 第二部分:hibernate的可选配置 -->
<!-- 是否显示hibernate生成的SQL语句 -->
<property name="hibernate.show_sql" value="true"/>
<!-- 是否使用格式化输出sql语句到控制台 -->
<property name="hibernate.format_sql" value="false"/>
<!-- 配置hibernate采用何种方式生成DDL语句 -->
<property name="hibernate.hbm2ddl.auto" value="update"/><!-- update表示检测实体类的映射配置和数据库的表结构是否一致,如果不一致,更新表结构 -->
<!-- 设置hibernate的连接池提供商 -->
<property name="hibernate.connection.provider_class" value="org.hibernate.connection.C3P0ConnectionProvider"/>
</properties>
</persistence-unit>
</persistence>
2.2.3 第三步:编写工具类,用于获取JPA的操作数据库对象
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
/**
* JPA的工具类
*/
public class JPAUtil {
//它就相当于SessionFactory
private static EntityManagerFactory factory;
private static ThreadLocal<EntityManager> tl ;
static{
factory = Persistence.createEntityManagerFactory("myJPAUnit");
tl = new ThreadLocal<EntityManager>();
}
/**
* 获取EntityManager对象
* @return
*/
public static EntityManager createEntityManager(){
//1.从当前线程上获取entityManager对象
EntityManager em = tl.get();
if(em == null){
em = factory.createEntityManager();
tl.set(em);
}
return tl.get();
}
}
2.2.4 第四步:编写实体类并使用注解配置
/**
* 客户的实体类
*/
@Entity
@Table(name="cst_customer")
public class Customer implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="cust_id")
private Long custId;
@Column(name="cust_name")
private String custName;
@Column(name="cust_source")
private String custSource;
@Column(name="cust_industry")
private String custIndustry;
@Column(name="cust_level")
private String custLevel;
@Column(name="cust_address")
private String custAddress;
@Column(name="cust_phone")
private String custPhone;
public Long getCustId() {
return custId;
}
public void setCustId(Long custId) {
this.custId = custId;
}
public String getCustName() {
return custName;
}
public void setCustName(String custName) {
this.custName = custName;
}
public String getCustSource() {
return custSource;
}
public void setCustSource(String custSource) {
this.custSource = custSource;
}
public String getCustIndustry() {
return custIndustry;
}
public void setCustIndustry(String custIndustry) {
this.custIndustry = custIndustry;
}
public String getCustLevel() {
return custLevel;
}
public void setCustLevel(String custLevel) {
this.custLevel = custLevel;
}
public String getCustAddress() {
return custAddress;
}
public void setCustAddress(String custAddress) {
this.custAddress = custAddress;
}
public String getCustPhone() {
return custPhone;
}
public void setCustPhone(String custPhone) {
this.custPhone = custPhone;
}
@Override
public String toString() {
return "Customer [custId=" + custId + ", custName=" + custName + ", custSource=" + custSource
+ ", custIndustry=" + custIndustry + ", custLevel=" + custLevel + ", custAddress=" + custAddress
+ ", custPhone=" + custPhone + "]";
}
}
2.3 常用注解说明
@Entity
作用:指定当前类是实体类。写上此注解用于在创建SessionFactory/EntityManager时,加载映射配置。
@Table
作用:指定实体类和表之间的对应关系。
属性:
name:指定数据库表的名称
@Id
作用:指定当前字段是主键。
@GeneratedValue
作用:指定主键的生成方式。JPA的主键生成方式详解见2.4小节的说明。
属性:
strategy :指定主键生成策略。JPA支持四种生成策略,具体介绍看2.4小节。
@Column
作用:指定实体类属性和数据库表之间的对应关系
属性:
name:指定数据库表的列名称。
unique:是否唯一
nullable:是否可以为空
inserttable:是否可以插入
updateable:是否可以更新
columnDefinition: 定义建表时创建此列的DDL
secondaryTable: 从表名。如果此列不建在主表上(默认建在主表),该属性定义该列所在从表的名字。
2.4 主键生成策略
通过annotation(注解)来映射hibernate实体的,基于annotation的hibernate主键标识为@Id, 其生成规则由@GeneratedValue设定的.这里的@id和@GeneratedValue都是JPA的标准用法。
JPA提供的四种标准用法为TABLE,SEQUENCE,IDENTITY,AUTO。具体说明如下:
2.4.1 IDENTITY
主键由数据库自动生成(主要是自动增长型)
用法:
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long custId;
2.4.2 SEQUENCE
根据底层数据库的序列来生成主键,条件是数据库支持序列。
用法:
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,generator="payablemoney_seq")
@SequenceGenerator(name="payablemoney_seq", sequenceName="seq_payment")
说明:
@SequenceGenerator源码中的定义
@Target({TYPE, METHOD, FIELD})
@Retention(RUNTIME)
public @interface SequenceGenerator {
String name();
String sequenceName() default "";
int initialValue() default 0;
int allocationSize() default 50;
}
name:表示该表主键生成策略的名称,它被引用在@GeneratedValue中设置的“generator”值中。
sequenceName:属性表示生成策略用到的数据库序列名称。
initialValue:表示主键初识值,默认为0。
allocationSize:表示每次主键值增加的大小,例如设置1,则表示每次插入新记录后自动加1,默认为50。
2.4.3 AUTO
主键由程序控制。
用法:
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
2.4.4 TABLE
使用一个特定的数据库表格来保存主键(了解)
用法:
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator="payablemoney_gen")
@TableGenerator(name = "pk_gen",
table="tb_generator",
pkColumnName="gen_name",
valueColumnName="gen_value",
pkColumnValue="PAYABLEMOENY_PK",
allocationSize=1
)
这里应用表tb_generator,定义为 :
CREATE TABLE tb_generator (
id NUMBER NOT NULL,
gen_name VARCHAR2(255) NOT NULL,
gen_value NUMBER NOT NULL,
PRIMARY KEY(id)
)
插入纪录,供生成主键使用:
INSERT INTO tb_generator(id, gen_name, gen_value)VALUES (1,PAYABLEMOENY_PK', 1);
在主键生成后,这条纪录的value值,按allocationSize递增。
@TableGenerator的定义:
@Target({TYPE, METHOD, FIELD})
@Retention(RUNTIME)
public @interface TableGenerator {
String name();
String table() default "";
String catalog() default "";
String schema() default "";
String pkColumnName() default "";
String valueColumnName() default "";
String pkColumnValue() default "";
int initialValue() default 0;
int allocationSize() default 50;
UniqueConstraint[] uniqueConstraints() default {};
}
其中属性说明:
* name:
表示该表主键生成策略的名称,它被引用在@GeneratedValue中设置的“generator”值中。
* table:
表示表生成策略所持久化的表名,例如,这里表使用的是数据库中的“tb_generator”。
* catalog和schema:
具体指定表所在的目录名或是数据库名。
* pkColumnName:
属性的值表示在持久化表中,该主键生成策略所对应键值的名称。例如在“tb_generator”中将“gen_name”作为主键的键值
* valueColumnName:
属性的值表示在持久化表中,该主键当前所生成的值,它的值将会随着每次创建累加。例如,在“tb_generator”中将“gen_value”作为主键的值
* pkColumnValue:
属性的值表示在持久化表中,该生成策略所对应的主键。例如在“tb_generator”表中,将“gen_name”的值为“CUSTOMER_PK”。
* initialValue:
表示主键初识值,默认为0。
* allocationSize:
表示每次主键值增加的大小,例如设置成1,则表示每次创建新记录后自动加1,默认为50。
* UniqueConstraint:
与@Table标记中的用法类似。
2.5 JPA的CRUD操作
2.5.1 保存
//保存
@Test
public void test1(){
//创建客户对象
Customer c = new Customer();
c.setCustName("JPA Customer");
//1.获取EntityManager对象
EntityManager em = JPAUtil.createEntityManager();
//2.获取事务对象,并开启事务
EntityTransaction tx = em.getTransaction();
tx.begin();
//3.执行保存操作
em.persist(c);
//4.提交事务
tx.commit();
//5.关闭资源
em.close();
}
2.5.2 修改
//更新操作
@Test
public void test3(){
//1.获取EntityManager对象
EntityManager em = JPAUtil.createEntityManager();
//2.获取事务对象,并开启事务
EntityTransaction tx = em.getTransaction();
tx.begin();
//3.执行更新操作(需要把更新的对象先查询出来)
Customer c = em.find(Customer.class, 1L);
//修改客户的地址为:丰台区
c.setCustAddress("丰台区");
//4.提交事务
tx.commit();
//5.关闭资源
em.close();
}
merge方法实现修改
@Test
public void test4(){
//1.获取EntityManager对象
EntityManager em = JPAUtil.createEntityManager();
//2.获取事务对象,并开启事务
EntityTransaction tx = em.getTransaction();
tx.begin();
//3.执行更新操作(需要把更新的对象先查询出来)
Customer c = em.find(Customer.class, 1L);
//修改客户的地址
c.setCustAddress("北京市丰台区");
em.merge(c);
//4.提交事务
tx.commit();
//5.关闭资源
em.close();
}
2.5.3 删除
//删除操作
@Test
public void test5(){
//1.获取EntityManager对象
EntityManager em = JPAUtil.createEntityManager();
//2.获取事务对象,并开启事务
EntityTransaction tx = em.getTransaction();
tx.begin();
//3.执行更新操作(需要把更新的对象先查询出来)
Customer c = em.find(Customer.class, 1L);
//删除操作
em.remove(c);
//4.提交事务
tx.commit();
//5.关闭资源
em.close();
}
2.5.4 查询一个
//查询一个实体 立即加载
@Test
public void test2(){
//1.获取EntityManager对象
EntityManager em = JPAUtil.createEntityManager();
//2.获取事务对象,并开启事务
EntityTransaction tx = em.getTransaction();
tx.begin();
//3.执行更新操作(需要把更新的对象先查询出来)
Customer c = em.find(Customer.class, 1L);
System.out.println(c);
//4.提交事务
tx.commit();
//5.关闭资源
em.close();
}
//查询一个实体 延迟加载
@Test
public void test2_1(){
//1.获取EntityManager对象
EntityManager em = JPAUtil.createEntityManager();
//2.获取事务对象,并开启事务
EntityTransaction tx = em.getTransaction();
tx.begin();
//3.执行更新操作(需要把更新的对象先查询出来)
Customer c = em.getReference(Customer.class, 1L);
System.out.println(c);
//4.提交事务
tx.commit();
//5.关闭资源
em.close();
}
2.5.5 查询所有
/*
* 查询所有
*
* 涉及的对象是:
* JPA的Query
* 如何获取该对象:
* EntityManager的createQuery(String jpql)
* 参数含义:
* JPQL:Java Persistence Query Language
* 他的写法和HQL很相似。也是把表名换成类名,把字段名换成属性名称
* 它在写查询所有时,不能直接用 from 实体类
* 需要使用select关键字
* select c from Customer c
*/
@Test
public void test6(){
//1.获取EntityManager对象
EntityManager em = JPAUtil.createEntityManager();
//2.获取事务对象,并开启事务
EntityTransaction tx = em.getTransaction();
tx.begin();
//3.获取JPA的查询对象Query “select”不能省略
Query query = em.createQuery("select c from Customer c where custName like ? and custLevel = ? ");
//给占位符赋值
query.setParameter(1, "%集%");
query.setParameter(2,"23");
//执行方法获取结果集
List list = query.getResultList();
for(Object o : list){
System.out.println(o);
}
//4.提交事务
tx.commit();
//5.关闭资源
em.close();
}
第三章 JPA的多表操作
3.1 一对多关系映射
3.1.1 常用注解
3.1.1.1 @OneToMany
作用:
建立一对多的关系映射
属性:
targetEntityClass:指定多的多方的类的字节码
mappedBy:指定从表实体类中引用主表对象的名称。
cascade:指定要使用的级联操作
fetch:指定是否采用延迟加载
orphanRemoval:是否使用孤儿删除
3.1.1.2 @ManyToOne
作用:
建立多对一的关系
属性:
targetEntityClass:指定一的一方实体类字节码
cascade:指定要使用的级联操作
fetch:指定是否采用延迟加载
optional:关联是否可选。如果设置为false,则必须始终存在非空关系。
3.1.1.3 @JoinColumn
作用:
用于定义主键字段和外键字段的对应关系。
属性:
name:指定外键字段的名称
referencedColumnName:指定引用主表的主键字段名称
unique:是否唯一。默认值不唯一
nullable:是否允许为空。默认值允许。
insertable:是否允许插入。默认值允许。
updatable:是否允许更新。默认值允许。
columnDefinition:列的定义信息。
3.1.2 配置代码
3.1.2.1 客户配置
/**
* 客户的实体类
* 明确使用的注解都是JPA规范的
* 所以导包都要导入javax.persistence包下的
*
*/
@Entity//表示当前类是一个实体类
@Table(name="cst_customer")//建立当前实体类和表之间的对应关系
public class Customer implements Serializable {
@Id//表明当前私有属性是主键
@GeneratedValue(strategy=GenerationType.IDENTITY)//指定主键的生成策略
@Column(name="cust_id")//指定和数据库表中的cust_id列对应
private Long custId;
@Column(name="cust_name")//指定和数据库表中的cust_name列对应
private String custName;
@Column(name="cust_source")//指定和数据库表中的cust_source列对应
private String custSource;
@Column(name="cust_industry")//指定和数据库表中的cust_industry列对应
private String custIndustry;
@Column(name="cust_level")//指定和数据库表中的cust_level列对应
private String custLevel;
@Column(name="cust_address")//指定和数据库表中的cust_address列对应
private String custAddress;
@Column(name="cust_phone")//指定和数据库表中的cust_phone列对应
private String custPhone;
@OneToMany(targetEntity=LinkMan.class,mappedBy="customer")
private Set<LinkMan> linkmans = new HashSet<LinkMan>(0);
public Long getCustId() {
return custId;
}
public void setCustId(Long custId) {
this.custId = custId;
}
public String getCustName() {
return custName;
}
public void setCustName(String custName) {
this.custName = custName;
}
public String getCustSource() {
return custSource;
}
public void setCustSource(String custSource) {
this.custSource = custSource;
}
public String getCustIndustry() {
return custIndustry;
}
public void setCustIndustry(String custIndustry) {
this.custIndustry = custIndustry;
}
public String getCustLevel() {
return custLevel;
}
public void setCustLevel(String custLevel) {
this.custLevel = custLevel;
}
public String getCustAddress() {
return custAddress;
}
public void setCustAddress(String custAddress) {
this.custAddress = custAddress;
}
public String getCustPhone() {
return custPhone;
}
public void setCustPhone(String custPhone) {
this.custPhone = custPhone;
}
public Set<LinkMan> getLinkmans() {
return linkmans;
}
public void setLinkmans(Set<LinkMan> linkmans) {
this.linkmans = linkmans;
}
@Override
public String toString() {
return "Customer [custId=" + custId + ", custName=" + custName + ", custSource=" + custSource
+ ", custIndustry=" + custIndustry + ", custLevel=" + custLevel + ", custAddress=" + custAddress
+ ", custPhone=" + custPhone + "]";
}
}
3.1.2.2 联系人配置
/**
* 联系人的实体类(数据模型)
*/
@Entity
@Table(name="cst_linkman")
public class LinkMan implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="lkm_id")
private Long lkmId;
@Column(name="lkm_name")
private String lkmName;
@Column(name="lkm_gender")
private String lkmGender;
@Column(name="lkm_phone")
private String lkmPhone;
@Column(name="lkm_mobile")
private String lkmMobile;
@Column(name="lkm_email")
private String lkmEmail;
@Column(name="lkm_position")
private String lkmPosition;
@Column(name="lkm_memo")
private String lkmMemo;
//多对一关系映射:多个联系人对应客户
@ManyToOne(targetEntity=Customer.class)
@JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")
private Customer customer;//用它的主键,对应联系人表中的外键
public Long getLkmId() {
return lkmId;
}
public void setLkmId(Long lkmId) {
this.lkmId = lkmId;
}
public String getLkmName() {
return lkmName;
}
public void setLkmName(String lkmName) {
this.lkmName = lkmName;
}
public String getLkmGender() {
return lkmGender;
}
public void setLkmGender(String lkmGender) {
this.lkmGender = lkmGender;
}
public String getLkmPhone() {
return lkmPhone;
}
public void setLkmPhone(String lkmPhone) {
this.lkmPhone = lkmPhone;
}
public String getLkmMobile() {
return lkmMobile;
}
public void setLkmMobile(String lkmMobile) {
this.lkmMobile = lkmMobile;
}
public String getLkmEmail() {
return lkmEmail;
}
public void setLkmEmail(String lkmEmail) {
this.lkmEmail = lkmEmail;
}
public String getLkmPosition() {
return lkmPosition;
}
public void setLkmPosition(String lkmPosition) {
this.lkmPosition = lkmPosition;
}
public String getLkmMemo() {
return lkmMemo;
}
public void setLkmMemo(String lkmMemo) {
this.lkmMemo = lkmMemo;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
@Override
public String toString() {
return "LinkMan [lkmId=" + lkmId + ", lkmName=" + lkmName + ", lkmGender=" + lkmGender + ", lkmPhone="
+ lkmPhone + ", lkmMobile=" + lkmMobile + ", lkmEmail=" + lkmEmail + ", lkmPosition=" + lkmPosition
+ ", lkmMemo=" + lkmMemo + "]";
}
}
3.2 多对多关系映射
3.2.1 常用注解
3.2.1.1 @ManyToMany
作用:
用于映射多对多关系
属性:
cascade:配置级联操作。
fetch:配置是否采用延迟加载。
targetEntity:配置目标的实体类。映射多对多的时候不用写。
3.2.1.2 @JoinTable
作用:
针对中间表的配置
属性:
nam:配置中间表的名称
joinColumns:中间表的外键字段关联当前实体类所对应表的主键字段
inverseJoinColumn:中间表的外键字段关联对方表的主键字段
3.2.1.3 @JoinColumn
作用:
用于定义主键字段和外键字段的对应关系。
属性:
name:指定外键字段的名称
referencedColumnName:指定引用主表的主键字段名称
unique:是否唯一。默认值不唯一
nullable:是否允许为空。默认值允许。
insertable:是否允许插入。默认值允许。
updatable:是否允许更新。默认值允许。
columnDefinition:列的定义信息。
3.2.2 配置代码
3.2.2.1 用户配置
/**
* 用户的数据模型
*/
@Entity
@Table(name="sys_user")
public class SysUser implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="user_id")
private Long userId;
@Column(name="user_code")
private String userCode;
@Column(name="user_name")
private String userName;
@Column(name="user_password")
private String userPassword;
@Column(name="user_state")
private String userState;
//多对多关系映射
@ManyToMany(mappedBy="users")
private Set<SysRole> roles = new HashSet<SysRole>(0);
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUserCode() {
return userCode;
}
public void setUserCode(String userCode) {
this.userCode = userCode;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserPassword() {
return userPassword;
}
public void setUserPassword(String userPassword) {
this.userPassword = userPassword;
}
public String getUserState() {
return userState;
}
public void setUserState(String userState) {
this.userState = userState;
}
public Set<SysRole> getRoles() {
return roles;
}
public void setRoles(Set<SysRole> roles) {
this.roles = roles;
}
@Override
public String toString() {
return "SysUser [userId=" + userId + ", userCode=" + userCode + ", userName=" + userName + ", userPassword="
+ userPassword + ", userState=" + userState + "]";
}
}
3.2.2.2 角色配置
/**
* 角色的实体类
* @author zhy
*
*/
@Entity
@Table(name="sys_role")
public class SysRole implements Serializable {
@Id
@Column(name="role_id")
@GenericGenerator(name="uuid",strategy="uuid")//声明一个主键生成器 name属性:给生成器起个名字。strategy:指定的就是hibernate中包含的生成策略
@GeneratedValue(generator="uuid")
private String roleId;
@Column(name="role_name")
private String roleName;
@Column(name="role_memo")
private String roleMemo;
//多对多关系映射:一个角色可以赋予多个用户
@ManyToMany(cascade=CascadeType.ALL)
//加入一张表
@JoinTable(name="user_role_ref",
joinColumns={@JoinColumn(name="role_id",referencedColumnName="role_id")},//写的是当前实体在中间表的外键字段
inverseJoinColumns={@JoinColumn(name="user_id",referencedColumnName="user_id")}//写的是对方实体在中间表的外键字段
)
private Set<SysUser> users = new HashSet<SysUser>(0);
public String getRoleId() {
return roleId;
}
public void setRoleId(String roleId) {
this.roleId = roleId;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public String getRoleMemo() {
return roleMemo;
}
public void setRoleMemo(String roleMemo) {
this.roleMemo = roleMemo;
}
public Set<SysUser> getUsers() {
return users;
}
public void setUsers(Set<SysUser> users) {
this.users = users;
}
@Override
public String toString() {
return "SysRole [roleId=" + roleId + ", roleName=" + roleName + ", roleMemo=" + roleMemo + "]";
}
}
第四章 JPA的多表操作
4.1 一对多关系的增删改操作
4.1.1 保存操作
保存原则:先保存主表,再保存从表。
/**
* 保存操作
* 创建一个客户和一个联系人
* 建立客户和联系人的双向关联关系
* 先保存客户,再保存联系人
*/
@Test
public void test1(){
Customer c = new Customer();
LinkMan l = new LinkMan();
c.setCustName("JPA One To Many Customer");
l.setLkmName("JPA One To Many LinkMan");
c.getLinkmans().add(l);
l.setCustomer(c);
EntityManager em = JPAUtil.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
em.persist(c);
em.persist(l);
tx.commit();
em.close();
}
JPA注解的配置方式:不涉及多一条update语句的问题
4.1.2 修改操作
@Test
public void test2(){
LinkMan l = new LinkMan();
l.setLkmName("JPA One To Many LinkMan 2");
EntityManager em = JPAUtil.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
Customer c = em.find(Customer.class, 3L);
c.getLinkmans().add(l);
l.setCustomer(c);
tx.commit();
em.close();
}
解决没有保存联系人的问题,配置级联保存更新:
注解的配置方式:
@OneToMany(targetEntity=LinkMan.class,mappedBy="customer",cascade=CascadeType.ALL)
private Set<LinkMan> linkmans = new HashSet<LinkMan>(0);
4.1.3 删除操作
/**
* 删除操作
* 删除从表数据:可以随时任意删除。
* 删除主表数据:
* 有从表数据引用
* 1、不能删除
* 2、如果还想删除,使用级联删除
* 没有从表数据引用:随便删
*
* 在实际开发中,级联删除请慎用!(在一对多的情况下)
*/
@Test
public void test3(){
//获取JPA操作对照
EntityManager em = JPAUtil.getEntityManager();
//获取JPA事务对象
EntityTransaction tx= em.getTransaction();
//开启事务
tx.begin();
//查询id为1的客户
Customer c1 = em.find(Customer.class, 2L);
//删除id为1的客户
em.remove(c1);
tx.commit();
}
级联删除的配置:
@OneToMany(targetEntity=LinkMan.class,mappedBy="customer",cascade=CascadeType.ALL) //用CascadeType.REMOVE也可以
private Set<LinkMan> linkmans = new HashSet<LinkMan>(0);
4.2 多对多关系的增删改操作
4.2.1 保存操作
/**
* 需求:
* 保存用户和角色
* 要求:
* 创建2个用户和3个角色
* 让1号用户具有1号和2号角色(双向的)
* 让2号用户具有2号和3号角色(双向的)
* 保存用户和角色
*/
@Test
public void test1(){
//创建对象
SysUser u1 = new SysUser();
u1.setUserName("用户1");
SysUser u2 = new SysUser();
u2.setUserName("用户2");
SysRole r1 = new SysRole();
r1.setRoleName("角色1");
SysRole r2 = new SysRole();
r2.setRoleName("角色2");
SysRole r3 = new SysRole();
r3.setRoleName("角色3");
//建立关联关系
u1.getRoles().add(r1);
u1.getRoles().add(r2);
r1.getUsers().add(u1);
r2.getUsers().add(u1);
u2.getRoles().add(r2);
u2.getRoles().add(r3);
r2.getUsers().add(u2);
r3.getUsers().add(u2);
//获取JPA操作对照
EntityManager em = JPAUtil.getEntityManager();
//获取JPA事务对象
EntityTransaction tx= em.getTransaction();
//开启事务
tx.begin();
em.persist(u1);
em.persist(u2);
em.persist(r1);
em.persist(r2);
em.persist(r3);
tx.commit();
}
JPA注解的配置方式:不涉及保存失败的问题
4.2.2 删除操作
/**
* 删除操作
* 在多对多的删除时,双向级联删除根本不能配置
* 禁用
* 如果配了的话,如果数据之间有相互引用关系,可能会清空所有数据
*/
@Test
public void test2(){
//获取JPA操作对照
EntityManager em = JPAUtil.getEntityManager();
//获取JPA事务对象
EntityTransaction tx= em.getTransaction();
//开启事务
tx.begin();
SysUser u1 = em.find(SysUser.class,3L);
em.remove(u1);
tx.commit();
}
在多对多映射配置中不能出现双向级联删除的配置,无论注解还是XML配置
第五章 JPA的其他说明
5.1 在JPA中使用C3P0连接池
5.1.1 第一步:导入jar包
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>3.6.10.Final</version>
</dependency>
5.1.2 第二步:在persistence.xml中配置
<!-- 配置使用C3P0数据源 -->
<property name="hibernate.connection.provider_class"
value="org.hibernate.connection.C3P0ConnectionProvider"/>
5.1.3 验证
/**
* 测试是否绑定数据源成功
*/
@Test
public void testConnection(){
EntityManager em = JPAUtil.getEntityManager();
Session s = em.unwrap(Session.class);
s.doWork(new Work() {
@Override
public void execute(Connection conn) throws SQLException {
System.out.println(conn);
}
});
}
5.2 JPA和Hibernate中操作数据的方法对照
操作 | Hibernate中的方法 | JPA中的方法 | 说明 |
---|---|---|---|
保存操作 | save(Object entity) | persist(Object entity) | 共同点:都是把临时态对象转成了持久态。 区别:提供者不一样: save方法是hibernate提供的。 persist方法是JPA规范提供的。 在没有事务的情况下: save会去数据库中保存,hibernate提供了一个内置的事务来执行。 persist什么都不会做。 |
更新操作 | update (Object entity) | merge (Object entity) | Hibernate和jpa都可以利用快照机制,不调用任何方法去更新。Update方法在更新时,如果遇到一级缓存已经包含了一个相同OID的对象会报错。merge则可以执行成功。 |
删除操作 | delete (Object entity) | remove (Object entity) | 都是删除一个实体 |
查询一个操作 | get (Class clazz,Serializable id); load(Class clazz,Serializable id) |
find(Class clazz,Object id) getReerence(Class clazz,Object id) |
get和find都是立即加载。load和getReference一样都是延迟加载。 |
查询所有操作 | Query:使用HQL语句查询 | Query:使用JPQL查询 | 查询语句的形式不一样。 |
查询返回唯一结果操作 | uniqueResult() | getSingleResult() | 查询都是返回一个唯一的结果。 |
5.3 关于 JTA 和 RESOURCE LOCAL 的区别
5.3.1 JTA
JTA事务(Java Transaction API)是J2EE规范中有关事务的标准。它是容器级别的事务,只能运行在J2EE服务器中。它的最大优势是可以支持分布式的事务,如果系统采用的是分布式的数据库,那么只能选择JTA管理EntityManager事务。
使用JTA管理EntityManager事务时,需要注意以下几个问题。
-
JTA事务只能运行在J2EE的环境中,即EJB容器中和Web容器中;而在J2SE环境中只能使用RESOURCE_LOCAL管理事务。
-
容器托管的EntityManager对象只能采用JTA的事务,而不能采用RESOURCE_LOCAL事务。
5.3.2 RESOURCE LOCAL
RESOURCE_LOCAL事务数据库本地的事务。它是数据库级别的事务,只能针对一种数据库,不支持分布式的事务。对于中小型的应用,可以采用RESOURCE_LOCAL管理EntityManager事务。
使用RESOURCE_LOCAL管理EntityManager事务时需要注意以下几个问题。
-
在J2SE环境中,只能使用RESOURCE_LOCAL管理EntityManager事务,并且EntityManager对象是以应用托管方式获得的。
-
代码中使用RESOURCE_LOCAL管理事务时,要通过调用EntityManager的getTransaction()方法获得本地事务。
5.4 常用注解
JPA | 含义和作用 |
---|---|
@Entity | 将 一个类声明为一个持久化类 |
@Id | 声明了持久化类的标识属性(相当于数据表的主键) |
@GeneratedValue | 定义主键生成策略 |
@Table | 为持久化类映射指定表(table)、目录(catalog)和schema的名称。默认值,持久化类名,不带包名 |
@UniqueConstraint | 定义表的唯一约束 |
@Column | 将属性映射到列 |
@Transient | 忽略这些字段和属性,不用持久化到数据库 |
@OneToOne | 建立持久化类之间一对一关联关系 |
@OneToMany | 建立关联一对多 |
@ManyToOne | 建立关联多对一 |
@ManyToMany | 建立关联多对多 |
@JoinColumn | 用于描述一个关联的字段,如外键字段名 |
@GenericGenerator | 策略生成器 |
@GeneratedValue
* strategy 属性的可选值
GenerationType.AUTO - 根据底层数据库自动选择(默认),若数据库支持自动增长类型,则为自动增长。
GenerationType.INDENTITY - 根据数据库的Identity字段生成,支持DB2、MySQL、 MS、SQLServer、SyBase与HyperanoicSQL数据库的Identity 类型主键。
GenerationType.SEQUENCE - 使用Sequence来决定主键的取值,适合Oracle、DB2等支持Sequence的数据库,一般结合@SequenceGenerator使用。
(Oracle没有自动增长类型,只能用Sequence)
GenerationType.TABLE - 使用指定表来决定主键取值,结合@TableGenerator使用。
* generator 属性,可以使用hibernate中的主键生成策略
@Column注解的属性
name - 可选,表示数据库表中该字段的名称,默认情形属性名称一致
nullable - 可选,表示该字段是否允许为null,默认为true
unique - 可选,表示该字段是否是唯一标识,默认为 false
length - 可选,表示该字段的大小,仅对 String 类型的字段有效,默认值255.
insertable - 可选,表示在ORM框架执行插入操作时,该字段是否应出现INSETRT语句中,默认为true
updateable - 可选,表示在ORM框架执行更新操作时,该字段是否应该出现在UPDATE语句中,默认为true.对于一经创建就不可以更改的字段,该属性非常有用,如对于birthday字段。
columnDefinition - 可选,表示该字段在数据库中的实际类型。通常ORM框架可以根据属性类型自动判断数据库中字段的类型,但是对于Date类型仍无法确定数据库中字段类型究竟是DATE,TIME还是TIMESTAMP. 此外,String的默认映射类型为VARCHAR,如果要将String 类型映射到特定数据库的BLOB或TEXT字段类型,该属性非常有用。
@OneToOne、@OneToMany、@ManyToOne、ManyToMany的共有属性:
fetch - 配置加载方式。取值有
Fetch.EAGER - 及时加载,多对一默认是Fetch.EAGER
Fetch.LAZY - 延迟加载,一对多默认是Fetch.LAZY
cascade - 设置级联方式,取值有:
CascadeType.PERSIST - 保存
CascadeType.REMOVE - 删除
CascadeType.MERGE - 修改
CascadeType.REFRESH - 刷新
CascadeType.ALL - 全部
targetEntity - 配置集合属性类型,如:@OneToMany(targetEntity=Book.class)