Hibernate 注解配置
近几年来,注解方式的配置因其简单易用的特点深受广大程序员的青睐,Hibernate也添加了对注解配置的支持。接下来我们就以论坛系统为例来讲解基于注解配置实体类和表的映射关系,以及实体和实体的关联关系。
核心技能部分
1.1 创建SessionFactory
基于xml配置的配置信息位于实体类映射文件中,如Category.hbm.xml;基于注解配置配置信息位于类源代码中,如Category.class。由于配置信息位置不同,导致SessionFactory的配置也产生了变化。请看示例3.1:
示例3.1
-
<!-- xml配置方式 -->
-
<hibernate-configuration>
-
<session-factory>
-
……
-
<mapping resource="com/hibernate/ch3/entity/Category.hbm.xml" />
-
……
-
</session-factory>
-
</hibernate-configuration>
-
-
<!-- 注解配置方式 -->
-
<hibernate-configuration>
-
<session-factory>
-
……
-
<mapping class="com.hibernate.ch3.entity.Category" />
-
……
-
</session-factory>
-
</hibernate-configuration>
如示例3.1所示,xml配置方式的mapping元素通过resource属性指定xml映射文件的位置,注解配置方式的mapping元素通过class属性指定实体类。
另外, Hibernate针对注解配置设计了AnnotationConfiguration类,该类扩展自Configuration类,用于加载配置文件,创建SessionFactory对象,请看示例3.2:
示例3.2
-
Configuration configuration = new AnnotationConfiguration();
-
configuration.configure("/hibernate.cfg.xml");
-
SessionFactory sessionFactory = configuration.buildSessionFactory();
1.1 映射实体
1.1.1 声明实体
@Entity用于将一个类声明为一个实体类,即持久化类。使用方法请看示例3.3:
示例3.3
@Entity
public class Category implements java.io.Serializable { …… }
@Entity唯一的一个配置选项叫做name,用于为实体类指定别名,以和其它同名不同包的实体类进行区别,请看示例3.4:
示例3.4
@Entity (name="版块分类")
public class Category implements java.io.Serializable { …… }
示例3.4将 Category 实体类的别名指定为“版块分类”,这样我们就可以根据此别名编写HQL语句执行查询,请看示例3.5:
示例3.5
Session se = HibernateSessionFactory.getSession();
List<Category> list = se.createQuery(" from 版块分类").list();
for (Category category : list) {
System.out.println(category.getName());
}
HibernateSessionFactory.closeSession();
执行示例3.5,控制台输出如下:
Hibernate:
select
category0_.id as id4_,
category0_.dateCreated as dateCrea2_4_,
category0_.deleted as deleted4_,
category0_.name as name4_,
category0_.version as version4_
from
forum.category category0_
文学
军事
财经
历史
1.1.1 实体类映射表
@Table 用于在实体类和表之间建立映射关系,它有以下配置选项:
l name,用于指定实体映射的数据库表名称
l schema,用于指定数据库表所在的用户模式,如oracle的scott用户模式,sqlserver的dbo用户模式
l catalog用于指定数据库的名字
请看@Table的应用示例3.6:
示例3.6
@Entity
@Table(name = "category", schema="scott",catalog = "forum")
public class Category implements java.io.Serializable { …… }
1.1.2 映射普通属性
@Column 注解用于声明实体类属性到数据库表的列的映射。该注解有如下配置选项:
l name 可选,列名(默认值是属性名)
l unique 可选,是否在该列上设置唯一约束(默认值false)
l nullable 可选,是否设置该列的值可以为空(默认值false)
l insertable 可选,该列是否作为生成的insert语句中的一个列(默认值true)
l updatable 可选,该列是否作为生成的update语句中的一个列(默认值true)
l columnDefinition 可选,为这个特定列覆盖sql ddl片段(这可能导致无法在不同数据库间移植)
l table 可选,定义对应的表(默认为主表)
l length 可选,列长度(默认值255)
l precision 可选,列十进制精度(decimal precision)(默认值0)
l scale 可选,如果列十进制数值范围(decimal scale)可用,在此设置(默认值0)
@Column 注解需要声明在指定属性的getter方法上面,请看示例3.7,将Category类的name属性(版块分类名称)映射到了“name”列,并声明该属性为非空、不可编辑、唯一的,限制长度为200:
示例3.7
@Column(name="name", updatable=false, nullable=false, unique=true,length=200)
public String getName() {
return this.name;
}
1.1.3 映射标识符属性
在第二章,我们用<id>元素在实体映射文件中映射一个标识符属性(主键),如示例3.8所示:
示例3.8
<id name="id" type="java.lang.Integer">
<column name="id" />
<generator class="native">
<param name="sequence">SEQ_ID</param>
</generator>
</id>
下面我们用注解来重新进行上述的配置。
我们可以用注解@Id来声明某属性为一个标识符属性,该注解无任何配置选项。
接着用@GeneratedValue注解声明主键的生成策略,该注解有如下配置选项:
l strategy 指定主键值生成策略(由JPA定义),通过GenerationTyped的常量提供:
n GenerationType.AUTO,默认的生成策略,生成器采用native,取决于底层数据库的能力,使用该生成器保证映射元数据可以移植到不同的数据库管理系统。
n GenerationType.TABLE,使用一个特定的数据库表格来保存主键
n GenerationType.IDENTITY,生成器采用identity,适用于DB2、MySql、MS SqlServer等直接支持主键自动增长的数据库系统,主键值由数据库自动生成。返回的标示符类型为long、short或int
n GenerationType.SEQUENCE,生成器采用sequence,适用于DB2、 ORACLE等通过序列对象提供有序数列来作为主键值的数据库。(这个生成策略要与generator一起使用)
l generator 指定生成主键使用的生成器,例如采用orcale时指定序列名称,。
最后用@Column注解映射属性到列,请看示例3.9,在Category类的id属性的getter方法之上使用上述3个注解映射标示符属性:
示例3.9
@Id
@SequenceGenerator(name="SEQ_ID",allocationSize=1, sequenceName="SEQ_ID")
@GeneratedValue(strategy = GenerationType.SEQUENCE,generator="SEQ_ID")
@Column(name = "id", unique = true, nullable = false)
public Integer getId() {
return this.id;
}
如果SEQ_ID不存在,Hibernate会创建该序列;allocationSize=1配置每次增加的数,默认值: 50;默认情况下,JPA 持续性提供程序使用的分配大小为 50。如果此分配大小与应用程序要求或数据库性能参数不匹配,请将 allocationSize 设置为所需的 int 值。initialValue,默认值: 0.默认情况下,JPA 持续性提供程序假设持续性提供程序将所有主键值的起始值设置为 0。如果这与现有数据模型不匹配,请将 initialValue 设置为所需的 int 值。如果是Hibernate创建的序列,即使指定initialValue=100,序列也不会从100开始;而是从1开始,因为Hibernate创建序列的时候指定的最小值是1。
sequenceName : JPA 持续性提供程序分配它自己创建的序列名。如果要使用事先存在或预定义的序列,请将 sequenceName 设置为所需的 String 名称。
name 必需 SequenceGenerator 的名称必须匹配其 startegy 设置为 SEQUENCE 的 GeneratedValue 的名称。
如果是采用的MySql、MS SqlServer等直接支持主键自动增长的数据库系统,并在建表时设置主键为自动增长的列,则@GeneratedValue的strategy选项指定为GenerationType.AUTO即可,无需指定generator选项。因为GeneratedValue默认采用GenerationType.AUTO策略,故strategy选项也可以省略,示例3.9简写为:
@Id
@GeneratedValue
@Column(name = "id", unique = true, nullable = false)
public Integer getId() {
return this.id;
}
到这里我们就能够利用注解完整实现一个实体类的映射配置了,Category类完整代码如示例3.10所示:
示例3.10
@Entity
@Table(name = "category", schema="scott",catalog = "forum")
public class Category implements java.io.Serializable {
……省略构造函数
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "SEQ_ID")
@Column(name = "id", unique = true, nullable = false)
public Integer getId() {
return this.id;
}
@Column(name = "dateCreated", length = 19)
public Timestamp getDateCreated() {
return this.dateCreated;
}
@Column(name = "deleted", nullable = false)
public Boolean getDeleted() {
return this.deleted;
}
@Column(name = "version", nullable = false)
public Integer getVersion() {
return this.version;
}
@Column(name = "name",updatable = false, nullable = false, length=200)
public String getName() {
return this.name;
}
……省略属性的setter方法
}
1.1 实体关联关系映射
Hibernate提供了以下注解用于配置实体关联关系:
l @OneToOne,用于配置一对一关系
l @OneToMany,用于配置一对多关系
l @ManyToOne,用于配置多对一关系
l @ManyToMany,用于配置多对多关系
这四个注解拥有如下共有配置选项:
l targetEntity,指定关联实体的类型
l cascade,指级联级别,有以下几种选项:
n CascadeType.PERSIST(级联保存)
n CascadeType.REMOVE(级联删除)
n CascadeType.REFRESH(级联刷新)
n CascadeType.MERGE(级联更新)
n CascadeType.ALL(全部四项)
l fetch,指定数据抓取策略,有以下几种选项:
n FetchType.LAZY,表示延迟加载,
n FetchType.EAGER表示立即加载
单向一对多、单向多对一和双向一对多,我们将采用论坛系统版块分类和版块之间的关系为例来讲解。从图3.1.1 category(版块分类表)和board(版块表)E-R关系图我们可以看出board表通过category_id列和category表建立了外键关系,由此形成了版块分类到版块一对多的关系(即一个版块分类可以拥有多个版块)和版块到版块分类的多对一关系(多个版块可以同属一个版块分类,且一个版块只能属于某一个版块分类)。
图3.1.1 category(版块分类表)和board(版块表)E-R关系图
1.1.1 单向一对多关联
一对多关系需要使用@OneToMany来声明,该注解除了共有属性外还拥有一个叫做mappedBy的配置选项,在双向一对多关系中使用,作用和xml映射文件中<set/>标签的inverse属性作用相同,在一的一端中设置mappedBy,说明多端反向控制一端。
另外,我们还需要用到@JoinColumn注解,它有一个name属性,用于指定数据库表中的外键列名称。
接下来我们来配置从Category类到Board类的单向一对多关系,请看示例3.11:
示例3.11
@Entity
@Table(name = "category", schema="scott",catalog = "forum")
public class Category implements java.io.Serializable {
……
private Set<Board> boards = new HashSet<Board>(0);
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "category_id") // board表列,category表外键
public Set<Board> getBoards() {
return this.boards;
}
……
}
@Entity
@Table(name = "board",schema="scott",catalog = "forum")
public class Board implements java.io.Serializable {
……
//对应于board表category_id列,作为外键参考category表的主键id列值
private int categoryId;
@Column(name = "category_id")
public int getCategoryId() {
return categoryId;
}
……
}
示例3.12通过调用Category类的getBoards()方法执行关联属性操作来测试单向一对多关联关系是否成立:
示例3.12
Session se = HibernateSessionFactory.getSession()
Category category = (Category)se.get(Category.class, 1);
System.out.println(category.getName());
Set<Board> list= category.getBoards();
for (Board board : list) {
System.out.println(board.getName());
}
HibernateSessionFactory.closeSession();
执行示例3.12在控制台输出内容如下,说明单向一对多关系配置正确:
Hibernate:
Select ……
from
forum.category category0_
where
category0_.id=?
文学
Hibernate:
Select ……
from
forum.board boards0_
where
boards0_.category_id=?
莲蓬鬼话
奇幻文学
示例3.11 在配置一对多关系时并没有使用targetEntity属性来指定关联实体的类型,那么Hibernate怎么知道关联关系实体类型是Board呢?因为我们在声明getBoards()方法的返回的类型时为Set指定了泛型信息,即Set<Board>。Hibernate通过反射获取返回类型的泛型信息便知关联关系类型了。
如果我们将getBoards()方法的返回的类型从Set<Board>修改为Set,执行示例3.12将会抛出如下异常信息:
org.hibernate.AnnotationException: Collection has neither generic type or OneToMany.targetEntity() defined: com.hibernate.ch3.entity.Category.boards
从异常信息可以看出我们有两个选择,一是为集合指定泛型信息,另一个是通过@OneToMany的targetEntity属性指定关联实体类型,我们需要二选其一。
1.1.2 单向多对一关联
@ManyToOne注解用来配置多对一关系,该注解除了共有属性外还拥有一个叫做optional的配置选项,设置为true时,即使外键为空仍可以向表中添加数据。
从板块(Board)到版块分类(Category)是多对一关系,下面我们就以此为例来配置单向多对一关系。请看示例3.13,我们将Category类的boards属性和其getter/settter方法去掉,将Board类的categoryId属性和getter/settter方法换成Category类型的属性category,并在其getter方法上进行多对一配置:
示例3.13
@Entity
@Table(name = "category", schema="scott",catalog = "forum")
public class Category implements java.io.Serializable {
……
}
@Entity
@Table(name = "board",schema="scott",catalog = "forum")
public class Board implements java.io.Serializable {
……
private Category category;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_id")
public Category getCategory() {
return this.category;
}……
}
示例3.14通过调用Board类的getCategory ()方法执行关联属性操作来测试单向多对一关联关系是否成立:
示例3.14
Board board = (Board)HibernateSessionFactory.getSession().get(Board.class, 1);
System.out.println(board.getName());
System.out.println(board.getCategory().getName());
HibernateSessionFactory.closeSession();
执行示例3.14,控制台输出如下,证明我么成功配置了单向多对一关系:
Hibernate:
Select ……
from
forum.board board0_
where
board0_.id=?
莲蓬鬼话
Hibernate:
Select ……
from
forum.category category0_
where
category0_.id=?
文学
1.1.3 双向一对多关联
双向关联关系即同时在一方配置一对过关系,在多方配置多对一关系即可。请看示例3.15:
示例3.15
@Entity
@Table(name = "category", schema="scott",catalog = "forum")
public class Category implements java.io.Serializable {
……
private Set<Board> boards = new HashSet<Board>(0);
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "category_id") // board表列,category表外键
public Set<Board> getBoards() {
return this.boards;
}
……
}
@Entity
@Table(name = "board",schema="scott",catalog = "forum")
public class Board implements java.io.Serializable {
……
private Category category;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_id")
public Category getCategory() {
return this.category;
}……
}
由于是双向一对多关系,Category的一对多声明也可以不必通过@JoinColumn指定外键列,而是改为配置@OneToMany的mappedBy 为Board的属性 "category",如示例3.16所示:
示例3.16
@Entity
@Table(name = "category", schema="scott",catalog = "forum")
public class Category implements java.io.Serializable {
……
private Set<Board> boards = new HashSet<Board>(0);
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
public Set<Board> getBoards() {
return this.boards;
}
……
}
1.1.4 多对多关联
在论坛系统中,版块和版主属于多对多关系,请看图3.1.2,版块表(board)、用户表(person,版主即某用户)和版块用户关系表(board_administrator)的E-R关系图:
图3.1.2,表board、person和board_administrator E-R关系图
在数据库中,多对多关系必须借助中间表来完成,版块用户关系表(board_administrator)就是这样一个角色。它仅拥有board_id和person_id两个列,且分别是版块表和用户表的外键。我们在设计实体类时只需设计Board类来映射board表,Person类来映射person表,无需设计类来映射board_administrator 表。
在实体类中配置多对多关联关系需要使用@ManyToMany注解,该注解的配置选项和 @OneToMany一模一样。同时通过 @JoinTable 注解描述中间关联表和通过中间表关联到两方的外键。
下面我们来配置Board类和Person类之间的双向多对多关联关系,请看示例3.17:
示例3.17
@Entity
@Table(name = "board",schema="scott",catalog = "forum")
public class Board implements java.io.Serializable {
……
private Set<Person> persons = new HashSet<Person>(0);
@ManyToMany(cascade=CascadeType.ALL,fetch=FetchType.LAZY,mappedBy="boards")
public Set<Person> getPersons() {
return this.persons;
}
……
}
@Entity
@Table(name = "person", schema="scott",catalog = "forum")
public class Person implements java.io.Serializable {
……
private Set<Board> boards = new HashSet<Board>(0);
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinTable(name = "board_administrator", schema="scott",catalog = "forum",
joinColumns = { @JoinColumn(name = "person_id") },
inverseJoinColumns = { @JoinColumn(name = "board_id") })
public Set<Board> getBoards() {
return this.boards;
}
……
}
我们通过示例3.18来测试一下Board类和Person类之间的双向多对多关联关系:
示例3.18
Board board = (Board)HibernateSessionFactory.getSession().get(Board.class, 1);
Set<Person> admins = board.getPersons();
System.out.println("版块:"+board.getName()+",该版块拥有版主位数"+admins.size());
for (Person admin : admins) {
System.out.println("版主名称:"+admin.getName());
}
Person person = (Person)HibernateSessionFactory.getSession().get(Person.class, 1);
Set<Board> boards = person.getBoards();
System.out.println("用户:"+person.getName()+",他是以下版块的版主");
for (Board b : boards) {
System.out.println(b.getName());
}
HibernateSessionFactory.closeSession();
执行示例3.18,在控制台输出如下:
Hibernate:
Select
……
from
forum.board board0_
where
board0_.id=?
Hibernate:
select
……
from
forum.board_administrator persons0_
left outer join
forum.person person1_
on persons0_.person_id=person1_.id
where
persons0_.board_id=?
版块:莲蓬鬼话,该版块拥有版主位数:3
版主名称:lee
版主名称:茶农
版主名称:三叔
用户:lee,他是以下版块的版主
Hibernate:
select
……
from
forum.board_administrator boards0_
left outer join
forum.board board1_
on boards0_.board_id=board1_.id
where
boards0_.person_id=?
莲蓬鬼话
巩固练习
选择题
1. Hibernate提供了哪些注解用于配置实体关联关系()。
A. @OneToOne,用于配置一对一关系
B. @OneToMany,用于配置一对多关系
C. @ManyToOne,用于配置多对一关系
D. @ManyToMany,用于配置多对多关系
2. 下列哪些配置选项是@OneToOne、@OneToMany、@ManyToOne、@ManyToMany共有属性()
A. targetEntity
B. cascade
C. fetch
D. mappedBy
3. cascade,指级联级别,以下那些选项是cascade正确可用的选项值?():
A. CascadeType.SAVE(级联保存)
B. CascadeType.REMOVE(级联删除)
C. CascadeType.UPDATE(级联更新)
D. CascadeType.ALL(全部四项)
4. fetch,指定数据抓取策略,以下那两个选项是fetch正确可用的选项值?():
A. FetchType.LAZY,表示延迟加载,
B. FetchType.EAGER,表示立即加载
C. FetchType.JOIN,表示关联加载
D. FetchType.BATCH,表示批量加载
5. @GeneratedValue注解用于声明主键的生成策略,下面哪些配置选项属于该注解?()
A. GenerationType.AUTO,默认的生成策略,生成器采用native,取决于底层数据库的能力,使用该生成器保证映射元数据可以移植到不同的数据库管理系统。
B. GenerationType.IDENTITY,生成器采用identity,适用于DB2、MySql、MS SqlServer等直接支持主键自动增长的数据库系统,主键值由数据库自动生成。返回的标示符类型为long、short或int。
C. GenerationType.SEQUENCE,生成器采用sequence,适用于DB2、 ORACLE等通过序列对象提供有序数列来作为主键值的数据库。
D. GenerationType.INCREMENT,生成器采用INCREMENT,适用于MySql,主键值由数据库自动生成。返回的标示符类型为long、short或int。
上机练习
角色和权限属于多对多关联关系,一个角色可以包含多个权限,一个权限可以属于多个角色。请参考设置版块版主实现新角色的创建。
文章来源: aaaedu.blog.csdn.net,作者:tea_year,版权归原作者所有,如需转载,请联系作者。
原文链接:aaaedu.blog.csdn.net/article/details/79831137
- 点赞
- 收藏
- 关注作者
评论(0)