全面解析Hibernate关联操作、查询操作、高级特性、并发处理机制
本文所需的数据库初始文件,hibernate常用操作的完整示例代码(包含所有hibernate操作所需jar文件)提供下载学习:http://download.csdn.net/detail/daijin888888/9551724
1、hibernate关联映射
1)什么是关联映射?
如果表之间具有关联关系,hibernate允许我们在hbm.xml中描述他们的关联关系,然后在我们操作其中一张表时,自动的根据这种关系操作到另外的关系表,那么这种关联关系的设置,我们称之为关联映射。
2)关联映射的好处?
一次访问可以关联操作多张表
--关联查询出关系表的数据
--关联新增、修改关系表的数据
--关联删除关系表的数据
3)关联映射实现步骤
--了解表之间的关系,且明确关系字段
--在实体类中追加关联属性,用于封装关联的数据
--需要配置hbm.xml文件,设置关联关系
*2、一对多关联
例:假如一个account有多条service记录,希望在查询account数据时,自动连带着查询出它对应的service数据。
1)表关系
一对多的关系,关系字段是service.account_id。(即service表中要有与account表关联的字段account_id,该字段对应account表中的id)
2)追加关系属性
在account类中,追加关联属性services,其类型为set<service>。
private set<service> services = new hashset<service>();
3)设置关联关系
--语法
在account.hbm.xml中,设置一对多关系
<set name="关联属性名"> <key column="关联字段名"/> <one-to-many class="关联表的实体类名"/> </set> --实现 <set name="services"> <key column="account_id"/> <one-to-many class="com.*.entity.service"/> </set>
4)示例代码
已知:
--关联属性services
--关联字段account_id
--关联对象service --> 表名service
<span style="font-size:14px;">public void testfind() { session session = hibernateutil.getsession(); account a = (account) session.load(account.class, 1010); system.out.println("---显示account账号信息---"); system.out.println(a.getid() + " " + a.getrealname() + " " + a.getidcardno()); system.out.println("---显示当前账号下的业务账号---"); set<service> services = a.getservices(); for (service s : services) { system.out.println(s.getid() + " " + s.getosusername() + " " + s.getunixhost()); } system.out.println(a.tostring()); }</span>
*3、多对一关联
例:希望在查询service数据后,可以自动的查询出它对应的account数据。
1)关联关系
service与account具有多对一的关系,关系字段为service.account_id
2)追加关联属性
--在service实体类中追加关联属性account,其类型为account。
private account account;
此后accountid属性可以去掉,可以通过getaccount().getid()方法来得到account_id的值
3)设置关联关系
a、语法:
--在service.hbm.xml中,追加关联关系的配置
--<many-to-one name="关联属性名"
column="关系字段名"
class="关联表的实体类名"/>
b、实现:
<many-to-one name="account"
column="account_id"
class="com.*.entity.account"/>
4)示例代码
已知:
--关联属性account
--关联字段account_id
--关联对象account --> 表名account,主键id
<span style="font-size:14px;">public void testfind() { session session = hibernateutil.getsession(); service s = (service) session.get(service.class, 2002); system.out.println("----显示业务账号信息---"); system.out.println(s.getid() + " " + s.getosusername() + " " + s.getunixhost()); system.out.println("----显示相关账务账号信息---"); system.out.println(s.getaccount().getid() + " " + s.getaccount().getrealname()); }</span>
*4、关联操作
1)关联查询
如果需要当前对象和关联属性一起采用一个sql语句实例化,可以采用下面方法:
a、(不推荐)修改hbm.xml中的关联属性映射
lazy属性:
true表示启用延迟加载;
false表示关闭延迟加载
fetch属性:
join表示采用连接方法与主对象一起查询,此时,lazy="false"失效;
select(默认)表示单独发送一个sql查询关联数据
b、(推荐)通过hql及join fetch语法
--from account a join fetch a.services where a.id=?
含义:在查询account对象时,将services关联属性数据采用表连接方式一并查出来。
--from service s join fetch s.account where s.id=?
含义:在查询service对象时,将account关联属性数据采用表连接方式一并查出来。
--from service s join fetch s.account where s.account.id=?
注意:
--hql中写的是对象和属性
--join fetch后面没有on子句,而fetch的是关联属性
--query.setinteger来设置整型参数值,下标从0开始。
--如果明确该hql只会返回一条记录,可以使用query.uniqueresult()方法返回唯一的记录
c、示例代码(改写上述一对多关联的查询代码)
<span style="font-size:14px;">public void testfind() { session session = hibernateutil.getsession(); // account a = (account) session.load(account.class, 1010); string hql = "from account a join fetch a.services where a.id=?"; query query = session.createquery(hql); query.setinteger(0, 1010);// ?从0开始 account a = (account) query.uniqueresult();// 单行查询可采用 system.out.println("---显示account账号信息---"); system.out.println(a.getid() + " " + a.getrealname() + " " + a.getidcardno()); system.out.println("---显示当前账号下的业务账号---"); set<service> services = a.getservices(); for (service s : services) { system.out.println(s.getid() + " " + s.getosusername() + " " + s.getunixhost()); } system.out.println(a.tostring()); }</span>
2)级联添加、级联修改
a、当表具有关联关系时,hibernate不仅仅提供关联查询的功能,还具有关联添加、修改、删除关联表中数据的能力,这种能力称之为级联操作。
b、如何实现级联操作
需要在关联属性设置的位置,追加属性cascade
--none:默认不支持级联
--save-update:支持级联添加与更新
--delete:支持级联删除
--all:支持级联添加、更新、删除
c、说明
通常1对多的表关系,1的一方是主表,多的一方是从表,往往是需要在添加、更新、删除主表时连带添加、更新、删除从表的数据。如:删除账务账号时,要连带删除业务账号的数据
3)级联删除
a、设置cascade="delete"或cascade="all"就可以支持级联删除
b、通常情况下,需要在1的一方set标签处,追加属性inverse="true"
c、session.delete(obj);obj需要是持久对象,不能new,需要load/get取出
d、批量删除的方法:
级联删除采用n+1个delete语句清除主表和外键表的关联数据。
如果是批量删除,不推荐级联删除,建议采用hql编写delete删除语句。
delete from service where account.id=?//删除service表中所有account.id=?的数据,(该句替代级联操作中的n个delete语句)
delete from account where id=?
4)inverse属性(理解)详细说明点这里
是否交出关系维护的控制权。即默认情况下,account和service对象之间的关系由双方负责维护。意思是对account或service对象做级联操作时,需要执行update语句将关联字段设置成相同id。如果需要取消某一方的关系维护工作,可以在关联属性部分添加inverse="true"设置,这样可以避免update语句执行。
true:交出控制权,当前的对象不负责维护两张表的关联关系
false:不交出控制权,当前的对象要负责维护两张表的关联关系
提示:往往是一的一方(即<one-to-many>映射部分)设置为inverse="true",这样在对一方级联操作时,可以避免大量的update更新语句。
*5、多对多关联
例:管理员admin_info和角色role_info具有多对多的关系,希望在查询管理员时能够连带着查询出他对应的角色。 数据库设计时,需采用3张表表示。
admin_info(管理员)
admin_role(管理员和角色关系)
role(角色)
1)关系字段
关系字段位于他们的中间表admin_role中,
admin_id=admin_info.id
role_id=role_info.id
2)追加关联属性在管理员的实体类中追加角色相关属性
set<role> roles
3)在admin.hbm.xml中追加关联映射配置
--语法 <set name="关联属性名" table="中间表名"> <key column="admin的关联字段名"/> <many-to-many class="关联表的实体类名" column="关联表的关系字段名"/> </set> --代码 <set name="roles" table="admin_role"> <key column="admin_id"/> <many-to-many class="com.*.entity.role" column="role_id"/> </set>
4)级联操作
cascade表示支持级联操作,操作的是另一方的表,而并不是表示级联操作中间表。对于中间表的维护,不需要写cascade属性。
5)inverse
通常情况下,多对多关联,不需要写inverse="true",原因是另一方在插入数据时,可能没有管中间表的数据,需要当前的一方来维护,因此不能写inverse="true",否则的话,双方都不维护这个关系,数据上有问题。
6)示例java代码
<span style="font-size:14px;"> // 移除角色 @test public void testdeleterole() { session session = hibernateutil.getsession(); transaction tx = session.begintransaction(); try { admin a = (admin) session.load(admin.class, 1); role r1 = (role) session.load(role.class, 1); a.getroles().remove(r1); session.update(a); tx.commit(); } catch (hibernateexception e) { e.printstacktrace(); tx.rollback(); } finally { session.close(); } } // 追加角色 @test public void testaddrole() { session session = hibernateutil.getsession(); transaction tx = session.begintransaction(); try { admin a = (admin) session.load(admin.class, 1); role r1 = (role) session.load(role.class, 1); role r2 = (role) session.load(role.class, 43); role r3 = (role) session.load(role.class, 44); a.getroles().add(r1); a.getroles().add(r2); a.getroles().add(r3); session.update(a); tx.commit(); } catch (hibernateexception e) { e.printstacktrace(); tx.rollback(); } finally { session.close(); } } @test public void testfind() { session session = hibernateutil.getsession(); transaction tx = session.begintransaction(); try { admin a = (admin) session.load(admin.class, 1); system.out.println("----显示管理员信息---"); system.out.println(a.getid() + " " + a.getname() + " " + a.gettelephone()); system.out.println("----显示管理员角色信息---"); for (role role : a.getroles()) { system.out.println(role.getname() + " "); } tx.commit(); } catch (hibernateexception e) { e.printstacktrace(); tx.rollback(); } finally { session.close(); } }</span>
6、继承关联
例:在电商网站上搜索商品,比如输入iphone来搜索,搜索结果中可以包含手机、手机膜、手机壳、充电器、耳机等等相关的产品信息。这种功能,我们可以在设计表时用一种特殊的一对一的关系来表现。即将所有商品通用的属性提取到一张公共的表中product,具体商品表中只保存该商品特有的属性,那么具体商品表和product表具有一对一的关系。在搜索时只搜索product表,就可以将相关的信息模糊查找出来。
--通用信息表product(id,name,price,desc)
--书的商品表book(id,authod,publishing,words)
--希望在操作book表时,能够自动的将通用的字段维护到product表中。
1)明确表的关系
book与product具有一对一的关系,这种关系的目的是为了复用product表中的字段,像是一种继承关系
2)实体类
book extends product
3)配置文件中体现关联关系
--父类型同原先的配置文件写法一致
--子类型具有特殊性
<joined-subclass name="类型名" table="表名" extends="父类名称"> <key column="关联字段名"/> <property name="" type="" column=""/> ... </joined-subclass>
4)继承关系中,由于2张表具有类似于父子的关系,那么子表中必须引用父表中的数据,不存在不引用的情况,即必须在维护子表同时维护父表。所以这是固定的情况,就不用写cascade,inverse。
5)描述类别(了解)
<joined-subclass>数据库有父类表和子类表
<union-subclass>数据库有子类表、没有父类表(子类表已包含父类表字段,无父类表,但有父类实体对象)
<subclass>数据库有父类子类都用一个表(设计较乱,即没有进行表拆分,很少用)
*7、hibernate查询
1)*hql查询(hibernate query language)
属于面向对象查询语句,针对hibernate映射过来的pojo进行查询,从而实现对数据库的查询。
a、以非主键做条件查询
--条件参数以?表示,query.setstring(0,"");
--条件参数以:x表示,query.setstring("x","");
示例代码:
<span style="font-size:14px;"> // 测试按非主键做条件查询 @test public void testfind1() { string hql = "from service where account.id=? and unixhost=?"; session session = hibernateutil.getsession(); query query = session.createquery(hql); query.setinteger(0, 1011); query.setstring(1, "192.168.0.23"); list<service> list = query.list(); for (service s : list) { system.out.println(s.getid() + " " + s.getosusername() + " " + s.getunixhost()); } session.close(); } // 等价于testfind1,采用“:标识符”替代 @test public void testfind2() { string hql = "from service where account.id=:aid and unixhost=:host"; session session = hibernateutil.getsession(); query query = session.createquery(hql); query.setinteger("aid", 1011); query.setstring("host", "192.168.0.23"); list<service> list = query.list(); for (service s : list) { system.out.println(s.getid() + " " + s.getosusername() + " " + s.getunixhost()); } session.close(); }</span>
b、只查询一部分属性
--默认返回的集合中封装的是object[]
--new service(id,unixhost,osusername)使返回的结合中封装的是service对象,
注意:
service中需要追加响应的构造器;
不要丢掉无参构造器;
示例代码:
<span style="font-size:14px;"> // 获取部分字段结果,默认采用object[]封装数据 @test public void testfind3() { string hql = "select s.id,s.unixhost,s.osusername from service s where s.account.id=?"; session session = hibernateutil.getsession(); query query = session.createquery(hql); query.setinteger(0, 1011); list<object[]> list = query.list(); for (object[] objs : list) { system.out.println(objs[0] + " " + objs[1] + " " + objs[2] + " "); } session.close(); } // 等价于testfind3,需要添加对应的构造方法 @test public void testfind4() { string hql = "select new service(s.id,s.unixhost,s.osusername) from service s where s.account.id=?"; session session = hibernateutil.getsession(); query query = session.createquery(hql); query.setinteger(0, 1011); list<service> list = query.list(); for (service s : list) { system.out.println(s.getid() + " " + s.getosusername() + " " + s.getunixhost()); } session.close(); }</span>
c、hql定义在配置文件中(了解即可)
--通过query元素在配置文件中定义hql
--query元素写在class的后面
--session.getnamedquery(hql名);
示例代码:<query/>与<class/>在hbm.xml中同级摆放
<span style="font-size:14px;"><query name="findall"><!-- cdata内包括纯文本字段,防止出现特殊字符 --> <![cdata[ from service ]]> </query></span> <span style="font-size:14px;"> // 将hql定义到hbm.xml中(只适用静态hql语句结构) @test public void testfind5() { session session = hibernateutil.getsession(); // 获取hbm.xml中<query>定义的hql语句 query query = session.getnamedquery("findall"); list<service> list = query.list(); for (service s : list) { system.out.println(s.getid() + " " + s.getosusername() + " " + s.getunixhost()); } session.close(); }</span>
d、分页查询
--查询记录
query.setfirstresult((page-1)*pagesize);
query.setmaxresults(pagesize);
--查询总页数
select count(*) from service
示例代码:
<span style="font-size:14px;"> // 测试分页查询 @test public void testfind6() { int page = 2; string hql = "from service order by id"; session session = hibernateutil.getsession(); query query = session.createquery(hql); // 追加分页参数设置 query.setfirstresult((page - 1) * 3);// 设置抓起记录的起点,从0开始 query.setmaxresults(3);// 设置最大抓取数量 list<service> list = query.list();// 执行查询,如果没有分页设置就查所有值 for (service s : list) { system.out.println(s.getid() + " " + s.getosusername() + " " + s.getunixhost()); } session.close(); } // select count(*) from service @test public void testfind7() { string hql = "select count(*) from service"; session session = hibernateutil.getsession(); query query = session.createquery(hql); long size = (long) query.uniqueresult(); system.out.println("记录总数:" + size); session.close(); }</span>
e、关联查询(记住一种即可)
--from service s,account a
where s.account.id=a.id
--from service s inner join s.account a
--select s.account.realname from service s
总结:
hql与sql的相同点:
--支持select,from,where,group,order by子句
--支持inner join,left join等连接
--支持>,<,>=,<=,in,not in,between,like等条件
--支持分组统计函数count,sum,max,min,avg
hql与sql的不同点:
--hql语句区分大小写,即大小写敏感。关键字不区分。
--hql中写的是对象名和属性名,而不是表名和字段名
--不支持join on中的on子句
--不支持select *
--不支持数据库函数,比如日期函数to_date()、字符函数to_char()等
2)criteria查询(不够直观,了解即可)
使用hibernate的api来拼一个hql
criteria c = session.createcriteria(service.class);
示例代码:
<span style="font-size:14px;"> // 使用hibernate的api来拼一个hql @test public void testfind1() { session session = hibernateutil.getsession(); criteria c = session.createcriteria(service.class); c.add(restrictions.and(restrictions.like("osusername", "huang%"), restrictions.eq("unixhost", "192.168.0.26")));// 追加查询条件 list<service> list = c.list();// 执行查询,如果没有分页设置就查所有值 c.addorder(order.desc("id"));// 追加排序 // c.setfirstresult(arg0);//分页 // c.setmaxresults(arg0); for (service s : list) { system.out.println(s.getid() + " " + s.getosusername() + " " + s.getunixhost()); } session.close(); }</span>
3)sql查询
直接帮助我们调用jdbc来执行sql查询
sqlquery sqlquery = session.createsqlquery(sql);
示例代码:
<span style="font-size:14px;"> @test public void testfind1() { string sql = "select * from service"; session session = hibernateutil.getsession(); sqlquery sqlquery = session.createsqlquery(sql); sqlquery.setfirstresult(0);// 分页 sqlquery.setmaxresults(3); // 默认采用数组封装一条记录 list<object[]> list = sqlquery.list(); for (object[] objs : list) { system.out.println(objs[0] + " " + objs[2]); } session.close(); } //同testfind1(),指定封装记录的实体 @test public void testfind2() { string sql = "select * from service"; session session = hibernateutil.getsession(); sqlquery sqlquery = session.createsqlquery(sql); sqlquery.setfirstresult(0);// 分页 sqlquery.setmaxresults(3); // 指定封装记录的实体类 sqlquery.addentity(service.class); // 采用指定的service类型封装一条记录 list<service> list = sqlquery.list(); for (service s : list) { system.out.println(s.getid() + " " + s.getosusername()); } session.close(); }</span>
8、hibernate高级特性(了解)
1)二级缓存
a、二级缓存(默认关闭)
--二级缓存是sessionfactory级别的缓存,由sessionfactory负责管理
--缓存的也是实体对象
--缓存数据可以被不同的session间共享
--适用环境:对象数据频繁共享;对象数据变化频率小
b、二级缓存的使用步骤
--导缓存包ehcache.jar
--导入缓存配置文件ehcache.xml
--在hibernate.cfg.xml中,设置开启二级缓存,并且设置缓存驱动类
<span style="font-size:14px;"><!-- 使用二级缓存 --> <property name="hibernate.cache.use_second_level_cache">true</property> <!-- 指定二级缓存组件的驱动类ehcache.jar --> <property name="hibernate.cache.provider_class">org.hibernate.cache.ehcacheprovider</property></span>
--在要缓存的pojo的关系映射文件hbm.xml中设置元素<cache usage="readonly"/>
示例代码:
<span style="font-size:14px;"><span style="font-size:14px;"> @test public void testfind1() { // 第一次查询,使用session1 session session1 = hibernateutil.getsession(); service s1 = (service) session1.get(service.class, 2002); system.out.println(s1.getosusername() + " " + s1.getunixhost()); session1.close(); // 第二次查询,使用session2(配置二级缓存后,两次查询均到二级缓存中取数据) // hibernateutil.getsessionfactory().evict(service.class);// 移除后则还是查询两次 session session2 = hibernateutil.getsession(); service s2 = (service) session2.get(service.class, 2002); system.out.println(s2.getosusername() + " " + s2.getunixhost()); }</span></span>
2)查询缓存
a、查询缓存
一级和二级缓存只能是缓存单个对象。如果遇到某个字符串结果、数组结果或者list集合,可以使用查询缓存存储。
--可以看成是特殊的二级缓存
--使用的是二级缓存的缓存空间
--缓存的是除实体对象之外的数据类型
--依赖于二级缓存,默认是关闭的,需开启二级缓存才能使用。
b、使用步骤
--开启二级缓存
--在hibernate.cfg.xml中设置开启查询缓存
<span style="font-size:14px;"><!-- 开启查询缓存 --> <property name="hibernate.cache.use_query_cache">true</property></span>
--query.list()查询之前,设置允许进行查询缓存,query.setcacheable(true);
c、使用环境
--需要频繁执行的相同的查询语句
--查询结果集内容改变频率小
--结果集数据量不要太多
提示:相同的sql,第一次去数据库查询,后续几次从缓存取出。因为缓存的是sql+结果集内容
示例代码:
<span style="font-size:14px;"><span style="font-size:14px;"> @test public void testfind() { find(); system.out.println("-------"); find(); } private void find() { string hql = "from service where account.id=?"; session session = hibernateutil.getsession(); query query = session.createquery(hql); query.setinteger(0, 1011); // 启用查询缓存执行 query.setcacheable(true); list<service> list = query.list(); for (service s : list) { system.out.println(s.getid() + " " + s.getosusername()); } }</span></span>
9、hibernate并发处理-加锁
举例:
模拟12306购票机制,假设当前有一张火车票的表tickets(id,line,amount,version),模拟多人同时购票的场景。
1)悲观锁
--程序悲观的认为,每个访问者都存在并发的问题,于是会对每条数据加锁,那么只有当前持有锁的访问者释放该锁时,下一个访问者才能访问数据,这种机制称之为悲观锁。
--就是说无论如何都要给一条数据加锁,不管该数据是否会发生并发的问题。
--特点
效率低,安全性高
--实现:
get(class,id,lockmode.upgrade)
select * from emp for update
注:这里利用的是数据库自带的for update子句进行加锁,并非是hibernate自己发明的加锁机制。
2)乐观锁
--程序乐观的认为,每个访问者都不会有并发的问题产生,因此不去加锁。而是当数据更新时,判断该数据是否发生了版本的变化,如果发生变化,则说明在此期间有并发产生,因此报错给予提示,本次更新失败。
--借助版本字段,当第一个用户更新提交后,hibernate会将该字段更新加1,这样后续用户提交的对象版本字段比数据库中的小,即在更新时发现版本变了,就抛出异常并更新失败。
--一个成功,其他失败
--特点
效率高,用户体验差
--实现
a、需要在表中追加版本字段,用于记录数据的版本;
b、实体类中追加版本属性;
c、hbm.xml中追加版本的配置<version name="" type="" column="">
3)如何选择
--如果是并发量大的情况,应选择乐观锁
--如果是并发量小的情况,应选择悲观锁
以上所述是小编给大家介绍的 全面解析hibernate关联操作、查询操作、高级特性、并发处理机制的相关知识,希望对大家有所帮助
上一篇: Java中抓取 Thread Dumps 的方式汇总
下一篇: struts2自定义MVC框架