hibenate集合映射

原文地址:hibenate集合映射 作者:大灰狼

* 集合映射
如果实体类有一个集合类型的属性,就需要一张额外的表,即所谓的集合表,这
个表有一个外键引用实体类对应的表的主键。

根据集合中存放的元素类型可以分为两种:1,值类型集合,元素是可识别的数据
库类型,如基本数据类型或字符串类型等;2,实体类型集合,元素是其他的实体。

** 对集合做映射时一般需要指定:
1)集合表的表名,在相应元素中使用属性table指定;
2)集合外键,是在子元素key中用属性column属性指定一个列名,他是一个外键
   引用了实体表的主键;
3)集合元素,对于值类型集合,使用元素element指定;对于实体类型集合,使
   用元素one-to-many或many-to-many。
4)如果是索引集合,如List,数组或Map(bag不是),还需要指定一个集合表中
   的索引字段,用于对应到数组索引或List的索引,或者Map的key。
   a) 如果是List或数组,需要指定(集合表中的)一个用于保存索引的列,用
      元素list-index指定。
   b) 如果是Map:key用map-key元素指定;如果key是一个实体引用,则是通过
      map-key-many-to-many来配置。

*** 注意
JAVA的实体类中集合只能定义成接口不能定义成具体类,因为在运行时集合会被
替换成Hibernate的实现。

在实体类中声明集合时,可进行初始化。比如Set addresses = new HashSet();
这样做的目的是方便往集合中添加元素,调用getAddresses().add(element)就可
以了,不需再做是否为null的判断。  注意,我们只在把addresses初始化为一个
HashSet的实例,在运行时,集合会被Hibernate替换为自已的实现。

用于映射集合类的元素是由集合接口的类型决定的:
  <set>  元素用来映射 java.util.Set  类型的属性;
  <list> 元素用来映射 java.util.List 类型的属性;
  <array>元素用来映射 对象类型的数组;
  <primitive-array>元素用来映射 原始数据类型的数组;
  <bag>  元素用来映射 java.util.List 类型的属性(允许重复但没有顺序);
  <map>  元素用来映射 java.util.Map  类型的属性;

1,Set,一个用户(User)有多个地址,地址是字符串类型的。
    <set name="addresses" table="itcast_useraddresses">
     <key column="userId"></key>
     <element type="string" column="address"></element>
    </set>

2,List,与Set相比,需要在表中增加一列用于保存索引(元素在List中保存的
   顺序,不对应实体的任何属性)。用<list-index>元素指定:
    <list name="phoneNumbers" table="itcast_user_phonenumbers">
     <key column="userId"></key>
     <list-index column="idx"></list-index>
     <element type="string" column="phoneNumber"></element>
    </list>

3,Map,需要映射key的value:
    <map name="phoneNumbers3" table="itcast_user_phonenumbers3">
     <key column="userId"></key>
     <map-key type="string" column="idx"></map-key>
     <element type="string" column="phoneNumber"></element>
    </map>

* 一对多与多对一映射:User与Group
在User.hbm.xml中增加:
    <many-to-one name="group" column="groupId"></many-to-one>
在Group.hbm.xml中增加:
    <set name="users">
        <key column="groupId"></key>
        <one-to-many class="User"/>
    </set> 

这是做的双向关联,即从任何一方都可以得到另一方(User.getGroup得到所属的
组或Group.getUsers得到组中所有的用户);双方也都可以维护关系,维护关系
是指更新外键这一列的的值; 如果只有一方进行映射,就是单向关联。 如果去
掉了User.hbm.xml中的many-to-one映射,只有Group.hbm.xml中有一个set的映射,
这就是单向一对多关联,这时只能由Group维护关系,并且只能从Group得到User。
同样,如果只去掉Group.hbm.xml中的set映射,就是单向多对一关联。

树形结构,就是自己和自己是一个 一对多/多对一 的关系。如Group有父组和子组,
映射文件中为:
    <many-to-one name="parent" column="parentId"/>
    <set name="children">
        <key column="parentId"/>
        <!-- 集合中的元素还是Group -->
        <one-to-many class="Group"/>
    </set>


* 索引(有序)集合
例如Group中的users希望以user的age升序排列.

有两种方式实现:
  a) 使用sort属性实现在内存中排序;
  b) 使用order-by属性实现: 在使用sql进行查询时使用order by子句指定顺序。

有序集合对于List或数组无效,因为列表元素的顺序由列表索引指明。

如果使用的是Set或Map类型的集合,并且在实体中对这个集合属性进行了初始化,
应是SortedSet或SortedMap实现类的实例(TreeSet或TreeMap)。

** sort属性
可以用于set或map映射,默认为unsorted,即不排序;可以设为natural,要求实
体要实现java.lang.Comparable接口。分类集合的行为象TreeSet或者TreeMap,
这是从数据库中取出记录后再在内存中进行排序。(在查询时有效)

** order-by属性
在set、bag或map映射中使用order-by属性,指定查询时生成的sql的order by子
句,这是在执行sql查询时指定排序(推荐)。如果使用了order-by,则返回的就
是可以保存顺序的集合实现类。


* 一对一映射:User与IdCard
有两种映射方式:基于主键和一对一和基于外键和一对一(先分析对象中应怎么
表示(各有一个属性),再用分析数据库表应怎么设计)。不管哪种方式,都是有
外键的那个表对应的实体(IdCard)来维护关系;只为只有IdCard能维护关系,所
以如果要做单向关联,只能做从IdCard到User的单向关联。

不管是多对一还是一对一,有外键的那个表所对应的实体始终能够维护关系。

** 基于主键的一对一
在IdCard.hbm.xml中把主键生成策略改为foreign,并设置one-to-one:
    ...
    <generator class="foreign">
        <param name="property">user</param>
    </generator>
 ...
    one-to-one name="user" constrained="true"></one-to-one>
 
    属性constrained="true"表示该类对应的表的的主键同时作为外键引用User
    表的主键,默认为false。

在User.hbm.xml中增加:
    <one-to-one name="idCard"></one-to-one>

** 基于外键的一对一
在IdCard.hbm.xml中增加:
    <many-to-one name="user" column="userId" unique="true"/>
   
    其中属性unique="true"说明这一列的值是唯一的,不能重复。

在User.hbm.xml中增加:
    <one-to-one name="idCard" property-ref="user"></one-to-one>

    !!其中property-ref属性用来指定关联类的一个属性,这个属性将会和外
    键相对应,如果没有指定,会使用对方关联类的主键(所以上面的基于主键
    的一对一时不用指定)。

 one-to-one也说明有一个表跟他关联,就像set元素一样。set元素需要一个
 子元素key来指定集合外键,而one-to-one也要指定关联类对应表的外键,
 如果这儿是让指定这个外键应该就好理解了(就像映射集合时指定的集合外
 键一样),只不过这儿让指定的是这个列对应的对象中的属性而已,这个属
 性就是那个外键列对应的属性。


* 多对多映射:Group与Privilege
在Group.hbm.xml中增加:
    <set name="privileges" table="itcast_groups_privileges">
        <key column="groupId"></key>
        <many-to-many class="Privilege" column="privilegeId"></many-to-many>
    </set>
在Privilege.hbm.xml中增加:
    <set name="groups" table="itcast_groups_privileges">
        <key column="privilegeId"></key>
        <many-to-many class="Group" column="groupId"></many-to-many>
    </set>

双方一定要在set元素中指定相同的表名(这是指定的中间表的名字)。元素
many-to-many 的class 属性用于指定关联类(集合中的实体元素)的名称;因为
是多对多,关联类在中间表中也是用一个外键指定的,many-to-many中的column
属性用于指定这个外键的列名。

这是做的双向关联,此时双方都可以维护关系。多对多中的维护关系是指操作中
间表(增加与删除)的记录。去掉任何一方的映射,就是单向的多对多关联。

如果想要清空Group中含有的Role,可以使用:
    group.getRoles().clear();
这样在提交事务时会删除相应的中间表记录。

* inverse
此属性表示 "是否放弃维护关联关系",在one-to-many和many-to-many中使用,
默认值是"false"。  维护关联关系是指在Java里两个对象关联时,对数据库表中
的数据产生影响。在一对多/多对一中,是指设置外键列的值;在多对多中是指在
中间表是增减记录。

设置inverse=true表示放弃维护关联关系,即由对方来维护。在使用时要注意:
  a) 在索引(有序)集合中,不要设置inverse=true,否则不能生成索引值。
  b) 在多对多映射中,不要双方都设置为inverse=true,否则都不维护关系。

维护关联关系总结:
  a) 一对多/多对一:多的一方始终可以维护关系;一的一方可以设置是否维护
    关联关系。
  b) 一对一:只能由 有外键的表对应的实体 来维护关联关系。
  c) 多对多:双方都可以设置是否维护关联关系。

* cascade
Casade用来说明当对主对象进行某种操作时是否对其关联的从对象也作类似的操
作,常用的cascade值有:
  none(默认值,无级连操作);
  all(所有级连操作);
  save-update(对主对象调用save()或update()或saveOrUpdate()方法时对从对
      象调用saveOrUpdate());
  delete(delete()方法);
其他还有lock,refresh,evict,replicate,persist,merge,delete-orphan等。

同时使用多种级联风格时使用逗号分隔,如 cascade="save-update,delete"。

通常在many-to-one或many-to-many关系中应用级联没有意义。如删除一个用户不
应该删除他所属的组(组中的其他用户怎么办);或删除了一个组不应该把他对
应的权限实体也删除掉(其他组还要用呢)。 一般在one-to-one和one-to-many
中设置级联比较有用。


* 在Set中添加重复的未保存状态的实体
在一对多中,可以通过设置级联 cascade="save-update" 来实现在save()一方的
实体时级联的插入多方的数据,但这时如果集合如果使用的是Set,就会出现只插
入一个记录的现象,这是因为未保存的对象的主键值相等,hashCode()相同并且
equals()为true。

在Set中不能添加重复的元素,是否重复是通过hashCode和equals方法决定的。所
以可以通过重写hashCode与equals方法实现在Set中可以添加多个未保存的实体对
象(有相同的主键值): hashCode:IF id==null THEN return super.hashCode。

 

* 集合的lazy属性
延迟加载,默认值为true,即集合的属性值默认是不加载的。强制加载可以通过
在session环境中使用这个集合属性或者使用:Hibernate.initialize(proxy)。
当相关联的session关闭后,再访问懒加载的对象将出现异常。

** get 与 load 方法
get 方法:立即加载。
  1,会马上执行一条select语句。
load方法:延迟加载。
  1,会返回一个代理,不会执行select语句;
  2,会在第一次使用这个对象时,执行一条select语句,即从数据库中获取对象;
  3,调用了除getId()(获取主键的方法)之外的方法,就是使用了这个对象。

区别:
  1,get立即加载,load延迟加载;
  2,如果指定主键的记录不存在:
     a)get 方法返回null;
     b)load方法在加载时抛异常:org.hibernate.ObjectNotFoundException:
        No row with the given identifier exists.


* 继承映射
论坛中的文章有主题和回复两种:Topic和Reply,他们都是文章,都有content与
author属性,即继承自同一个父类。

可以使用两种方式映射:
  a) 每个继承结构一张表,需要一个字段用于区分子类的具体类型;
  b) 每个类一张表,这是就有三张表,各自都只包含自己的属性,子类对应的表
     的主键也做为外键引用超类对应的表的主键。

** 每个继承结构一张表 (subclass)
<class name="Article" table="itcast_article" discriminator-value="A">
...
<!-- 指定一个列,用于区分子类的具体类型(鉴别器) -->
<discriminator column="class" type="string"></discriminator>
...
<subclass name="Topic" discriminator-value="T">
    <property name="title"></property>
</subclass>
<subclass name="Reply" discriminator-value="R">
    <property name="floor"></property>
</subclass>

1,指定一个列,用于存放代码类型的标志。
   a)使用discriminator元素配置:
   b)他的type属性指定这个列的类型,如果不指定,默认为string;
   c)这个元素要写在id元素的后面,不要把顺序弄错了。
2,为此继承结构中的每一个实体类都指定一个唯一的标志:
   a)在 class 或 subclass 元素中通过 discriminator-value 属性配置;
   b)这个值要是唯一的;
   c)如果不指定,默认值是这个子类的全限定名。


** 每个类一张表 (joned-subclass)
...
<joined-subclass name="Topic" table="itcast_topic">
    <key column="id"></key>
    <property name="title"></property>
</joined-subclass>
...

其中key指定的是子类表的主键,同时也会做为外键引用父类表的主键。


** 获取实体
使用 session.get(Article.class, 1); 即可返回相应类型的对象实例。


* 查询
Hibernate提供的查询方式有:HQL或Criteria。

** HQL
1,HQL是面向对象的查询语言,他查的是对象而不是表;
2,HQL中的对象名和属性名是区分大小写的(他部分不区分大小写);
3,HQL主要通过Query来操作,Query的创建方式:session.createQuery(hql);

*** 1,from 子句
例:查询所有User实体:
    from cn.itcast.domain.User    
或  from User  (当auto-import="true"时)

1,from后指定的是要查询的实体的名字;
   a)实体的名字是映射文件中的class元素的entity-name属性指定的;
   b)如果没有指定entity-name,则默认是类的全限定名。
   c)这样可以让一个类映射多个表,指定不同的entity-name就可以了。

2,如果没有指定entity-name,在from后应写类的全限定名;
   a)当映射文件中的 hibernate-mapping 元素的属性 auto-import="true" 时,
      也可以使用非全限定名,其默认值为true。
   b)如果存在 多个 非全限定名相同 的类,都应设置 auto-import="false"。

3,可以为查询的实体指定一个别名,使用as关键字,但as关键字也可以省略。例:
   from User u

*** 2,select 子句
例:查询所有User实体:
    select u from User as u

1,以上是查询整个实体(所有属性,返回的是实体对象);
2,也可以只查询几个属性,指定要查询的属性即可,多个属性之间用逗号隔开,
   a)例:select u.id,u.name from User u
   b)这时返回的每一结果都是一个Object数组,数组中依次存放着id和name属
      性的值。

3,可以让查询部分属性的语句返回结果是一个个相应对象实例,只填充了查询中
   出现的属性,
   a)应使用如下语句:select new User(u.id,u.name) from User u ;
   b)这样要求User类有相应的构造方法:public User(int id, String name)。

小问题:
  1,执行的语句 "select new Group(g.id,g.name) from Group g",抛如下异常:
  2,java.lang.ClassCastException: org.hibernate.hql.ast.tree.SqlNode,
  3,是在 session.createQuery() 时报的错。
问题原因:
  Group为关键字. 应改为如下语句(使用全限定名):
  select new cn.itcast.entities.Group(g.id,g.name) from Group g

*** 3,where 子句
例,查询所有id值小于10的User:
    from User u where u.id < 10

1,其中的 u.id 是指的实体的属性;
2,如果属性名与关键字冲突的话,就需在前面加上实体的别名,如u.order或
   u.desc等。

*** 4, order by
例:from User u order by u.id desc

1,升序:asc;降序:desc ;
2,多个排序属性之间用逗号隔开。

*** 5.聚集函数
avg(),count(),max(),min(),sum()

例,查询所有用户的总数:
    select count(u) from User u

执行后,返回的结果可能是Integer类型,也可能是Long型,这是由jdbc驱动决定
的,所以使用时要注意。可以把返回值转为Number,然后根据需要取intValue或
longValue,代码为:((Number) result).intValue();

 

1,大多数sql的表达式在where子句中都允许使用;
2,Hibernate还提供有操作集合的函数,如:
   a)size(),集合的大小;
   b)maxelement()和minelement(),引用一个基本数据类型的集合中的最大与
      最小的元素;
   c)minindex()与maxindex(),引用有序集合的最小与最大的索引;
   d)elements(),返回集合的元素;
   e)他们都需要一个集合类型的参数。

例1:查询用户数量大于2的所有的组,hql如下:
     select g from Group g where size(g.users) < 2

例2:查询含有某个权限的组:
     from Group g where ? in elements(g.privileges)
     还需要设置一个参数,这个参数是Privilege的一个对象实例。

*** 使用参数
有两种方式:

1,使用?占位,例:from User where id > ?,指定参数值使用
Query.setParameter(int position, Object val),位置索引从0开始;

2, 或使用变量名占位,例:from User where id > :id,指定参数值是使用
Query.setParameter(String name, Object val);

两种方法不能同时使用。

*** Query的分页支持和查询的方法
Query.list()          是返回一个List集合;
Query.iterate()       返回一个Iterator,这是先查询出所有的id,再一条一条
       的查询记录;
Query.uniqueResult()  是返回唯一的结果或null(无匹配时),如果有多条记录
       匹配,则抛异常NonUniqueResultException
分页是通过Query.setFirstResult() 与Query.setMaxResults() 实现。

*** 练习
1, 根据用户名和密码查询一个用户.
2, 查询有指定权限的组.
3, 树形的Group中, 方法 findGroups(Group parent) 查询指定Group的所有子组,
   如果parent=null, 则返回所有的顶级Group.


* Criteria 与 DetachedCriteria
Criteria是一种比HQL更面向对象的查询方式。

*** Criteria的创建方式:
    Criteria c = session.createCriteria(User.class);

*** 添加过滤条件:
    c.add(Restrictions.gt("id",3)); // id的值要大于3
    c.add(Restrictions.isNull("desc")); // desc不能为空
   
    这样添加的条件之间是 and 的关系。添加or关系的条件使用:
    c.add(Restrictions.or(Restrictions.eq("name", "xx"),
          Restrictions.eq("name", "yy")));
    这是查询名字为'xx'或'yy'的组。

*** 指定排序:
    c.addOrder(Order.desc("id")); // 按id的值降序排列

*** 分页:
    Criteria.setFirstResult(0);
    Criteria.setMaxResults(10);
二级缓存:配置:二级缓存是SessionFactory级别的全局缓存,它底下可以使用
不同的缓存类库,hibernate内置了对EhCache, OSCache, TreeCache,
SwarmCache的支持,可以通过实现CacheProvider和Cache接口来加入Hibernate
不支持的缓存实现。使用时要在hibernate.cfg.xml中增加配置:
    <property name="cache.provider_class">
        org.hibernate.cache.HashtableCacheProvider
    </property>
其中内置的缓存实现在文档19.2. The Second Level Cache中有说明。

以上只是说明可以使用二级缓存,具体的使用是每个类的缓存单独存放,也需要
单独配置。  使用方式为:在hibernate.cfg.xml中增加:
    <class-cache class="className" usage="read-only"/>
或在映射文件的class元素加入子元素:
    <cache usage="read-write"/>

其中usage:read-only,read-write,nonstrict-read-write,transactional。如果
应用程序只需读取一个持久化类的实例,而无需对其修改,应可以对其进行只读
(read-only)缓存;如果应用程序需要更新数据,那么使用读/写缓存(read-write)
比较合适。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值