Hibernate属性映射(一)

Hibernate属性映射(一)

  1. 普通属性

    假设有A类属性有属性x,A类与数据表a形成orm映射,属性x与字段y相对应。

    class A{
        private int x;
    }
    

    在.hbm.xml中配置(省去id,包名,xml schema的引入):

    <hibernate-mapping>
        <class name="A" table="a">
            <property name="x" column="y"/>
        </class>
    </hibernate-mapping>
    
  2. 特殊属性(set/list/map等)

    问题的发生:这其实是因为oop语言的粒度问题(granularity),当一个持久化类中出现了一些复杂的属性,我们更希望用另一个类或者是方便的数据结构来替代它。但是在数据库中并不能这样做,只能建立两个表,其中一个是持久化对象对应的表,另一个是复杂属性的对应表。hibernate通过xml配置描述这样的关系,并能够自动建立这两个表,同时添加外键约束。

    1. 特殊属性的种类(一些常见的数据结构):

      • set
      • bag(允许重复的set,java没有对应的标准类库)
      • list
      • map
      • array(少用)

      ​ 这些种类无法使用简单的字段一一映射,需要一个辅助表来完成数据持久化,hibernate对于不同的这些结构提供了不同的实现

    2. set(对应实现类HashSet)

      Person.java(例子:一个人的档案里有上过多所学校的记录)

      @AllArgsConstructor@NoArgsConstructor@Data@Builder
      public class Person {
          private int id;
          private int age;
          private String name;
          private HashSet<String> schools;
      }
      

      person.hbm.xml

      <!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.mycompany.myapp.entity.Person" table="person">
              <id name="id" unsaved-value="null" column="id">
                  <generator class="increment"/>
              </id>
              <property name="name" column="name"/>
              <property name="age" column="age"/>
              <set name="schools">
                  <key column="school_id"/>
                  <element type="java.lang.String" column="school_name" not-null="true"/>
              </set>
          </class>
      </hibernate-mapping>
      

      注意到标签下的代码,其实是新建了一个school的表,其主键为school_id,有字段school_name,这储存的就是Set中的数据了。

      我们用hibernate自动生成表,并向其中插入一些数据

      SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
              Session session = sessionFactory.openSession();
              Transaction tx = session.beginTransaction();
              HashSet<String> school = new HashSet<>();
              school.add("s1");
              school.add("s2");
              Person person = Person.builder()
                      .name("he1")
                      .age(16)
                      .schools(school)
                      .build();
              session.save(person);
              tx.commit();
              session.close();
      

      hibernate执行的sql有如下

      1. 新建了两张数据表person和person_school(建表语句省略)
      2. 建立外键约束(外键名是随机字符串)
          alter table Person_schools 
             add constraint FK8ojsqha3dsxkh9bddf4icluvr 
             foreign key (school_id) 
             references person (id)
      
      1. 插入数据(两张表)

         #person
         insert 
            into
                person
                (name, age, id) 
            values
                (?, ?, ?)
         #person_school
         insert 
            into
                Person_schools
                (school_id, school_name) 
            values
                (?, ?)
        
    3. bag

      java并没有提供标准的API支持bag(可重复集合),但是我们可以采用以下方法实现:

      @AllArgsConstructor@NoArgsConstructor@Data@Builder
      public class Person {
          private int id;
          private int age;
          private String name;
          private Collection<String> schools=new ArrayList<>();
      }
      

      接下来更改person.hbm.xml

       <idbag name="schools">
                  <collection-id column="id" type="long">
                      <generator class="increment"/>
                  </collection-id>
                  <key column="person_id"/>
                  <element type="java.lang.String" column="school_name" not-null="true"/>
      </idbag>
      
        hibernate提供了<bag>和<idbag>两种标签来映射bag。事实上,我们在Person类中声明的只是用集合模拟的bag,得到映射后,hibernate将其转化为PersistentBag,在进行下一步操作。
      

      ​ 与上面的不同之处在于,它声明了一个代理主键,因为bag的有重复性,使得person_id不能作为真正的主键,但主键又必不可少,此时便使用一个代理主键解决问题。

    4. list(对应实现类List)

      @AllArgsConstructor@NoArgsConstructor@Data@Builder
      public class Person {
          private int id;
          private int age;
          private String name;
          private List<String> schools;
      }
      

      person.hbm.xml:

      <list name="schools" table="person_school">
                  <key column="person_id"/>
                  <list-index column="position"/>
                  <element type="java.lang.String" column="school_name" not-null="true"/>
      </list>
      

      ​ hibernate提供标签映射list,我们需要通过额外声明一个字段来映射下标,此时这张表的主键是person_id和position的复合主键。

    5. map(对应实现类HashMap)

      1. 将schools改成HashMap

      2. person.hbm.xml:

        <map name="schools" table="person_school">
                    <key column="person_id"/>
                    <map-key type="int" column="school_id"/>
                    <element column="school_name" type="java.lang.String" not-null="true"/>
                </map>
        

        ​ hibernate提供标签映射map,我们需要通过额外声明一个字段来映射键值,此时这张表的主键是person_id和school_id的复合主键。

  3. 排序

    ​ java中使用TreeMap(实现了SortedMap接口)来对map进行排序,那如何在映射的过程中保持这样的排序(sorted)呢?

    ​ 首先我们把schools改一下类型:

        private SortedMap<Integer,String> schools=new TreeMap<>();
    

    ​ 然后在person.hbm.xml有两种方式完成映射:

    1. 使用java排序语法

      <map name="schools" table="person_school" sort="natural">
          <key column="person_id"/>
          <map-key type="int" column="school_id"/>
          <element column="school_name" type="java.lang.String" not-null="true"/>
      </map>
      

      本质上是传入java的Comparator对象,natural会使用默认的排序方法进行排序,也可以传入Java的排序类,或者实现Comparator接口自定义排序类。这种方法使用java来处理数据的排序,在数据量大的时候造成内存压力大,推荐使用sql排序法

    2. 使用sql排序语法

      这里的写法与sql语句一样

      <!--根据school_id升序排序-->
      <map name="schools" table="person_school" order-by="school_id asc">
                  <key column="person_id"/>
                  <map-key type="int" column="school_id"/>
                  <element column="school_name" type="java.lang.String" not-null="true"/>
              </map>
      
  4. 注意(一个大坑)

    假设我们使用了ArrayList,TreeMap等实现类,如下:

     private TreeMap<Integer,String> schools
    

    会报出以下异常:

    四月 13, 2020 5:34:25 下午 org.hibernate.property.access.spi.SetterMethodImpl set
    ERROR: HHH000123: IllegalArgumentException in class: com.mycompany.myapp.entity.Person, setter method of property: schools
    四月 13, 2020 5:34:25 下午 org.hibernate.property.access.spi.SetterMethodImpl set
    ERROR: HHH000091: Expected type: java.util.TreeMap, actual value: org.hibernate.collection.internal.PersistentMap
    

    原因:Hibernate使用PersistentMap等一系列PersistentXXX类包装了集合类,而这些类直接继承了Map等接口,所以我们声明时也要用Map。

      private Map<Integer,String> schools=new TreeMap<>();
    

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值