例子:
@Entity(name = "T_TOPIC") ①
public class Topic implements Serializable ...{
@Id ② -1
@GeneratedValue(strategy = GenerationType.TABLE) ② -2
@Column(name = "TOPIC_ID") ② -3
private int topicId;
@Column(name = "TOPIC_TITLE", length = 100) ③
private String topicTitle;
@Column(name = "TOPIC_TIME")
@Temporal(TemporalType.DATE) ④
private Date topicTime;
@Column(name = "TOPIC_VIEWS")
private int topicViews;
...
}
① Entity 标明该类 (Topic) 为一个实体类,它对应数据库中的表表名是 T_TOPIC ,这里也可以写成:
② -1 Id 标明该属性对应数据表中的主键
② -2 GeneratedValue 通过 strategy 属性指明主键生成策略,默认情况下, JPA 自动选择一个最适合底层数据库的主键生成策略。
在 javax.persistence.GenerationType 中定义了以下几种可供选择的策略:
③ Column 的一个属性 length 指明的是该属性的允许的长度。 ( 个人认为设定该属性只是对于程序中操作该属性时增加了一验证过程,
对数据库中该列原来的设置并没有影响,但是 length 属性指定的值必须不能大于数据库创建表时给该列限制的最大长度否则会出错 )
④ Temporal(TemporalType.DATE) :如果属性是时间类型,因为数据表对时间类型有更严格的划分,所以必须指定具体时间类型。
在 javax.persistence.TemporalType 枚举中定义了 3 种时间类型:
对继承关系进行注解,必须在 父类 中声明继承实体的映射策略。
例子:
@Entity(name = "T_TOPIC")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) ①
@DiscriminatorColumn(name = "TOPIC_TYPE", discriminatorType =DiscriminatorType.INTEGER, length = 1) ②
@DiscriminatorValue(value="1") ③
public class Topic implements Serializable ...{ … }
① Inheritance 通过 strategy 属性指明实体的继承策略。
在 javax.persistence.InheritanceType 定义了 3 种映射策略:
② DiscriminatorColumn 如果继承策略采用第一种继承策略,则需要指明区分父子类的字段, DiscriminatorColumn 就是用来指明区分字段的注解。
③ DiscriminatorValue 同样的采用第一种继承策略通过字段区分父子类,则用这个注解给该实体的区分字段赋值在这里赋的值为 ”1”.
例子:
@Entity
@DiscriminatorValue(value="2") ①
public class PollTopic extends Topic ...{ ②继承于 Topic 实体
private boolean multiple; ③
@Column(name = "MAX_CHOICES")
private int maxChoices;
@OneToMany(mappedBy="pollTopic",cascade=CascadeType.ALL) ④
private Set options = new HashSet();
// 省略 get/setter 方法
}
① 通过 @DiscriminatorValue 将区分字段 TOPIC_TYPE 的值为 2 。由于 PollTopic 实体继承于 Topic 实体,其它的元数据信息直接从 Topic 获得。
④ OneToMany 指定了一个一对多的关联关系, mappedBy 属性指定 “Many” 方类引用 “One” 方类 的属性名;
cascade 属性指明了级联方式(如果这里不指定为 CascadeType.ALL 的话,那么有关联关系的两个对象在做保存和删除操作时要分别来进行)
建议 :尽可能使用 cascade=CascadeType.ALL 来减少持久化操作的复杂性和代码量
注意 : JPA 规范规定任何属性都默认映射到表中,所以虽然我们没有给③处的 multiple 属性提供注解信息,但 JPA 将按照默认的规则对该字段进行映射:
字段名和属性名相同,类型相同。如果我们不希望将某个属性持久化到数据表中,则可以通过 @Transient 注解显式指定:
@Transient
private boolean tempProp1;
@Entity(name="T_POLL_OPTION")
Public class PollOption implements Serializable ...{
@Id
@GeneratedValue(strategy = GenerationType.TABLE)
@Column(name = "OPTION_ID")
private int optionId;
@Column(name = "OPTION_ITEM")
private String optionItem;
@ManyToOne ①
@JoinColumn(name="TOPIC_ID", nullable=false) ②
private PollTopic pollTopic;
}
① ManyToOne 描述了多对一的关联关系,他是对该类引用的 ”One” 类 (PollTopic) 的属性( pollTopic )进行注解的。
② JoinColumn 指定关联 ”One”(PollTopic) 实体所对应表的 “ 外键 ” 。
在 JPA 中 Lob 类型类型的持久化很简单,仅需要通过特殊的 Lob 注解就可以达到目的。
例子:
@Lob ① -1
@Basic(fetch = FetchType.EAGER) ① -2
@Column(name = "POST_TEXT", columnDefinition = "LONGTEXT NOT NULL") ① -3
private String postText;
@Lob
@Basic(fetch = FetchType. LAZY) ② -2
@Column(name = "POST_ATTACH", columnDefinition = "BLOB") ② -3
private byte[] postAttach;
解释:
① -1 JPA 通过 @Lob 将属性标注为 Lob 类型 ;
① -2 通过 @Basic 指定 Lob 类型数据的获取策略, FetchType.EAGER 表示非延迟 加载,而 FetchType. LAZY 表示延迟加载 ;
① -3 通过 @Column 的 columnDefinition 属性指定数据表对应的 Lob 字段类型。
@Table 是类级别的注解,用于声明实体映射到数据库中的具体的表。
参数 | 类型 | 描述 |
---|---|---|
name | String | 表的名称,默认为实体名称(参考 @Entity 注解的 name 参数说明),因此如果实体名称与映射的表名称一致时,@Table 注解常常可以省略。 |
catalog | String | 默认为数据库系统缺省的 catalog。 |
schema | String | 默认为用户缺省的 schema。 |
uniqueConstraints | UniqueConstraint[] | 表的唯一约束(除了由 @Column 和 @JoinColumn 注解指定的约束以及主键的约束之外的约束),通过使用 @UniqueConstraint 注解来声明,仅在允许自动更新数据库表结构的场景中起到作用,默认没有其他额外的约束条件。 |
indexes | Index[] | 表的索引,通过使用 @Index 注解来声明,仅在允许自动更新数据库表结构的场景中起到作用,默认没有其他额外的索引。 |
catalog 和 schema 主要用来解决数据库系统命名冲突的问题。一个数据库系统可以包含多个 catalog,每个 catalog 可以包含多个 schema,而每个 schema 又可以包含多个数据库对象(表、视图等)。不同的数据库系统对 catalog 和 schema 的支持方式有所不同,常见的数据库系统:
库系统 | catalog | schema |
---|---|---|
MySQL | 不支持 | 数据库名 |
Oracle | 不支持 | 用户 ID |
SQLServer | 数据库名 | 对象属主名 |
DB2 | 指定数据库对象时,Catalog 可以省略 | Catalog 属主名 |
Sybase | 数据库名 | 数据库属主名 |
@Entity(name = "person")
@Table(uniqueConstraints = {
@UniqueConstraint(name = "unique_name", columnNames = "name"),
@UniqueConstraint(name = "unique_mail", columnNames = "mail")
})
public class Person implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String mail;
// getters and setters
}
对应的DDL(MYSQL)
CREATE TABLE `person` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`mail` varchar(255) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_name` (`name`),
UNIQUE KEY `unique_mail` (`mail`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
用于声明表的唯一约束,这些仅在允许自动更新数据库表结构的场景中起到作用。
参数 | 类型 | 描述 |
---|---|---|
name | String | 约束名称,如果不指定,默认使用数据库提供商所生成的值。 |
columnNames | String[] | 约束的列名称 |
多列联合唯一约束,name 列和 mail 列不能同时出现相同的值:
@Entity(name = "person")
@Table(uniqueConstraints = @UniqueConstraint(name = "unique_name_mail", columnNames = {"name", "mail"}))
public class Person implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String mail;
// getters and setters
}
产生的 DDL 语句(MySQL):
CREATE TABLE `person` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`mail` varchar(255) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_name_mail` (`name`,`mail`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
用于声明表的索引,这些仅在允许自动更新数据库表结构的场景中起到作用。另外,不需要为表的主键指定索引,因为主键索引会自动被创建。
参数 | 类型 | 描述 |
---|---|---|
name | String | 索引名称,如果不指定,默认使用数据库提供商所生成的值。 |
columnList | String | 要包含在索引中的列名称。 |
unique | boolean | 索引是否唯一,默认为 false。 |
为 name 列和 mail 列分别建立索引:
@Entity(name = "person")
@Table(indexes = {
@Index(name = "index_name", columnList = "name"),
@Index(name = "index_mail", columnList = "mail")
})
public class Person implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String mail;
// getters and setters
}
产生的 DDL 语句(MySQL):
CREATE TABLE `person` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`mail` varchar(255) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_name` (`name`),
KEY `index_mail` (`mail`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
为 name 列和 mail 列建立多列索引:
@Entity(name = "person")
@Table(indexes = @Index(name = "index_name_mail", columnList = "name,mail"))
public class Person implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String mail;
// getters and setters
}
产生的 DDL 语句(MySQL):
CREATE TABLE `person` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`mail` varchar(255) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_name_mail` (`name`,`mail`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
当 SQL 查询条件中包含 name 和 mail 时:
SELECT * FROM PERSON WHERE NAME = 'U79028' AND MAIL = '[email protected]'
如果为 name 和 mail 列分别建立索引,当执行查询时,MySQL 只能使用一个索引。如果发现有多个单列索引可用,MySQL 会试图选择一个限制最严格的索引来检索,而其他索引则利用不上。
使用分析器分析查询 SQL:
EXPLAIN SELECT * FROM PERSON WHERE NAME = 'U79028' AND MAIL = '[email protected]'
结果如下:
+----+-------------+--------+------+-----------------------+------------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+-----------------------+------------+---------+-------+------+-------------+
| 1 | SIMPLE | PERSON | ref | index_name,index_mail | index_mail | 768 | const | 1 | Using where |
+----+-------------+--------+------+-----------------------+------------+---------+-------+------+-------------+
MySQL 优化器如果发现可以使用多个索引查找后的交集/并集定位数据,那么 MySQL 优化器就会尝试使用 index merge(索引合并)的方式来查询:
EXPLAIN SELECT * FROM PERSON WHERE NAME = 'U79028' AND MAIL = '[email protected]'
结果如下:
+----+-------------+--------+-------------+-----------------------+-----------------------+---------+------+------+------------------------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+-------------+-----------------------+-----------------------+---------+------+------+------------------------------------------------------------------+
| 1 | SIMPLE | PERSON | index_merge | index_name,index_mail | index_name,index_mail | 768,768 | NULL | 1 | Using intersect(index_name,index_mail); Using where; Using index |
+----+-------------+--------+-------------+-----------------------+-----------------------+---------+------+------+------------------------------------------------------------------+
对于多列索引,由于索引文件以B树的数据结构存储,MySQL 能够快速转到合适的 name,然后再转到合适的 mail。在建立多列索引时,应该将严格的索引放在前面,这样筛选数据的时候力度会更大,效率更高。
使用分析器分析查询 SQL:
EXPLAIN SELECT * FROM PERSON WHERE NAME = 'U79028' AND MAIL = '[email protected]'
结果如下:
+----+-------------+--------+------+-----------------+-----------------+---------+-------------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+-----------------+-----------------+---------+-------------+------+--------------------------+
| 1 | SIMPLE | PERSON | ref | index_name_mail | index_name_mail | 1536 | const,const | 2 | Using where; Using index |
+----+-------------+--------+------+-----------------+-----------------+---------+-------------+------+--------------------------+