Hibernate学习笔记
一、hibernate简介(网上搜的,理解性地看看)
1.概念:hibernate是持久层(数据访问层)的框架,对jdbc进行了封装,是对数据库访问提出的面向对象的解决方案。
2.作用:使用hibernate可以直接访问对象,hibernate自动将访问转换成sql执行,从而实现简介访问数据库的目的,简化了数据访问层的代码开发。
3.jdbc、mybatis对比:
a)jdbc需要编写大量sql语句,以及对大量参数赋值。需要手动将resultset结果集转换成实体对象;sql中包含特有函数,无法移植。而hibernate可以自动生成sql和参数赋值,自动将resultset结果集转换成实体对象,采用一致的方法对数据库操作,移植性好。
b)mybatis与hibernate都对jdbc进行了封装,采用orm思想解决了entity和数据库的映射问题。mybatis采用sql与entity映射,对jdbc封装程度比较轻,需要自己写sql,更具有灵活性;而hibernate采用数据库与entity映射,对jdbc封装程度比较重,自动生成sql,对于基本的操作,开发效率高。
4.原理:hibernate框架是orm思想的一种实现,解决了对象和数据库映射问题。我们可以通过hibernate提供的一系列api,允许我们直接访问实体对象,然后其根据orm映射关系,转换成sql并且执行,从而达到访问数据库的目的。
orm:object relation mapping,即对象关系映射,指java对象和关系数据库之间的映射。
orm思想:将对象与数据库数据进行相互转换的思想,不同的框架实现orm的手段不同,但更多的是采用配置+反射的方式来实现orm。
5.框架体系结构
a)主配置文件,通常为“hibernate.cfg.xml”,用于配置数据库连接参数,框架参数,已经映射关系文件。
b)实体类,与数据库对应的java类型,用于封装数据库记录的对象类型。
c)映射关系文件,通常为“实体类.hbm.xml”,并放置在与实体类相同的路径下。该文件是指定实体类和数据库的对应关系,以及类中属性和表中字段之间的对应关系。
d)底层api,对映射关系文件的解析,根据解析出来的内容,动态生成sql语句,自动将属性和字段映射。
二、hibernate使用
1.常用api
configuration:负责加载主配置文件信息,同时加载映射关系文件信息
/** 到src下找到名称为hibernate.cfg.xml的配置文件,创建对象,把配置文件放到对象中(加载核心配置文件) */ configuration cfg = new configuration().configure(); //加载指定的核心配置文件 configuration cfg = new configuration().configure("com/konrad/hibernate.cfg.xml"); //加载指定的映射配置文件 cfg.addresource("com/konrad/entity/user.hbm.xml");
sessionfactory:负责创建session对象,根据核心配置文件的配置,在数据库创建对应的表,一个项目只应有一个此对象
private static final sessionfactory sessionfactory; static{ try{ //配置文件的方式 sessionfactory = new configuration().configure("hibernate.cfg.xml") .buildsessionfactory(); /* 注解的方式 sessionfactory = new annotationconfiguration().configure().buildsessionfactory(); */ }catch (throwable ex){ ex.printstacktrace(); throw new exceptionininitializererror(ex); } }
session:数据库连接会话,负责执行增删改操作
transaction:负责事务控制
query:负责执行特殊查询
2.使用步骤
a)导入hibernate包,以及数据库驱动包。(开发需要的包不知道的自行百度,也可以通过maven构建)
b)引入hibernate主配置文件hibernate.cfg.xml
- <session-factory>标签要写在<hibernate-configuration>标签内部
c)创建实体类
d)创建映射关系文件(也可以通过注解的方式进行映射,这样就不需要xml映射文件)
- <class>标签中的name属性写的是类的全路径
- <id>和<property>标签中的name属性写的是实体类中的属性名称
- <id>和<property>标签中,column可以省略,若省略就是以name属性值生成表的字段名
- <property>标签中还有一个type属性,用于生成表的字段的类型,但是使用比较少
e)使用hibernate api执行增删改查等操作
f)额外说明:注解方式的应用
- 如果实体类属性名与表字段名称不同时,要么都注解在属性前,要么都注解在get方法前。
- 如果实体类属性名和表字段名称统一,可以部分注解在属性前,部分注解在get方法前。
- 若都不注解,则默认表字段名和属性名一致
- 若实体类中某个属性不需要存进数据库表,使用@transient进行注解即可
- 表名称可以在实体类前进行注解
- 所有注解都在javax.persistence包下
3.映射类型
a)java类型:映射关系文件中,配置属性和字段关系时,可以在type属性上指定java类型,用于做java属性和数据库字段的转换。指定时需要完整的类型名,如java.lang.string。
b)自定义类型:当某些特殊类型,java预置类型无法支持,需要自定义一个类来实现,这个类要求实现接口usertype。比如boolean类型,数据库中一般存char(1),存y/n或者t/f,java预置类型无法支持boolean类型的配置,需要自定义。
c)hibernate也提供了一些类型来支持这些映射,提供了7中映射类型,书写时全是小写。
三、hibernate的主键生成方式
1.sequence:采用序列生成主键,适用于oracle数据库。
<generator class="sequence"> <param name="sequence">序列名</param> </generator>
2.identity:采用数据库自增长机制生成主键,适用于oracle之外的其他数据库。
<generator class="identity"> </generator>
3.native:根据当前配置的数据库方言,自动选择sequence或者identity。
<generator class="native"> <param name="sequence">序列名</param> </generator>
4.increment:不是采用数据库自身的机制来生成主键,而是hibernate提供的一种生成主键的方式,它会获取当前表中主键的最大值,然后加1作为新的主键。ps:这种方式在并发量高时存在问题,可能会产生重复的主键,因此不推荐。
<generator class="increment"> </generator>
5.assigned:hibernate不负责生成主键,需要程序员自己处理主键的生成。
<generator class="assigned"> </generator>
6.uuid/hilo:采用uuid或hilo算法生成一个主键值,这个主键值是一个不规则的长数字。ps:这个方式生成的主键可以保证不重复,但是没有规律,因此不能按主键排序。
<generator class="uuid"> </generator>
四、一些使用的代码实例
1.核心配置文件
<?xml version="1.0" encoding="utf-8"?> <!doctype hibernate-configuration public "-//hibernate/hibernate configuration dtd 3.0//en" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- 配置数据库信息 必须的 --> <property name="connection.driver_class">com.mysql.jdbc.driver</property> <property name="connection.url">jdbc:mysql://localhost:3306/test</property> <property name="connection.username">root</property> <property name="connection.password"></property> <property name="javax.persistence.validation.mode">none</property> <!-- 支持mysql方言 --> <property name="dialect">org.hibernate.dialect.sqlserverdialect</property> <property name="current_session_context_class">thread</property> <!-- 配置是否在控制台显示sql语句 --> <property name="show_sql">true</property> <!-- 配置是否按照格式显示sql语句 --> <property name="format_sql">true</property> <!-- 配置生成策略 update:如果没有表创建之,有表就更新表 --> <property name="hbm2ddl.auto">update</property> <!-- 引入映射配置文件 必须的 -->
<mapping resource="com/maven/test/hibernate/entity/personentity.hbm.xml"/>
<!-- 若使用注解的方式,需要如下配置 --> <!-- <mapping class="com.maven.test.hibernate.entity.personentity">-->
</session-factory> </hibernate-configuration>
2.关系映射xml文件dtd约束:
<!doctype hibernate-configuration public "-//hibernate/hibernate configuration dtd 3.0//en" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
3.hibernateutil类:提供获取session和关闭session的方法。hibernate中我们使用threadlocal管理session。
package com.maven.test.hibernate.util; import org.apache.log4j.logger; import org.hibernate.hibernateexception; import org.hibernate.session; import org.hibernate.sessionfactory; import org.hibernate.cfg.configuration; public class hibernateutil { public static final threadlocal<session> sessionmap = new threadlocal<session>(); private static final sessionfactory sessionfactory; private static final logger logger = logger.getlogger(hibernateutil.class); static{ try{ logger.debug("hibernateutil.static - loading config"); sessionfactory = new configuration().configure("hibernate.cfg.xml") .buildsessionfactory(); logger.debug("hibernateutil.static - end"); }catch (throwable ex){ ex.printstacktrace(); logger.error("hibernateutil error: exceptionininitializererror"); throw new exceptionininitializererror(ex); } } private hibernateutil(){} public static session getsession() throws hibernateexception{ session session = sessionmap.get(); if(session == null){ session = sessionfactory.opensession(); sessionmap.set(session); } return session; } public static void closesession() throws hibernateexception{ session session = sessionmap.get(); sessionmap.set(null); if(session != null) session.close(); } }
4.添加操作
personentity person = new personentity(); person.setid(100); person.setname("konrad"); session session = hibernateutil.getsession(); transaction tx = session.begintransaction(); session.save(person); tx.commit(); hibernateutil.closesession();
5.删除操作
session session = hibernateutil.getsession(); session.begintransaction(); personentity person = session.get(personentity,class, 1); session.delete(person); session.gettransaction().commit(); hibernateutil.closesession();
6.查询操作
session session = hibernateutil.getsession(); session.begintransaction(); //单行查询,根据主键查询 personentity p = session.get(personentity.class, 1) //多行查询 @suppresswarnings("unchecked") list<personentity> personlist = session.createquery("select p from personentity p").list(); for(personentity person : personlist){ system.out.println(person); } session.gettransaction().commit(); hibernateutil.closesession();
7.修改操作
session session = hibernateutil.getsession(); session.begintransaction(); personentity person = session.get(personentity.class, 1); person.setname("haha"); session.update(person); session.gettransaction().commit(); hibernateutil.closesession();
五、hibernate进阶
1.一级缓存
a)hibernate创建每个session对象时,都会给该session对象分配一块独立的缓存区,用于存放该session查询出来的对象,这个分配给session的缓存区称之为一级缓存,也叫session级缓存。session的save、update、delete操作会触发缓存更新。
b)使用一级缓存的原因是,session取数据时,会优先向缓存区取数据,如果存在数据则直接返回,不存在才会去数据库查询,从而降低了数据库访问次数,提升代码性能。
c)一级缓存是默认开启的,使用hibernate api查询时会自动使用。
d)session.evict(obj) - 将obj从一级缓存中移除
session.clear() - 清除一级缓存中所有的obj
session.close() - 关闭session,释放缓存空间
2.hibernate中,实体对象的3中状态:临时态、持久态、游离态
a)临时态:临时态的对象可以被垃圾回收,未进行持久化,未与session关联
- 通过new创建的对象为临时态
- 通过delete方法操作的对象将转变为临时态
b)持久态:持久态对象垃圾回收器不能回收,进行了持久化,与session关联。实际上持久态对象存在于session缓存中,由session负责管理;持久态对象的数据可以自动更新到数据库中,在调用session.flush()时执行,而提交事务时会使用session.flush(),因此提交事务也会触发同步
- 通过get、load、list、iterate方法查询到的对象为持久态
- 通过save、update方法操作的对象转变为持久态
c)游离态:游离态的对象可以被垃圾回收,进行过持久化,但已与session解除了关联
- 通过session的evict、clear、close方法操作的对象会转变为游离态
3.延迟加载
a)使用某些hibernate方法查询数据时,hibernate返回的只是一个空对象(除id外属性都为null),并没有真正查询数据库。而在使用这个对象时才会触发查询数据库,并将查询到的数据注入到这个空对象中,这种将查询时机制推迟到对象访问时的机制称之为延迟加载。
b)延迟加载可以提升内存资源的使用率,降低对数据库的访问次数。
c)session.load()、query.iterate()、关联映射中对关联属性的加载,以上属于采用延迟加载的方法。
d)采用具有延迟加载机制的操作,需要避免session提前关闭。
4.关联映射
a)关联映射即使用hibernate操作一张表时,它可以通过配置关系自动地帮助我们操作另一张表
b)关联查询出关系表的数据、关联新增/修改关系表的数据、关联删除关系表的数据
c)类型:一对多关联、多对一关联、多对多关联、一对一关联、继承关联
一对多关联实现步骤:
- 在“一”方实体类添加集合属性,以及get、set方法
- 在“一”方hbm文件中配置关联关系
<!-- set指定属性类型为set集合 name指定属性名 --> <set name="courses"> <!-- column指定了关联字段名--> <key column="stu_id"/> <!-- one-to-many指定了关联关系,class指定了另一方类型--> <one-to-many class="com.konrad.entity.course" /> </set>
多对一关联实现步骤:
- 在“多”方实体类添加属性,以及get、set方法
- 在“多”方hbm文件中配置关联关系
<!-- many-to-one指定了关联关系, name指定了属性名, column指定了关系字段, class指定了另一方类型--> <many-to-one name="student" column="stu_id" class="com.konrad.entity.student" />
多对多关联实现步骤:
- 在双方方实体类添加集合属性,以及get、set方法
- 在双方hbm文件中配置关联关系
student映射配置文件
<!-- 配置学生对应的教师集合 name属性:配置教师实体类中的set集合属性的名称 table属性:表示中间表的名称 --> <set name="teachers" table="t_stu_tea"> <!-- column属性:配置当前映射文件在中间表中外键名称 --> <key column="sid"></key> <!-- class属性:配置另一个张表的实体类全路径名称 colunm属性:配置一张表在中间表中的外键名称 --> <many-to-many class="cn.konrad.entity.teacher" column="tid"></many-to-many> </set>
teacher映射配置文件 <!-- 配置教师对应的学生集合 name属性:配置学生实体类中的set集合属性的名称 table属性:中间表的名称 --> <set name="students" table="t_stu_tea"> <!-- column属性:配置当前映射文件在中间表中外键名称 --> <key column="tid"></key> <!-- class属性:配置另一个张表的实体类全路径名称 colunm属性:配置一张表在中间表中的外键名称 --> <many-to-many class="cn.konrad.entity.student" column="sid"></many-to-many> </set>
一对一关联实现步骤:
- 在双方方实体类添加属性,以及get、set方法
- 在双方hbm文件中配置关联关系
wife映射关系文件
<!-- property-ref属性:指定使用被关联实体主键以外的字段作为关联字段 --> <one-to-one name="husband" class="cn.konrad.entity.husband" property-ref="wife"></one-to-one>
husband映射配置文件 <id name="hid" column="hid"> <!-- 2.1配置主键的策略 foreign属性:表示主键参照外键生成 --> <generator class="foreign"> <!-- param标签配置当前实体类中引用的对方实体对象的引用名 --> <param name="property">wife</param> </generator> </id> <!-- 3.配置实体类与表的其他属性 name属性:实体类的属性名称 column属性:表中的列名 contrained=true表示生产外键 --> <property name="hname" column="hname"></property> <one-to-one name="wife" class="cn.konrad.entity.wife" constrained="true"></one-to-one>
5.关联操作
a)默认情况下,关联属性时采用延迟加载机制加载的,可以通过映射关系文件中关联属性配置标签中的lazy属性进行修改,true/false。
b)通过一个连接查询一次性取出2张表的数据,避免2次查询。在关联属性标签上通过fetch属性进行设置,称之为抓取策略。“join”表示查询时使用连接查询,一起把对方数据抓取过来;“select”表示查询时不使用连接查询,是默认情况。当fetch为“join”时,关联属性的延迟加载失效。
6.级联操作:通过关联映射,在对一方进行增删改时,连带增删改关联的另一方数据
a)实现级联添加/修改,需要在映射关系文件中的关联属性标签中,通过cascade属性进行设置,cascade="save-update"
b)实现级联删除,cascade="delete"
c)若想级联添加、修改、删除一起支持,cascade="all"
d)控制反转,在一对多关联中,使用级联新增、删除时,当前操作的“一”方会试图维护关联字段,然而关联字段是在“多”方对象中,它会自动维护这个字段,因此“一”方没必要做这样的处理。在关联属性标签上通过inverse属性(true/false)交出控制权,默认是false,不控制反转。
7.hibernate查询
a)hql按条件查询:条件中写的是属性名,在执行查询前调用query对象为条件参数赋值
string hql = "from course where name=?"; session session = hibernateutil.getsession(); query query = session.createquery(hql); query.setstring("math"); list<course> courses = query.list();
b)hql查询部分字段:可以只查询表中的一部分字段,需要在from之前追加select语句,指定要查询列对应的属性名
string hql = "select id,name" + " from course";
注意:查询部分字段时,query.list()方法返回的集合中封装的不是实体对象,而是一个object[],数组中的值与select语句后面的属性按顺序对应。
c)分页查询:通过api统一实现
int from = (page - 1) * pagesize; query.setfirstresult(from); query.setmaxresults(pagesize);
注意:查询的起点是本页第一行,按照jdbc计算,公式为(page -1) * pagesize +1;hibernate中行数的起点是0,不同于jdbc是从1开始,所以要在上面公式的基础上-1,即(page -1) * pagesize
d)查询总页数:根据以下hql查询总行数,再计算总页数
string hql = "select count(*) from student";
e)多表联合查询:可以使用hql进行多表联合查询,不过hql中写的是关联的对象的属性名;有3中关联查询的方式:
对象方式关联
string hql = "select s.id, s.name, c.name from student s, course c " + "where s.course.id = c.id";
join方式关联(不能直接join对象,需要join关联属性)
string hql = "select s.id, s.name, c.name from student s inner join s.courses c";
select子句关联
f)直接使用sql查询
string sql = "select * from t_course where c_name=?"; session session = hibernateutil.getsession(); sqlquery query = session.createsqlquery(sql); query.setstring(0,"math"); list<object[]> list = query.list(); //返回集合封装的是object[] //若想返回集合中封装实体对象 query.addentity(course.class); list<course> list = query.list();
g)使用criteria查询
criteria c = session.createcriteria(course.class); c.add(restriction.eq("name", "math")).add(restrictions.or(restrictions.eq(), restrications.eq())); list<course> list = c.list();
8.二级缓存
a)二级缓存类似于一级缓存,可以缓存对象,但它是sessionfactory级别的缓存,有sessionfactory负责管理。因此二级缓存的数据是session间共享的,不同的session对象都可以共享二级缓存中的数据。
b)二级缓存适用于:对象数据频繁共享,数据变化频率低
c)二级缓存使用步骤:
导入ehcache.jar
在src下添加缓存配置文件ehcache.xml
<ehcache> <!--缓存到硬盘时的缓存路径,java.io.tmpdir表示系统默认缓存路径--> <diskstore path="java.io.tmpdir"/> <!--默认缓存配置 maxelementsinmemory:二级缓存可容纳最大对象数 eternal:是否保持二级缓存中对象不变 timetoidleseconds:允许对象空闲的时间,即对象最后一次访问起,超过该时间即失效 timetoliveseconds:允许对象存活的时间,即对象创建起,超过该时间即失效 overflowtodisk:内存不足,是否允许使用硬盘缓存,写入路径参考diskstore --> <defaultcache> maxelementsinmemory = "300" eternal = "false" timetoidleseconds = "120" timetoliveseconds = "300" overflowtodisk = "true" /> <!--自定义配置--> <cache name="mycache"> maxelementsinmemory = "2000" eternal = "false" timetoidleseconds = "200" timetoliveseconds = "300" overflowtodisk = "true" /> </ehcache>
在hibernate.cfg.xml中开启二级缓存,指定采用的二级缓存驱动类
<property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.provider_class"> org.hibernate.cache.ehcacheprovider </property>
在要缓存的对象对应的映射关系文件中,开启当前对象的二级缓存支持,并指定缓存策略
<!--开启二级缓存,并指定缓存策略 可以用region属性指定自定义的缓存设置--> <cache usage="read-only" />
d)缓存策略:
-只读型(read-only):缓存不会更新,适用于不会发生改变的数据,效率最高,事务隔离级别最低
-读写型(read-write):缓存会在数据变化时更新,适用于变化的数据
-不严格读写型(nonstrict-read-write):缓存不定期更新,适用于变化频率低的数据
-事务型(transactional):缓存会在数据变化时更新,并且支持事务。效率最低,事务隔离界别最高。
<!-- 配置事务的隔离级别 1 -- read uncommitted isolation 2 -- read committed isolation 3 -- repeatable read isolation 4 -- serializable isolation --> <property name="hibernate.connection.isolation">2</property>
9.查询缓存
a)查询缓存依赖于二级缓存,可以理解为特殊的二级缓存,也是sessionfactory级别的,也是由sessionfactory负责维护
b)查询缓存可以缓存任何查询到的结果
c)查询缓存是以hql为key,缓存该hql查询到的整个结果。如果执行2次同样的hql,第二次执行可以从查询缓存中取到第一次查询缓存的内容
d)使用查询缓存步骤:
开启二级缓存
在hibernate.cfg.xml中,开启查询缓存
<!--开启查询缓存--> <property name="hibernate.cache.use_query_cache">true</property>
在查询代码执行前,指定开启查询缓存
query.setcacheable(true); //开启查询缓存
整理到此为止,有不足的欢迎指正讨论。。。☺