Hibernate框架学习之注解映射实体类

     前面的相关文章中,我们已经介绍了使用XML配置文件映射实体类及其各种类型的属性的相关知识。然而不论是时代的潮流还是臃肿繁杂的配置代码告诉我们,注解配置才是更人性化的设计,于是学习了基本的映射实体类的基本注解,此处做一点总结,后续文章将陆续更新使用注解的方式管理配置各种映射关联关系。本篇主要涉及以下内容:

  • 使用最基本的注解映射一个实体类
  • 使用注解映射属性
  • 使用注解映射主键
  • 其他特殊类型的属性映射

一、使用最基本的注解映射一个实体类

@Entity
@Table(name = "userInfo")
public class UserInfo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    //省略getter和setter方法
}
//在hibernate.cfg.xml中添加实体类
//这样hibernate就会根据配置文件去查找该实体类并做映射操作
<mapping class="User_Annotation.UserInfo"/>

这就是映射一个最简单的实体类所用到的最基本的注解。其中,

  • @Entity:指定当前被修饰的类是一个实体类,用于映射到数据库中的表。
  • @Table(name = "userInfo"):详细指定了该类映射到数据库中的哪张表,这里映射到userInfo表。
  • @Id:指定被修饰的属性将映射到数据表的主键列。
  • @GeneratedValue(strategy = GenerationType.IDENTITY):该注解指定了主键的生成策略,一般不单独出现,这里指定了主键自增的策略。
这里写图片描述

二、使用注解映射普通属性
对于实体类中属性的映射,一般我们使用@Column进行修饰。该注解有很多属性:

  • name:指定该属性映射到数据表中对应的名称
  • nullable:指定该属性映射的数据表中列是否可以为null,默认为true
  • unique:指定该属性映射到数据表中的列是否具有唯一约束
  • length:指定该属性映射到数据表中的列所能保存数据的最大长度,默认是255

默认情况下,我们不使用@Column修饰属性的时候,hibernate会自动以该属性的名称映射到数据表中的列。

我们也可以使用注解@Transient修饰属性,它指明了该属性不会被映射到数据表中某一列,而只是作为一个属性被定义在实体类中。例如:

@Entity
@Table(name = "userInfo")
public class UserInfo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    @Transient
    private int age;
    //省略getter,setter方法
}

看看hibernate为我们生成的sql语句:

这里写图片描述

显然,我们age属性并没有被映射到userinfo表中。

对于枚举类型的属性,我们可以使用@Enumerated注解进行修饰。
在某些特殊情况下,有时我们的实体类属性会被定义为枚举类型,那么对于这种数据库中并无法对应的Java类型,该如何映射呢?Hibernate中提供@Enumerated注解来用于我们映射枚举类型,该注解提供一个value属性,该属性可以取两个值:

  • EnumType.STRING:该枚举类型的属性映射到数据表的字段的类型是字符串型
  • EnumType.ORDINAL:该枚举类型的属性映射到数据表的字段的类型是整数类型

例如:

//定义一个枚举类型
public enum Season {
    春季, 夏季, 秋季, 冬季
}
@Entity
@Table(name = "userInfo")
public class UserInfo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;

    @Enumerated(EnumType.STRING)
    private Season season;
    //省略getter,setter方法
}

看看我们的userinfo表:

这里写图片描述

而当我们@Enumerated(EnumType.ORDINAL)修饰属性的时候,那么Hibernate为我们生成的sql语句是:

这里写图片描述

这两种情况下,数据表中的season字段一种保存的是枚举类型的具体值,一种保存的是枚举值对应的序号。

使用@Temporal注解映射日期时间类型
对于Java来说,表示时间的两个类库,Java.util.Date和java.util.Calendar。而对于数据库而言,表示时间的类型就有很多,例如:date,time,datetime,timestamp等。如何准确的指定最终的映射情况就是我们的@Temporal注解的作用。@Temporal有一个value属性,可以取以下的一些值:

  • TemporalType.DATE:对应于数据库中的date类型
  • TemporalType.TIME:对应于数据库中的time类型
  • TemporalType.TIMESTAMP:对应于数据库中的timestamp类型

例如:

@Temporal(TemporalType.DATE)
private Date date;

上述代码指定了Java.util.Date类型属性映射到数据库中的date类型字段。

三、使用注解映射主键属性
最简单的情况下,我们使用注解@Id标识实体类中的某个属性,那么该属性将会被hibernate映射到数据库主键字段,并且无需指定任何属性值。使用使用@GeneratedValue指定主键的生成策略,通过它的strategy属性来指定具体的主键生成方案,该属性可以取如下几个值:

  • GenerationType.AUTO:hibernate默认为该值,它指明了hibernate自动根据底层数据库选择适当的生成策略
  • GenerationType.IDENTITY:适用于MySQL,SQLserver的主键自增长策略
  • GenerationType.SEQUENCE:适用于Oracle的子串策略
  • GenerationType.TABLE:基于辅助表的生成主键策略

如果不是使用Oracle做数据库的话,一般我们会使用IDENTITY作为默认的主键生成策略。

联合主键的映射可以通过多个@Id进行修饰即可,但要求该实体类必须继承 java.io.Serializable并尽可能的重写Object的两个方法,hashCode和equals,因为多个属性唯一确定一条记录,自然需要比较属性的值。例如:

@Entity
@Table(name = "userInfo")
public class UserInfo implements Serializable {
    @Id
    private int id;
    @Id
    private String name;
    //省略getter,setter方法
}

看看hibernate为我们创建的表结构:

这里写图片描述

四、特殊属性的映射
这里的特殊属性指的是实体类中属性类型非常规的基本类型、包装类型、引用类型,而是类似于集合类型、自定义类型等。我们首先看对于集合类型的属性映射情况。

1、映射集合类型的属性
在hibernate中,所有的集合类型属性都会被单独映射到一张表中,无论是List,Set或者Map都会对应于一张新表。首先我们看List的映射,在详细介绍之前,我们先完整的看看list的映射情况。

@Entity
@Table(name = "userInfo")
public class UserInfo implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;

    @ElementCollection(targetClass = String.class)
    @CollectionTable(name = "address",joinColumns = @JoinColumn(name = "user_id"))
    @OrderColumn(name = "list_id")
    @Column(name = "address")
    private List address;
    //省略getter,setter方法
}
//通过实体类实例向数据表中插入数据
UserInfo userInfo = new UserInfo();
userInfo.setName("single");
List<String> list = new ArrayList<String>();
list.add("NanJin");
list.add("XinJiang");
list.add("SiChuan");
list.add("ZheJiang");
list.add("NanTong");
userInfo.setAddress(list);

session.save(userInfo);

看看两张表:

这里写图片描述

现在,我们再来看看所用到的几个注解。@ElementCollection注解用于修饰一个集合类型的属性,targetClass 指定了该集合类型的对应的泛型类型,我们这里指定了String类型,那么hibernate底层会默认构建一个ArrayList来存放所有的集合元素并且每个元素都限定为String类型。

@CollectionTable注解用于配置为集合属性生成的那张新表的基本信息,name 指定新表的表名,joinColumns的值是一个注解@JoinColumn,该注解专门用于配置外键列,这里我们给他命名为user_id,该字段是address表的值依赖于userinfo表的id主键列的值。

@OrderColumn注解用于配置有序集合的序号,由于list是有序的集合,通过该注解将会在address表中增加一个字段保存各个元素在集合中的序号。

@Column注解则指向我们集合元素所在的列,可以配置他们列名等。

总的来说,一旦hibernate发现实体类中有集合类型的属性需要映射,那么就会为集合属性单独映射出一张表,该表至少有两个字段,一个字段依赖于主表的id字段值,在新表中相同该字段值的记录共同组合成为实体类中的集合属性的值,一个字段保存具体的集合元素的值信息。而对于有序集合来说,还应该包含一个字段用于保存每个集合元素在集合中的序号,该序号字段和第一个外键依赖字段组合成新表的联合主键,唯一标识一条记录。

在hibernate的管理下,当有数据添加进userinfo表的时候,hibernate将拿到该实体类实例的集合属性的值,并连带该实例的id一起插入到新表中。当然,当我们想要获取一个userinfo实例的时候,hibernate也会为我们查询address表,并注入到userinfo实例的集合属性中,默认的注入模式是懒加载。

接着,我们看Set集合的映射情况。Set是一种无序并不重复的集合。具体的配置如下:

@Entity
@Table(name = "userInfo")
public class UserInfo implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    @ElementCollection(targetClass = String.class)
    @CollectionTable(name = "address",joinColumns = @JoinColumn(name = "user_id",nullable = false))
    @Column(name = "value",nullable = false)
    private Set address;
    //省略getter,setter方法
}

相比List,Set由于是无序的,那么自然是没有索引序列,所以无需配置@OrderColumn,但是它要求所有元素必须不可重复,那么通过制定nullable为false即可。
看看表的生成情况:

这里写图片描述

对于像set一样的无序集合,新表的主键有user_id和value列联合作为主键,可以保证唯一确定一条数据记录。

最后,我们看看一下Map的映射情况,先看代码:

@Entity
@Table(name = "userInfo")
public class UserInfo implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    @ElementCollection(targetClass = String.class)
    @MapKeyClass(Integer.class)
    @CollectionTable(name = "address",joinColumns = @JoinColumn(name = "user_id"))
    @Column(name = "value")
    private Map map;
    //省略getter,setter方法
}

具体的表生成情况:

这里写图片描述

对于map这种键值对集合,targetClass 用于指定value值的类型,而@MapKeyClass则用于指定key值的类型,其他的几乎没什么变化,对于map集合映射出来的表,user_id和map的key字段将联合组成此表的主键,唯一确定一条记录。

对于性能的要求,hibernate不推荐实体类属性使用数组类型,建议优先使用集合类型。

2、组件属性映射
所谓的组件类型就是指我们自定义的类类型,在某些情况下,实体类中包含自定类型也是很常见的,那么对于我们自定义的类型该如何来映射到数据表呢?Hibernate的映射策略很简单,对于组件中的每个属性都映射出一个列,也就是相当于把组件给拆解了。例如:

//首先定义一个组件类
@Embeddable
public class Disposition {
    private String mood;
    private String hobby;
    //省略getter,setter方法
}

我们定义了一个类,Disposition并使用@Embeddable注解修改该类。当Hibernate对整个类路径进行扫描的时候,就会注册该类为一个组件类型,那么当我们在实体类中引用该类型的时候,hibernate就能找到相应的组件类型。

@Entity
@Table(name = "userInfo")
public class UserInfo implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    
    private Disposition disposition;
    //省略getter,setter方法
}

最后生成的数据表结构如下:

这里写图片描述

组件类的每个属性都被映射到userinfo表中了。当我们通过实体类实例向数据表中插入数据的时候,hibernate会将组件类实例拆分出来的各个属性插入到对应的表字段。当我们通过数据表获取userinfo实例的时候,hibernate判断userinfo中有一个组件类属性,于是创建组件类实例并装载相应的数据表中的数值赋值给userinfo的组件类型属性。

3、集合属性为组件类型的表级映射
集合中的元素除了可以是基本类型,包装类型以外,还可以是组件类型,也就是复合类型。那么对于他们的映射却稍显不同,例如:

//定义一个复合类型
@Embeddable
public class Person {
    private String name;
    private int age;
    private String address;
    //省略getter,setter方法
}
@Entity
@Table(name = "userInfo")
public class UserInfo implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;

    @ElementCollection(targetClass = Person.class)
    @CollectionTable(name = "persons",joinColumns = @JoinColumn(name = "user_id"))
    @OrderColumn(name = "list_index")
    private List list;
    //省略getter,setter方法
}

显然,在实体类中的集合类型属性的映射,大体上是一样的。首先我们通过targetClass 属性指定集合中的元素类型,通过CollectionTable配置为集合生成的新表的基本信息,通过OrderColumn指定索引列。当然,这里我们不需要使用Column注解配置集合元素本身在数据表中的字段名,因为数据库中没有相对应的类型存储。Hibernate选择将集合中的复合类型拆分成多个字段,其他的和普通的集合属性映射并没有太大变化。

这里写图片描述

只不过对于普通的集合类型映射来说,图中红色框中内容仅仅是一个字段,而对于复合类型,由于数据库中并没有相对应的类型来存储,所以就需要拆分成基本的字段类型。

至此,使用注解方法来配置实体类的基本内容已经简单介绍完了,还有很多相对而言并不常用的基于Hibernate自身的注解并没有做介绍,待作者深入使用后再做相关补充,总结不到之处,望指出!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容