近年来 ORM(Object-Relational Mapping,对象关系映射,即实体对象和数据库表的映射)技术市场热闹非凡,各类各样的持久化框架应运而生,其中影响最大的是 Hibernate 和 Toplink。Sun 公司在充分吸取现有的优秀 ORM 尤为是 Hibernate 框架设计思想的基础上,制定了新的 JPA(Java Persistence API)规范,对如今乱象丛生的持久化市场带来一个标准,大有统一持久化市场的气势。JPA 是经过 JDK5.0 注解或 XML 描述对象 - 关系表的映射关系,并将运行期实体对象持久化到数据库中去。JPA 规范小组的领导人就是 Hibernate 的发明者 Gavin King,JPA 规范的制定过程当中大量参考了 Hibernate 的内容,因此若是一个对 Hibernate 很熟悉的人,使用起来 JPA 会是轻车熟路,得心应手的,而且会感受到更简单一些,这主要得益于 JDK5 中引入的注解(annotation)。java
下面就使用注解介绍一下 JPA 的使用。web
首先用个小例子介绍一下如何将一个单个 Java 类映射到数据库中。数据库
@Entity public class Employee implements Serializable { private static final long serialVersionUID = 1L; @Id private Long id; private String name; private int age; private String addree; // Getters and Setters }
若是没有 @javax.persistence.Entity 和 @javax.persistence.Id 这两个注解的话,它彻底就是一个典型的 POJO 的 Java 类,如今加上这两个注解以后,就能够做为一个实体类与数据库中的表相对应。他在数据库中的对应的表为:app
映射规则:框架
1. 实体类必须用 @javax.persistence.Entity 进行注解;函数
2. 必须使用 @javax.persistence.Id 来注解一个主键;this
3. 实体类必须拥有一个 public 或者 protected 的无参构造函数,以外实体类还能够拥有其余的构造函数;spa
4. 实体类必须是一个顶级类(top-level class)。一个枚举(enum)或者一个接口(interface)不能被注解为一个实体;.net
5. 实体类不能是 final 类型的,也不能有 final 类型的方法;设计
6. 若是实体类的一个实例须要用传值的方式调用(例如,远程调用),则这个实体类必须实现(implements)java.io.Serializable 接口。
将一个 POJO 的 Java 类映射成数据库中的表如此简单,这主要得益于 Java EE 5种引入的 Configuration by Exception 的理念,这个理念的核心就是容器或者供应商提供一个缺省的规则,在这个规则下程序是能够正确运行的,若是开发人员有特殊的需求,须要改变这个默认的规则,那么就是对默认规则来讲就是一个异常(Exception)。
如上例所示:默认的映射规则就是数据库表的名字和对应的 Java 类的名字相同,表中列的名字和 Java 类中相对应的字段的名字相同。
如今咱们能够改变这种默认的规则:
@Entity @Table(name="Workers") public class Employee implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue private Long id; @Column(name="emp_name", length=30) private String name; @Column(name="emp_age", nullable=false) private int age; @Column(name="emp_address", nullable=false ,unique=true) private String addree; // Getters and Setters }
改变默认规则后 在数据库中对应的表为:
首先咱们能够能够使用
@ Javax.persistence.Table 这个注解来改变 Java 类在数据库表种对应的表名。这个注解的定义以下:
@Target(value = {ElementType.TYPE}) @Retention(value = RetentionPolicy.RUNTIME) public @interface Table { public String name() default ""; public String catalog() default ""; public String schema() default ""; public UniqueConstraint[] uniqueConstraints() default {}; }
从它的定义上能够看出来,这是一个类级别(class level)的注解 , 只能用在类的前面,其中 name 属性的值就是映射到数据库中时对应表的名字,缺省是类名。
@javax.persistence.Column 注解,定义了列的属性,你能够用这个注解改变数据库中表的列名(缺省状况下表对应的列名和类的字段名同名);指定列的长度;或者指定某列是否能够为空,或者是否惟一,或者可否更新或插入。
它的定义以下:
@Target(value = {ElementType.METHOD, ElementType.FIELD}) @Retention(value = RetentionPolicy.RUNTIME) public @interface Column { public String name() default ""; public boolean unique() default false; public boolean nullable() default true; public boolean insertable() default true; public boolean updatable() default true; public String columnDefinition() default ""; public String table() default ""; public int length() default 255; public int precision() default 0; public int scale() default 0; }
从它的定义能够看出他只能够用在类中的方法前面或者字段前面。
其中 name 属性的值为数据库中的列名,unique 属性说明该烈是否惟一,nullable 属性说明是否能够为空,length 属性指明了该列的最大长度等等。其中 table 属性将在 @SecondaryTable 的使用中已有过介绍。
JPA 中将一个类注解成实体类(entity class)有两种不一样的注解方式:基于属性(property-based)和基于字段(field-based)的注解。
1,基于字段的注解,就是直接将注解放置在实体类的字段的前面。前面的 Employee 实体类就是使用的这种注解方式;
2,基于属性的注解,就是直接将注解放置在实体类相应的 getter 方法前面,而不是 setter 方法前面(这一点和 Spring 正好相反)。前面的 Employee 实体类若是使用基于属性注解的方式就能够写成以下形式。
@Entity @Table(name="Employees") public class Employee implements Serializable { private static final long serialVersionUID = 1L; private Long id; private String name; private int age; private String addree; @Id @GeneratedValue public Long getId() { return id; } public void setId(Long id) { this.id = id; } @Column(name="emp_address", nullable=false ,unique=true) public String getAddree() { return addree; } public void setAddree(String addree) { this.addree = addree; } @Column(name="emp_age", nullable=false) public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Column(name="emp_name", length=30) public String getName() { return name; } public void setName(String name) { this.name = name; } }
他在数据库对应的表结构为:
能够看出,使用两种注解方式在数据库中映射成的表都是相同的。
可是同一个实体类中必须而且只能使用其中一种注解方式,要么是基于属性的注解,要么是基于字段的注解。两种不一样的注解方式,在数据库中对应的数据库表是相同的,没有任何区别,开发人员能够根据本身的喜爱任意选用其中一种注解方式。
上面介绍的几个例子都是一个实体类映射到数据库中的一个表中,那么可否将一个实体类映射到数据库两张或更多表中呢表中呢。在有些状况下如数据库中已经存在原始数据类型,而且要求不能更改,这个时候若是能实现一个实体类对应两张或多张表的话,将是很方便的。JPA2.0 中提供了一个 @SecondaryTablez 注解(annotation)就能够实现这种状况。下面用一个例子说明一下这个注解的使用方法:
@Entity @SecondaryTables({ @SecondaryTable(name = "Address"), @SecondaryTable(name = "Comments") }) public class Forum implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue private Long id; private String username; private String password; @Column(table = "Address", length = 100) private String street; @Column(table = "Address", nullable = false) private String city; @Column(table = "Address") private String conutry; @Column(table = "Comments") private String title; @Column(table = "Comments") private String Comments; @Column(table = "Comments") private Integer comments_length; // Getters and Setters }
清单 5 中定义了两个 Secondary 表,分别为 Address 和 Comments,同时在 Forum 实体类中也经过 @Column 注解将某些子段分别分配给了这两张表,那些 table 属性得值是 Adress 的就会存在于 Address 表中,同理 table 属性的值是 Comments 的就会存在于 Comments 表中。那些没有用 @Column 注解改变属性默认的字段将会存在于 Forum 表中。图 4 就是持久化后在数据库中对应的表的 ER 图,从图中可看出来,这些字段如咱们预料的同样被映射到了不一样的表中。
在使用嵌套映射的时候首先要有一个被嵌套的类,清单 5 中 Address 实体类使用 @Embeddable 注解,说明这个就是一个可被嵌套的类,与 @EmbeddedId 复合主键策略中的主键类(primary key class)稍有不一样的是,这个被嵌套类不用重写 hashCode() 和 equals() 方法,复合主键将在后面进行介绍。
@Embeddable public class Address implements Serializable { private String street; private String city; private String province; private String country; // Getters and Setters }
清单 6 中 Employee 实体类是嵌套类的拥有者,其中使用了 @Embedded 注解将 Address 类嵌套进来了。
@Entity public class Employee implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; private String email; private String cellPhone; @Embedded private Address address; // Getters and Setters }
清单 7 是持久化后生成的数据库表,能够看出被嵌套类的属性,也被持久化到了数据库中,默认的表名就是嵌套类的拥有者的类名。
CREATE TABLE `employee` ( `ID` bigint(20) NOT NULL, `EMAIL` varchar(255) default NULL, `NAME` varchar(255) default NULL, `CELLPHONE` varchar(255) default NULL, `STREET` varchar(255) default NULL, `PROVINCE` varchar(255) default NULL, `CITY` varchar(255) default NULL, `COUNTRY` varchar(255) default NULL, PRIMARY KEY (`ID`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
被嵌套类的注解方式,field 方式或者 property 方式,依赖于嵌套类的拥有者。上面例子中的 Employee 实体类采用的是 field 注解方式,那么在持久化的过程当中,被嵌套类 Address 也是按照 field 注解方式就行映射的。
咱们也能够经过 @Access 注解改变被嵌套类映射方式,清单 8 经过使用 @Access 注解将 Address 被嵌套类的注解方式设定成了 property 方式。清单 9 Employee 仍然采用 filed 注解方式。这种状况下,持久化的时候,被嵌套类就会按照本身设定的注解方式映射,而不会再依赖于嵌套类的拥有者的注解方式。但这并不会映射的结果。
@Embeddable @Access(AccessType.PROPERTY) public class Address implements Serializable { private String street; private String city; private String province; private String country; @Column(nullable=false) public String getCity() { return city; } public void setCity(String city) { this.city = city; } @Column(nullable=false,length=50) public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } @Column(nullable=false,length=20) public String getProvince() { return province; } public void setProvince(String province) { this.province = province; } public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } }
@Entity @Access(AccessType. FIELD) public class Employee implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; private String email; private String cellPhone; @Embedded private Address address; // Getters and Setters }
事先设定被嵌套类的注解方式,是一种应该大力提倡的作法,由于当同一个类被不一样的注解方式的类嵌套时,可能会出现一些错误。