hibernate总结
Hibernate总结
1.什么是Hibernate?
首先,hibernate是数据持久层的一个轻量级框架。数据持久层的框架有很多比如:iBATIS,myBatis,Nhibernate,Siena等等。
并且Hibernate是一个开源的orm(object relations mapping)框架,提供了查询获取数据的方法,用面向对象的思想来操作数据库,节省了我们开发处理数据的时间。
2.那使用Hibernate的优点呢?
1.使用简介的hql语句(Hibernate query language)。可以不使用传统的insert,update等sql语句。比如insert一个对象,原来的做法是:insert into 表名称 alue(值1,值2,值3,……),而现在的做法是:save(对象)。
2.使用or映射。对象到关系数据库之间的映射。是从对象的角度操作数据库,再次体现了面向对象思想。原来的实体抽取方法:首先有了表,然后表映射实体对象。而现在Hibernate做法是:直接由对象映射到表。
3.没有侵入性,移植性比较好。什么是没有侵入性?就是Hibernate采用了pojo对象。所谓的pojo对象就是没有继承Hibernate类或实现Hibernate接口。这样的话,此类就是一个普通的Java类,所以移植性比较好。
4.支持透明持久化。透明是针对上层而言的。三层架构的理念是上层对下层的依赖,只是依赖接口不依赖具体实现。而Hibernate中的透明是指对业务逻辑层提供了一个接口session,而其他的都封装隐藏。持久化是指把内存中的数据存放到磁盘上的文件中。
3.当然一个事物,不可能十全十美,即使如此优秀的Hibernate也有自己的弱点。比如:若是大量数据批量操作。则不适合使用Hibernate。并且一个持久化对象不能映射到多张表中。
4.Hibernate中核心5个接口
1.Configuration接口:负责配置及启动Hibernate,用来创建sessionFactory
2.SessionFactory接口:一个SessionFactory对应一个数据源存储,也就是一个数据库对应一个SessionFactory。SessionFactory用来创建Session对象。并且SessionFactory是线程安全的,可以由多个线程访问SessionFactory共享。
3.Session接口:这个接口是Hibernate中常用的接口,主要用于对数据的操作(增删改查)。而这个Session对象不是线程安全的。不能共享。
4.Query接口:用于数据库的查询对象。
5.Transaction接口:Hibernate事务接口。它封装了底层的事务操作,比如JTA(;Java transcation architecture)所有的数据操作,比如增删改查都写在事务中。
1.创建普通的Java项目。
因为hibernate是一个轻量级的框架,不像servlet,还必须需要tomcat的支持,Hibernate只要jdk支持即可。
2.引入jar包。
可以在项目中直接引入jar包,在:项目--->属性--->然后如下图:
另一种办法就是引入库,相当于一个文件夹,把所有的jar包放到自己新建的文件夹中。在:窗体-->选项-->然后如下图:
3.提供Hibernate的配置文件。hibernate.cfg.xml文件。完成相应的配置
- <hibernate-configuration>
- <session-factory>
- <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
- <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate_first</property>
- <property name="hibernate.connection.username">root</property>
- 10.
11. <property name="hibernate.connection.password">bjpowernode</property>
- 12.
13. <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
- 14.
15. </session-factory>
- 16.
17. </hibernate-configuration>
在这里连接MySQL数据库,解释一下上面的标签。按照顺序来依次解释:第一个是连接mySql的驱动;第二个是连接的url;url后面的hibernate_first是数据库名字;第三个是和第四个分别是用户名和密码。第五个是方言。因为 hibernate对数据库封装,对不同的数据库翻译成不同的形式,比如drp中的分页,若是使用Oracle数据库,则翻译成sql语句三层嵌套。若是使用mySql数据库,则翻译成limit语句。
4.建立实体User类:
- package com.bjpowernode.hibernate;
- import java.util.Date;
- public class User {
- private String id;
- private String name;
- 10.
- 11. private String password;
- 12.
- 13. private Date createTime;
- 14.
- 15. private Date expireTime;
- 16.
- 17. public String getId() {
- 18. return id;
- 19. }
- 20.
- 21. public void setId(String id) {
- 22. this.id = id;
- 23. }
- 24.
- 25. public String getName() {
- 26. return name;
- 27. }
- 28.
- 29. public void setName(String name) {
- 30. this.name = name;
- 31. }
- 32.
- 33. public String getPassword() {
- 34. return password;
- 35. }
- 36.
- 37. public void setPassword(String password) {
- 38. this.password = password;
- 39. }
- 40.
- 41. public Date getCreateTime() {
- 42. return createTime;
- 43. }
- 44.
- 45. public void setCreateTime(Date createTime) {
- 46. this.createTime = createTime;
- 47. }
- 48.
- 49. public Date getExpireTime() {
- 50. return expireTime;
- 51. }
- 52.
- 53. public void setExpireTime(Date expireTime) {
- 54. this.expireTime = expireTime;
- 55. }
56. }
5.建立User.hbm.xml,此文件用来完成对象与数据库表的字段的映射。也就是实体类的那些字段需要映射到数据库表中呢。
- <?xml version="1.0"?>
- <!DOCTYPE hibernate-mapping PUBLIC
- "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
- "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
- <hibernate-mapping>
- <class name="com.bjpowernode.hibernate.User">
- <id name="id">
- <generator class="uuid"/>
- </id>
- 10. <property name="name"/>
- 11. <property name="password"/>
- 12. <property name="createTime"/>
- 13. <property name="expireTime"/>
- 14. </class>
15. </hibernate-mapping>
6.我们也映射完毕了,但是hibernate怎么知道我们映射完了呢,以及如何映射的呢?这就需要我们把我们自己的映射文件告诉hibernate,即:在hibernate.cfg.xml配置我们的映射文件。
- <mapping resource="com/bjpowernode/hibernate/User.hbm.xml"/>
7.生成数据库表。大家也看到了我们上述还没有新建数据表呢,在第三步我们只是新建了数据库而已。按照我们普通的做法,我们应该新建数据表啊,否则实体存放何处啊。这个别急,数据库表这个肯定是需要有的,这个毋庸置疑,但是这个可不像我们原来需要自己亲自动手建立哦,现在hibernate需要帮我们实现哦,如何实现嗯,hibernate会根据配置文件hibernate.cfg.xml和我们的映射文件User.hbm.xml会自动给我们生成相应的表,并且这个表的名字也给我们取好:默认是User。那如何生成表呢?
- //默认读取hibernate.cfg.xml文件
- Configuration cfg = new Configuration().configure();
- SchemaExport export = new SchemaExport(cfg);
- export.create(true, true);
8.那我们就开始进行操作啦,我们添加一个用户对象,看看hibernate是如何添加的呢?跟我们以前的做法有什么不同呢?
- public class Client {
- public static void main(String[] args) {
- //读取hibernate.cfg.xml文件
- Configuration cfg = new Configuration().configure();
- //建立SessionFactory
- SessionFactory factory = cfg.buildSessionFactory();
- 10.
- 11. //取得session
- 12. Session session = null;
- 13. try {
- 14. session = factory.openSession();
- 15. //开启事务
- 16. session.beginTransaction();
- 17. User user = new User();
- 18. user.setName("张三");
- 19. user.setPassword("123");
- 20. user.setCreateTime(new Date());
- 21. user.setExpireTime(new Date());
- 22.
- 23. //保存User对象
- 24. session.save(user);
- 25.
- 26. //提交事务
- 27. session.getTransaction().commit();
- 28. }catch(Exception e) {
- 29. e.printStackTrace();
- 30. //回滚事务
- 31. session.getTransaction().rollback();
- 32. }finally {
- 33. if (session != null) {
- 34. if (session.isOpen()) {
- 35. //关闭session
- 36. session.close();
- 37. }
- 38. }
- 39. }
- 40. }
41. }
第八步,我们可以看到,没有我们熟悉的insert into表的sql语句了,那怎么添加进去的呢,到底添加了没?让我真实滴告诉你,确实添加进去了,不信的,可以自己尝试哦,这也是hibernate的优点,对jdbc封装的彻底,减少了我们对数据的操作时间哈。
那我们看一下hibernate中整体的内容:
hibernate框架中主键的生成策略有三种方式:
1,数据库负责生成主键(代理主键)
a,native:表示由设置的方言决定采用什么数据库生成主键方式,例如:在MySQL中会采用自增长的方式,主键字段必须都是整形类型;在Oracle数据库中,会采用序列的增长方式。
b,sequence:表示采用数据库的序列生成主键,适用于Oracle,DB2数据库中。
c,identity:表示采用自增长的主键生成方式,适用于MySQL,SQL Server中。
2,Hibernate框架负责生成主键值(代理主键):
a,increment:表示由框架本身提供计数器,累加数据,获取主键。
b,uuid:由框架根据参数(IP地址,JVM虚拟机启动时间,系统时间,计数器等)生成32位16进制的数字字符串。
3,用户提供主键值(自然主键):
Assigned:业务(自己)提供主键。
当然这里常用的是native,uuid和Assigned三值。在设置POJO类与表映射时,进行主键设置,标签为<id>,在其中的<generator>标签中进行设置,例如:
[html] view plain copy
- <!--
- id标签用来映射主键字段
- name属性表示类的属性
- column属性表示表的字段
- -->
- <id name="usercode" column="usercode" length="32" type="java.lang.String">
- <!-- 主键生成策略 -->
- <generator class="assigned"/>
- </id>
Hibernate,对三种状态下的POJO的增删改查操作:
|
瞬时状态 |
持久化状态 |
游离状态 |
增(Save) |
可以 |
不可以(没必要) |
不可以(没必要) |
改(Update) |
不可以 |
修改后会自动更新,不需手动 |
可以 |
删(delete) |
不可以 |
可以 |
可以 |
查 |
不可以 |
可以 |
可以 |
1,保存(Save):
通过session.save(user);就可以保存数据了,但是这里想提一下,主键的生成策略不同,框架发送sql语句的时间是不同的:
a,native:在调用save方法时发送insert语句。
b,uuid主键生成策略和assigned主键生成策略:在提交事务时发送insert语句。
这是因为主键的生成时机不同,由于native是在数据库中生成的,所以发送的比较早。
2,更新(Update):
这里想说一下对游离对象的更新:
|
uuid |
assigned |
native |
存在记录 |
发送upate语句 |
会查询判断,再更新 |
发送update语句 |
记录不存在 |
发送语句,剖异常 |
会查询判断,进行插入操作 |
发送语句,剖异常 |
3,删除(delete):
这里只要提供主键,可以根据主键id删除,只要id存在即可。
4,查询:
a,主键查询:
get:返回结果可能是:持久化对象或null,所以需要对结果进行非空判断。它利用了缓存,是立即查询。
//get方法查询如果成功,那么返回的对象状态是持久化状态
Objectobj = session.get(User.class, "admin");
load:返回结果可能是:持久化对象或cglib代理对象或异常,利用缓存,默认为延迟加载。
//load方法第一查询结果存放到缓存中,支持延迟加载,效率更高,但是主要
Objectobj = session.load(User.class, "admin");
b,普通查询(面向对象查询):后边会介绍
1,Query:HQL (HibernateQuery Language),HQL语言是对SQL语言的封装,是面向对象的查询语言。例如:SQL : select * from t_user(表名) whereusername(字段)="tom"
HQL : from User(类名) where username(属性)="tom"
[java] view plain copy
- //分页查询
- String hql = "from User u "; //面向对象查询 HQL!!!!!
- Query query = session.createQuery(hql);
- int pageno = 3 ;
- int pagesize = 2 ;
- int index = (pageno - 1) * pagesize ;
- query.setFirstResult(index);//某页的第一个下标
- query.setMaxResults(pagesize); //页数的大小
- 10.
11. //条件查询
12. String hql = "from User u where u.usercode=? and u.userpswd=?"; //面向对象查询 HQL!!!!!
13. Query query = session.createQuery(hql);
14. query.setString(0, "admin"); //索引从0开始
15. query.setString(1, "admin");
2, Criteria:将所有的操作都以面向对象的方式进行完成。
[java] view plain copy
- //分页查询
[java] view plain copy
- Criteria cra = session.createCriteria(User.class);
- cra.setFirstResult(0); //开始索引
- cra.setMaxResults(2); //每页数量
- //排序
- Criteria cra = session.createCriteria(User.class);
- cra.addOrder(Order.desc("username"));
- //条件查询
10. Criteria cra = session.createCriteria(User.class);
11. cra.add(Restrictions.eq("username", "aaa"));
12. cra.add(Restrictions.eq("usercode", "aaa"));
一对多,多对一关系映射,这里拿学生和班级进行简单演示:
1,学生的类和对应的映射文件的编写:
[java] view plain copy
- private int sid ;
- private String sname ;
- private Classes classes ; //,引入班级对象,多对一
[html] view plain copy
- <class name="com.ljh.hibernate.pojo.Student" table="t_student" lazy="false">
- <id name="sid" column="sid">
- <generator class="native"/>
- </id>
- <property name="sname" column="sname" type="java.lang.String" length="20" not-null="true"/>
- <!--
- 表示对象的关系:多对一
- name 表示当前类的关系对象
- 10. column 表示数据库中外键字段(也是描述数据关系)
- 11. class 表示name属性值的类型
- 12. cascade 级联
- 13. 主动方所做的操作(insert,update,delete),被动方也跟着做相同的操作。
- 14. 取值:save-update、delete 、all
- 15. save-update : 保存或更新当前对象时,级联保存或更新关联对象
- 16. delete : 删除当前对象时,级联删除关联对象
- 17. all : 包含save,update,delete三种操作。
- 18. 对于多对一的场合,级联不能使用delete和all,否则会违背数据关系完整性。
- 19. lazy : 延迟加载
- 20. 延迟初始化对象信息,等使用对象时再查询数据库。
- 21. false : 禁用延迟加载
- 22. proxy : 使用延迟加载(默认值),采用cglib代理完成延迟加载的扩展功能。
- 23. no-proxy : 不使用代理,完成延迟加载 。可以使用第三方字节码增强工具。
- 24. fetch : 数据抓取策略 :根据主动方,查询被动方时所采用的查询方式。
- 25. fetch="select" 默认值,会采用多条语句的方式查找,往往会延迟加载数据
- 26. fetch="join" 默认会采用左连接查询数据,不会延迟加载数据。
- 27. not-null 如果取值为true,那么框架采用内连接查询数据。
- 28. -->
- 29. <many-to-one name="classes" column="cid" cascade="save-update" lazy="no-proxy" fetch="join" not-null="true" class="com.ljh.hibernate.pojo.Classes"></many-to-one>
30. </class>
2,班级的实体类和对应的映射文件:
[java] view plain copy
- private int cid ;
- private String cname ;
- private Set<Student> studentSet = new HashSet<Student>(); //引入学生类的集合,一对多
[html] view plain copy
- <class name="com.ljh.hibernate.pojo.Classes" table="t_classes" >
- <id name="cid" column="cid">
- <generator class="native"/>
- </id>
- <property name="cname" column="cname" type="java.lang.String" length="20" not-null="true"/>
- <!-- 声明 一对多映射
- lazy : 延迟加载
- false : 禁用延迟加载
- 10. true : 延迟加载(默认值)
- 11. extra : 支持延迟加载的。(推荐)
- 12. 当获取集合自身信息时,可以发送高效的查询语句。
- 13. 例如:只希望获取集合的长度,而不需要获取集合中数据的信息,框架会通过函数执行查询进行计算集合长度。。
- 14.
- 15. fetch : 数据抓取策略
- 16. 查询关联对象的数据时,所采用的查询方式。
- 17. join : 通过一条连接语句进行立即查询。(延迟加载不起作用)
- 18. select : 通过多条查询语句进行查询。
- 19. subselect : 通过子查询语句进行立即查询。 (不推荐使用)(默认情况下和select取值结果相同)
- 20.
- 21. not-null="true" 对于一对多的查询,即使设置了not-null语句,依然采用左连接查询。
- 22.
- 23. Inverse="true",表示控制反转,由对方也就是学生方来进行管理外键。因为外键在学生
- 24. -->
- 25. <set name="studentSet" cascade="all" inverse="true" fetch="subselect">
- 26. <key column="cid" not-null="true"></key>
- 27. <one-to-many class="com.bjpowernode.hibernate.pojo.Student"/>
- 28. </set>
29. </class>
二,自关联:就是在自己的类进行关联自己,例如父菜单与子菜单的关系,对应的实体类,和映射文件
[java] view plain copy
- private int mid ; private String name ;
- private Set<Menu> menuSet = new HashSet<Menu>(); //父菜单与子菜单的关系为:一对多
- private Menu pmenu ; //子菜单和父菜单的关系为:多对一
[html] view plain copy
- <class name="com.ljh.hibernate.pojo.Menu" table="t_menu" >
- <id name="mid" column="mid">
- <generator class="native"/>
- </id>
- <property name="name" column="name" type="java.lang.String" length="20" not-null="true"/>
- <many-to-one name="pmenu" column="m_id" cascade="save-update"></many-to-one>
- <!--
- 10. 自关联表的设计:外键字段不能为非空。
- 11. -->
- 12. <set name="menuSet" cascade="all" inverse="true">
- 13. <key column="m_id"></key>
- 14. <one-to-many class="com.ljh.hibernate.pojo.Menu"/>
- 15. </set>
16. </class>
一对一关系映射:
1,假如是主键一对一用来映射:也就是说被动方的主键是来自于主动方的主键,也可以将之称之为外键:
类之间相互添加彼此的应用。
映射文件中主动方,增加一对一标签:
[html] view plain copy
- <one-to-one name="userinfo" cascade="all" class="com.ljh.hibernate.pojo.UserInfo"></one-to-one>
- 被动方,主键又是外键,也添加一对一的映射标签:
- <id name="uid" column="uid">
- <generator class="foreign">
- <param name="property">user</param>
- </generator>
- </id>
- 10. <!--
- 11. 描述一对一关系关系映射
- 12. constrained="true" : 表示强制要求一对一使用外键关联。增加外键约束。
- 13. -->
- 14. <one-to-one name="user" constrained="true" class="com.ljh.hibernate.pojo.User"></one-to-one>
2,使用外键约束,其实是多对一的特殊情况,例如学生对班级,
类添加彼此的应用。
映射文件中学生端主动端,添加外键进行约束,添加多对一标签,
[html] view plain copy
- <!--
- unique : 唯一约束
- 如果外键含有unique约束,那么表示主动方和被动方的关系为一对一。
- 对于一对一映射来讲,可以设置级联关系为delete 和 all
- 根据主动方查询被动方关联数据,是支持延迟加载的。
- -->
10. <many-to-one name="classes" column="cid" unique="true" cascade="all" class="com.ljh.hibernate.pojo.Classes"></many-to-one>
- 11.
- 12. 班级端的映射文件:添加一对一的标签:
13. <one-to-one name="student" cascade="all" property-ref="classes" class="com.ljh.hibernate.pojo.Student"></one-to-one>
多对多的关系
例如学生对课程的对应,在数据库中会生成第三张表进行维护:
在各自的类中引入对方的set集合,表示多对多。
学生端:
[html] view plain copy
- <set name="courseSet" table="t_student_course" cascade="save-update">
- <key column="sid"></key>
- <many-to-many class="com.ljh.hibernate.pojo.Course" column="cid"></many-to-many>
- ;/set>
课程端:
[html] view plain copy
- <!--
- 对于多对多来讲,级联只能设置cascade="save-update"是合理的
- inverse="true" 让集合一端去维护中间表数据。任意一端都可以。
- -->
- <set name="studentSet" table="t_student_course" cascade="save-update" inverse="true">
- <key column="cid"></key>
- <many-to-many class="com.ljh.hibernate.pojo.Student" column="sid"></many-to-many>
- </set>
联合主键的映射
出现了这种联合键时,需要我们为其定义一个联合主键的类(实现Serializable接口),类中声明多个字段的属性,表示联合主键字段。
在配置映射文件时,主键的配置利用联合主键的标签即可:
[html] view plain copy
- composite-id name="id"> <key-property name="title"></key-property>
- <key-property name="author"></key-property>
- </composite-id>
一级缓存(Session缓存):
Session缓存表示将查询结果放置到Session的临时存储空间(一级缓存中)。Hibernate框架默认支持一级缓存的。一级缓存的范围较小,一旦Session关闭,那么缓存失效。我们使用框架的各种方法,例如:get,load,save,update,delete等都支持一级缓存的。
二级缓存(SessionFactory缓存):
1,概述:二级缓存其实就是将查询的数据放置在SessionFactory临时存储空间中,因为一个SessionFactory可以创建多个Session对象,所以范围比Session缓存的要大,多个Session可以共享二级缓存的数据。当然了二级缓存也不能存储大量的数据,这个要根据我们电脑配置进行设置。
2,如何设置使用二级缓存呢?Hibernate默认是不支持二级缓存的。
a,首先在hibernate.cfg.xml配置文件中添加属性标签,启用二级缓存:
<!--启用二级缓存 -->
<propertyname="hibernate.cache.use_second_level_cache">true</property>
b,二级缓存需要使用额外的第三方组件:ehcache。需要我们拷入对应的jar包,以及将对应的ehcache.xml存放到src目录下。在这个配置文件中,我们可以设置二级缓存的大小等。
c,让框架识别添加入的ehcache缓存组件,在hibernate.cfg.xml配置文件中添加属性标签:
<!--让框架识别ehcache缓存组件 -->
<propertyname="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
d,设置需要缓存的映射类,这里只是将一些查询操作比较频繁的类指定即可,哪些不经常操作的数据,是没有必要利用缓存的。这里例如:
<!-- 将指定的类存放到二级缓存中,其中read-only是指放入缓存的数据是只读的-->
<class-cache usage="read-only" class="com.ljh.hibernate.pojo.Student"/>
3,缓存的操作数据原理:
查询数据时,会首先从一级缓存中取数据,如果取上,则直接使用,否则到二级缓存中取,如果取到则直接使用,否则,就会发送语句查询数据库。这样利用一级和二级缓存会提高访问效率。
当然使用二级缓存要慎重,要考虑数据的操作频繁性,服务器的硬件配置,数据的安全性等等方面,这样才能很好的利用二级缓存,提高我们操作数据的效率,否则使用不当就会带来不必要的麻烦。
Lazy(懒加载)
因为感觉Lazy也用到了缓存,可能放到这里不太合理吧。在前边我们已经设计到了lazy了,这里简单总结一下。
1,lazy(Hibernate的延迟加载功能):表示查询当前对象或关联对象数据时,不真正访问数据库,当使用对象非主键属性时,才真正发送查询语句,访问数据库。由于在某些情况下,查的数据在后续流程到可能用不上,如果做查询处理就多余了,所以延迟加载功能可以提高性能,合理使用即可。当然了Hibernate框架是通过Cglib代理来完成延迟加载功能的扩展的。
2,用到lazy地方总结,这里只是对类和映射的地方简单总结了一下,在实际运用中我们还要学会恰当的选择:
标签 |
起作用到什么地方 |
取值解释 |
<class> |
针对load方法 |
true : 延迟加载 (默认) false : 禁用延迟加载 |
<set> |
针对于一对多,多对多 |
true : 延迟加载 (默认) false : 禁用延迟加载 extra : 支持延迟加载,在访问集合自身信息时可以发送高效的查询语句。(推荐) |
<many-to-one> |
针对于多对一,一对一 |
false : 禁用延迟加载 proxy : 延迟加载 (默认) no-proxy : 支持延迟加载,不使用cglbi代理的方式完成延迟加载。通过第三方的字节码增强工具。 |
<one-to-one> |
针对一对一 |
和<many-to-one>标签一样,这里需要注意: 一对一的主键关联映射,根据主动方(不含外键)查询被动方(既是主键,又是外键)时不支持延迟加载的。 |
3,遇到的问题:lazy,延迟加载功能是运用的一级缓存,也就是利用的是session的内存,一般情况下,我们在用完session后会进行关闭,但是关闭后我们就无法进行延迟查询数据了,也就是说,延迟加载功能就将失效,剖出异常:No Sesssion,所以这是需要我们解决的。
4.NoSession异常的解决:
a,解决原理:我们可以在dao层不进行session的关闭,通过前边学习的AOP编程思想,添加过滤器,在视图层进行数据取完毕后进行关闭session,这样就可以解决了NoSession异常了。但是怎么获取呢?这里又想到了前边学习的ThreadLocal,我们可以将Session对象放到Threadlocal对象中,和线程进行绑定,到视图层再通过ThreadLocal进行获取关闭即可。
b,解决方案,AOP编程思想,想必都想到了spring框架了吧,的确,Spring框架提供了OpenSessionInViewFilter过滤器解决NoSession异常,这里我们只需要配置一下即可。在web.xml中进行此过滤器的配置即可:
[html] view plain copy
- <!-- 过滤器需要配置在Struts2框架过滤器前面,否则不起作用。 -->
- <filter>
- <filter-name>OpenSessionInViewFilter</filter-name>
- <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>OpenSessionInViewFilter</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
c,配置的顺序问题,应该将openSessionInViewFilter配置到Struts2的流程前边,这是因为Filter执行顺序是从上向下的,而且Struts2框架的执行流程基本上包含了整个项目顺序的全部流程,MVC流程框架么,就体现在这里了。配置到Struts2框架的流程后边的过滤器是起不到作用的。
持久化对象的三种状态。
分别为:瞬时状态(Transient),持久化状态(Persistent),离线状态(Detached)。三种状态下的对象的生命周期如下:
三种状态的区别是:瞬时状态的对象:没有被session管理,在数据库没有;持久化状态的对象:被session管理,在数据库存在,当属性发生改变,在清理缓存时,会自动和数据库同步;离线状态:没有被session管理,但是在数据库中存在。
5.测试工具Juit。
测试类需要继承TestCase,编写单元测试方法,方法名称必须为test开头,方法没有参数没有返回值,采用public修饰。其中在测试中,查询对象时,使用get或者load两种方法进行加载,这种方法的区别:get不支持延迟加载,而load默认情况下是支持延迟加载。并且get查询对象不存在时,返回null;而load查询对象不存在时,则抛出ObjectNotFoundException异常。
6.悲观锁和乐观锁解释。
悲观锁为了解决并发性,跟操作系统中的进程中添加锁的概念一样。就是在整个过程中在事务提交之前或回滚之前,其他的进程是无法访问这个资源的。悲观锁的实现方式有两种:一种使用数据库中的独占锁;另一种是在数据库添加一个锁的字段。hibernate中声明锁如下:
Account account = (Account)session.get(Account.class, 1, LockMode.UPGRADE);而net.sf.hibernate.LockMode类表示锁模式,当取值LockMode.UPGRADE时,则表示使用悲观锁for update;而乐观锁是为了解决版本冲突的问题。就是在数据库中添加version字段,每次更新时,则把自己的version与数据库中的version进行比较,若是版本相比较低,则不允许进行修改更新。
7.H ibernate中的缓存机制。
缓存是什么呢?缓存是应用程序和数据库之间的内存的一片区域。主要的目的是:为了减少对数据库读取的时间。当查询数据时,首先在缓存中查询,若存在,则直接取出,若不存在,然后再向数据库中查询。所以应该把经常访问数据库的数据放到缓存中,至于缓存中的数据如何不断的置换,这也需要涉及一种淘汰数据的算法。
谈到这个hibernate中的缓存,你想到了什么呢?刚才叙述缓存时,是否感觉很熟悉,感觉从哪也听过似的。嗯呢,是呢,是很熟悉,写着写着就很熟悉,这个刚才的缓存以及缓存的置换算法就和计算机组成中的cache类似。
好吧,来回到我们hibernate中的缓存。
hibernate中的缓存可以分为两种:一级缓存,也称session缓存;二级缓存,是由sessionFactory管理。
那一级缓存和二级缓存有什么区别呢?区别的关键关于:缓存的生命周期,也就是缓存的范围不同。
那首先介绍一下缓存的生命周期,也就是缓存的范围。
1.事务缓存,每个事务都有自己的缓存,当事务结束,则缓存的生命周期同样结束,正如上篇博客中我们提到,对数据库的操作,增删改查都是放到事务中的,和事务保持同步,若是事务提交完毕,一般是不允许是再次对数据库进行操作。所以session是属于事务缓存的。
2.应用缓存,一个应用程序中的缓存,也就是应用程序中的所有事务的缓存。只有当应用程序结束时,此时的缓存的额生命周期结束。二级缓存就是应用缓存。
3.集群缓存,被一台机器或多台机器的进程共享。
这下明白了一级缓存和二级缓存的区别了吧。那一级缓存和二级缓存的共同点是:都是缓存实体属性,
二级缓存一般情况都是由第三方插件实现的。第三方插件如:
EHCache,JbossCache(是由Jboss开源组织提供的),osCache(open symphony),swarmCache。前三种对hibernate中的查询缓存是支持的,后一种是不支持hibernate查询缓存。
那什么是hibernate查询缓存呢?
查询缓存是用来缓存普通属性的,对于实体对象而言,是缓存实体对象的id。
8.hql查询。
hibernate query language。hql查询中关键字不区分大小写,但是类和属性都是区分大小写的。
1.简单属性查询。
单一属性查询,返回属性结果集列表,元素类型和实体类的相应的类型一致。
[java] view plain copy
- List students = session.createQuery("select name from Student").list();
- for (Iterator iter=students.iterator(); iter.hasNext();) {
- String name = (String)iter.next();
- System.out.println(name);
- }
//返回结果集属性列表,元素类型和实体类中的属性类型一致
多个属性查询,多个属性查询返回数组对象,对象数组的长度取决于属性的个数,对象数组中的元素类型与实体类中属性一致。
[java] view plain copy
- List students = session.createQuery("select id, name from Student").list();
- for (Iterator iter=students.iterator(); iter.hasNext();) {
- Object[] obj = (Object[])iter.next();
- System.out.println(obj[0] + ", " + obj[1]);
- }
2.实体对象查询
List students = session.createQuery("from Student").list();
当然这种hql语句,可以使用别名,as可以省去,如:from Student as s,若是使用select关键字,则必须使用别名。如:select s from Student as s.但是不支持select * from Student格式。
查询中使用list和Iterate区别:
list查询是直接运行查询的结果,所以只有一句sql语句。而iterate方法则有可能会产生N+1条sql语句。这是怎么回事呢?要理解N+1条语句,首先得弄明白iterate是如何执行查询的?
首先发出一条查询对象ID的语句,然后根据对象的ID到缓存(缓存的概念上篇博客已经提到)中查找,若是存在查询出此对象的其他的属性,否则会发出N条语句,此时的N语句,是刚才第一次查询的记录条数。这种现象就是N+1sql语句。
其中list是默认情况下都发出sql语句,查询出的结果会放到缓存中,但是它不会利用缓存,即使放进去,下次执行时,仍然继续发出sql语句。
而:iterate默认情况下会利用缓存,若是缓存中有则不会发出N+1条语句。
3.条件查询。
这种方式就是传入参数,使用参数占位符“?”。也可以使用“:参数名”
Java代码如下:
[java] view plain copy
- List students = session.createQuery("select s.id, s.name from Student s where s.name like ?")
- .setParameter(0, "%0%")
- .list();
- List students = session.createQuery("select s.id, s.name from Student s where s.name like :myname")
- .setParameter("myname", "%0%")
- 10.
11. .list();
4.使用原生sql语句。
和咱们原先写入的sql语句一样。在此不介绍了。
5.外置命名查询。
这个听起来有点晦涩,怎么理解呢?其实通俗的说就是把hql语句写在外面,写在映射文件中。使用标签:
[html] view plain copy
- <query name="queryStudent">
- <![CDATA[
- select s from Student s where s.id <?
- ]]>
- </query>
那在程序中是如何使用此标签的呢?使用session.getNameQuery(),并进行赋值,代码如下:
[java] view plain copy
- List students = session.getNamedQuery("queryStudent")
- .setParameter(0, 10)
- .list();
6.查询过滤器。
这个是什么意思呢?过滤器大家很熟悉吧,不熟悉的可以参考我的以前博客<>.原来我们接触过编码过滤器,编码过滤器就是为了避免当时每个页面需要设置编码格式而提出的。这个查询过滤器其实也是这个意思。若是代码都需要某一句sql语句的话,可以考虑使用它。这样可以避免每次都写查询语句。
使用如下:首先在映射文件中配置标签:
[html] view plain copy
- <filter-def name="testFilter">
- <filter-param type="integer" name="myid"/>
- </filter-def>
然后程序中如下使用并进行赋值:
[java] view plain copy
- session.enableFilter("testFilter")
- .setParameter("myid", 10);
7.分页查询。
分页查询,这个肯定不陌生,因为在做drp项目时,做的最多的是分页,当时使用Oracle数据库,分页查询涉及到三层嵌套。直接传入的参数为:每页的大小(记录数),页号。
Hibernate中给我们已经封装好了,只要设置开始的页号以及每页的大小即可,不用亲自动手写嵌套的sql语句。
代码如下:
[java] view plain copy
- List students = session.createQuery("from Student")
- .setFirstResult(1)
- .setMaxResults(2)
- .list();
8.对象导航查询。
这个什么意思呢?这个只要是用于一个类的属性是另一个类的引用。比如:student类中有一个classes属性。其中的classes也是一个类Class的引用。
当我们查询的时候可以这样使用:
[java] view plain copy
- List students = session.createQuery("from Student s where s.classes.name like '%t%'")
- .list();
相当于:s.getClasses.getName(),直接使用get后面的属性,然后首字母小写。
这种语法,是不是很熟悉?想想我们在哪是不是也用过?想起来了吗?估计你猜出来啦,呵呵,是JSTL(jsp standard tag library)中。若是想进一步了解,可以参考我的博客哈,当时是转载滴貌似。
9.连接查询。
连接分为:内连接和外连接,其中外连接分为左连接,右连接,完全连接。这个跟数据库中的左右连接其实是一样的。我们通俗解释一下:
左连接:以左边为准,右边即使没哟匹配的,也要把这条记录查询出来,此时没有匹配的右边以null填充。
右连接:以右边为准,左边即使没有匹配的,也要把这条记录查询出来,此时没有匹配的左边以null填充。
完全连接:只要一方存在即可。
内连接:必须两方都存在才可以查询提取此记录。
10.统计查询。
其实就是查询count的记录数。其中查询出来的额count是long类型。
11.DML风格的操作。
DML?其实DML=Data Manipulate Language(数据操作语言),举个例子:
[java] view plain copy
- session.createQuery("update Student s set s.name=? where s.id<?")
- .setParameter(0, "张三")
- .setParameter(1, 2)
- .executeUpdate();
假若原来的名字是:李四,更新完数据库后变成王斌,若是我们此时取出数据,其姓名是李四还是王斌?按照道理应该是王斌,但是结果确实李四,若不信,可以自己去实践一下。
这个原因,是因为更新了数据库,但是缓存中没有更新,才会造成这种数据库和缓存不同步的问题。
所以,我们应该尽量不使用这种形式。扬其长避其短嘛。