Session接口
Session接口是Hibernate向应用程序提供的操纵数据库的最主要的接口,它提供了基本的保
存,更新,删除和查询的方法。
Session缓存
Session是有一个缓存, 又叫Hibernate的一级缓存
session缓存是由一系列的Java集合构成的。当一个对象被加入到Session缓存中,这个对
象的引用就加入到了java的集合中,以后即使应用程序中的引用变量不再引用该对象,只要
Session缓存不被清空,这个对象一直处于生命周期中。
Session缓存的作用:
1)减少访问数据库的频率。
2)保证缓存中的对象与数据库中的相关记录保持同步。-
Session清理缓存的时机:
1)当调用Transaction的commit()方法时,commit()方法先清理缓存(前提是
FlushMode.COMMIT/AUTO),然后再向数据库提交事务。
2)当应用程序调用Session的find()或者iterate()等查询数据的方法时,如果缓存中的持久化
对象的属性发生了变化,就会先清理缓存,以保证查询结果能反映持久化对象的最新状态。
3)当应用程序显示调用Session的flush()方法的时候。
-
Hibernate处理的对象在不同的程序执行阶段存在不同的状态
我们知道Hibernate的核心就是对数据库的操作,里面的核心接口就是org
接口。要想对数据库操作我们就要理清楚Hibernate里的对象在整个操作中的所属的状态
(主要有三个:临时,持久,游离)。
写一个测试架子, 来验证Session缓存机制和处理对象的状态
建javabean,配置映射,建映射文件这里不讲,前面已讲了过程,照做即可。
1. 验证缓存减少了sql操作。
public class HibernateTest {
SessionFactory sessionFactory=null;
Session session=null;
Transaction transaction=null;
//以上三条语句,生产环境中不能这样干,因为多线程时,会乱,会话要分开来
@Before
public void init() {
//Configuration类对象封装我们的配置文件里的配置信息
Configuration configuration=new Configuration().configure();
//hibernate规定,所有的配置或服务,要生效,必须配置或服务注册到一个服务注册类
ServiceRegistry serviceRegistry=configuration.getStandardServiceRegistryBuilder().build();
//获取会话工厂类对象
sessionFactory=new MetadataSources(serviceRegistry).buildMetadata().buildSessionFactory();
//获取hibernate会话
session =sessionFactory.openSession();
//开启事务处理
transaction=session.beginTransaction();
}
@After
public void destory() { //After表示在test后执行
transaction.commit();
session.close();
sessionFactory.close();
}
//创建STUDENT表和操作
@Test
public void test1() {
//操作数据库
/*1.创建一个SessionFactory工厂类:通过它建一个与数据库连接会话,Session,init()方法创建*/
/*2.创建表*/
//第一次使用的测试,这里只创建表STUDENT加了一第记录,执行多次后,增加多条重复记录
Student student = new Student("张三","男",new Date());
session.save(student);
}
//创建USER表
@Test
public void test2() {
User user = new User();
user.setUsername("熊少文");
user.setPassword("1234546");
System.out.println("用户对象为: "+user.toString());
session.save(user); //持久化插入到数据库中。
}
//测试session 缓存机制,session的get/load方法:前提是已执行了test1 test2,保证有数据库有记录对象可用。
@Test
public void test3() {
//get/load方法,通过ID,获取数据库中对应的记录数据,转为对象(返回对象),放到session缓存中。
Student student = session.get(Student.class, 3); //(查看了表某ID对应的记录没有),这里可是动态变化的,不固定id.
System.out.println("student:"+student);
student = null; //引用变量清空(内存清空),并不清空缓存。
Student student2 = session.get(Student.class, 3);
System.out.println("student2:"+student2);
//结合上面两个get情况来看,获取两个对象,控制台看到只发送了一条sql语句,后一个是从缓存中获取的,明显减少访问数据库的频率当然了是同一个对象。
}
}
2. 验证 缓存中的对象与数据库中的相关记录保持同步。
Hibernate提供了哪些方法去保证缓存中的对象数据和数据库中的数据记录之间一定同步
呢?
-
flush():强制让数据库里的数据记录和session中缓存的对象数据保存一致,不一致就发起
update这条sql语句修改数据让其一致, 一致, 它不动作,不会发去sql语句.
有关于flush()方法的知识点:
a、在事务的commit()提交之前自动调用了session的flush()方法,然后再提交事务。
b、flush()可能会发送sql语句,但是不会提交事务。
上代码中,在没有session.save()的情况下,如果更改了缓存中的对象属性,再commit()后。缓存中的student数据更新了,说明同步了,三个输出也是一样的。。
注意:此例测试是用了上面的测试类,此时要在 destory()注释 //transaction.commit();掉。 -
refresh(): refresh()要显示写出来,它会强制发出一条select语句, 保证session缓存中的数据和数据库里数据记录是
一致的, 如果发现不一致它会修改缓存中的对象中的数据让其一致。
这里用一张示意图总结一下这两个方法!
- 另外Hibernate操作缓存的方法还有clear() : 清理session缓存(数据库中的数据不会清除)
Student student =session.get(Student.class,3);
System.out.println(student);
session.clear(); //清空缓存
Student student2 = session.get(Student.class, 3);
System.out.println("第二次输出,会从缓中读取,但缓存清空,会发送sql语句从数据库中读"+student2);
Hibernate里的对象不同状态和Session的核心方法
1. 临时状态的测试:
Student student = new Student("张三", "男", 22, new Date());
以上student就是一个Transient(临时状态),此时student并没有被session进行托管,即在
session的缓存中还不存在student这个对象,当执行完save方法后,此时student被session
托管,且数据库中也存在了该对象,student就变成了一个Persistent(持久化对象)
session.save(student); //save方法就是往数据库插入一条记录
结论: 此时我们知道hibernate会发出一条insert的语句,执行完save方法后,该student对象就变成了持久化对象了。
2. 持久状态的测试
- 测试,临时状态对象转变成的持久对象:
Student student = new Student("张三", "男", 22, new Date());
session.save(student);
student.setSex("女");
transaction.commit(); //不加这代码,控制台看到的数据是没有变化的,而缓存和数据库变化了
//此时会发出2条sql,一条用户做插入,一条用来做更新
此时student是持久化状态,已经被session所管理,当在commit()提交时,会把session中
的对象和目前的对象进行比较如果两个对象中的值不一致就会继续发出相应的sql语句
student.setSex("女");此时会发出2条sql,一条用户做插入,一条用来做更新
结论: 在调用了save方法后,此时student已经是持久化对象了,被保存在了session缓存当
中,这时student又重新修改了属性值,那么在提交事务时,此时hibernate对象就会拿当前
这个student对象和保存在session缓存中的student对象进行比较,如果两个对象相同,则不
会发送update语句,否则,如果两个对象不同,则会发出update语句
注意: 在调用save方法后,student此时已经是持久化对象了,记住一点:
如果一个对象已经是持久化状态了,那么此时对该对象进行各种修改,或者调用多次update、save方法
时,hibernate都不会发送sql语句,只有当事物提交的时候,此时hibernate才会拿当前这
个对象与之前保存在session中的持久化对象进行比较,如果不相同就发送一条update的
sql语句,否则就不会发送update语句
OID---hibernate从缓存中对比数据库记录的id,拿到的id叫OID.
- 用get/load方法加载来的持久对象状态:
Student student = session.get(Student.class, 4);
student.setSex("nan"); //效果与1一样,只是这里不用insert语句。
transaction.commit(); // 不加这代码,控制台看到的数据是没有变化的,而缓存和数据库变化了 */
- 持久状态的测试,清除缓存后:
Student student = session.get(Student.class, 4);
student.setSex("nan");
session.clear();
transaction.commit(); //不加这代码,控制台看到的数据是没有变化的,而缓存和数据库变化了,但可以重新写获取对象代码看看 */
结论:这个时候在提交事务,发现已经session中已经没有该对象了,所以就不会进行任何
操作
3. 游离状态测试:
- 创建对象后,如果设置id号,该id号在数据库中已存,又没被session托管,则该对象为游离状态。
Student student = new Student();
student.setId(3); //数据库有id=3记录,student已为游离状态
//session.save(student); //持久化对象,但设置id不成功,它会插一条新记录,id=4,新记录其它字段值重复了。
session.update(student); //持久化对象,更新成功,但是把原先id=7的记录插入到其它地方。
Student student2 = session.get(Student.class,3);
System.out.println(student2); //可看持久化对象更新id成功
- 持久化对象不允许修改id
Student student = new Student();
student.setId(3); //数据库有id=3记录,student已为游离状态
//session.save(student); //持久化对象,但设置id不成功,它会插一条新记录,id=4,新记录其它字段值重复了。
session.update(student); //持久化对象,更新成功,但是把原先id=7的记录插入到其它地方。
student.setId(4); //持久化异常错误。
- delete()后,游离状态到临时状态。
Student student =new Student("徐会凤","女",new Date());
student.setId(7); //因为数据库中已有id=7的记录,此刻student为游离状态
session.delete(student);//提交后,数据库中的7号记录删除,但原记录数据插到新的一行中。
- 游离状态测试四。
Student student = new Student();
student.setId(2);
//程序执行到这里,student就是一个游离对象的状态, 因为数据库中存在id=2的记录, 并且这个student对象,又没被session托管,是离线状态的,所以它是游离状态
session.saveOrUpdate(student);
// saveOrUpdate这个方法,是个偷懒的方法,当对象的状态是游离的,Hibernate就会去调用update方法,如果对象是临时状态的话,就调用save方法,这个方法不常用!
- 游离状态测试5:
Student student = session.load(Student.class, 1);
// load方法的作用:取出id等于1的学生记录
// 上面的student是取出后就是持久状态的对象
Student student2 = new Student();
//这时候的stutent2是临时的状态
student2.setId(1);
// 修改里student2的ID值,它就转游离的状态了
session.saveOrUpdate(student2);
//执行更新操作,它的状态会从游离转持久,我们的对象是用ID值来辨别身份的,所以在session的缓存中就存在了两份同样的对象,在session中不能存在两份拷贝, 所以报错
结论: Hibernate的session的缓存中不许用出现两个或两个以上有相同ID值的对象
Session中的核心方法梳理:
1 save()
这个方法表示将一个对象保存到数据库中,可以将一个不含OID的new出来的临时对象转换
为一个处于Session缓存中具有OID的持久化对象。
需要注意的是:在save方法前设置OID是无效的但是也不会报错,在save方法之后设置OID
程序会抛出异常,因为持久化之后的对象的OID是不可更改的,因为对象的OID此时和数据
库中的一条记录对应。
控制台中可以看到 一个 [id=0...]表没有id. 一个[id=xx]
结论总结:
- 临时对象变持久对象
- 给对象分配id,这个id叫oid, 它和数据库的记录id对应一致
- 执行save方法时会发起一条insert语句, 但要等到事务提交时才会作用到数据库
- save方法前设置id无效, save方法后设置id报异常,持久对象的id不准修改
2 persist方法
这个方法基本个save方法差不多,唯一的区别是,在这个方法之前也不可以设置对象的
OID,否则不会执行插入操作,而是会抛出异常。
3 get/load方法
get这个方法是从数据库中获取一条数据记录转成对象放到Session缓存中,load方法也是这
个功能。二者有着明显的区别先看代码:
而load方法后,若没有用到获取的对象,如注释掉输出语句,即不发送sql语句。若都使用了,get和load看不出差别。
结论1.get方法会立即加载对象发起sql语句, load方法后面如果没有使用到加载的对象,不
会立即加载对象发起sql语句,返回一个代理对象,当使用到该对象的时候才会通过代理对象
加载真正需要对象并发起sql语句,这种做法我们又叫做延迟加载或懒加载!
结论2: 如果查询的数据在数据库中没有对应的id的记录值, get方法返回null, 不报异常,
load方法,它不会立即加载对象发起sql, 直接返回一个代理对象, 当使用加载对象的时候,
代理对象才加载真正的对象并发起sql,这时才发现查不到对象,所以就只能报出异常了!
结论3: load方法可能会抛出懒加载异常! 什么时候回抛出这个异常呢? 执行了laod方法,
返回了代理对象了, 往后还没有执行到使用这个预加载的对象的时候,session(数据库连接
会话)关闭了, 后面再执行到使用加载对象, 代理对象才想起去加载真正的对象发起sql执行
查询,啊!才发现数据库连接断掉了, 就包懒加载异常!
public void loadTest() {
Student student =session.load(Student.class, 23);
session.close();
System.out.println(student);
transaction.commit(); //lazyInitialzerException 懒加载异常!
}
}
4. update 方法(游离对象更新中使用,更新后为持久对象)
a. 这个方法顾名思义就是更新一个对象在数据库中的对照情况,从而使一个游离对象转换为一个持久化对象。
b. 若是更新一个持久化对象,不需要再显式子的进行 update 方法,因为在 commit 方法中已经进行过 flush 了 , 它会自动发起 update 语句。
Student student =session.load(Student.class, 23);
student.setName("熊少华");
session.update(student); //不用显示写出来,可以省略不定,flush会自同步时调用update
c. 若是关闭了一个 session ,而又打开了一个 session ,这时,前一个 session 对象相对于第二个 session 来说就是游离的对象了,此时,做更新的时候 , 必须显式的用第二个 session 进行update 一下才可以将这个对象变成相对于第二个 session 的持久化对象。才会发起 sql 语句
上图:session.close();后,对象成为游离状了。
更新没效果,并且出错。若把上面代码注释的全部解开注释,则创创建了一个新的session,已不是原来的session,这时更新要显示的写上update(),这样更新就没问题了。
Student student = session.get(Student.class, 23);
transaction.commit();
session.close(); //对象成为游离状态了。
session = sessionFactory.openSession();
transaction =session.beginTransaction(); //开启事务。
student.setName("熊少文");
session.update(student);
transaction.commit(); //要显示的写上上句代码更新才成功。
d. c的情况下更新游离对象时无论Java对象中的内容和数据库中记录是否一样都
会发送update语句,若是在数据库中将update语句和某个触发器绑定在了一起,那么就会
造成触发器的错误触发。而我们在更新持久化对象时Hibernate flush()会验证一下,若是Java对象
和数据库中对应的记录一致的话就不会发送update语句。那么我们怎么避免这种在更新游离
对象时多发update语句的情况呢?可以在hbm.xml文件的class节点设置一个属性叫做
select-before-update为true,就可以避免了,但会报错,还是会发送select语句。通常我们不需要设置这个属性,除非多发
送update语句触发触发器二者相关联使用。
<class name="cn.ybzy.hibernatedemo.model.Student" table="STUDENT" select-before-update="true">
e. 若表中没有与 Java 对象对应的记录,则会抛出异常
f. 在 update 语句之前在用 get 方法 , 获取同一个 id 的数据记录 , update 会同时将两个相同 id 的对象往 session 缓存里放,那么会抛出异常,注意:同一个 session 中不可以存在两个相同 OID的对象。
5. saveOrUpdate 方法
这个方法同时包含了前边 save 和 update 的功能。当对象时临时的,那么执行 save 方法,当
对象时游离的,那么执行 update 方法。
6. delete 方法
顾名思义,这个方法就是来删除游离的或者持久化的对象及其在数据库中对应的记录。
总结:
61.删除对象
62 删除持久对象
63 删除数据库里对应的记录
64 当删除的对象数据库里没有对应的id值的记录是抛出异常
65.默认的删除的时候,会把缓存从对象和数据库中记录删除,但这个对象会保留id, 妨碍后面
重复利用这个对象, 这个问题通过配置来处理,作用删除操作后把对象的id设置null
<property name="hibernate.use_identifier_rollback">true</property>
Hibernate 的 cfg.xml 配置文件中有一个 hibernate.use_identifier_rollback 属
性,其默认值为 false,若把它设为 true,将改变 delete() 方法的运行行为:delete()
方法会把持久化对象或游离对象的 OID 设置为 null,使它们变为临时对象。这样程序就可
以重复利用这些对象了
7 evict 方法
这个方法就是将持久化对象从 session 缓存中删除,使其成为一个游离的对象。
8 doWork 方法 ,
jdbc: 存储过程 , 批量操作 , jdbc 原生 conn
这个方法也可验证:hibernate是否成功连接mysql,输出为:com.mysql.cj.jdbc.ConnectionImpl@6331250e
Hibernate的配置文件
1. Hibernate配置文件有两种形式:
XML与properties
建议使用XML,因为properties中不能配置关联的映射文件,在后续的实现中会带来一些没必要的编码;
2. c3p0连接池设定。
企业级应用里, 都不必须使用一款数据库连接池, 这里我们讲一下c3p0的连接池怎么中
Hibernate里配置:
①: 导入jar包:
c3p0的jar包, hibernate连接c3p0的jar包,maven 导入hibernate-core包时,会自动导入它的依赖包之一c3p0包。
②: 配置C3P0
配置DataSource的: hibernate.connection.provider class:org.hibernate.c3p0.internal.C3P0ConnectionProvider
对应的代码:
<session-factory name=""> <!--要注意:开发时去掉 name=""-->
<!-- 注意每更改该文件保存后,容易出现name="",这个要删除,不然会出问题 -->
<property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://xiongshaowen.com:3306/hibernate5? serverTimezone=Asia/Shanghai</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">xiong</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
<!-- 数据库方言,mysql5版以下不用写5 -->
<property name="hibernate.show_sql">true</property>
<!-- 开发阶阶段有用,在控制台显示sql语句 -->
<property name="hibernate.format_sql">true</property>
<!-- 格式化控制台sql语句 -->
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- 设置delete()删除持久对象空,清空对象id -->
<property name="hibernate.use_identifier_rollback">true</property>
<!--开启连接池 -->
<property name="hibernate.connection.provider_class">org.hibernate.c3p0.internal.C3P0ConnectionProvider</property>
<!-- 当连接池里不够用了,一次性在连接池里增加多少个连接 -->
<property name="hibernate.c3p0.acquire_increment">20</property>
<!--表示连接池检查线程多长时间检查一次连接池里所有的连接是不是已离开是移除,连接池本身是不会把超时的连接移除出连接池的,这个线程
是通过比较连接的最后时间和当前系统时间这间的差值再和超时的时间值来判断是不是把该连接移除 -->
<property name="hibernate.c3p0.idle_test_period">2000</property>
<property name="hibernate.c3p0.max_size">20</property>
<!-- 指定连接池里最大缓存多少个Statement对象 -->
<property name="hibernate.c3p0.max_statements">10</property>
<!-- 指定连接池里最小连接数据 -->
<property name="hibernate.c3p0.min_size">5</property>
<!-- 指定连接池里连接的超时时长毫秒 -->
<property name="hibernate.c3p0.timeout">2000</property>
<property name="hibernate.jdbc.batch_size">30</property>
<property name="hibernate.jdbc.fetch_zie">100</property> <!-- mysql不技持-->
<mapping resource="Student.hbm.xml"/>
<mapping resource="User.hbm.xml"/>
</session-factory>
测试配置成功不? 用dowork打印一下连接看看是不是c3p0提供的就可以了:
测试类中写:
@Test
public void doWorkTest() {
session.doWork(new Work() {
public void execute(Connection connection)throws SQLException{
System.out.println(connection); //这里没有配连接池的输出com.mysql.cj.jdbc.ConnectionImpl@6331250e
}
});
}
输出如下字样,表连接成功
com.mchange.v2.c3p0.impl.NewProxyConnection@60b85ba1 [wrapping: com.mysql.cj.jdbc.ConnectionImpl@7b14aaa1]
配置文件的其它两个重要的项:
-
Fetch Size:
Fetch Size 是设定JDBC的Statement读取数据的时候每次从数据库中取出的记录条
数。例如一次查询1万条记录,对于Oracle的JDBC驱动来说,是不会1次性把1万条取出
来的,而只会取出Fetch Size条数,当纪录集遍历完了这些记录以后,再去数据库取
Fetch Size条数据。因此大大节省了无谓的内存消耗。当然Fetch Size设的越大,读
数据库的次数越少,速度越快;Fetch Size越小,读数据库的次数越多,速度越慢。
这有点像平时我们写程序写硬盘文件一样,设立一个Buffer,每次写入Buffer,等
Buffer满了以后,一次写入硬盘,道理相同。
Oracle数据库的JDBC驱动默认的Fetch Size=10,是一个非常保守的设定,根据测
试,当Fetch Size=50的时候,性能会提升1倍之多,当Fetch Size=100,性能还能继
续提升20%,Fetch Size继续增大,性能提升的就不显著了。因此建议使用Oracle的一
定要将Fetch Size设到50。
不过并不是所有的数据库都支持Fetch Size特性,例如MySQL就不支持。MySQL就像我
上面说的那种最坏的情况,他总是一下就把1万条记录完全取出来,内存消耗会非常非
常惊人!这个情况就没有什么好办法了! - Batch Size
是设定对数据库进行批量删除,批量更新和批量插入的时候的批次大小,
有点相当于设置Buffer缓冲区大小的意思。Batch Size越大,批量操作的向数据库发
送sql的次数越少,速度就越快。做的一个测试结果是当Batch Size=0的时候,使用
Hibernate对Oracle数据库删除1万条记录需要25秒,Batch Size = 50的时候,删除
仅仅需要5秒!!! 可见有多么大的性能提升!很多人做Hibernate和JDBC的插入性能
测试会奇怪的发现Hibernate速度至少是JDBC的两倍,就是因为Hibernate使用了
Batch Insert,而他们写的JDBC没有使用Batch的缘故。Oracle数据库 Batch Size
= 30 的时候比较合适,50也不错,性能会继续提升,50以上,性能提升的非常微弱,反而消耗内存更加多,就没有必要了。
注:人们常说,JDBC是效率最高的,是不是打脸了呢,不是的,JDBC综合效率是最高的,但在有的情况下不作设置,是不如hibernate的。
Hibernate的映射关系的基本配置
----Hibernate的持久化类和关系数据库之间的映射通常是用一个XML文档来定义的。该文档通过一系列XML元素的配置,来将持久化类与数据库表之间建立起一一映射。这意味着映射文档是按照持久化类的定义来创建的,而不是表的定义。
一、根元素:<hibernate-mapping>,每一个hbm.xml文件都有唯一的一个根元素,包含一些可选的属性:
-
package:指定一个包前缀,如果在映射文档中没有明确写出完整的包全名的类,会默认使用这个作为包名做前缀,如:
上图显示,如果package中已指定了包路径,class中就不用完整写出包路么,直接写类名即可,其实这样做很方便。
- schema: 指定当前映射文件对应的数据库表的schema名
- catalog: 指定当前映射文件对应的数据库表的catalog名
- default-cascade: 设置默认的级联方式(默认值为none)
- default-access: 设置默认的属性访问方式(默认值为property)
- default-lazy: 设置对没有指定配置是否延迟加载映射类和集合设定是否默认延迟加载(默认值为true)
- auto-import: 设置当前映射文件中是否可以在HQL中使用非完整的类名(默认值为true)
hibernate-mapping节点的子节点:
(1).class: 为当前映射文件指定对应的持久类名和对应的数据库表名
(2).subclass: 指定多态持久化操作时当前映射文件对应的持久类的子类
(3).meta: 设置类或属性的元数据属性
(4).typedef: 设置新的Hibernate数据类型
(5).joined-subclass: 指定当前联结的子类
(6).union-subclass: 指定当前联结的子类
(7).query: 定义一个HQL查询
(8).sql-query: 定义一个SQL查询
(9).filter-def: 指定过滤器
二、子元素 <class>定义类:
1. <class>
根元素的子元素,用以定义一个持久化类与数据表的映射关系,如下是该元素包含的一些可选的属性
该元素包含的一些可选的属性:
(1).name: 为当前映射文件指定对应的持久类名,没有package要求包全名
(2).table: 为当前映射文件指定对应的数据库表名
(3).schema: 设置当前指定的持久类对应的数据库表的schema名
(4).catalog: 设置当前指定的持久类对应的数据库表的catalog名
(5).lazy: 设置是否使用延迟加载
(6).batch-size: 设置批量操作记录的数目(默认值为1)
(7).check: 指定一个SQL语句用于Schema前的条件检查
(8).where: 指定一个附加的SQL语句的where条件
(9).rowid: 指定是否支持ROWID
(10).entity-name:实体名称,默认值为类名
(11).subselect: 将不可变的只读实体映射到数据库的子查询中
(12).dynamic-update: 指定用于update的SQL语句是否动态生成 默认值为false
(13).dynamic-insert: 指定用于insert的SQL语句是否动态生成 默认值为false
(14).insert-before-update: 设定在Hibernate执行update之前是否通过select语句来确
定对象是否确实被修改了,如果该对象的值没有改变,update语句将不会被执行(默认值为false)
(15).abstract: 用于在联合子类中标识抽象的超类(默认值为false)
(16).emutable: 表明该类的实例是否是可变的 默认值为fals
(17).proxy: 指定延迟加载代理类
(18).polymorphism: 指定使用多态查询的方式 默认值为implicit
(19).persister: 指定一个Persister类
(20).discriminator-value: 子类识别标识 默认值为类名
(21).optimistic-lock: 指定乐观锁定的策略 默认值为vesion
2 class节点的字节点:
(1).id: 定义当前映射文件对应的持久类的主键属性和数据表中主键字段的相关信息
(2).property: 定义当前映射文件对应的持久类的属性和数据表中字段的相关信息
(3).sql-insert: 使用定制的SQL语句执行insert操作
(4).sql-delete: 使用定制的SQL语句执行delete操作
(5).sql-update: 使用定制的SQL语句执行update操作
(6).subselect: 定义一个子查询
(7).comment: 定义表的注释
(8).composite-id: 持久类与数据库表对应的联合主键
(9).many-to-one: 定义对象间的多对一的关联关系
(10).one-to-one: 定义对象间的一对一的关联关系
(11).any: 定义any映射类型
(12).map: map类型的集合映射
(13).set: set类型的集合映射
(14).list: list类型的集合映射
(15).array: array类型的集合映射
(16).bag: bag类型的集合映射
(17).primitive-array: primitive-array类型的集合映射
(18).query: 定义装载实体的HQL语句
(19).sql-query: 定义装载实体的SQL语句
(20).synchronize: 定义持久化类所需要的同步资源
(21).query-list: 映射由查询返回的集合
(22).natural-id: 声明一个唯一的业务主键
(23).join: 将一个类的属性映射到多张表中
(24).sub-class: 声明多态映射中的子类
(25).joined-subclass: 生命多态映射中的来连接子类
(26).union-subclass: 声明多态映射中的联合子类
(27).loader: 定义持久化对象的加载器
(28).filter: 定义Hibernate使用的过滤器
(29).component: 定义组件映射
(30).dynamic-component: 定义动态组件映射
(31).properties: 定义一个包含多个属性的逻辑分组
(32).cache: 定义缓存的策略
(33).discriminator: 定义一个鉴别器
(34).meta: 设置类或属性的元数据属性
(35).timestamp: 指定表中包含时间戳的数据
(36).vesion: 指定表所包含的附带版本信息的数据
3 <id>定义主键:
Hibernate使用OID(对象标识符)来标识对象的唯一性,OID是关系数据库中主键在Java对象
模型中的等价物,在运行时,Hibernate根据OID来维持Java对象和数据库表中记录的对应关系
id节点的属性:
(1).name: 指定当前映射对应的持久类的主键名
(2).column: 指定当前映射对应的数据库表中的主键名(默认值为对应持久类的属性名)
(3).type: 指定当前映射对应的数据库表中的主键的数据类型
(4).unsaved-value: 判断此对象是否进行了保存
(5).daccess: Hibernate访问主键属性的策略(默认值为property)
4 generator节点的属性:
1).class: 指定主键生成器,id作为主键的生成策略: 前面详细讲过,这里不说了!
(2).name: 指定当前映射对应的持久类的主键名
(3).column: 指定当前映射对应的数据库表中的主键名(默认为对应持久类的主键名)
(4).type: 指定当前映射对应的数据库中主键的数据类型
(5).unique: 设置该字段的值是否唯一(默认值为false)
(6).not-null: 设置该字段的值是否可以为null(默认值为false)
(7).update: 设置update操作时是否包含本字段的数据(默认值为true)
(8).insert: 设置insert操作时是否包含本字段的数据(默认值为true)
(9).formula: 设置查询操作时该属性的值用指定的SQL来计算
(10).access: Hibernate访问这个属性的策略(默认值为property)
(11).lazy: 设置该字段是否采用延迟加载策略(默认值为false)
(12).optimistic-lock: 指定此属性做更新操作时是否需要乐观锁定(默认值为true)
5 property节点的属性:
用于持久化类的属性与数据库表字
用于持久化类的属性与数据库表字段之间的映射,包含如下属性:
- name:持久化类的属性名,以小写字母开头
- column:数据库表的字段名,也就是可以将子节点中的column标签提到属性上
-
type:Hibernate映射类型的名字,这个类型是Java里的POJO里的属性类型和
Hibernate自己内部的映射类型和数据库里表格的字段类型链接纽带,具体设置来讲可以设
置java的类型(一般使用这个,自动生成也是这个),也可以写hibernate内部的映射类型
(需要特别类型的时候,必须只要日期不要时间):
日期时间类型:
在java中日期时间类型: java.util.Date, java.util.Calendar
在JDBC的API中扩展了java.util.Date类扩了三个:
java.sql.Date, java.sql.Time,java.sql.Timestamp
分别对应数据库中:
DATE, TIME, TIMESTAMP
大对象映射:
(大对象用得比较少,因为我们平时处理图片或大文件,都是像以前做的项目一样,只是在数据库里保存相关文件名再编号,文件实际存在磁盘上。)
但是: 有时候,不是很精准,这个时候我们可以用sql-type属性做精准的类型映射:
写在<column />里:
<property name="regDate" type="blob"> <!--可写type="java.sql.Blob"-->
<column name="content" sql-type="meadumblob" />
</property>
数据库里blob大对象包括好几种,上面是其中一种。注:property 里的type只能写上图对应的java类型java.sql,Blob(jdbc的API里的类型,当然了,它属于java)或hibernate映射类型blob,不可写数据库里的类型,如BLOB.
-
formula: 设置当前节点对应的持久类中的属性的值由指定的SQL从数据库获取,主要用在派生属性上面,这个配置里有里formula就不要在配置column了,
<property name="age" type="int" formula="(select floor(datediff(now(),s.birthday)/365.25) from studentages s where s.id = id)"/>
@Test
public void test() throws ParseException {
/* StudentAge student = new StudentAge();
student.setName("cexoit");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date db = sdf.parse("1983-08-12");
student.setBirthday(db);
session.save(student);*/
//创建了把它注释掉,不然有很多重复记录,接下来我们获取age(由birthday派生来的)
StudentAge studentAge = session.get(StudentAge.class,1);
System.out.println(studentAge.getAge());
}
测试结果:
37
注:指定的SQL必须用()括起来,指定SQL中使用表里的列名时必须用表的别名加.加列名的方式访问,但如果指SQL中要使用当前映射对应的列名时不能用表的别名加.加列名的方式访问,而是直接访问即可
- unique: 设置该字段的值是否唯一(默认值为false)
- not-null: 设置该字段的值是否可以为null(默认值为false)
- not-found: 设置当当前节点对应的数据库字段为外键时引用的数据不存在时如何让处
理(默认值为exception:产生异常,可选值为ignore:对不存在的应用关联到null) - property-ref: 设置关联类的属性名,此属性和本类的关联相对应 默认值为关联类的
主键 - entity-name: 被关联类的实体名
- lazy: 指定是否采用延迟加载及加载策略(默认值为proxy:通过代理进行关联,可选值为true:此对象采用延迟加载并在变量第一次被访问时抓取、false:此关联对象不采用延迟加载)
- access: Hibernate访问这个属性的策略(默认值为property),所有的访问策略具体
的, 值是property,Hibernate获取model实体类(POJO类/持久化类)的属性值时使用
getter、setter方法,如果设置为field,则Hibernate会忽略getter和setter方法,直接
用反射的方式访问成员变量的值 - optimistic-lock: 指定此属性做更新操作时是否需要乐观锁定(默认值为true)
- length: 指定数据类型的长度,并不是都指数据的大小, 数据的大小只和数据类型有
关。具体说就是:
CHAR、VARCAHR的长度是指字符的长度,例如CHAR[3]则只能放字符串"123",如果插入数
据"1234",则从高位截取,变为"123"。 VARCAHR同理。
TINYINT、SMALLINT、MEDIUMINT、INT和BIGINT的长度,其实和数据的大小无关!Length
指的是显示宽度,如id的显示宽度为3,不足的左边补0,数据长度超过的则原样输出
FLOAT、DOUBLE和DECIMAL的长度指的是全部数位(包括小数点后面的),例如
DECIMAL(4,1)指的是全部位数为4,小数点后1位,如果插入1234,则查询的数据是999.9。
但是注意: Hibernate内部有限制机制,它是不允许插入数据库的数据超出类型长度的!超
出了Hibernate会报异常! - update: 设置执行update的sql语句的时候,这个字段会不会被修改! false的话,修改
操作对数据库失效,这个字段的值不允许修改! - index: 设置数据库中该字段的索引
很多人机械的理解索引的概念,认为增加索引只有好处没有坏处。
首先明白为什么索引会增加速度,DB在执行一条Sql语句的时候,默认的方式是根据搜索条
件进行全表扫描,遇到匹配条件的就加入搜索结果集合。
如果我们对某一字段增加索引,查询时就会先去索引列表中定位到特定值的行数,大大减少
遍历匹配的行数,所以能明显增加查询的速度。
那么在任何时候都应该加索引么?这里有几个反例:
1、如果每次都需要取到所有表记录,无论如何都必须进行全表扫描了,那么是否加索引也
没有意义了。
2、对非唯一的字段,例如“性别”这种大量重复值的字段,增加索引也没有什么意义。
3、对于记录比较少的表,增加索引不会带来速度的优化反而浪费了存储空间,因为索引是
需要存储空间的,而且有个致命缺点是对于update/insert/delete的每次执行,字段的索
引都必须重新计算更新。
那么在什么时候适合加上索引呢?我们看一个Mysql手册中举的例子,这里有一条sql语句:
SELECT c.companyID, c.companyName FROM Companies c, User u WHERE c.companyID
= u.fk_companyID AND c.numEmployees >= 0 AND c.companyName LIKE '%i%' AND
u.groupID IN (SELECT g.groupID FROM Groups g WHERE g.groupLabel =
'Executive')
这条语句涉及3个表的联接,并且包括了许多搜索条件比如大小比较,Like匹配等。
在没有索引的情况下Mysql需要执行的扫描行数是77721876行。而我们通过在companyID和
groupLabel两个字段上加上索引之后,扫描的行数只需要134行。
可以看出来在这种联表和复杂搜索条件的情况下,索引带来的性能提升远比它所占据的磁盘
空间要重要得多。 - scale:指的是所映射的数据列的小数位数, 只对double,float,decimal等小数类
型的数据列有效!
上面是Hibernate的关系映射配置文件中最核心和基本的配置, 接下来就是具体的关联关系
的映射配置了, 下一节来开始!
Hibernate 映射关系讲解。
1 单向多对一的映射
我们在实际开发中,数据表之间往往不是孤立的,而是纵横交叉、相互联的,比如一个用户发表了多篇文章,一个文章又有多个评论,这里涉用户信息表, 文章表, 评论表, 它们之间存在很多关系的!
具体来讲数据表间存在的多种关系:多对一(单向和双向)、多对多和一对一等几种:
在Hibernate中单向多对一关系怎么样实现:
应用场景:每个班主任老师可以带很多个学生,反过来每个学生只能有一个班主任老师!单向的多对一: 只需要n(多)这端访问1(一)这端的情况!没有1的端访问多的端的时候:
在数据表结构上来看,就是要学生信息表中加外键(不用我们建配好了自动创建外键)字段teacher_id, 来关联teacher表的主键id !具体在Hibernate上实现:
建javabean时,最好不要设置toString()的关联字段的字符串,这样当load加载时,我们不需要显示关联数据时,少发sql语句,这很有用的,当有几千万条语句时,就显尔易见。
---------------------------------Student.hbm.xml映射文件-----------------------------------------------
要么在建javabean时,不设Teacher字段前建映射文件,要么在建好映射文件后,删除Teacher的<property>.
name:对应一端的属性名字,也是Student定义的字段名。
---------------------------------Teacher.hbm.xml映射文件---------------------------------------------
--------------------------测试先执行test(),也只要执行一次,再执行test1()---------------------
junit测试之前,cfg.xml中要添加这两个映射文件。
研究一下, 单向的多对一的关联下的增删改查
- 新增情况:
结论: 先增加一的一端的数据,发起的sql语句少, 先增加多的一端的数据发起sql语句条数多, 建议先加一的一端, 效率高些! - 查询情况:
//查询
Student student = session.load(Student.class, 1);
System.out.println(student);
//System.out.println(student.getTeacher().toString()); //无此语句时少发sql语句查询teacher信息
结论: 默认情况下, 查询多的一端对象, 只要没使用到关联的对象, 不会发起关联的对象的查询! 因为使用的懒加载, 所以在使用关联对象之前关闭session, 必然发生赖加载异常!
- 删除的时候,
删除多的一端的对象和数据没问题, 删除一的一端,有外键约束! 也没什么好说的。
2 双向多对一的联系,用到集合(students)了。
如果只需要在学生信息的页面显示这个学生的班主任老师的信息, 就是单向多对一, 但是如果班主任老师从信息显示页面也要显示老师班
上有哪些学生的话, 那么就变成双向多对一/双向一对多的情形了!
-----------------------------------------Student.hbm.xml---------
--------------------------------Teahcer.hbm.xml----------------------
property只对基本数据类型的应用 ,集合要用set
测试, 和单向多对一的测试的结论雷同!
还要补充一下, set元素标签上还有几个属性:
①inverse: 它的作用是指定双向多对一的映射中, 由那一边来维护关系 , 默认值是inverse="false" 要维护, 建议在一的那端的set元素上还是都设置为true! 在双向联系中让一端不维护, 多端不用设置默认会维护的!
②cascade: 这个属性在实际开发中是不建议用的, 怕出问题! 它的作用是设置级联的模式, 如果设置为delete, 表示执行级联删除, 也就是说删除多的这端的某条记录,它会连着将与之有关联的一的那端的记录一块儿删除掉; 如果设置save-update级联保存, 会保存对象关联的临时对象到数据库. (只做了解)
③order-by:设置查询出来的数据的排序, 实质就是在sql语句后面加order by语句,看下例子!
3 一对一的联系
在Hibernate中一对一关系怎么样实现, 先看一下应用场景:
从上图中可以看出:
- 一个人只有一张身份证,唯一的一个身份证号, 反之亦然,两个对象(两张表中的对应记录)之间是一对一的关系;
- 场景中应该是人(Person)持有身份证(IdCard)的引用,所以两个对象关系维护应该由person端来控制。
-
一对一关联,有两种方式:
<1>. 主键关联(不推荐用)和唯一外键关联(实际应用中我们推荐用后面这种)
主键关联的关系模型: 数据结构上两个表里都没有外键, 直接让对应记录的id值相同!
具体实现:
测试:
<2>. 唯一外键关联:
唯一外键和前面的多对一的配置是差不多的, 只不过为了实现一对一, 就在多的这端的外键加上了唯一性约束, 从而实现了一对一的关联:
-------------------------------Person.hbm.xml-------------------------------------
----------------------------------------IdCard.hbm.xml-------------------------------
测试:
4 多对多的联系
应用场景:每个老师可以上多门课程,反过来每门课程也有多个老师来上!
在数据表结构的层面, 我们一般是将多对多的两个数据表转换为两组多对一的关系来实现:
由<set>产生的列,同是产生一个中间表TEACHERS_COURSES,用于维护关系。
1. 单向多对多,teacher里查询course, 反过来没有:。
2. 双向的多对多:
注意1: 中间表用同一个
注意2: 其中一端一定要设置inverse="true", 避免两房都去维护中间, 造成冲突
注意3: 两端的many-to-many指定的类交叉相同
测试代码:
public void test3() {
Teacherc teacher1 = new Teacherc();
Teacherc teacher2 =new Teacherc();
teacher1.setTname("熊少文");
teacher2 .setTname("徐会凤");
Course course1= new Course();
Course course2 = new Course();
course1.setCname("JAVA");
course2.setCname("javaweb");
Set<Course> courses = new HashSet<>();
courses.add(course1);
courses.add(course2);
teacher1.setCourses(courses);
teacher2.setCourses(courses);
Set<Teacherc> teachercs= new HashSet<>();
teachercs.add(teacher1);
teachercs.add(teacher2);
course1.setTeachercs(teachercs);
course2.setTeachercs(teachercs);
//因为设置了老师维护关系,所以插入顺序没有先后,先老师也可以,先课程也可以
session.save(teacher1);
session.save(teacher2);
session.save(course1);
session.save(course2);
}
5 组件映射
组件映射中, 组件也是一个类, 但是这个类它不独立称为一个实体, 也就是说, 数据库中没有一个表格单独的和它对应, 如学生的信息祥情类,它是一组件,最明显的区别是没有id。具体情况呢, 看演示:
操:classes是班级的意思,不是类的意思。
6 继承映射。
----我们前面讲过,Hibernate 是 ORM框架的一个具体实现,最大的一个优点就是我们的开
发更加的能体现出“面向对象”的思想。在面向对象开发中,类与类之间是可以相互继
承的(单向继承),而Hibernate中也对这种继承关系提供了自己风格的封装,这就是
我们接下来要介绍的Hibernate继承映射的三种策略:
我们把这三个类, 看成一颗倒过来的树, Animal类是树根, Pig和Bird是两条树枝
-
第一种策略是把树上的三个类, 映射到一张数据表上
每个子类继承树一张表(可以理解为整棵树一张表,表内有所类的所有字段)用subclass
映射到同一张表,要有辨别字段标签。discriminator
<class name="cn.ybzy.hibernatedemo.modelextends.Animal" table="t_animals" discriminator-value="animal">
<id name="id" type="int">
<column name="id" />
<generator class="native" />
</id>
<!--辨别字段的标签 ,要定义在id之后 -->
<discriminator column="type" type="string"></discriminator>
<property name="name" type="java.lang.String">
<column name="name" />
</property>
<property name="sex" type="java.lang.String">
<column name="sex" />
</property>
<subclass name="cn.ybzy.hibernatedemo.modelextends.Pig" discriminator-value="pig">
<property name="weight"></property>
</subclass>
<subclass name="cn.ybzy.hibernatedemo.modelextends.Bird" discriminator-value="bird">
<property name="height"></property>
</subclass>
</class>
测试:可以看到,插入的记录,当没有插入的字段为null
public void extendsTest() {
Pig pig = new Pig();
pig.setName("小花");
pig.setSex("公");
pig.setWeight(200);
session.save(pig);
Bird bird =new Bird();
bird.setName("鸽子");
bird.setSex("母");
bird.setHeight(1000);
session.save(bird);
}
这种映射方式可以把多个类放在一张表中,但是粒度比较粗,有冗余字段;但又是因为多个类的相关记录都存放在一张表中,查询时不用关联,因此效率较高。
- 每个类一张表(父类Animal、子类Pig、子类Bird各一张表,父表中有公共字段,
子表中有个性字段+外键约束)用joined-subclass, 其他没变化:
Animal.hbm.xml中去掉辨别字段标签。
<hibernate-mapping>
<class name="cn.ybzy.hibernatedemo.modelextends.Animal" table="t_animals" >
<id name="id" type="int">
<column name="id" />
<generator class="native" />
</id>
<property name="name" type="java.lang.String">
<column name="name" />
</property>
<property name="sex" type="java.lang.String">
<column name="sex" />
</property>
<joined-subclass name="cn.ybzy.hibernatedemo.modelextends.Pig" table="t_pig" >
<key column="pid"></key>
<property name="weight" type="int"></property>
</joined-subclass>
<joined-subclass name="cn.ybzy.hibernatedemo.modelextends.Bird" table="t_bird">
<key column="bid"></key>
<property name="height" type="int"></property>
</joined-subclass >
</class>
</hibernate-mapping>
测试:
用1例的测。
综上所述:这种方法,会在t_animals表中插入公共字段的数据,因为表面上看起来这是两张表,但实际上存储的都是动物(同一类型),所以还可以看做是一张表。
缺点:查询时需要关联表,效率差;插入时也要执行多个insert语句,适合继承程度不深的情况。
优点:粒度较细,条理清楚,没有冗余。
- 每个具体类一张表(每个子类一张表,每张表都有自己所有的属性字段包括父类的公共字段,父类表也有公共字段)
在配置文件中 <union-subclass>标签中不需要key值了,注意Animal的主键生成策略不能是自增(native)了,如果自增的话,pig表中第一条记录id为1,bird表中第一条记录也为1,而它们在实际意义上属于同一类型(可以看做在一张表中),可能造成不同子类对应表中的主键相同,所以主键不可一致。
uuid2:使用JDK自带的UUID生成36位的ID
uuid:生成32位的uuid,不符合ETF( Internet工程委员会 ) RFC 4122标准,已被uuid2取代。
<class name="cn.ybzy.hibernatedemo.modelextends.Animal" table="t_animals" >
<id name="id" type="string">
<column name="id" />
<generator class="uuid2" />
</id>
<property name="name" type="java.lang.String">
<column name="name" />
</property>
<property name="sex" type="java.lang.String">
<column name="sex" />
</property>
<union-subclass name="cn.ybzy.hibernatedemo.modelextends.Pig" table="t_pig" >
<property name="weight" type="int"></property>
</union-subclass>
<union-subclass name="cn.ybzy.hibernatedemo.modelextends.Bird" table="t_bird">
<property name="height" type="int"></property>
</union-subclass >
</class>
用abstract属性表示父类Animal为抽象类,这样Animal就不会映射成表了。
继承映射总结:
如果系统需要经常进行查操作且子类数量较多,则建议用第一种方案,即每棵生成树映
射一张表,这也是最常用的方法,效率较高。如果追求细粒度的设计且子类数量不多,
则可以用后两种方案:每个类映射一张表或每个具体类映射一张表。
不想要的表要删除的代码。
时间长了,测试开发后,数据库中很多表,一个一个的删除太麻烦了,这里提供了一段代码
首先:查询要删除的表
其次:复制查询的结果,放到上面
最后:选中所有,执行
SELECT CONCAT ('drop table ', table_name, ';') FROM information_schema.tables WHERE table_schema='hibernate5';
查询结果如:
DROP TABLE t_courses;
DROP TABLE t_idcards;
DROP TABLE t_persons;
DROP TABLE t_teacher_course;
DROP TABLE t_teachercs;