三.对象关系映射-part2
基本映射
?
一、@Table
?
@javax.persistence.Table注解可以改变实体类默认映射的表名
把book实体映射给T_BOOK表
?
@Entity@Table(name = "t_book")public class Book {@Idprivate 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 {@Idprivate 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
查看下例:
?
@Embeddablepublic class NewsId implements Serializable{private String title;private String language;// Constructors, getters, setters, equals, and hashcode}?
@Entitypublic class News {@EmbeddedIdprivate NewsId id;private String content;// Constructors, getters, setters}?
?
?
看看我们怎么用复合主键查询:
NewsIdpk = new NewsId("Richard Wright has died", "EN")
Newsnews = 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 {@Idprivate String title;@Idprivate 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时:
selectn.title from News n
用@EmbeddedID时:
selectn.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文件。
?
@Entitypublic class Track {@Id@GeneratedValue(strategy = GenerationType.AUTO)private Long id;private String title;private float duration;@Basic(fetch = FetchType.LAZY)@Lobprivate 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实体
?
@Entitypublic 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.
看下例:
?
@Entitypublic class Customer {@Id@GeneratedValueprivate 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注解。
?
@Entitypublic class Customer {@Id@GeneratedValueprivate Long id;private String firstName;private String lastName;private String email;private String phoneNumber;@Temporal(TemporalType.DATE)private Date dateOfBirth;@Transientprivate 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 {@Idprivate 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 {@Idprivate String number;private String expiryDate;private Integer controlNumber;@Enumerated(EnumType.STRING)private CreditCardType creditCardType;// Constructors, getters, setters}??
十一、访问类型
到现在为止,我们标注类(@Entity,@Table)和属性(@Basic,@Column,@Temporal等),我们将注解应用在字段上(fieldaccess)也可以注解在相应的属性上(propertyaccess:gettermethod).例:我们将注解@Id标注在id字段上,同样也可以标注在getId()的方法上。这很大程度上是个人偏好。我趋向于注解在属性上(注解getters),这样可以很容易阅读实体类拥有的字段。在这个教程里,为了可读性,我们把注解标注在字段上。
当我们把注解标注在字段上,实体类的访问类型就为字段类型,持久化实现将映射所有的字段(没有标注@Transient)并将其持久化。
当我们把注解标注在属性(注解getters)上,实体类的访问类型就为属性类型,持久化实现将映射所有的属性(没有标注@Transient)并将其持久化。
如果没有通过显示指定访问类型,我们不能既标注字段又标注属性,否则会报错。
例:
标注字段:
@Entitypublic class Customer {@Id@GeneratedValueprivate 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}?标注属性:
?
@Entitypublic class Customer {private Long id;private String firstName;private String lastName;private String email;private String phoneNumber;// Constructors@Id@GeneratedValuepublic 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@GeneratedValueprivate 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来修改集合表列名字(默认是"集合的属性名")
例:
?
@Entitypublic 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")
例:
?
@Entitypublic class CD {@Id@GeneratedValueprivate Long id;private String title;private Float price;private String description;@Lobprivate byte[] cover;@ElementCollection@CollectionTable(name = "track")@MapKeyColumn(name = "position")@Column(name = "title")private Map<Integer, String> tracks = new HashMap<Integer, String>();// Constructors, getters, setters}?
?
映射表:
?
