Hibernate
简介
什么是框架
编程框架指的是实现了某应用领域通用完备功能的底层服务。使用这种框架的编程人员可以在一个通用功能已经实现的基础上开始具体的系统开发。框架提供了所有应用期望的默认行为的类集合。具体的应用通过重写子类(该子类属于框架的默认行为)或组装对象来支持应用专用的行为。
编程框架强调的是软件的设计重用性和系统的可扩充性,以缩短大型应用软件系统的开发周期,提高开发质量。
总之一句话,框架是帮我简化了很多基础代码,只需要程序员按照框架的规则写少量主要代码就可以实现相应功能的一个代码集合,简化开发。
什么是hibernate
Hibernate是一个开放源代码的轻量级的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。 Hibernate可以应用在任何使用JDBC的场合,既可以在Java的客户端程序使用,也可以在Servlet/JSP的Web应用中使用,最具革命意义的是,Hibernate可以在应用EJB的JavaEE架构中取代CMP,完成数据持久化的重任。
轻量级的意思就是只需要导入支持该框架的少量架包就可以使用。
语言特点
- 将对数据库的操作转换为对Java对象的操作,从而简化开发。通过修改一个“持久化”对象的属性从而修改数据库表中对应的记录数据。
- 提供线程和进程两个级别的缓存提升应用程序性能。
- 有丰富的映射方式将Java对象之间的关系转换为数据库表之间的关系。
- 屏蔽不同数据库实现之间的差异。在Hibernate中只需要通过“方言”的形式指定当前使用的数据库,就可以根据底层数据库的实际情况生成适合的SQL语句。
- 非侵入式:Hibernate不要求持久化类实现任何接口或继承任何类,POJO即可。
ORM思想
对象关系映射(Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”。
对象[Object] 编程语言实体类对象
关系[ Relational] 关系数据库表
映射[Mapping] 两者的一个对应关系
这是一种简化数据库操作的开发手段和思想,很多的框架中都采用了这种映射思想。
持久化思想
持久化是将程序数据在持久状态和瞬时状态间转换的机制。通俗的讲,就是瞬时数据(比如内存中的数据,是不能永久保存的)持久化为持久数据(比如持久化至数据库中,能够长久保存)。
持久化(Persistence),即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内存中的对象存储在数据库中,或者存储在磁盘文件中、XML数据文件中等等。
持久化是将程序数据在持久状态和瞬时状态间转换的机制。
JDBC就是一种持久化机制。文件IO也是一种持久化机制。
hibernate入门
搭建环境
在使用hibernate开发的时候可以使用HibernateTools工具来帮助简化开放,可以自动生成配置文件等,相当方便,但是这个插件和编程工具的版本有着较大相关性,要求版本对应,主要是eclipse。
插件可以去官网下载后在编程软件内手动安装也可以直接在编程软件内通过链接安装,这里我们不采用插件,就不做过多赘述。
1.百度搜索hibetnate找到官网下载架包,下载的是ORM项目
官网链接:http://hibernate.org/
点击红框位置就会弹出相应的版本号,选中其中一个点击
进入页面以后往下滑就可以看到如图界面,点击红框位置就可以下载相应版本的orm
要是想下载较老版本的就上滑点击如下图所示位置
下载之后就会得到一个压缩包,将压缩包解压以后得到如下目录文件。
点击其中的lib包,然后点击required
其中就是我们使用hibernate所需要的主要jar包
但是不要忘记,在将所有jar复制到项目的lib文件夹的时候需要加一个数据库的驱动架包
接下来就是在你的编程软件中新建一个项目,把各种jar包往里面添加,建立路径。
这里我用的是2020.9的eclipse软件来演示。
但需要记住的是,当jdk到jdk9,和以上版本,其中的javax.xml.bind包没有了,需要自己往里面导入,
所以这里还需导入另外的四个包才能正确的运行起来。
如果你建的是Maven项目就直接去maven仓库中找到这四个架包链接放到pom.xml中下载
javax.xml.bind jaxb-api 2.3.0 com.sun.xml.bind jaxb-impl 2.3.0 com.sun.xml.bind jaxb-core 2.3.0 javax.activation activation 1.1.1如果不是的话就需要手动下载导入这四个架包,同样也是在maven中来下。
具体如何下载,点击链接查看
https://editor.youkuaiyun.com/md/?articleId=115677042
第一个项目
新建一个普通java项目就可以,建立一个lib文件夹,将上面所述的jar包往里面导入并建立路径。
然后在src目录下新建一个hibernate的核心配置文件,hibernate.cfg.xml。
这个文件必须有,它是一个核心,并且名字必须叫做hibernate.cfg.xml,而且必须放置在src目录下。
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>
<!-- Hibernate 连接数据库的基本信息 -->
<property name="connection.username">root</property>
<property name="connection.password">123456</property>
<property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost:3306/hibernate5?useSSL=false&serverTimezone=UTC</property>
<!-- Hibernate 的基本配置 -->
<!-- Hibernate 使用的数据库方言 -->
<!-- 数据方言的意思是,这个框架支持了多个数据库类型,不同数据库操作语言是有差异的,你必须指定你使用的数据库是哪一个
框架才好匹配对应的数据库操作 -->
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- 运行时是否打印 SQL语句-->
<property name="show_sql">true</property>
<!-- 运行时是否格式化 SQL 语句-->
<property name="format_sql">true</property>
<!-- 生成数据表的策略 -->
<!-- 没有表的话生成表,有表的话更新表 -->
<property name="hbm2ddl.auto">update</property>
<!-- 将映射配置文件引入hibernate核心配置文件中,应为类加载的时候只会加载核心配置文件
想要映射配置文件起作用就要将其引入到核心配置文件中 -->
<mapping resource="com/yxs/helloword/Newshbm.xml"/>
</session-factory>
</hibernate-configuration>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
这一部分数据是一个data约束,是必须要有的,那里找呢,
如下:
到我们上面所下的架包解压文件目录中,点击如同所示的project
接着一直往下点直到如图
打开该文件
就是如图所示的信息,下面新建hbm.xml文件的时候,找数据也是同理。
接下来在src目录下建包,在包里新建一个实体类,这是一个标准的实体类,有私有属性,提供了set和get方法,提供有参无参构造
News.java
public class News {
private Integer id;
private String title;
private String author;
public News() {
super();
}
public News(String title, String author) {
super();
this.title = title;
this.author = author;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
@Override
public String toString() {
return "News [id=" + id + ", title=" + title + ", author=" + author + "]";
}
}
接下来在实体类的同一包下面新建一个xml映射文件
名字习惯上用 实体类名+hbm.xml来命名
Newshbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping >
<!-- 建立实体类和数据库表的映射文件 -->
<!-- class标签 name属性要填的是全路径下的实体类名 table属性是这个实体类对应数据库中的表的名字
如果表已经创建好了,则需要和数据库中的表的名字一样,如果没有则任意取一个名字,待会数据库就会自动帮我们生成-->
<class name="com.yxs.helloword.News" table="news">
<!-- 这个id标签是对应着数据库中的主键的字段,也就是要唯一标识的属性 -->
<id name="id" column="n_id">
<!-- generator标签是设置id在数据库中的策略
native代表的是自动增长 -->
<generator class="native"></generator>
</id>
<!-- 实体类中其他对属性应着的数据库中的普通字段 -->
<property name="title" column="n_title"></property>
<property name="author" column="n_author"></property>
</class>
</hibernate-mapping>
接下来在建一个测试包,里面建立一个测试类用来测试第一个项目
这里我引入了单元测试
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.Test;
import com.yxs.helloword.News;
public class SessionTset {
@Test
public void add() {
//加载核心配置文件
//默认到src目录下去找hibernate.cfg.xml文件
Configuration cfg = new Configuration();
cfg.configure();
/*
* 创建SessionFactory对象
* 读取核心配置文件信息创建SessionFactory
* 根据映射文件信息,在数据库中将对应的表创建出来
*/
SessionFactory sFactory = cfg.buildSessionFactory();
//根据SessionFactory创建Session
//相当于创建了一个数据库连接
Session seeSession = sFactory.openSession();
//开启事务
Transaction transaction =seeSession.beginTransaction();
//创建操作逻辑
//通过操作实体类来操作数据库
News news = new News("java","狂神");
//将实体类创建的对象添加到连接中,添加到数据库中
seeSession.save(news);
//提交事务
transaction.commit();
//关闭资源
seeSession.close();
sFactory.close();
}
}
记得要在数据库中建立对应的数据库,表就不用建了。
接下来就可以运行测试类,去查看数据库中有没有建立表,表中有没有数据变化。
hbm.xml详解
- 他的位置和名字并没有实质的要求
- class标签 name属性要填的是全路径下的实体类名 table属性是这个实体类对应数据库中的表的名字,如果表已经创建好了,则需要和数据库中的表的名字一样,如果没有则任意取一个名字,待会数据库就会自动帮我们生成。
- 这个id标签是对应着数据库中的主键的字段,也就是要唯一标识的属性
- generator标签是设置id在数据库中的策略native代表的是自动增长
- property标签实体类中其他对属性应着的数据库中的普通字段,name属性是实体类中的属性名,colum是对应在数据库表中生成的字段名
- colum属性可以不写上,默认在数据的表中会生成和实体类名字一样的字段名
- id标签,property标签中还有一个type属性,它的作用是设置实体类属性对应在数据中生成字段的数据类型,比如int,varchar等
核心API
Hibernate的API一共有6个,分别为:Session、SessionFactory、Transaction、Query、Criteria和Configuration。通过这些接口,可以对持久化对象进行存取、事务控制。
Session
Session接口负责执行被持久化对象的CRUD操作(CRUD的任务是完成与数据库的交流,包含了很多常见的SQL语句)。但需要注意的是Session对象是非线程安全的。同时,Hibernate的session不同于JSP应用中的HttpSession。这里当使用session这个术语时,其实指的是Hibernate中的session,而以后会将HttpSession对象称为用户session
它是一个单线程的对象,不能共性,自己用自己的,相当于connection中的连接
通过调用它的方法来对数据库进行操作
- sava(添加)
- update(修改)
- delete(删除)
- get(根据id查询)。
SessionFactory
SessionFactory接口负责初始化Hibernate。它充当数据存储源的代理,并负责创建Session对象。这里用到了工厂模式。需要注意的是SessionFactory并不是轻量级的,因为一般情况下,一个项目通常只需要一个SessionFactory就够,当需要操作多个数据库时,可以为每个数据库指定一个SessionFactory。
在核心配置文件中使用update那么在创建该对象的时候被创建的时候就会根据配置文件在数据库中建立一个表。
SessionFactory创建是非常耗资源的,所以一个项目通常只用一个SessionFactory,那我们就可以写一个工具类创建SessionFactory,当我们需要时就可以调用工具类创建,但是工具类需要保证创建一次,需要的时候直接调用就好。
//创建SessionFactory的工厂,一次创建,多次调用
public class FactoryUtil {
static Configuration configuration = null;
static SessionFactory factory = null;
//在静态代码块中创建SessionFactory,只执行一次
static {
configuration = new Configuration().configure();
factory = configuration.buildSessionFactory();
}
public SessionFactory getFactory() {
//调用该方法返回SessionFactory
return factory;
}
}
Transaction
Transaction 接口是一个可选的API,可以选择不使用这个接口,取而代之的是Hibernate 的设计者自己写的底层事务处理代码。 Transaction 接口是对实际事务实现的一个抽象,这些实现包括JDBC的事务、JTA 中的UserTransaction、甚至可以是CORBA 事务。之所以这样设计是能让开发者能够使用一个统一事务的操作界面,使得自己的项目可以在不同的环境和容器之间方便地移植。
//开启事务
Transaction transaction =seeSession.beginTransaction();
//提交事务
transaction.commit();
//回滚
transaction.rollback();
事务的四个特性
原子性:不可分割,一组操作,要成功都成功,要失败都失败
一致性:操作前后数据变化要一致,例如A给B转500,A的钱先减500 ,B的钱再加500,如果数据库中A的钱刚减了500就发生异常,B的500没加上,那总量就少了500.这就操作前后不一致。一致性就是要保证不出现这样的情况。
隔离性:多个事务同时操作同一条记录,它们之间不会产生影响。
持久性::对数据的操作要提交到数据库进行持久化操作。
Query
Query接口让你方便地对数据库及持久对象进行查询,它可以有两种表达方式:HQL语言或本地数据库的SQL语句。Query经常被用来绑定查询参数、限制查询记录数量,并最终执行查询操作。
Criteria
Criteria接口与Query接口非常类似,允许创建并执行面向对象的标准化查询。值得注意的是Criteria接口也是轻量级的,它不能在Session之外使用。
Configuration
Configuration 类的作用是对Hibernate 进行配置,以及对它进行启动。在Hibernate 的启动过程中,Configuration 类的实例首先定位映射文档的位置,读取这些配置,然后创建一个SessionFactory对象。虽然Configuration 类在整个Hibernate 项目中只扮演着一个很小的角色,但它是启动hibernate 时所遇到的第一个对象。
解决编写xml文件没有提示的问题
本身在我们编写hibernate.cfg.xml和hbm.xml文件的时候就导入了约束,约束是网络地址的,按道理能联网就可以有提示,如果没有,我们还可以使用本地导入提示,具体如下
点击进入XML Catalog 对User Specified Entries 进行编辑,点击Add
这里填入的名字是什么,例如给hibernate.cfg.xml添加提示,这里就是该配置文件头部的
http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd
这行代码,如果是给hbm.xml添加提示这里就是hbm.xml文件头部的这行数据
http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd
之后点击File System…选择这两个文件
是不是对这两个文件特别熟悉,没错就是上面我们将加入约束的时候找的那个文件,所以找这两个文件的路径就不多说了。
选中打开,接着点击应用就完工了。为了保险,重启eclipse。
如果还是不行,建议导入hibernatetool插件,导入该插件之后,即使你没有引入本地约束,同样有提示。
实体类编写规范
1. 要求实体类必须有自己的私有属性
2. 每个私有属性必须有自己的get和set方法
3. 私有属性中必须有一个属性是一个唯一表示的值,即这个属性的值必须唯一不能相同,一般用id,与数据库中的主 键字段相对应
4. 建议不使用基本数据类型,而使用基本类型的包装类
为什么使用包装类,因为包装类使用起来更规范,比如显示学生分数,使用int型,学生考0分和缺考都显示0,没有区分,如果使用Integer类型,缺考可以用null表示。
Hibernate主键生成策略
hibernate要求实体类中有属性是值唯一的,来与数据库中的主键字段相对应
主键可以有不同的生成策略
1、assigned
主键由外部程序负责生成,在 save() 之前必须指定一个。Hibernate不负责维护主键生成。与Hibernate和底层数据库都无关,可以跨数据库。在存储对象前,必须要使用主键的setter方法给主键赋值,至于这个值怎么生成,完全由自己决定,这种方法应该尽量避免。
“ud”是自定义的策略名,人为起的名字,后面均用“ud”表示。
特点:可以跨数据库,人为控制主键生成,应尽量避免。
2、increment
由Hibernate从数据库中取出主键的最大值(每个session只取1次),以该值为基础,每次增量为1,在内存中生成主键,不依赖于底层的数据库,因此可以跨数据库。
Hibernate调用org.hibernate.id.IncrementGenerator类里面的generate()方法,使用select max(idColumnName) from tableName语句获取主键最大值。该方法被声明成了synchronized,所以在一个独立的Java虚拟机内部是没有问题的,然而,在多个JVM同时并发访问数据库select max时就可能取出相同的值,再insert就会发生Dumplicate entry的错误。所以只能有一个Hibernate应用进程访问数据库,否则就可能产生主键冲突,所以不适合多进程并发更新数据库,适合单一进程访问数据库,不能用于群集环境。
官方文档:只有在没有其他进程往同一张表中插入数据时才能使用,在集群下不要使用。
特点:跨数据库,不适合多进程并发更新数据库,适合单一进程访问数据库,不能用于群集环境。
3、hilo
hilo(高低位方式high low)是hibernate中最常用的一种生成方式,需要一张额外的表保存hi的值。保存hi值的表至少有一条记录(只与第一条记录有关),否则会出现错误。可以跨数据库。
hilo生成器生成主键的过程(以hibernate_unique_key表,next_hi列为例):
-
获得hi值:读取并记录数据库的hibernate_unique_key表中next_hi字段的值,数据库中此字段值加1保存。
-
获得lo值:从0到max_lo循环取值,差值为1,当值为max_lo值时,重新获取hi值,然后lo值继续从0到max_lo循环。
-
根据公式 hi * (max_lo + 1) + lo计算生成主键值。
注意:当hi值是0的时候,那么第一个值不是0*(max_lo+1)+0=0,而是lo跳过0从1开始,直接是1、2、3……
那max_lo配置多大合适呢?
这要根据具体情况而定,如果系统一般不重启,而且需要用此表建立大量的主键,可以吧max_lo配置大一点,这样可以减少读取数据表的次数,提高效率;反之,如果服务器经常重启,可以吧max_lo配置小一点,可以避免每次重启主键之间的间隔太大,造成主键值主键不连贯。
特点:跨数据库,hilo算法生成的标志只能在一个数据库中保证唯一。
4、seqhilo
与hilo类似,通过hi/lo算法实现的主键生成机制,只是将hilo中的数据表换成了序列sequence,需要数据库中先创建sequence,适用于支持sequence的数据库,如Oracle。
特点:与hilo类似,只能在支持序列的数据库中使用。
5、sequence
采用数据库提供的sequence机制生成主键,需要数据库支持sequence。如oralce、DB、SAP DB、PostgerSQL、McKoi中的sequence。MySQL这种不支持sequence的数据库则不行(可以使用identity)。
Hibernate生成主键时,查找sequence并赋给主键值,主键值由数据库生成,Hibernate不负责维护,使用时必须先创建一个sequence,如果不指定sequence名称,则使用Hibernate默认的sequence,名称为hibernate_sequence,前提要在数据库中创建该sequence。
特点:只能在支持序列的数据库中使用,如Oracle。
6、identity
identity由底层数据库生成标识符。identity是由数据库自己生成的,但这个主键必须设置为自增长,使用identity的前提条件是底层数据库支持自动增长字段类型,如DB2、SQL Server、MySQL、Sybase和HypersonicSQL等,Oracle这类没有自增字段的则不支持。
特点:只能用在支持自动增长的字段数据库中使用,如MySQL。
7、native
native由hibernate根据使用的数据库自行判断采用identity、hilo、sequence其中一种作为主键生成方式,灵活性很强。如果能支持identity则使用identity,如果支持sequence则使用sequence。
注意:如果Hibernate自动选择sequence或者hilo,则所有的表的主键都会从Hibernate默认的sequence或hilo表中取。并且,有的数据库对于默认情况主键生成测试的支持,效率并不是很高。
特点:根据数据库自动选择,项目中如果用到多个数据库时,可以使用这种方式,使用时需要设置表的自增字段或建立序列,建立表等。
8、uuid
UUID:Universally Unique Identifier,是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。按照开放软件基金会(OSF)制定的标准计算,用到了以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字。
Hibernate在保存对象时,生成一个UUID字符串作为主键,保证了唯一性,但其并无任何业务逻辑意义,只能作为主键,唯一缺点长度较大,32位(Hibernate将UUID中间的“-”删除了)的字符串,占用存储空间大,但是有两个很重要的优点,Hibernate在维护主键时,不用去数据库查询,从而提高效率,而且它是跨数据库的,以后切换数据库极其方便。
使用时实体类中的对应主键的属性是字符串类型。
特点:uuid长度大,占用空间大,跨数据库,不用访问数据库就生成主键值,所以效率高且能保证唯一性,移植非常方便,推荐使用。
9、guid
GUID:Globally Unique Identifier全球唯一标识符,也称作 UUID,是一个128位长的数字,用16进制表示。算法的核心思想是结合机器的网卡、当地时间、一个随即数来生成GUID。从理论上讲,如果一台机器每秒产生10000000个GUID,则可以保证(概率意义上)3240年不重复。
Hibernate在维护主键时,先查询数据库,获得一个uuid字符串,该字符串就是主键值,该值唯一,缺点长度较大,支持数据库有限,优点同uuid,跨数据库,但是仍然需要访问数据库。
注意:长度因数据库不同而不同
MySQL中使用select uuid()语句获得的为36位(包含标准格式的“-”)
Oracle中,使用select rawtohex(sys_guid()) from dual语句获得的为32位(不包含“-”)
特点:需要数据库支持查询uuid,生成时需要查询数据库,效率没有uuid高,推荐使用uuid。
10、foreign
使用另外一个相关联的对象的主键作为该对象主键。主要用于一对一关系中。
该例使用domain.User的主键作为本类映射的主键。
特点:很少使用,大多用在一对一关系中。
11、select
使用触发器生成主键,主要用于早期的数据库主键生成机制,能用到的地方非常少。
这里主要使用的native和uuid这两个生成策略,native可以跨平台,自动帮你匹配数据库类型的自增关键字。
实现crud操作
添加数据
添加数据,入门项目已经演示
根据id查询
public void get() {
//调用工具类创建sessionFactory
SessionFactory sessionFactory = FactoryUtil.getFactory();
//创建Session
Session session = sessionFactory.openSession();
//开启事务
Transaction transaction = session.beginTransaction();
//操作查询
//get()根据id进行查询,第一参数是实体类.class,第二个参数是id的值
//返回值是一个实体类对象
News news = session.get(News.class, 1);
//输出实体类看看
System.out.println(news.toString());
//提交事务
transaction.commit();
//关闭session
session.close();
//这里使用工具类创建SessionFactory之后就不能关了,只创建一次,关了下一个没得用了
}
修改操作
public void testUpdate() {
//调用工具类创建sessionFactory
SessionFactory sessionFactory = FactoryUtil.getFactory();
//创建Session
Session session = sessionFactory.openSession();
//开启事务
Transaction transaction = session.beginTransaction();
//操作修改
//先进行查询
//get()根据id进行查询,第一参数是实体类.class,第二个参数是id的值
//返回值是一个实体类对象
News news = session.get(News.class, 1);
//得到需要修改的数据,通过实体类的先关set方法进行修改
news.setAuthor("小阿宝");
//通过update进行修改
//update根据传进去的实体类的id去修改表中数据
session.update(news);
//提交事务
transaction.commit();
//关闭session
session.close();
}
这里就有一个逻辑关系就是,先要找到要修改的对象,并且拿到那个对象,之后对要修行的对象通过代码set方法修改后,再使用update方法把修改后的对象返回给数据库,数据库的内容发生改变。
进行删除操作
public void testDelete() {
//调用工具类创建sessionFactory
SessionFactory sessionFactory = FactoryUtil.getFactory();
//创建Session
Session session = sessionFactory.openSession();
//开启事务
Transaction transaction = session.beginTransaction();
//操作删除
//第一种方法
/*
先进行查询
get()根据id进行查询,第一参数是实体类.class,第二个参数是id的值
返回值是一个实体类对象
* */
//News news = session.get(News.class, 2);
//通过delete进行删除
//session.delete(news);
/*第二种方法
*
*/
//创建一个实体类对象
News news2 = new News();
//设置id
news2.setId(2);
//通过delete进行删除
//delete通过实体类对象中的id值去表中找到对应id记录,删除。
session.delete(news2);
//提交事务
transaction.commit();
//关闭session
session.close();
}
既然如此,那update可不可以也像删除一样直接new一个实体类对象,赋值然后修改,绕过查询那一关呢,可以的呀,前提是你创建实体类对象赋值的时候需要把每个属性值都赋值,一旦哪一个属性没赋值,修改时那个属性值就是null,数据库中对应的字段就被改为了null。这种现象先查询后修改就不会出现,所以建议使用第一种。
实体类的三种时态
1.瞬时态
这种实体类对象中没有id值,只有其它属性的值,而且这种类也没有和Sesssion产生联系,一般用来进行添加操作。
2.持久态
持久态的实体类对象中有id属性的值,而且和Session也产生了关联。例如通过Session查询出的对象,里面id有值,且和Session有关系。
3.托管态
实体类对象中id属性有值,但是和Session没有关系。
session.saveOrUpdate(实体类对象),这个方法做的操作和加在方法中的实体类的对象有着很大的关系,
当里面是一个瞬时态则进行添加操作,当里面是一个托管态且对象的id值是数据库表中已经有的,则进行修改操作
Hibernate缓存
缓存:
数据存入数据库,数据库本身就是一个文件系统,用流的方式来读取文件效率不高。
怎么提高效率呢,那就开辟一片内存,把数据存入内存,直接从内存读取数据,避开用流操作的方式。要数据的时候先到内存中去找,找不到了再去数据库中取,这样便提高了效率,这样一种机制叫做缓存。
一级缓存
- hibernate中的一级缓存默认是打开的,不需要设置
- 一级缓存的范围是session范围,意思是缓存的范围从seesion创建开始到session关闭结束。
- 一级缓存中储存的数据必须是持久态的数据。
- 一级缓存,例如查询一个数据,当Session创建,进入了一级缓存的范围,当是查询的时候第一次,到以及缓存中查找数据,缓存中没有数据,会到数据库中取出查询的数据,此时Session并没有被关闭,此时查询到的数据被缓存。
- 当你再次查询同一条数据时,会先查看内存中有没有刚才数据的缓存,有就直接从内存中读取缓存数据,而不用再次到数据库中查找取出数据。
- 这个缓存直到将创建的Session关闭,则这个Session对应的缓存清空。
所以当你在同一个session中多次查询同一数据时,只会在第一次到数据库中查询取数据,其他都是之技读取缓存,故而控制台打印出的数据库语句只有一次,如下:
查询代码:
@Test
public void get() {
//调用工具类创建sessionFactory
SessionFactory sessionFactory = FactoryUtil.getFactory();
//创建Session
Session session = sessionFactory.openSession();
//开启事务
Transaction transaction = session.beginTransaction();
//操作查询
//get()根据id进行查询,第一参数是实体类.class,第二个参数是id的值
//返回值是一个实体类对象
News news = session.get(News.class, 1);
//输出实体类看看
System.out.println(news.toString());
News news2 = session.get(News.class, 1);
System.out.println(news2.toString());
//提交事务
transaction.commit();
//关闭session
session.close();
}
结果:
Hibernate:
select
news0_.n_id as n_id1_0_0_,
news0_.n_title as n_title2_0_0_,
news0_.n_author as n_author3_0_0_
from
news news0_
where
news0_.n_id=?
News [id=1, title=java, author=小阿宝]
News [id=1, title=java, author=小阿宝]
//查询了两次,数据库语句只输出了一次,证明只到数据库取了一次数据。
一级缓存的特征
持久态自动更新数据库
这里先举一个例子,
News news = session.get(News.class, 1);
//得到需要修改的数据,通过实体类的先关set方法进行修改
news.setAuthor("小阿宝");
//通过update进行修改
//session.update(news);
//提交事务
transaction.commit();
这是一个修改数据的例子,可以发现当我们查询出数据,对它的属性做出了修改,但是我并没有使用session.update()方法,而是直接进行事务提交,实践证明数据库数据还是发生了变化,原理是啥呢?原理就是持久态自动更新数据库这样一个特征。
过程:
- 当执行Session创建,进入一级缓存,执行查询语句之后,缓存中没有数据,去数据查询取回数据,数据进入一级缓存区,同时伴随一级缓存区还会生成一个对应的快照区也叫做副本,数据也进入快照区。
- 当修改了这个数据的时候,以及缓存区中的数据随之发生变化,变成修改后的数据,而快照区的数据却不会随着修改发生变化,还是原来的老数据。
- 当提交事务的时候会检查一级缓存区中的数据和快照区中的数据是否相同,相同则不会修改数据库中的数据,不相同则会按照一级缓存区中的数据对数据库中的对应数据进行修改。
二级缓存
- 二级缓存现在已经不使用了,被redis技术替代
- 二级缓存默认是关闭的需要配置才能打开
- 二级缓存的范围是SessionFactory的范围,一个项目中通常只创建一个SessionFactory,所以也可以说二级缓存贯穿整个项目。
事务操作
事务的概念
在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)
例如:在关系数据库中,一个事务可以是一条SQL语句,一组SQL语句或整个程序。
特性
事务是恢复和并发控制的基本单位。
事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
没有隔离性的后果:
脏读
一个事务处理过程里读取了另一个未提交的事务中的数据。
例子
A,B共用一张卡,卡中余额100,A要买20的书,付款时B也在付款买90的玩具,这时A发现卡里余额变成了10余额不足,B最后因为输入密码错误也没付款成功
幻读也叫虚读
一个事务执行两次查询,第二次结果集包含第一次中没有或某些行已经被删除的数据,造成两次结果不一致,只是另一个事务在这两次查询中间插入或删除了数据造成的。幻读是事务非独立执行时发生的一种现象。
例子
事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。
不可重复读
一个事务两次读取同一行的数据,结果得到不同状态的结果,中间正好另一个事务更新了该数据,两次结果相异,不可被信任。
例子
事务T1在读取某一数据,而事务T2立马修改了这个数据并且提交事务给数据库,事务T1再次读取该数据就得到了不同的结果,发送了不可重复读。
代码中事务的规范写法
try{
正常代码,session创建到事务提交
}catch{
事务回滚(发生异常进行事务回滚)
}finally{
关闭创建的资源
}
Hibernate中session与本地线程的绑定
session可以理解为基于JDBC的Connnection,Session是 Hibernate运作的中心,
对象的生命周期、事务的管理、数据库的存取都与Session息息相关。
首先,我们需要知道,SessionFactory负责创建Session,SessionFactory是线程安全的,多个并发线程可以同时访问一个SessionFactory 并从中获取Session实例。
而Session并非线程安全,也就是说,如果多个线程同时使用一个Session实例进行数据存取,则将会导致 Session 数据存取逻辑混乱.因此创建的Session实例必须在本地存取空上运行,使之总与当前的线程相关。这里就需要用到ThreadLocal,在很多种Session 管理方案中都用到了它。
ThreadLocal 是Java中一种较为特殊的线程绑定机制,通过ThreadLocal存取的数据,
总是与当前线程相关,
session单线程,而且并不安全,为了防止混乱必须保证一个线程一个session。
在hibernate核心配置文件中配置Session与本地线程绑定
<property name="hibernate.current_session_context_class">thread</property>
在工具类中调用SessionFactory.getCurrentSession();返回一个与本地线程绑定的session
//返回与本地线程绑定的session的方法
public static Session getSessionobject() {
return factory.getCurrentSession();
}
与本地线程绑定之后就不用在手动关闭session了,因为与本地线程绑定的缘故,当本地线程操作完成释放的时候Session也就被关闭了。
hibernate中几个API的查询功能
Query对象
- 使用Query对象来做查询,不需要写sql语句,但是要写hql语句
- hql语句是Hibernate Query Language的简写,是hibernate提供的一种语言
- hql语句和sql语句的区别在于sql语句时对数据库的表和表中字段进行操作,而hql语句则是对实体类对象和实体类的属性进行操作。
查询所有:
//创建query对象
//方法中填入from实体类名,返回全部实体类数据
Query query = session.createQuery("from News");
//使用一个集合接收对象
List<News> list = query.list();
//增强for循环遍历对象集合
for (News news : list) {
System.out.println(news.toString());
}
Criteria对象
使用该对象做查询不需要写语句直接调用方法就行
//创建criteria对象
//方法中填入实体类名.class,返回全部实体类数据
Criteria criteria = session.createCriteria(News.class);
//使用一个集合接收对象
List<News> list = criteria.list();
//增强for循环遍历对象集合
for (News news : list) {
System.out.println(news.toString());
}
SQLQuery对象
使用该对象的时候,调用底层的sql语句来实现,而且默认返回的不再是一个对象集合而是变成了一个数组集合。
//创建SQLQuery对象
//方法中填入sql语句,返回数组集合
SQLQuery sqlQuery = session.createSQLQuery("select * from news");
//使用一个集合接收数组
List<Object[]> list = sqlQuery.list();
//增强for循环遍历数组集合
for (Object[] objects : list) {
System.out.println(Arrays.toString(objects));
}
但是返回的是数组集合不方便,想要它返回的是一个对象集合,那就需要在其中加入一句sqlQuery.addEntity(实体类.class);
//创建SQLQuery对象
//方法中填入sql语句,返回数组集合
SQLQuery sqlQuery = session.createSQLQuery("select * from news");
//把数据放到对象中
sqlQuery.addEntity(News.class);
//使用一个集合接收对象
List<News> list = sqlQuery.list();
//增强for循环遍历对象集合
for (News news : list) {
System.out.println(news.toString());
}
hibernate中的一对多操作
一对多的映射文件的配置
一对多关系,对应着表的一对多关系,这里以客户和销售员为例,一个销售员可以对应多个客户,而一个客户只能对应一个销售员。
客户实体类:
public class Client {
private Integer clid;
private String cname;
private String adress;
//两个实体类互相表示
private Salesman salesman;
public Client() {
super();
}
public Client( String cname, String adress) {
super();
this.cname = cname;
this.adress = adress;
}
public Integer getClid() {
return clid;
}
public void setClid(Integer clid) {
this.clid = clid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
public String getAdress() {
return adress;
}
public void setAdress(String adress) {
this.adress = adress;
}
public Salesman getSalesman() {
return salesman;
}
public void setSalesman(Salesman salesman) {
this.salesman = salesman;
}
@Override
public String toString() {
return "Client [salesman=" + salesman + ", clid=" + clid + ", cname=" + cname + ", adress=" + adress + "]";
}
}
客户实体类映射文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping >
<class name="com.yxs.demo1.Client" table="client">
<!-- 这个id标签是对应着数据库中的主键的字段,也就是要唯一标识的属性 -->
<id name="clid" column="c_id">
<!-- generator标签是设置id在数据库中的策略
native代表的是自动增长 -->
<generator class="native"></generator>
</id>
<!-- 实体类中其他对属性应着的数据库中的普通字段 -->
<property name="cname" column="cname"></property>
<property name="adress" column="adress"></property>
<!-- name属性值是该配置文件对应实体类中互相表示的实体类的名字
class是那个被表示的实体类全路径
column是外键,注意,一和多的外键名要是同一个 -->
<many-to-one name="salesman" class="com.yxs.demo1.Salesman" column="slid"></many-to-one>
</class>
</hibernate-mapping>
销售员实体类
public class Salesman {
//实体类互相表示
private Set<Client> sClients = new HashSet<Client>();
private Integer sid;
private String sname;
private String sex;
public Salesman() {
super();
}
public Salesman(String sname, String sex) {
super();
this.sname = sname;
this.sex = sex;
}
public Integer getSid() {
return sid;
}
public void setSid(Integer sid) {
this.sid = sid;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Set<Client> getsClients() {
return sClients;
}
public void setsClients(Set<Client> sClients) {
this.sClients = sClients;
}
@Override
public String toString() {
return "Salesman [sClients=" + sClients + ", sid=" + sid + ", sname=" + sname + ", sex=" + sex + "]";
}
}
销售员实体类映射配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping >
<class name="com.yxs.demo1.Salesman" table="salesman">
<!-- 这个id标签是对应着数据库中的主键的字段,也就是要唯一标识的属性 -->
<id name="sid" column="s_id">
<!-- generator标签是设置id在数据库中的策略
native代表的是自动增长 -->
<generator class="native"></generator>
</id>
<!-- 实体类中其他对属性应着的数据库中的普通字段 -->
<property name="sname" column="sname"></property>
<property name="sex" column="sex"></property>
<!-- 一对多建表,有外键,set标签来表示所有客户 -->
<!-- name属性表示该set集合的名字 -->
<set name="sClients">
<!-- key标签colnm表示外键名 采用双向维护机制,一对多,一和多两边都要引入外键外键名同 一个-->
<key column="slid"></key>
<!-- 一对多中,多的全路径 -->
<one-to-many class="com.yxs.demo1.Client"/>
</set>
</class>
</hibernate-mapping>
记得在hibernate.cfg.xml核心配置文件中引入两个映射文件
<mapping resource="com/yxs/demo1/Clienthbm.xml"/>
<mapping resource="com/yxs/demo1/Salesmanhbm.xml"/>
最后这个slid外键字段是在客户表中生成的,售货员表中没有,那代表同一个销售员表的主键可以在客户表外键字段中出现多次,即多个客户就对应了一个销售员
一对多的级联操作
级联操作,因为两张表通过外键绑定,所有操作一张表往往就会对另一张表产生操作效果。
级联保存:
创建一个销售员,为它添加一个或多个客户
级联删除:
删除一个销售员,删除了他对应的一个或多个客户。
一对多的级联保存
这里有两种代码写法,复杂的:
public class Test1 {
private SessionFactory sessionFactory = null;
private Session session = null;
private Transaction transaction = null;
@Test
public void addtest1() {
try {
//调用工具类创建SessionFactory
sessionFactory = FactoryUtil.getFactory();
//根据SessionFactory创建Session
//相当于创建了一个数据库连接
session = sessionFactory.openSession();
//开启事务
transaction = session.beginTransaction();
//创建操作逻辑
//创建两个实体类
Client client = new Client("古力娜扎","新疆");
Salesman salesman = new Salesman("马尔扎哈","男");
//把创建的两个实体类对象互相添加到实体类中建立的关于对方的属性中,以此建立联系
client.setSalesman(salesman);
salesman.getsClients().add(client);
//将实体类创建的对象添加到连接中,添加到数据库中
session.save(client);
session.save(salesman);
//提交事务
transaction.commit();
} catch (Exception e) {
//回滚事务
transaction.rollback();
}finally {
//关闭资源
session.close();
//这里使用工具类创建SessionFactory之后就不能关了,只创建一次,关了下一个没得用了
sessionFactory.close();
}
}
简单的:
简单的有这么一个逻辑:是先有销售员,再为销售员创建多个客户
1.在销售员映射配置文件中的标签中加入属性cascade,它的值设置为save—update
<set name="sClients" cascade="save-update">
接下里是java代码
public class Test1 {
private SessionFactory sessionFactory = null;
private Session session = null;
private Transaction transaction = null;
@Test
public void addtest1() {
try {
//调用工具类创建SessionFactory
sessionFactory = FactoryUtil.getFactory();
//根据SessionFactory创建Session
//相当于创建了一个数据库连接
session = sessionFactory.openSession();
//开启事务
transaction = session.beginTransaction();
//创建操作逻辑
//创建两个实体类
Client client = new Client("迪丽热巴","新疆");
Salesman salesman = new Salesman("黑旋风","男");
//把客户给销售员加进去产生联系
salesman.getsClients().add(client);
//将实体类创建的对象添加到连接中,添加到数据库中
session.save(salesman);
//提交事务
transaction.commit();
} catch (Exception e) {
//回滚事务
transaction.rollback();
}finally {
//关闭资源
session.close();
//这里使用工具类创建SessionFactory之后就不能关了,只创建一次,关了下一个没得用了
sessionFactory.close();
}
}
}
区别在于通过配置了销售员的映射配置文件,代码中不再需要将销售员实体类对象添加到客户实体类对象属性中去了,而执行Session.save()方法的时候也只需将销售员实体类对象放进去即可进行级联操作。
一对多级联删除操作
逻辑:删除一个销售员就要删除该销售员对应的所有多个客户
1.在销售员映射配置文件中设置标签属性cascade="delete"如果有多个属性,英文状态下逗号隔开
<set name="sClients" cascade="save-update,delete">
java代码:
public class test2 {
@Test
public void deleteTest1() {
SessionFactory sessionFactory = null;
Session session = null;
Transaction transaction = null;
try {
//调用工具类创建SessionFactory
sessionFactory = FactoryUtil.getFactory();
//根据SessionFactory创建Session
//相当于创建了一个数据库连接
session = sessionFactory.openSession();
//开启事务
transaction = session.beginTransaction();
//根据id查询出将要删除的销售员id
Salesman salesman = session.get(Salesman.class, 3);
//调用delete()进行删除
session.delete(salesman);
//提交事务
transaction.commit();
} catch (Exception e) {
//回滚事务
transaction.rollback();
}finally {
//关闭资源
session.close();
//这里使用工具类创建SessionFactory之后就不能关了,只创建一次,关了下一个没得用了
//sessionFactory.close();
}
}
}
过程:
- 根据id查询销售员
- 根据外键查询客户
- 把外键设置为null
- 删除销售员和客户
级联修改
public void updateTest1() {
//修改一对多的关系
//将一个客户对一个的销售员改为另一个
SessionFactory sessionFactory = null;
Session session = null;
Transaction transaction = null;
try {
//调用工具类创建SessionFactory
sessionFactory = FactoryUtil.getFactory();
//根据SessionFactory创建Session
//相当于创建了一个数据库连接
session = sessionFactory.openSession();
//开启事务
transaction = session.beginTransaction();
//根据id查询
Salesman salesman = session.get(Salesman.class, 1);
Client client = session.get(Client.class, 7);
//将客户放到销售员中建立联系
//持久态不用调用方法也可以实现修改功能
salesman.getsClients().add(client);
//提交事务
transaction.commit();
} catch (Exception e) {
//回滚事务
transaction.rollback();
}finally {
//关闭资源
session.close();
//这里使用工具类创建SessionFactory之后就不能关了,只创建一次,关了下一个没得用了
//sessionFactory.close();
}
}
执行上面的代码,将销售员5对应的客户改去对应销售员1了。但是这样执行代码外键会被修改两遍,因为外键是两边维护的,一去维护一次,多也去维护一次。为了提高效率,我们采取的方法就是让一的那一方去放弃维护,只让一对多中的多的那一方去维护外键。
这就需要在一的那一方的映射配置文件的标签中添加属性inverse="true,它默认是false意思是不放弃维护外键,我们将它设置为true就行。
<set name="sClients" cascade="save-update,delete" inverse="true">
Hibernate多对多操作
多对多的映射文件的配置
思路,建立用户实体类和角色实体类,一个用户可以扮演多个角色,一个角色可以对应着多个用户。
用户实体类:
public class User {
private Set<Role> uRoles = new HashSet<Role>();
private Integer uid;
private String username;
private String password;
public User() {
super();
}
public User(String username, String password) {
super();
this.username = username;
this.password = password;
}
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Set<Role> getuRoles() {
return uRoles;
}
public void setuRoles(Set<Role> uRoles) {
this.uRoles = uRoles;
}
@Override
public String toString() {
return "User [uRoles=" + uRoles + ", uid=" + uid + ", username=" + username + ", password=" + password + "]";
}
}
用户实体类对应的映射配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping >
<class name="com.yxs.demo2.User" table="u_user">
<!-- 这个id标签是对应着数据库中的主键的字段,也就是要唯一标识的属性 -->
<id name="uid" column="u_id">
<!-- generator标签是设置id在数据库中的策略
native代表的是自动增长 -->
<generator class="native"></generator>
</id>
<!-- 实体类中其他对属性应着的数据库中的普通字段 -->
<property name="username" column="username"></property>
<property name="password" column="password"></property>
<!-- 多对多建表,有外键,set标签来表示所有用户 -->
<!-- name属性表示该配置文件对应实体类中set集合的名字 -->
<!-- table表示第三张表的名字 -->
<set name="uRoles" table="user_role" cascade="save-update">
<!-- key标签column表示该实体类在第三张表中的外键名 -->
<key column="userid"></key>
<!-- class多对多中,角色实体类的全路径 -->
<!-- column表示角色实体类在第三张表的外键名称 -->
<many-to-many class="com.yxs.demo2.Role" column="roleid"/>
</set>
</class>
</hibernate-mapping>
角色实体类:
public class Role {
private Set<User> rUsers = new HashSet<User>();
private Integer rid;
private String rname;
public Role() {
super();
}
public Role(String rname) {
super();
this.rname = rname;
}
public Integer getRid() {
return rid;
}
public void setRid(Integer rid) {
this.rid = rid;
}
public String getRname() {
return rname;
}
public void setRname(String rname) {
this.rname = rname;
}
public Set<User> getrUsers() {
return rUsers;
}
public void setrUsers(Set<User> rUsers) {
this.rUsers = rUsers;
}
@Override
public String toString() {
return "Role [rUsers=" + rUsers + ", rid=" + rid + ", rname=" + rname + "]";
}
}
角色实体类对应映射配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping >
<class name="com.yxs.demo2.Role" table="r_role">
<!-- 这个id标签是对应着数据库中的主键的字段,也就是要唯一标识的属性 -->
<id name="rid" column="r_id">
<!-- generator标签是设置id在数据库中的策略
native代表的是自动增长 -->
<generator class="native"></generator>
</id>
<!-- 实体类中其他对属性应着的数据库中的普通字段 -->
<property name="rname" column="rname"></property>
<!-- 多对多建表,有外键,set标签来表示所有用户 -->
<!-- name属性表示该配置文件对应实体类中set集合的名字 -->
<!-- table表示第三张表的名字 -->
<set name="rUsers" table="user_role" cascade="save-update">
<!-- key标签column表示该实体类在第三张表中的外键名 -->
<key column="roleid"></key>
<!-- class多对多中,用户实体类的全路径 -->
<!-- column表示用户实体类在第三张表的外键名称 -->
<many-to-many class="com.yxs.demo2.User" column="userid"/>
</set>
</class>
</hibernate-mapping>
将映射文件引入到hibernate核心配置文件中
<mapping resource="com/yxs/demo2/Userhbm.xml"/>
<mapping resource="com/yxs/demo2/Rolehbm.xml"/>
运行工具类查看表是否生成,因为工具类是用来创建SessionFactory的,创建SessionFactory的时候就根据核心配置文件和映射文件生成了表。
多对多级联操作
多对多级联保存
java代码
public class Test3 {
//多对多级联保存用户和角色
@Test
public void addTest1() {
SessionFactory sessionFactory = null;
Session session = null;
Transaction transaction = null;
try {
//调用工具类创建SessionFactory
sessionFactory = FactoryUtil.getFactory();
//根据SessionFactory创建Session
//相当于创建了一个数据库连接
session = sessionFactory.openSession();
System.out.println(session);
//开启事务
transaction = session.beginTransaction();
//创建操作逻辑
//创建两个用户,三个角色
User user1 = new User("安禄山","123");
User user2 = new User("大马猴","456");
Role role1 = new Role("节度使");
Role role2 = new Role("表演者");
Role role3 = new Role("猴子");
//让他们产生多对多联系
//user1对应 role1/2
user1.getuRoles().add(role1);
user1.getuRoles().add(role2);
//user2对应 role2/3
user2.getuRoles().add(role2);
user2.getuRoles().add(role3);
//保存添加到数据库
session.save(user1);
session.save(user2);
//提交事务
transaction.commit();
} catch (Exception e) {
//回滚事务
transaction.rollback();
}finally {
//关闭资源
session.close();
//这里使用工具类创建SessionFactory之后就不能关了,只创建一次,关了下一个没得用了
//sessionFactory.close();
}
}
}
记住把映射配置文件中的
<set name="rUsers" table="user_role" cascade="save-update">
cascade="save-update"属性配置起来,不然不会报错但是数据库数据不会变。
多对多级联删除操作
java代码
//多对多级联删除用户和角色
public void deleteTest1() {
SessionFactory sessionFactory = null;
Session session = null;
Transaction transaction = null;
try {
//调用工具类创建SessionFactory
sessionFactory = FactoryUtil.getFactory();
//根据SessionFactory创建Session
//相当于创建了一个数据库连接
session = sessionFactory.openSession();
System.out.println(session);
//开启事务
transaction = session.beginTransaction();
//创建操作逻辑
//根据id查询需要删除用户
User user = session.get(User.class,12);
//调用方法删除用户
session.delete(user);
//提交事务
transaction.commit();
} catch (Exception e) {
//回滚事务
transaction.rollback();
}finally {
//关闭资源
session.close();
//这里使用工具类创建SessionFactory之后就不能关了,只创建一次,关了下一个没得用了
//sessionFactory.close();
}
}
就上面的代码来看,你需要在用户的映射配置文件中的做如下设计
<set name="uRoles" table="user_role" cascade="save-update,delete">
cascade=“save-update,delete” 加了一个delete。
但是有个小问题,删除的时候会把用户2对用的两个角色全部删除了,那用户1就只能对应剩下的一个角色了。
还要注意的是不要把角色映射配置文件也加上cascade=“delete”,不然就一连串的删除了,因为他们之间有角色来关联着,用户1和用户2有角色重合,做了上面的设置就会一条链的把所有的一次删除。
多对多关系第三张表的维护
多对多关系中,该表关系通常都是通过第三张表的维护来实现
关于维护第三张表,通过下面两个例子演示
1.让某个用户拥有某个角色
@Test
//第三张表的维护,让某个用户拥有某个角色
public void threeTable() {
SessionFactory sessionFactory = null;
Session session = null;
Transaction transaction = null;
try {
//调用工具类创建SessionFactory
sessionFactory = FactoryUtil.getFactory();
//根据SessionFactory创建Session
//相当于创建了一个数据库连接
session = sessionFactory.openSession();
System.out.println(session);
//开启事务
transaction = session.beginTransaction();
//创建操作逻辑
//根据id查询需要删除用户
User user = session.get(User.class,13);
Role role = session.get(Role.class,15);
//调用方法把角色加给用户
user.getuRoles().add(role);
//提交事务
transaction.commit();
} catch (Exception e) {
//回滚事务
transaction.rollback();
}finally {
//关闭资源
session.close();
//这里使用工具类创建SessionFactory之后就不能关了,只创建一次,关了下一个没得用了
//sessionFactory.close();
}
}
让某个用户失去某个角色:
//第三张表的维护,让某个用户失去某个角色
@Test
public void threeTabledown() {
SessionFactory sessionFactory = null;
Session session = null;
Transaction transaction = null;
try {
//调用工具类创建SessionFactory
sessionFactory = FactoryUtil.getFactory();
//根据SessionFactory创建Session
//相当于创建了一个数据库连接
session = sessionFactory.openSession();
System.out.println(session);
//开启事务
transaction = session.beginTransaction();
//创建操作逻辑
//根据id查询需要删除用户
User user = session.get(User.class,13);
Role role = session.get(Role.class,15);
//调用方法把角色加给用户
user.getuRoles().remove(role);
//提交事务
transaction.commit();
} catch (Exception e) {
//回滚事务
transaction.rollback();
}finally {
//关闭资源
session.close();
//这里使用工具类创建SessionFactory之后就不能关了,只创建一次,关了下一个没得用了
//sessionFactory.close();
}
}
Hibernate的查询方式
对象导航查询
意思是查询某个对象,在查询到和这个对象对应的另外对象或者记录。
例如根据id查询销售员,通过销售员查询到该销售员对应的客户
@Test
//演示对象导航查询
//要求是查出用户id为1的销售员对应的所有客户
public void getUser() {
SessionFactory sessionFactory = null;
Session session = null;
Transaction transaction = null;
try {
//调用工具类创建SessionFactory
sessionFactory = FactoryUtil.getFactory();
//根据SessionFactory创建Session
//相当于创建了一个数据库连接
session = sessionFactory.openSession();
System.out.println(session);
//开启事务
transaction = session.beginTransaction();
//创建操作逻辑
//根据id查询查询销售员
Salesman salesman = session.get(Salesman.class, 1);
//得到该对象属性set集合中关于客户的信息
Set<Client> sClients = salesman.getsClients();
//遍历输出结果
for (Client client : sClients) {
System.out.println(client);
}
//提交事务
transaction.commit();
} catch (Exception e) {
//回滚事务
transaction.rollback();
}finally {
//关闭资源
session.close();
//这里使用工具类创建SessionFactory之后就不能关了,只创建一次,关了下一个没得用了
//sessionFactory.close();
}
}
OID查询
根据id查询数据,返回对象。
通过session调用get()方法查询。
hql查询
Query对象,通过写hql语句实现查询
这个查询是hibernate提供的一种查询语句,它与sql区别就是sql语句操作数据库表和字段,hql操作的是实体类对象和属性。
查询所有:
//创建一个Query对象
Query query = session.createQuery("from Client");
//创建对象集合接受数据
List<Client> list = query.list();
//遍历集合输出对象
for (Client client : list) {
System.out.println(client.toString());
}
条件查询
from 实体类名 where 属性名=? and 属性名=?
from 实体类名 where 属性名 like?
//创建一个Query对象
Query query = session.createQuery("from Client where clid=? and cname=?");
//往?占位符中赋值
query.setParameter(0, 1);
query.setParameter(1, "古力娜扎");
//创建对象集合接受数据
List<Client> list = query.list();
//遍历集合输出对象
for (Client client : list) {
System.out.println(client.toString());
}
条件查询之模糊查询:
//创建操作逻辑
//创建一个Query对象
Query query = session.createQuery("from Client where cname like?");
//往?占位符中赋值
query.setParameter(0, "%娜%");
//创建对象集合接受数据
List<Client> list = query.list();
//遍历集合输出对象
for (Client client : list) {
System.out.println(client.toString());
}
排序查询
from 实体类名 order by 属性名 asc(升序)/desc(降序)
默认为升序
//升序
/创建操作逻辑
//创建一个Query对象
Query query = session.createQuery("from Client order by clid asc");
//创建对象集合接受数据
List<Client> list = query.list();
//遍历集合输出对象
for (Client client : list) {
System.out.println(client.toString());
}
//降序
/创建操作逻辑
//创建一个Query对象
Query query = session.createQuery("from Client order by clid desc");
//创建对象集合接受数据
List<Client> list = query.list();
//遍历集合输出对象
for (Client client : list) {
System.out.println(client.toString());
}
分页查询
//创建操作逻辑
//创建一个Query对象查询所有
Query query = session.createQuery("from Client");
//设置分页开始位置
query.setFirstResult(0);
//设置每一页显示数据
query.setMaxResults(1);
//创建对象集合接受数据
List<Client> list = query.list();
//遍历集合输出对象
for (Client client : list) {
System.out.println(client.toString());
}
投影查询
查询的只有部分字段,并不是所有字段
select 属性名称1,属性名称2 from 实体类名
//创建操作逻辑
//创建一个Query对象查询所有
Query query = session.createQuery("select cname from Client");
//创建属性集合接受数据
List<Object> list = query.list();
//遍历集合输出对象
for (Object object : list) {
System.out.println(object);
}
聚集函数查询
常见聚集函数
count sum avg max min
//创建操作逻辑
//创建一个Query对象查询所有
Query query = session.createQuery("select count(*) from Client");
//返回的只是单一数据,就不用集合了
Object object = query.uniqueResult();
//输出结果
System.out.println(object);
//返回的结果实际是一个long类型的数,通常把它转为int 但是不能直接转,会报错。先把object转为long在由long转为int
Long long = (Long) object;
int i = long.intvalue();
QBC查询
Criteria对象
不需要再写语句,调用方法实现查询
操作实体类和属性
通过criteria对象实现操作
查询所有:
//创建操作逻辑
//创建一个criteria对象查询所有
Criteria criteria = session.createCriteria(Client.class);
//返回一个对象集合
List<Client> list = criteria.list();
//循环输出
for (Client client : list) {
System.out.println(client);
}
条件查询
/创建操作逻辑
//创建一个criteria对象查询所有
Criteria criteria = session.createCriteria(Client.class);
//设置查询条件
criteria.add(Restrictions.eq("clid", 1));
criteria.add(Restrictions.eq("cname", "古力娜扎"));
//返回一个对象集合
List<Client> list = criteria.list();
//循环输出
for (Client client : list) {
System.out.println(client);
}
条件查询之模糊查询
//创建操作逻辑
//创建一个criteria对象查询所有
Criteria criteria = session.createCriteria(Client.class);
//设置查询条件
criteria.add(Restrictions.like("cname", "%娜%"));
//返回一个对象集合
List<Client> list = criteria.list();
//循环输出
for (Client client : list) {
System.out.println(client);
}
排序查询
//降序
//创建操作逻辑
//创建一个criteria对象查询所有
Criteria criteria = session.createCriteria(Client.class);
//设置降序排序
criteria.addOrder(Order.desc("clid"));
//返回一个对象集合
List<Client> list = criteria.list();
//循环输出
for (Client client : list) {
System.out.println(client);
}
//升序
//创建操作逻辑
//创建一个criteria对象查询所有
Criteria criteria = session.createCriteria(Client.class);
//设置升序排序
criteria.addOrder(Order.asc("clid"));
//返回一个对象集合
List<Client> list = criteria.list();
//循环输出
for (Client client : list) {
System.out.println(client);
}
分页查询
//创建操作逻辑
//创建一个criteria对象查询所有
Criteria criteria = session.createCriteria(Client.class);
//设置分页
//数据起始位置
criteria.setFirstResult(0);
//每页显示数据条数
criteria.setMaxResults(1);
//返回一个对象集合
List<Client> list = criteria.list();
//循环输出
for (Client client : list) {
System.out.println(client);
}
统计查询
得到表中有多少条记录
//创建操作逻辑
//创建一个criteria对象查询所有
Criteria criteria = session.createCriteria(Client.class);
//查询有多少条记录
criteria.setProjection(Projections.rowCount());
Object object = criteria.uniqueResult();
//数据类型转换
Long long1 = (Long)object;
int i = long1.intValue();
//输出结果
System.out.println(i);
离线查询
//创建操作逻辑
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Client.class);
Criteria criteria = detachedCriteria.getExecutableCriteria(session);
List<Client> list = criteria.list();
for (Client client : list) {
System.out.println(client);
}
本地sql查询
SQLQuery对象,使用普通的sql语句实现查询
HQL多表查询
内连接
from 实体类名 别名 inner join 别名.set集合属性
//创建操作逻辑
//创建一个Query对象
Query query = session.createQuery("from Salesman s inner join s.sClients");
//创建集合接受数据
List list = query.list();
list中的每部分是数组的形式。
左外连接
from 实体类名 别名 left outer join 别名.set集合属性
返回list每个部分是数组形式
右外连接
from 实体类名 别名 right outer join 别名.set集合属性
迫切内连接
内连接和迫切内连接底层实现代码相同
不同的是迫切内连接返回的list每部分是对象
from 实体类名 别名 inner join fetch 别名.set集合属性
//创建操作逻辑
//创建一个Query对象
Query query = session.createQuery("from Salesman s inner join fetch s.sClients");
//创建集合接受数据
List list = query.list();
左迫切内连接
from 实体类名 别名 left outer join fetch 别名.set集合属性
返回list每个部分是对象
Hibernate 的检索策略
立即查询
根据id查询,调用get()方法会立刻发送语句查询数据库
延迟查询
根据id查询,调用load()方法不会立刻发送语句查询数据库,而是对象要得到属性中的值才会去查询数据库,你不用我就不查,你要获取属性了我才去查。
类级别延迟:调用load()查询出的是一个实体类对象
关联级别的延迟:例如查询销售员对应的所有客户的过程是否需要延迟,这个过程称为关联级别延迟。
其中框架中默认就有一个关联级别的延迟。
关联级别延迟操作
在配置文件中进行配置实现
在set标签上进行操作
fetch 值select(默认)
lazy 值:
true 延迟
false 不延迟
extra 及其延迟
批量抓取操作
例子,根据id查询出所有的销售员,然后查出销售员对应的所有客户。
这样用常规查询的话,会向数据库发送多条语句,效率不高。
优化方案:批量抓取
在销售员的映射配置文件的set标签上加属性 batch-size="10"它可以简化发送语句,提高查询效率。
里面的值可以是任意整数。但是往往值越大效率越高。
ent> list = criteria.list();
//循环输出
for (Client client : list) {
System.out.println(client);
}
**分页查询**
~~~~java
//创建操作逻辑
//创建一个criteria对象查询所有
Criteria criteria = session.createCriteria(Client.class);
//设置分页
//数据起始位置
criteria.setFirstResult(0);
//每页显示数据条数
criteria.setMaxResults(1);
//返回一个对象集合
List<Client> list = criteria.list();
//循环输出
for (Client client : list) {
System.out.println(client);
}
统计查询
得到表中有多少条记录
//创建操作逻辑
//创建一个criteria对象查询所有
Criteria criteria = session.createCriteria(Client.class);
//查询有多少条记录
criteria.setProjection(Projections.rowCount());
Object object = criteria.uniqueResult();
//数据类型转换
Long long1 = (Long)object;
int i = long1.intValue();
//输出结果
System.out.println(i);
离线查询
//创建操作逻辑
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Client.class);
Criteria criteria = detachedCriteria.getExecutableCriteria(session);
List<Client> list = criteria.list();
for (Client client : list) {
System.out.println(client);
}
本地sql查询
SQLQuery对象,使用普通的sql语句实现查询
HQL多表查询
内连接
from 实体类名 别名 inner join 别名.set集合属性
//创建操作逻辑
//创建一个Query对象
Query query = session.createQuery("from Salesman s inner join s.sClients");
//创建集合接受数据
List list = query.list();
list中的每部分是数组的形式。
左外连接
from 实体类名 别名 left outer join 别名.set集合属性
返回list每个部分是数组形式
右外连接
from 实体类名 别名 right outer join 别名.set集合属性
迫切内连接
内连接和迫切内连接底层实现代码相同
不同的是迫切内连接返回的list每部分是对象
from 实体类名 别名 inner join fetch 别名.set集合属性
//创建操作逻辑
//创建一个Query对象
Query query = session.createQuery("from Salesman s inner join fetch s.sClients");
//创建集合接受数据
List list = query.list();
左迫切内连接
from 实体类名 别名 left outer join fetch 别名.set集合属性
返回list每个部分是对象
Hibernate 的检索策略
立即查询
根据id查询,调用get()方法会立刻发送语句查询数据库
延迟查询
根据id查询,调用load()方法不会立刻发送语句查询数据库,而是对象要得到属性中的值才会去查询数据库,你不用我就不查,你要获取属性了我才去查。
类级别延迟:调用load()查询出的是一个实体类对象
关联级别的延迟:例如查询销售员对应的所有客户的过程是否需要延迟,这个过程称为关联级别延迟。
其中框架中默认就有一个关联级别的延迟。
关联级别延迟操作
在配置文件中进行配置实现
在set标签上进行操作
fetch 值select(默认)
lazy 值:
true 延迟
false 不延迟
extra 及其延迟
批量抓取操作
例子,根据id查询出所有的销售员,然后查出销售员对应的所有客户。
这样用常规查询的话,会向数据库发送多条语句,效率不高。
优化方案:批量抓取
在销售员的映射配置文件的set标签上加属性 batch-size="10"它可以简化发送语句,提高查询效率。
里面的值可以是任意整数。但是往往值越大效率越高。