基本映射
一、 @Table
@javax.persistence.Table 注解可以改变实体类默认映射的表名
把 book 实体映射给 T_BOOK 表
@Entity
@Table(name = "t_book")
public class Book {
@Id
private Long id;
private String title;
private Float price;
private String description;
private String isbn;
private Integer nbOfPage;
private Boolean illustrations;
public Book() {
}
// Getters, setters
}
注意,根据数据库的不同,实体类默认映射到数据库的表名,有可能全大写,有可能全小写。
二、 @SecondaryTable
我们可以用该注解定义实体关联的一个或多个次表,用 @Column(table = "XXX") 指定属性指向的次表,看下面的例子:
@Entity
@SecondaryTables({ @SecondaryTable(name = "city"),
@SecondaryTable(name = "country") })
public class Address {
@Id
private Long id;
private String street1;
private String street2;
@Column(table = "city")
private String city;
@Column(table = "city")
private String state;
@Column(table = "city")
private String zipcode;
@Column(table = "country")
private String country;
// Constructors, getters, setters
}
该实体映射图:
每个表都有相同的主键。
注意:当使用次表时,你应该考虑性能问题。每次你访问这样的一个实体,持久化实现类访问多个表,用 join 的方式将他们关联。当你有大数据对象 (BLOBs), 用次表将大数据对象隔离是很好的。
三、主键
@javax.persistence.Id 注释属性可以是以下类型:
1. 基本 Java 类型: byte,int,short,long,char 。
2. 包装的基本 Java 类型: Byte,Integer,Short,Long,Character 。
3. 基本 Java 类型或包装的基本 Java 类型数组 :int[],Integer[] 等等。
4. 字符串,数字类型和日期 :java.lang.String,java.math.BigInteger,java.util.Date,java.sql.Date
主键值可以手动赋值,也可以通过 @GeneratedValue 注解自动赋值。该注解有 4 个值:
-
SEQUENCE 和 IDENTITY 是根据数据库的 sequence 或 identity 自增主键值
-
TABLE 是用数据库表来储存主键 ( 主键名和主键值 )
-
AUTO 是默认值,根据特定数据库选择相应的主键生成策略
四、复合主键
在 JPA 里我们有 2 种方式定义主键: @EmbeddedId 和 @IdClass
通常将复合主键相关属性,单独抽取出来,建立一个独立的类,这个类就是主键类,要求:
1. 复合主键必须重写 equals 和 hashcode 方法 (JPA 查找缓存的持久化对象 )
2. 必须实现 Serializable 接口
@EmbeddedId
查看下例:
@Embeddable
public class NewsId implements Serializable{
private String title;
private String language;
// Constructors, getters, setters, equals, and hashcode
}
@Entity
public class News {
@EmbeddedId
private NewsId id;
private String content;
// Constructors, getters, setters
}
看看我们怎么用复合主键查询:
NewsId pk = new NewsId("Richard Wright has died", "EN")
News news = em.find(News.class, pk);
@IdClass
以这种方式的复合主键类不需要任何注解:
public class NewsId implements Serializable{
private String title;
private String language;
// Constructors, getters, setters, equals, and hashcode
}
@Entity
@IdClass(NewsId.class)
public class News {
@Id
private String title;
@Id
private String language;
private String content;
// Constructors, getters, setters, equals, and hashcode
}
@EmbeddedId 和 @IdClass 这两种方式都被映射为同一个表结构
CREATE TABLE `news` (
`language` varchar(255) NOT NULL,
`title` varchar(255) NOT NULL,
`content` varchar(255) DEFAULT NULL,
PRIMARY KEY (`language`,`title`)
)
明显的不同是 JPQL 查询:
用 @IDClass 时:
select n.title from News n
用 @EmbeddedID 时:
select n.newsId.title from News n
五、属性
实体属性可以是以下类型:
-
Java 基本数据类型 (int,double,float 等 ) 及其包装类 (Integer,Double,Float 等 )
-
字节和字符数组 (byte[],Byte[],char[],Character[])
-
字符串,大数据类型,时间类型 (java.lang.String,java.math.BigInteger,java.math.BigDecimal,java.util.Date,java.util.Calendar,java.sql.Date,java.sql.Time,java.sql.Timestamp)
-
枚举类和用户自定义实现 Serialiable 接口类型
-
基础集合类和嵌入类型
六、 @Basic
可选注解 @javax.persistence.Basic 是映射数据库列的最基本类型。
@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface Basic {
FetchType fetch() default EAGER;
boolean optional() default true;
}
这个注解有 2 个参数: optional 和 fetch.
optional 元素指定属性的值是否为空 ( 忽略基本类型,因为基本类型是非空类型 )
fetch 元素有 2 个值: LAZY 或 EAGER 。指定属性数据是否是懒加载或立即加载。
比如我们有一个 Track( 音轨 ) 实体,有一个 WAV 属性 (WAV 文件是一个 BLOB ,可能有几兆 ) ,当我们访问 Track 实体时,我们不想立即加载 WAV 文件。
@Entity
public class Track {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
private float duration;
@Basic(fetch = FetchType.LAZY)
@Lob
private byte[] wav;
private String description;
// Constructors, getters, setters
}
七、 @Column
@Column 注解元素
@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface Column {
String name() default "";
/**
* (Optional) Whether the column is a unique key. This is a
* shortcut for the <code>UniqueConstraint</code> annotation at the table
* level and is useful for when the unique key constraint
* corresponds to only a single column. This constraint applies
* in addition to any constraint entailed by primary key mapping and
* to constraints specified at the table level.
*/
boolean unique() default false;
/**
* (Optional) Whether the database column is nullable.
*/
boolean nullable() default true;
/**
* (Optional) Whether the column is included in SQL INSERT
* statements generated by the persistence provider.
*/
boolean insertable() default true;
/**
* (Optional) Whether the column is included in SQL UPDATE
* statements generated by the persistence provider.
*/
boolean updatable() default true;
/**
* (Optional) The SQL fragment that is used when
* generating the DDL for the column.
* <p> Defaults to the generated SQL to create a
* column of the inferred type.
*/
String columnDefinition() default "";
/**
* (Optional) The name of the table that contains the column.
* If absent the column is assumed to be in the primary table.
*/
String table() default "";
/**
* (Optional) The column length. (Applies only if a
* string-valued column is used.)
*/
int length() default 255;
/**
* (Optional) The precision for a decimal (exact numeric)
* column. (Applies only if a decimal column is used.)
* Value must be set by developer if used when generating
* the DDL for the column.
*/
int precision() default 0;
/**
* (Optional) The scale for a decimal (exact numeric) column.
* (Applies only if a decimal column is used.)
*/
int scale() default 0;
}
你可以通过该注解修改列名,列字段大小,是否为空, unique( 唯一 ) ,允许它的值被更新、插入。
我们重新定义原来的 Book 实体
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "book_title", nullable = false, updatable = false)
private String title;
private Float price;
@Column(length = 2000)
private String description;
private String isbn;
@Column(name = "nb_of_page", nullable = false)
private Integer nbOfPage;
private Boolean illustrations;
// Constructors, getters, setters
}
BOOK 表 DDL :
CREATE TABLE `book` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`description` longtext,
`illustrations` bit(1) DEFAULT NULL,
`isbn` varchar(255) DEFAULT NULL,
`nb_of_page` int(11) NOT NULL,
`price` float DEFAULT NULL,
`book_title` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
)
updatable 和 insertable 默认值是true ,当他们的值是 false 时,产生的 SQL 语句将不包含相应的列。
八、 @Temporal
@javax.persistence.Temporal 注解用来表示时间日期,可以是 :DATE,TIME,TIMESTAMP.
看下例:
@Entity
public class Customer {
@Id
@GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String email;
private String phoneNumber;
@Temporal(TemporalType.DATE)
private Date dateOfBirth;
@Temporal(TemporalType.TIMESTAMP)
private Date creationDate;
// Constructors, getters, setters
}
九、 @Transient
JPA 中,注解 @Entity 的类,所有的属性都会被自动映射到相应的表中。如果你不需映射类中的某个属性,可以使用 @javax.persistence.Transient 注解。
@Entity
public class Customer {
@Id
@GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String email;
private String phoneNumber;
@Temporal(TemporalType.DATE)
private Date dateOfBirth;
@Transient
private Integer age;
@Temporal(TemporalType.TIMESTAMP)
private Date creationDate;
// Constructors, getters, setters
}
age 属性映射被忽略
十、 @Enumerated
我们看一下 CreditCardType 枚举类
public enum CreditCardType {
VISA,
MASTER_CARD,
AMERICAN_EXPRESS
}
枚举类的值是常量,并且以声明的顺序赋予 int 值。编译时, VISA 赋予 0 , MASTER_CARD 赋予 1 , AMERICAN_EXPRESS 赋予 2. 持久化实现默认将枚举类型映射为 Integer 。
@Entity
@Table(name = "credit_card")
public class CreditCard {
@Id
private String number;
private String expiryDate;
private Integer controlNumber;
private CreditCardType creditCardType;
// Constructors, getters, setters
}
如果从中加入新的枚举常量,保存在数据库中的一些值将不再匹配枚举。一个好的办法是保存字符值,而不是顺序值。通过 @Enumerated(EnumType.STRING) ,顺序值 (ORDINAL 是默认值 ) :
@Entity
@Table(name = "credit_card")
public class CreditCard {
@Id
private String number;
private String expiryDate;
private Integer controlNumber;
@Enumerated(EnumType.STRING)
private CreditCardType creditCardType;
// Constructors, getters, setters
}
十一、访问类型
到现在为止,我们标注类 (@Entity,@Table) 和属性 (@Basic,@Column,@Temporal 等 ) ,我们将注解应用在字段上 (field access) 也可以注解在相应的属性上 (property access:getter method). 例:我们将注解 @Id 标注在 id 字段上,同样也可以标注在 getId() 的方法上。这很大程度上是个人偏好。我趋向于注解在属性上 ( 注解 getters) ,这样可以很容易阅读实体类拥有的字段。在这个教程里,为了可读性,我们把注解标注在字段上。
当我们把注解标注在字段上,实体类的访问类型就为字段类型,持久化实现将映射所有的字段 ( 没有标注 @Transient) 并将其持久化。
当我们把注解标注在属性 ( 注解 getters) 上,实体类的访问类型就为属性类型,持久化实现将映射所有的属性 ( 没有标注 @Transient) 并将其持久化。
如果没有通过显示指定访问类型,我们不能既标注字段又标注属性,否则会报错。
例:
标注字段:
@Entity
public class Customer {
@Id
@GeneratedValue
private Long id;
@Column(name = "first_name", nullable = false, length = 50)
private String firstName;
@Column(name = "last_name", nullable = false, length = 50)
private String lastName;
private String email;
@Column(name = "phone_number", length = 15)
private String phoneNumber;
// Constructors, getters, setters
}
标注属性:
@Entity
public class Customer {
private Long id;
private String firstName;
private String lastName;
private String email;
private String phoneNumber;
// Constructors
@Id
@GeneratedValue
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Column(name = "first_name", nullable = false, length = 50)
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
@Column(name = "last_name", nullable = false, length = 50)
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Column(name = "phone_number", length = 15)
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
通过注解 @Access 可以指定标注类型,有 2 种参数值: AccessType.FIELD,AccessType.PROPERTY
@Entity
@Access(AccessType.FIELD)
public class Customer {
@Id
@GeneratedValue
private Long id;
@Column(name = "first_name", nullable = false, length = 50)
private String firstName;
@Column(name = "last_name", nullable = false, length = 50)
private String lastName;
private String email;
@Column(name = "phone_number", length = 15)
private String phoneNumber;
// Constructors, getters, setters
@Access(AccessType.PROPERTY)
@Column(name = "phone_number", length = 555)
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
标注在属性上的 @Access 将覆盖实体类级别的访问类型。所以最后 "phone_number" 的列上为 555(VARCHAR(555)).
十二、基本类型集合映射
在 JPA2.0 中, @ElementCollection 注解指定下列基本类型集合:
-
java.util.Collection
-
java.util.Set
-
java.util.List
持久化实现默认会生成一个 " 实体类名 _ 集合属性名 " 的集合表来储存集合值。
我们可以通过 @CollectionTable 注解来修改集合表名,通过 @Column 来修改集合表列名字 ( 默认是 " 集合的属性名 ")
例:
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
private Float price;
private String description;
private String isbn;
private Integer nbOfPage;
private Boolean illustrations;
@ElementCollection(fetch = FetchType.LAZY)
@CollectionTable(name = "Tag")
@Column(name = "Value")
private List<String> tags = new ArrayList<String>();
// Constructors, getters, setters
}
映射表:
十三、基本类型 Map 映射
在 JPA1.0 中, Map 映射中的 Keys 必须是基本类型, Values 必须是实体。现在,无论是 Keys 还是 Values 都可以包含基本数据类型,嵌入对象,和实体。
和基本类型集合映射一样,我们用 @ElementCollection 注解标注属性类型是 Map 类型。
通过 @CollectionTable 注解来修改 Map 表名 ( 默认是 " 实体类名 _Map 的属性名 ") ,
通过 @Column 来修改 Map 表 value 列名字 ( 默认是 "Map 的属性名 ")
通过 @MapKeyColumn 来修改 Map 表 key 列名字 ( 默认是 "Map 的属性名 _KEY")
例:
@Entity
public class CD {
@Id
@GeneratedValue
private Long id;
private String title;
private Float price;
private String description;
@Lob
private byte[] cover;
@ElementCollection
@CollectionTable(name = "track")
@MapKeyColumn(name = "position")
@Column(name = "title")
private Map<Integer, String> tracks = new HashMap<Integer, String>();
// Constructors, getters, setters
}
映射表: