forge 发表于 2013-1-29 22:03:12

三.对象关系映射-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} 
 
该实体映射图:
 
http://dl.iteye.com/upload/attachment/496208/51c3c429-21f9-34cd-9383-0a515421f246.png
 
 
每个表都有相同的主键。

注意:当使用次表时,你应该考虑性能问题。每次你访问这样的一个实体,持久化实现类访问多个表,用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} 
 
映射表:
 
http://dl.iteye.com/upload/attachment/496240/10f55a54-f7d2-363e-89fa-b4d09ad063b4.png
 



 
十三、基本类型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} 
 
映射表:
 
http://dl.iteye.com/upload/attachment/496245/567a316c-be89-389f-88a3-7da89bb346fb.png
页: [1]
查看完整版本: 三.对象关系映射-part2