hibernate基础详解

目录

最新版本

Hibernate框架

hibernate的目录结构

hibernate配置

表的结构

Bean的xml配置文件

引入xml约束

配置文件内容

主键的生成策略

hibernate核心配置文件

核心配置内容

核心配置文件的其它内容

自动创建表

了解java利用hibernate的流程

hibernate的连接池配置

配置日志文件log4j

hibernate操作数据库

查询方法

get(立即加载)和load(懒加载)的区别

更新方法

删除方法

更新或保存

查询所有数据

条件查询

分页查询

Criteria条件查询

持久化类的规则

持久化类的3种状态

自动更新数据库

hibernate的缓存机制

hibernate的事务管理

配置事务级别

线程绑定session

多表映射关系

一对多关系案例

设置外键约束

建立bean类

建立多对一映射

建立一对多映射

保存数据

级联操作

级联删除

放弃外键维护

多对多关联映射

中间表的设计

外键的配置

bean类

多对多的配置

保存数据

hibernate的查询方式

OID查询

对象导航检索

HQL查询

简单查询

设置别名查询

排序查询

条件查询

按名称绑定

投影查询

封装查询

分页查询

聚合函数,单一结果获得

分组查询

QBC

聚合函数和排序查询

离线条件查询

多表查询

内连接

外连接

迫切内连接

hibernate抓取策略(这个尽量在应用篇讲解)


最新版本

hibernate5.x以上

写hql语句,在?号的后面需要加上数字,0代表第1个参数,1代表第2个参数

前言

本篇讲解hibernate基础知识,不会涉及到hibernate的应用,应用在另一篇里

hibernate概念和知识点总结 https://www.cnblogs.com/Java-web-wy/p/6533672.html

Hibernate框架

Hibernate是持久层的框架,它对JDBC做了轻量级的封装,而我们java程序员可以使用面向对象的思想来操纵数据库

从而到达不需要写sql语句的目的,当前目前来说,Hibernate已经快被淘汰了,写sql语句是主流

使用的时候必须导入jdbc的驱动包

hibernate的目录结构

日志包

hibernate配置

使用hibernate操作数据库,对应的Bean必须要有映射文件

表的结构

对应Bean类里的字段也要相同(最好是相同的),会方便很多

Bean的xml配置文件

因为不需要我们自己写sql语句,所以需要对应的配置

引入xml约束

配置文件内容

类的属性和表的字段名相同,不需要写column

需要注意的id需要另外处理,一般我们会叫它主键

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC 
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
	<!-- 建立类与表的映射,name是类名,table是数据库表名 -->
	<class name="com.bean.Customer" table="Customer">
		<!-- 建立类中的属性与表中主键对应,主键比较特殊需要单独处理 -->
		<id name="cust_id" column="cust_id">
			<!-- generator是策略的意思,native表示本地策略 -->
			<!-- 如果主键是String类型的,那么应该用uuid -->
			<generator class="native"></generator>
		</id>

		<!-- 建立类中普通属性和表字段的对应关系 -->
		<!-- 如果类中属性名和表字段名称相同,那么不需要写column -->
		<property name="cust_name" column="cust_name"></property>
		<property name="cust_phone"></property>
	</class>
</hibernate-mapping>  

可以指定数据库,不过一般会在核心配置文件里配置

主键的生成策略

自然主键(上面我们使用的是自然主键)

主键本身就是表中的一个字段,比如id,是bean类里的一个具体的属性

比如身份证号,每个人的身份证id,使用了身份证id作为主键,其他属性是名字,地址,在bean类里,是需要写id属性的

代理主键(在开发中尽量使用此主键)
主键本身并不在表中,在bean类里是不需要写的
就好比excle表格中,最前面有编号,是表示第几行的,但是还有一个编号,是人员编号,二者并不相干

在generator的class里有如下几个选择

increment :  hibernate中提供的自动增长机制,适用于short,int,long类型主键,在单线程中使用
会发送一点语句 selec max(id) from 然后让id+1,作为下一条数据的主键,支持mysql和oracle

identity : ,适用于short,int,long类型主键,使用的是数据库本身的增长机制,适用所有自动增长型数据库,
如mysql,sqlserver,但是oracle并没有自动增长机制

sequence : 适用于short,int,long类型主键,采用的是序列的方法,oracle支持序列,但是mysql不支持

uuid :适用于字符串类型主键,使用的是hibernate随机方式生成字符串主键

native : 本地策略,可以在identity和sequence之间自动切换,如果是mysql,那就是identity,如果是oracle
那就是sequence


 

hibernate核心配置文件

上面只是配置bean的配置文件,hibernate还有核心配置文件,用于配置hibernate自身

以及数据库的一些连接参数,在src路径下创建

hibernate核心配置约束文件

就是bean配置文件的上面一点

核心配置内容

hibernate自带了实例

在hibernate.properti里

在hibernate.cfg.xml里

localhsot:3306/test   这个test数据库名

<hibernate-configuration>
	<session-factory>
		<!-- 数据库的基本参数 -->
		<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
		<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/test</property>
		<property name="hibernate.connection.username">root</property>
		<property name="hibernate.connection.password">root</property>
		<!-- 配置方言,指定hibernate生成哪种sql语句,因为各种数据库的sql语句不一样 -->
		<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>

		<!-- 配置bean的映射路径 -->
		<mapping resource="com/bean/Customer.hbm.xml"></mapping>
	</session-factory>
</hibernate-configuration>

核心配置文件的其它内容


 

自动创建表

如果使用自动创建表,那么上面的那些配置里,就必须把属性写全,type,length这些都需要指定

了解java利用hibernate的流程

浅显的了解一下,业务代码并没有写

@Test
	public void demo1()
	{
		//加载hibernate核心配置文件,默认是加载src目录下
		Configuration configuration=new Configuration().configure();
		
		//创建sessionFactory工厂,类是JDBC连接池的那种工厂
		SessionFactory sessionFactory=configuration.buildSessionFactory();
		
		//获取到session事务对象
		Session session=sessionFactory.openSession();
		
		//手动开启事务,增,删,改才需要事务
		Transaction transaction=session.beginTransaction();
		
		//编写业务代码,后面会讲
        
		
		//事务提交
		transaction.commit();
		
		//资源释放
		session.close();
	}

hibernate的连接池配置

hibernate是一个持久层的框架,自然需要用到数据库连接池

在hibernate里有一个SessionFactory(事务工厂),SessionFactory在里面维护了连接池

一般来说,一个项目里SessionFactory只需要一个,负责创建session对象

写一个工具类来确保一个项目里只有一个SessionFactory

需要注意的是,这里的session对象并不是线程安全的,就是说并不是单例对象,所以写在工具类的内部

不然资源共享的时候会触发线程session的安全问题

public class hibernateUtils
{
	public static final Configuration cfg;
	public static final SessionFactory sf;
	
	static
	{
		cfg=new Configuration().configure();
		sf=cfg.buildSessionFactory();
			
	}
	public static Session openSession()
	{
		return sf.openSession();
	}
	
}

但是SessionFactory只是维护了连接池,它自己并不是连接池,所以需要用到第三方连接池,这里使用c3p0做例子

导入c3p0的jar包以及依赖包,在hibernate目录下的lib/option(可选)里文件里有

并且在hibernate的核心配置文件里加入c3p0连接池的配置

下面这些内容在hibernate的\project\hibernate-c3p0\src\test\resources   有参考,但是没有讲具体作用

		<!-- 配置C3P0连接池 -->
<!-- 		从c3p0连接词中获取数据库连接,因为我们上面已经配置连接了,按道理来说,其实这句是不需要的 -->
<!-- 		<property name="connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property> -->
		<!--在连接池中可用的数据库连接的最少数目 -->
		<property name="c3p0.min_size">5</property>
		<!--在连接池中所有数据库连接的最大数目 -->
		<property name="c3p0.max_size">20</property>
		<!--设定数据库连接的过期时间,以秒为单位, 如果连接池中的某个数据库连接处于空闲状态的时间超过了timeout时间,就会从连接池中清除 -->
		<property name="c3p0.timeout">120</property>
		<!--每3000秒检查所有连接池中的空闲连接 以秒为单位 -->
		<property name="c3p0.idle_test_period">3000</property>

配置日志文件log4j

下面这些在hibernate下project的任何一个项目中都有,且内容都差不多

唯一需要注意的就是最后一行

如果写的info,就会显示info,error warnning 信息,写error,就会输出error和warnning 级别的信息,这是一个层级关系

stdout是标准输出,表示输出到控制台,如果想输出到文件,需要写file

### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.err
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c\:mylog.log  #日志文件的路径
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ###
# error warn info debug trace
log4j.rootLogger= info, stdout

hibernate操作数据库

保存方法

save方法,提交完事务后便会把数据插入进数据库,返回的是序列化的id

这个id是我们插入到数据库的id

@Test
	public void demo1()
	{
		//获取到session事务对象
		Session session=hibernateUtils.openSession();
		
		//手动开启事务,增,删,改才需要事务
		Transaction transaction=session.beginTransaction();
		
		//编写业务代码
		Customer customer=new Customer();
		customer.setCust_name("lover");
		customer.setCust_phone("123456");
		
		//需要保把我们操作的对象存到session里
		session.save(customer);
		
		//事务提交
		transaction.commit();
		
		//资源释放
		session.close();
	}

查询方法

查询有2个方法,一个get,一个是load

二者的使用方法和结果都是一样的

get(立即加载)和load(懒加载)的区别

似乎get和load方法没有区别,但是用调试模式去看就知道

load方法采用的延迟加载,当使用了数据库里除了id以外的属性的时候才会去发送语句

load返回对象的时候,返回的是代理对象,不是真正的Customer对象,虽然数值都一样

get 只要执行完这行代码,就会去发送sql语句,get返回对象的时候,返回的是真正的对象

上面 customer2.toString会使用到其它属性,所以执行toString的时候就会发送sql语句

但是,当get方法查询了id为1的时候数据后,再去执行load方法,也去查询id为1的数据

会发现,sql语句不会发送,同理,先执行load,再执行get方法,sql语句同样只会发送一次

这里面涉及到的hibernate的缓存机制,同一条数据不会重复查询,这个后面再详解

更新方法

推荐先查询,再修改,这时候之前的数据都查询出来了

而直接更新,数据库现在cust_phone是有数值的,如果这里没有指定,也就是null

那么更新后数据库那边就会变成空

而先查询出来就不会出现这种情况

删除方法

同样是推荐先查询后删除,因为多表查询的时候可能还需要查询映射关系,有映射关系的,不能直接删除

更新或保存

设置了id,那么就是更新,没有设置id,那么就是保存

查询所有数据

这里涉及到了简单的HQL(hibernate query language)

利用HQL查询所有数据,需要注意的是,HQL里所有的表名,其实是类名

也就是说Customer其实是类名,如果说表名不是Customer,而类名是Customer

也是可以查询的,因为我们设置的映射关系的

 

当然也可以写sql语句

需要注意的是list返回的是List<Object []>, 把属性变成Object数组

条件查询

这里使用的是hql,所以也是对类里的属性进行条件判断,而不是表的字段

分页查询

Criteria条件查询

Criteria可以用面向对象的语言进行条件查询

like还能写第3个参数,anywhere,就是相当于 %u% 不管出现在字符串哪个地方都匹配到,还有end,start

持久化类的规则

1 hibernate因为会把对应字段的数据映射到数据库中,所以需要用到反射,

而反射就需要一个无参的构造函数,类中默认有一个无参的构造,但是如果写了有参构造

那么无参构造就失效了,所以还是需要提供无参构造,保险起见

2 需要提供一个id,也就是类里面必须写一个id属性,因为hibernate是通过这个id来判断是否是同一个java对象

3 类里的属性,必须使用包装类的类型,比如integer,Long

4 这个java类不能用final修饰,因为被final修饰的类,不能被继承,而hibernate内部是有延迟加载的

延迟加载是一种增强技术,产生对应的代理类返回给我们,而代理类是需要继承我们的类的,所以不能被final修饰

持久化类的3种状态

瞬时态
这种对象没有唯一的标识OID,也就是id没有数值,没有被session,也就是session并没有操作这个对象
这种对象被称为瞬时态对象

持久态
这种对象有唯一的标识OID,也就是id有数值,被session管理,也就是被session被操作了

托管态
这种对象有唯一的标识OID,也就是id有数值,没有被session管理,也就是没有被session操

自动更新数据库

hibernate不仅不需要写sql语句,还能自动更新数据库,但是使用自动更新数据库必须要了解上面的3种时态

可以看到,即使我们不写update语句,还是会向数据库发送语句,这就是持久态的特征

持久态的数据会和hibernate的缓存数据进行比较,如果相同,那么不会发送sql语句

如果不同,只要检测到不同,提交事务后,就会发送语句

hibernate的缓存机制

我们在一开始使用get和load方法的时候就发现,如果要查询的id相同,那么第二遍是不会发送sql语句的

为什么这样,根据上面持久化的3种状态可以解释,因为查询出来的数据是持久态数据,会优先和hibernate

的缓存数据进行比较

hibernate的事务管理

事务最复杂的就是读的问题,各种问题的理解详情https://blog.csdn.net/yzj17025693/article/details/81219928  最后面的事务部分

配置事务级别

从上到下,级别分别是 1  2  4  8

线程绑定session

之前我们说了,session并不是线程安全的,所以我们要绑定session在当前线程上,当前线程结束,session就结束

而不需要我们自己关闭,也就是不需要写session.close(),这样就不会被其它线程使用

hibernate内部已经帮我们绑定好了对象,我们只需要返回对应绑定的session即可

这个hibernateUtils工具类是在最开始的时候写的,往上能翻到

还需要在核心配置文件里配置

多表映射关系

之前我们使用了简单的增删改查,但是如果是多表查询,因为不需要写sql语句

那么就需要配置多表的映射关系

多的一方是员工,一的一方是部门,多个员工对应一个部门,多个部门不能对应一个员工,因为一个员工只能有一个部门

在多的一方建立外键对应一的一方

一对多关系案例

一个部门,一个员工

设置外键约束

建立bean类

需要在一的一方,写多的一方的集合,这里是set集合

man表的最后一个字段是dept_id,但是bean类必须写的是一的一方的对象,hibernate会给你处理

建立多对一映射

建立一对多映射

我们一的一方填写的是set集合,那么这里就要用<set>

要注意的就是key那里,那里填的是多的一方的外键,并不是一的一方的主键

只有2方都指定了外键,那么才能建立外键关系

记得还需要在核心配置文件里假如2个文件的映射

保存数据

执行完后,我们发现总共发送了11条sql语句(包含了update外键),但是我们session.save也就只使用了5次,所以这里面肯定有猫腻

是否因为设置了关联.保存一次会发送2条语句?

	@Test
	public void demo1()
	{
		//获取到session事务对象
		Session session=hibernateUtils.getCurrentSession();
		
		//手动开启事务,增,删,改才需要事务
		Transaction transaction=session.beginTransaction();
		
		//保存2个部门,3个员工
		Dept dept=new Dept();
		dept.setDept_name("销售部");
		Dept dept2=new Dept();
		dept2.setDept_name("技术部");
		
		Man man=new Man();
		man.setMan_name("小红");
		Man man2=new Man();
		man2.setMan_name("小明");
		Man man3=new Man();
		man3.setMan_name("小军");
		
		//建立关系,man 和man2 都是销售部的,man3是技术部
		man.setDept(dept); 
		man2.setDept(dept);
		man3.setDept(dept2);
		
		//把人员添加进来,对应的部门添加对应的人
		dept.getMans().add(man);
		dept.getMans().add(man2);
		dept2.getMans().add(man3);
		
		//保存数据
		session.save(man);
		session.save(man2);
		session.save(man3);
		session.save(dept);
		session.save(dept2);
		
		//事务提交
		transaction.commit();
	}
}

通过只保存一边来测试,只保存一的一方

发现,不管是只保存多的一方还是只保存一的一方,都会出错,报错的是瞬时态对象异常,持久态对象关联了一个瞬时态对象

也就是一个保存了,一个没保存

这里就要涉及到级联操作了

级联操作

级联操作,顾名思义,就是执行一个事物,顺带会执行相关联的事务

需要在一的一方配置cascade(串联),因为我们想只保存一的一方,所以在一的一方配置

如果想只保存多的一方,那就在多的一方配置cascade

save-updae的意思,保存和更新都使用级联操作,delete是级联删除

保存一条数据做测试,这时候只会发送2条插入语句,还有1条更新外键的语句

2条插入语句,一个是保存dept2,一个是dept2关联的man3

下面这情况会发送5条插入语句,3条update 更新外键的语句,更新外键的条数,根据我们添加了几个员工来算,因为

每个员工都要有外键

级联删除

删除一边的数据,另一边也跟着删,因为如果2个表通过外键关联了,那么有外键的一方,必须要先删除,不然没外键的一方删除不了

我们在一的一方设置了级联删除,这样就可以删除一的一方,并且会把多的一方对应的数据也删了

放弃外键维护

我们发现级联删除的时候,会多产生一条update更新外键的语句

这条语句是将外键置为空,其实不用置为空,因为它会先删除与外键关联的多的一方数据

删除了之后,再删除一的一方的数据,而这条update是多余的,

可以设置一的一方放弃外键维护权利,因为一的一方没有外键

保存外键的时候,是多的一条发送语句保存的,一的一方只会产生多余的数据

inverse

这样的话,最好就是只保存多的一方,这是合理的

 

多对多关联映射

一个用户表,一个角色表,1个用户可以选多个角色(部门经理兼职技术员),1个角色也可以被多个用户使用

这时候就需要一个中间表

2张表的设计

中间表的设计

相等于是2个一对多的关系,多的一方其实是中间表,一的一方是角色表和用户表

外键的配置

应该是在多的一方设置外键, 也就是在中间表设置外键

这里中间表的user_id对应的应该是user的user_id,中间表的role_id对应的是role的role_id

bean类

一的一方放多的一方的集合

中间表是多的一方,但是中间表不用建立bean类

多对多的配置

拆分成一对多配置,注意的是,因为我们并没有写中间表的bean类,所以需要指定中间表名

虽然是一对多,但是本质上还是多对多,所以需要写上many-to-many

保存数据

@Test
	public void demo1()
	{
		//获取到session事务对象
		Session session=hibernateUtils.getCurrentSession();
		
		//手动开启事务,增,删,改才需要事务
		Transaction transaction=session.beginTransaction();
		
		User user1=new User();
		user1.setUser_name("小明");
		User user2=new User();
		user2.setUser_name("小军");
		User user3=new User();
		user3.setUser_name("小红");
		
		Role role1=new Role();
		role1.setRole_name("经理");
		Role role2=new Role();	
		role2.setRole_name("洗完工");

		//设置双向关系
		user1.getRoles().add(role1);
		user2.getRoles().add(role1);
		user3.getRoles().add(role2);
		role1.getUsers().add(user1);
		role1.getUsers().add(user2);
		role2.getUsers().add(user3);
		
		session.save(user1);
		session.save(user2);
		session.save(user3);
		session.save(role1);
		session.save(role2);
		
		//事务提交
		transaction.commit();
	}

报错,重复的主键,因为设置了双向关系,那么2者都会去操作同一个主键,所以会有问题

需要让一方放弃外键的维护权

用户选者角色,角色是被动选的,所以角色放弃外键维护权,用户的主动权更强

hibernate的查询方式

OID查询

对象导航检索

HQL查询

简单查询

设置别名查询

排序查询

条件查询

位置绑定从0开始

按名称绑定

可以写一些直观的字符串

投影查询

注意,这里是Object

其实直接写cust_name也是可以的

封装查询

询到的同时封装到对象中,这个对象必须有一个无参的构造,而且要有相应的构造函数

分页查询

聚合函数,单一结果获得

使用.list().get(0)也可以获得第1个结果,用uniqueResult同养可以获得唯一的结果

分组查询

QBC

query by criteria 条件查询

聚合函数和排序查询

rowCount其实就是count(*)

离线条件查询

这个等参数多了就很方便,因为可以在任何地方添加参数

离线查询并不支持多表的语句

需要设置别名

多表查询

建议参考 https://blog.csdn.net/xlecho/article/details/79725967

hibernate抓取策略(这个尽量在应用篇讲解)

猜你喜欢

转载自blog.csdn.net/yzj17025693/article/details/88604879