Hibernate属性映射(一)
-
普通属性
假设有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>
-
特殊属性(set/list/map等)
问题的发生:这其实是因为oop语言的粒度问题(granularity),当一个持久化类中出现了一些复杂的属性,我们更希望用另一个类或者是方便的数据结构来替代它。但是在数据库中并不能这样做,只能建立两个表,其中一个是持久化对象对应的表,另一个是复杂属性的对应表。hibernate通过xml配置描述这样的关系,并能够自动建立这两个表,同时添加外键约束。
-
特殊属性的种类(一些常见的数据结构):
- set
- bag(允许重复的set,java没有对应的标准类库)
- list
- map
- array(少用)
这些种类无法使用简单的字段一一映射,需要一个辅助表来完成数据持久化,hibernate对于不同的这些结构提供了不同的实现
-
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有如下
- 新建了两张数据表person和person_school(建表语句省略)
- 建立外键约束(外键名是随机字符串)
alter table Person_schools add constraint FK8ojsqha3dsxkh9bddf4icluvr foreign key (school_id) references person (id)
-
插入数据(两张表)
#person insert into person (name, age, id) values (?, ?, ?) #person_school insert into Person_schools (school_id, school_name) values (?, ?)
-
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不能作为真正的主键,但主键又必不可少,此时便使用一个代理主键解决问题。
-
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的复合主键。
-
map(对应实现类HashMap)
-
将schools改成HashMap
-
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提供
-
-
-
排序
java中使用TreeMap(实现了SortedMap接口)来对map进行排序,那如何在映射的过程中保持这样的排序(sorted)呢?
首先我们把schools改一下类型:
private SortedMap<Integer,String> schools=new TreeMap<>();
然后在person.hbm.xml有两种方式完成映射:
-
使用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排序法
-
使用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>
-
-
注意(一个大坑)
假设我们使用了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<>();