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自身的注解并没有做介绍,待作者深入使用后再做相关补充,总结不到之处,望指出!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Golang语言社区

Golang中巧用defer进行错误处理

问题引入 毫无疑问,错误处理是程序的重要组成部分,有效且优雅的处理错误是大多数程序员的追求。很多程序员都有C/C++的编程背景,Golang的程序员也不例外,他...

3707
来自专栏分布式系统和大数据处理

Go语言Tips

转型到Go以后,因为语言的不熟悉,以往很常见的一些操作有时候也需要去Google一下。这里将一些结果记录下来,方便日后查阅。

1062
来自专栏喔家ArchiSelf

全栈必备 Java基础

那一年,从北邮毕业,同一年,在大洋的彼岸诞生了一门对软件业将产生重大影响的编程语言,它就是——Java。1998年的时候,开始学习Java1.2,并在Java ...

684
来自专栏大内老A

让“链式调用(方法链)”更加自然一点

不论是JavaScript还是C#程序,我们已经习惯了采用如下所示的“链式调用”的方式进行编程,这样确实会使我们的程序变得很精练。 1: new Foo(...

1839
来自专栏别先生

一脸懵逼学习oracle

oracle的默认用户:system,sys,scott; 1:查看登录的用户名:show user; 2:查看数据字典:dba_users; 3:创建新...

1607
来自专栏liulun

学习WPF——WPF布局——了解布局容器

WPF布局工作内部原理 WPF渲染布局时主要执行了两个工作:测量和排列 测量阶段,容器遍历所有子元素,并询问子元素所期望的尺寸 排列阶段,容器在合适的位置...

1735
来自专栏微信公众号:Java团长

《深入理解Java虚拟机》笔记

也就是说,我们完全可以做一个工具,从一个文件中读入指令,然后将这些指令运行起来。上面代码中“编好的机器指令”当然指的是能在CPU上运行的,如果这里我还实现了一个...

461
来自专栏JMCui

MongoDB 日期类型查询

一、前言 MongoDB 里面的日期类型是没有时区概念的,默认存储的是 ISODate("2018-04-02T13:19:16.418Z") 这种格式的零时区...

4198
来自专栏逸鹏说道

C# 温故而知新:Stream篇(—)

目录: 什么是Stream? 什么是字节序列? Stream的构造函数 Stream的重要属性及方法 Stream的示例 Stream异步读写 Stream ...

3339
来自专栏CRPER折腾记

Angular 2 + 折腾记 :(5) 动手实现一个自定义管道

一般是用于Mustache语法的双向数据内,eg: {{item | json}}

722

扫码关注云+社区