这是我09年初写的,当时比较肤浅,应该会有些错误的地方,希望对大家有所帮助。
下载pdf地址:http://download.csdn.net/detail/partner4java/1995131 (无加密,可以修改)
如果ORM比较有用的话,推荐看《hibernate in action 2》--翻译的一般,但是内容很精湛。我也阅读后做了一些笔记:
http://blog.csdn.net/partner4java/article/category/839919
前言
本笔记的由来:首先我是 jpa 的爱好者,这个爱好导致我疏远了 Hibernate,当然没有说 Hibernate 不好的意思,Hibernate3 的新特性也支持注解。或许感觉 jpa 是规范,并且或多或 少也想接触 ejb3 的缘故。当然这个笔记也是在我阅读了 ejb3 in action 后进行书写的。 笔记的特点:摘取了 ejb3 inaction 里面的一些简短重要的讲解,大部分的篇幅是我一些不伦不类的想法和理解吧,因为个人水平有限,也只能用不伦不类描述了。不过请不要仅仅只看
了前面一点内容就评论我很垃圾。
笔记阅读者适合人群:本人或多或少做了几个使用 jpa 的项目。所以希望阅读者,也需要或
多或少对 jpa 有些初步的了解。
搭建环境 我使用的是 Hibernate3
需要的 jar:
Hiberante 核心包(8 个文件)
hibernate-distribution-3.3.1.GA
---------------------------------------------hibernate3.jar
lib\bytecode\cglib\hibernate-cglib-repack-2.1_3.jar
lib\required\*.jar
Hiberante 注解包(3 个文件):hibernate-annotations-3.4.0.GA
------------------------------------------------------------------------------------ hibernate-annotations.jar
lib\ejb3-persistence.jar、hibernate-commons-annotations.jar
Hibernate 针对 JPA 的实现包(3
个文件):hibernate-entitymanager-3.4.0.GA
------------------------------------------------------------------------------------------------------hibernate-entitymanager.jar
lib\test\log4j.jar、slf4j-log4j12.jar
JPA 规范要求在类路径的 META-INF 目录下放置 persistence.xml,文件的名称是固定的,配置模版如下:
<?xml version="1.0"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"version="1.0">
<persistence-unit name="partner4java"
transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="hibernate.dialect"value="org.hibernate.dialect.MySQL5Dialect"/>
<property name="hibernate.connection.driver_class"value="org.gjt.mm.mysql.Driver"/>
<property name="hibernate.connection.username" value="root"/>
<property name="hibernate.connection.password"value="123456"/>
<property name="hibernate.connection.url"value="jdbc:mysql://localhost:3306/jpa?useUnicode=true&characterEncoding=UTF-8"/>
<propertyname="hibernate.max_fetch_depth" value="3"/>
<propertyname="hibernate.hbm2ddl.auto" value="update"/>
<propertyname="hibernate.jdbc.fetch_size" value="18"/>
<propertyname="hibernate.jdbc.batch_size" value="10"/>
<propertyname="hibernate.show_sql" value="true"/>
<propertyname="hibernate.format_sql" value="false"/>
</properties>
</persistence-unit>
</persistence> 配置文件你可以从网上或者包里面带的例题了找或者从使用包里面的默认配置文件里面找。并且配置文件的位置和名称是固定不允许改变的,在使用 spring 时,文件名可以改变。
每个配置项的具体含义自己 google一下吧,很简单,我就懒得打上了。
导读:
第一部分:主要是想给您灌输一下域模型的概念,初步接触实体 bean 的实现方式,让您对 实体 bean 有初步的思想理念(当然我省略了很多面向对象的思想概念,这也就最开始希望 您对 JPA 有所基本的了解)
第二部分:详细说明了如何进行对象关系映射。
第一部分:实现域模型
1.一些基本理论 域模型是和持久化是密不可分的概念。通常,开发企业项目的第一个步大都就是创建域
模型,即列出域中的实体并且定义实体之间的关系。(当然这基本说的是领域建模,好多公司的项目还是采用的数据库建模的形式。)
1.1 域模型的相关概念 域模型的四个参与者:对象、关系、关系多样性和关系的可选性。
1》 对象
与 java 对象一样,域模型可以同时具有行为(在 java 在的方法)和状态(java 的实 体变量)
2》 关系
从 java 角度来看,关系表现为一个对象引用另一个对象。 充血域模型与贫血域模型
域模型实际上被持久化到数据库中,数据库建模经常和域建模是同义的,域对象包含属性(映射到数据库表的列)而非行为,这种类型的建模被称为贫血模型。 充血模型封装对象的属性、行为,并且利用面向对象实用程序设计方法。(比如继 承、多态和封装)。域模型充血度越高,就越难以映射到数据库。
1.2Ejb3 java 持久化 API
Ejb3java 持久化 API 是元数据驱动的 pojo 技术。也就是说,为了把 java 对象中的数据保持到数据库中,我们的对象不必实现接口、扩展类,或采用框架模式。
(简单的 java 对象实际就是普通的 javabean 是使用 pojo 名称是为了避免和 ejb 混淆起来)声明:如果你熟悉 sql 和 jdbc 并且喜欢通过自己动手的便利方式得到控制权和灵活性,那么 O/R 的成熟、技巧和自动化形式可能不适合你。如果情况是这样,那么你应该多了解 SpinrgJdbcTemplate(这个我不喜欢用)和 iBATIS(这个框架应该对你对对象的理解要求低很多)。
可是我要警告您,java 本身就是对象化的语言,我们要完美化,就要一切为对象。
2.使用 JPA 实现域对象
本章意在描述域对象的实现过程,下一张将详细介绍如何使用对象-关系映射把我们创建的实体和关系映射到数据库中。
(当然现在 JPA 不仅仅是 EJB3值得骄傲的地方,其它好多公司也相继实现了 JPA 规范,做出 了很多优秀的 orm框架。如:hibernate、openjpa、toplink 等。并且规范的好处就在于,你 只要完全按照规范来写代码,这些框架你可以随意更换,并是不更改代码的前提下。)书签:下面我们主要说的是如何定义一个实体
bean,也就是如何把你的 POJO映射为数据 库对象。
@Entity 注解,它把 POJO 转换为实体。(以后我们常叫这样的 POJO 为实体 Bean)
(jpa主要是支持注解的形式,简单是说也就是使用注解对你对象化的 POJO进行声明表示, 生成对应的数据库和对数据库进行对象化的封装。
那么,我们学习 JPA 也就无非是学习几个注解,并且通过我们对对象的理解,标识对象的关 系等。我们常用的注解无法就二三十个注解,所以 JPA 会让你感觉原来那么简单)package
cn.partner4java.bean.part2_1;
import javax.persistence.Entity;
import javax.persistence.Id;
public class User {
String userId; String username; String email;
}
2.2 持久化实体数据
?我们注解标识的位置:持久化分为两种:基于字段与基于属性的持久化。
使用实体的字段或实体变量定义的 O/R 映射被称为基于字段的访问,也就是注解标注在字段上面。当然基于字段如果你省略了 get 和 set,也可以生产成功。
如:
package cn.partner4java.bean.part2_1; import
javax.persistence.Entity; import javax.persistence.Id;
public class User {
String userId; String username; String email;
}
基于属性,必须标注在 get 方法上面,而非 set。 如:
package cn.partner4java.bean.part2_1; import
javax.persistence.Entity; import javax.persistence.Id;
public class User {String userId; String username; String email;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
} 注意:同一个类中,不可同时使用两种方式。因为两种方式的内部实现方式有所区别。(当 然你标注在哪里看你个人爱好了,我比较喜欢标注在字段上面,因为我查找和修改的时候方
便一些,我也见很多人标注在属性上面,具体哪种性能上更优秀一些,寡人没具体研究过,
的需要查看源码了,并且每种实现产品又不一样,所以一直懒得去看)
建议:当您定义字段时,最好声明为私有的。 因为持久化提供器可能需要覆盖设置器和获取器,所以不能持久化的设置器和获取器声明为final。也就是 get和 set。
?如果你也随着我说的,自己打并执行了上面的代码,你会发现 username 和 email 我们并没有进行任何的标识,也生产了相应的数据库字段,但是如果我们不想生成该怎么办?定义 顺时字段:加上@Transsient。也就是有些字段你不想映射进数据库时。这里我们就简单的说了一下,后面我们会通过其他的解决,顺便解决了这里的详细使用。
2.3 指定实体身份
?为什么要有实体身份并且规定是必须进行指定:上面我们介绍了第一个注解@Entity,把 POJO声明为实体 bean。这是不够的,因为实体在某个时间必须被持久化为数据库表中唯一可表示的行(或多个表中行的集合),如果没有进行标识,你就不知道在执行保存操作之后
数据被保持到哪一行了。(我们还需要重写 equals 方法,是比较两个明显不同数据库记录的 主键在对象中的等效方式)
身份表示注解:a.@Id b.@IdClass c.@EmbeddedId
?问题:怎么会有 3 种注解,我平时用到的仅仅是@Id,我感觉应该学着带着问题和思考去学习一个知识。
?什么样的类型可标识为实体身份:EJB3 支持原始类型、原始包装器和 Serializable 类型(比 如 java.lang.String、java.util.Date、java.sql.Date)作为身份,此外因为 float、Float、double 等类型不确定的特点,当选址数据类型时应该避免使用这些类型。
第一种:@Id,相信对 jpa 有一点点了解的人就会知道这是做什么的,怎么用。
?后两种注解出项的原因:你有时使用一个以上的属性或字段(复合键)唯一的标识实体。 第二种:@IdClass
如:(一般通过例子就完全可以理解的,我就不会多说了)
package cn.partner4java.bean.part2_3;
import java.io.Serializable;
import java.util.Date;
public class CategoryPK implements
Serializable {
private String name;
private Date CreateDate;
…hashCode()…equals(Object obj)
package cn.partner4java.bean.part2_3;
import java.util.Date;
import javax.persistence.Entity; import
javax.persistence.Id; import javax.persistence.IdClass;
@IdClass(CategoryPK.class)
public class Category {
private String name;
private Date CreateDate;
private String realname;
}
package cn.partner4java.bean.part2_3;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Embeddable;
public class CategoryPK implements
Serializable {
private String name;
private Date CreateDate;
。。。
package cn.partner4java.bean.part2_3; import
javax.persistence.EmbeddedId; import javax.persistence.Entity;
public class Category {
private CategoryPK categoryPK;
private String realname;
}
有些地方说:这里的 CategoryPK 可以不实现序列化 Serializable,但事实是必须也实现。因为 所有的字段都必须是序列化的。
这后两种生产复合主键的形式,都可以,看您个人爱好了
2.4@Embeddable 注解 上面我们第三种方式用到了这个注解进行复合主键对象的声明。它主要用于嵌入对象,我们平时常用的是域对象。如实体关系,一对一。使用场景:纯粹的面向对象的域建模,所有的域对象总都是可以独立标识的么?如果对象仅 仅在其他的对象内部用作方便的数据占位器/组织方式会怎么样?一个常见的例子,是
User 对象内部使用 Address 对象,它作为优雅的面向对象的替换方式,替代列出街道地址、城市、 邮编等直接作为 User 对象的字段。因为不太可能在 User 之外使用 Address 对象,所以 Adress 对象没有必要具有身份,这正好是@Embeddable 的适用场景。
如:
package cn.partner4java.bean.part1_2_4;
import javax.persistence.Embeddable;
public class Address { private
String city; private String state; private String country;
}
package cn.partner4java.bean.part1_2_4;
import javax.persistence.Embedded; import javax.persistence.Entity;
import javax.persistence.Id;
public class User {
private Long id;
private String username;
private Address address;
} 生成的表仅一个。但是我们如果想生成的是两个表,怎么办呢?是不是有什么属性表示一下?但是我们上面就说过,如果是单独表映射就需要身份表示,否则我们就不知道是对哪个字段
进行操作。但是嵌入对象不需要唯一身份。那么就的选择使用实体关系的方式。
3.实体关系 域关系类型和相应注解
关系类型 注解
一对一 |
|
一对多 |
|
多对一 |
|
多对多 |
|
3.1 @OneToOne
单向一对一:
User 对象具有对 BillingInfo 的引用,但是没有反向引用,换句话说,次关系是单向的。
package cn.partner4java.bean.part1_3_1;
import javax.persistence.Entity;
import javax.persistence.Id;
public class BillingInfo {
private Long billingId;
private String bankName;
}
package cn.partner4java.bean.part1_3_1;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToOne;
@Entity
public class User {
private String userId;
private String email;
private BillingInfo billingInfo;
}
在 User 表里面生成了相应的关系引用字段。
@OneToOne(optional=false)
private BillingInfo billingInfo;
Optional 元素:通知持久化提供器相关对象是否必须存在。默认情况下为 true,不必存在。 双向一对一:
你需要从关系的任何一端访问相关实体:
package cn.partner4java.bean.part1_3_1;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToOne;
public class BillingInfo {
private Long billingId;
private String bankName;
@OneToOne(mappedBy="billingInfo",optional=false)
private User user;
}
package cn.partner4java.bean.part1_3_1;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToOne;
public class User {
private String userId;
private String email;
private BillingInfo billingInfo;
}
mappedBy=”billingInfo”指定,它通知容器关系的“拥有”端在 User 类的 billingInfo 实体变量 中,也就是在表 User 里生产外键引用字段。
Optional参数被设置为false,标识BillingInfo对象不能在没有相关User对象时存在。想想,你
如果在User的:
@OneToOne(optional=false)
private BillingInfo billingInfo;
也设置为不能为空,会产生什么后果,保存执行 sql 语句会产生什么后果?(这个问题留在后面 EntityManager 使用时解决)
3.2 @OneToMany 和
@ManyToOne
package cn.partner4java.bean.part1_3_2;
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
public class Item {
private Long id;
private String title;
@OneToMany(mappedBy="item")
private Set<Bid> bids;
}
package cn.partner4java.bean.part1_3_2;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
public class Bid {
private Long bidId;
private Double amount;
private Item item;
}
在关系中的非维护端的实体的列上使用 mappingBy,从而标记关系的维护端,指定为 Bid 的 item 字段为维护端。并且规定必须多的一端为维护端。因为,让世界人名记住小泉的名字,总比让小泉记住世界人民的名字容易的多。
3.3@ManyToMany虽然多对多关系可以是单向的,但是由于这种很想特殊、相互独立的性质,所以他们经常是 双向的。
package cn.partner4java.bean.part1_3_3;
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
public class CateGory {
private Long categoryId;
private String name;
private Set<Item> items;
}
package cn.partner4java.bean.part1_3_3;
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
public class Item {
private Long itemId;
private String title;
@ManyToMany(mappedBy="items")
private Set<CateGory> categories;
}
双向的多对多会单独生成一个表进行关系的维护。
总结:我们上面介绍了各种关系,但是没有详细告诉大家一些注解参数的配置,如改变多对多关系表的名称等等,相信这些还是比较简单的东西,大家可以从文档里面查找。并且下面的第二部分会有详细的介绍。
第二部分:关系对象映射
ORM是 JPA 的基础。实际上,ORM 指定如何把一组 java 对象(包括对象之间的引用)映射 到数据库表的行和列。
我们从介绍 O/R 映射背后的基本动机(即所谓的阻抗失配)开始讨论,然后通过映射域对象开始分析,再转向映射关系的讨论。最后讲解映射继承的概念,你将学习 JPA 支持的继承策略。
1.1 阻抗失配
?什么是阻抗失配:这一术语是面向对象和(数据库的)关系范例之间的区别以及应用程序 开发工作中因这些区别引起的问题。域模型所在的持久化层通常是阻抗失配表现的最明显的 地方,问题的根源在于两种技术的基本目的之间的区别。如:JVM 提供丰富的继承和多态,而(数据库的)关系领域中不存在这些特性。数据库表固定的只有行、列和约束,并不包含业务逻辑。
1.1.1 把对象映射到数据库 仅是介绍一下映射概念。
1.一对一映射
我们上面说过嵌入对象和域对象。当对象不需要身份时,就可以考虑使用嵌入对象,这样会生成进同一张表。但是当表结构过于庞大时,会影响我们的查询效率,且我们的 ORM 框架 需要挨个封装成对象的数据,也会相应的影响些效率,这时就需要考虑域对象,如果双方都 需要得到对方的话,就是双向一对一,否者单方查询,就是单向一对一。
2.一对多关系
?EntityManager 执行查询时,通过一的一方获取多的一方会执行多少条 sql?性能上有什么 弊端?通过多的一方获取一的一方会执行多少条 sql?性能上有什么弊端?
?当保存的时候,如何设计级联保存关系,先保持哪一端?会尽量减少 sql 执行的次数?
( 请 记 住 这 些 我 们 提 到 过 的 EntityManager 执 行 相 关 的 问 题 , 在 第 三 部 分 去 学 习
EntityManager)
3.多对多 可以这么说多对多是一对多关系的一个特例,因为是双方都具有一对多的关系,导致需要第三张表去维系这种双向一对多的关系。
?那么上面这种保存关系必将导致保存上的复杂性。那么当你如何设计这种关系时,才能尽 量提高性能,避免 sql执行的次数?
4.继承
继承可能是对象-关系失配最严重的我问题。因为继承需要不适合(数据库的)关系理论的解决方案。
代码如下:
package cn.partner4java.bean.part2_1; import
javax.persistence.Entity; import javax.persistence.Id;
public class User {
String userId; String username; String email;
}
package cn.partner4java.bean.part2_1;
import javax.persistence.Entity;
public class Seller extends User {
String sellerGroup;
}
package cn.partner4java.bean.part2_1;
import javax.persistence.Entity;
public class Bidder extends User { String Bidder;
}
问题:那么父类有或者没有进行@Entity 标识时,被继承后表结构生成方式如何?在父类是 抽象类时有什么区别?
a. 当 User 和 Seller
都有@Entity 注解,三个实体 bean 仅仅生成了一个 user 表,另外两个 实体 bean的字段被包含进了 user。并且额外生成字段 btype。
b. 当 user 去掉注解,如果子类没有主键@Id 会报错误,加上后只生成了子类自己的字段,没有生成user 的字段,生成两个字类表
c. 当 user 为抽象,没有进行@Entity 声明是生成如 b d. 当 user 为抽象也为实体生成如 1
(注意:我的测试也许不是完全正常,因为首先的测试环境为 Hibernate3 的实现) 问题总结:这样让我感觉继承没有什么实际的作用。
那么 JPA 给我们提供了专门的注解去实现继承的三种应用场景:
·每个类分层结构一张表
·每个子类一张表
·每个具体类一张表
2. 映射实体
下面我们会给出一个很有意思的例子:也许里面的有些方法你没使用过。
(本第二小节,会根据这个例子详细讲解,如何映射简单的实体—不包含关系的实体)
package cn.partner4java.bean.part2_2;
import javax.persistence.Embeddable;
public class Address { private
String street; private String city;
}
package cn.partner4java.bean.part2_2;
import java.util.Date;
import javax.persistence.Basic;
……//省略了一些导入
@Table(name="USERS")
@SecondaryTable(name="USER_PICTURES", pkJoinColumns=@PrimaryKeyJoinColumn(name="USER_ID"))
public class User {
@Column(name="USER_ID",nullable=false)
@GeneratedValue(strategy=GenerationType.AUTO)
private Long userId;
@Column(name="USER_NAME",nullable=false)
private String username;
@Column(name="FIRST_NAME",nullable=false,length=1)
private String firstName;
@Enumerated(EnumType.ORDINAL)
@Column(name="USER_TYPE",nullable=false)
private UserType userType;
@Column(name="PICTURE",table="USER_PICTURES")
@Basic(fetch=FetchType.LAZY)
private byte[] picture;
@Column(name="CREATE_DATE",nullable=false)
@Temporal(TemporalType.DATE)
private Date creationDate;
private Address address;
}
注解与 O/R 映射中的 XML相比较
相信看到这里,接触过 Hibernate 的朋友,心里会暗自进行了比较。在注解和 XML 部署
描述文件之间做出选择非常困难。XML 描述文件很庞大和难以管理。但是注解是通过硬编码 数据库设计方式。幸运的是,你可以使用 XML 描述文件覆盖 ORM注解。
2.1 指定表
@Table(name="USERS")我感觉这个很简单,没什么好说明的,还有 一些不常用的参数就不 再啰嗦了,当然,如果你不用Table注解指定表名,也可以这样写:@Entity(name="USERS")
2.2 映射列
@Column(name="USER_NAME",nullable=false,insertable=false,updatable=fa lse)inserttable 和 updatable 参数用户控制持久化行为,如果 insertable参数为
false,那么当持久化提供器创建对应实体的新记录时,字段或属性就不会包含在持久化提供其
生成的 insert 语句中。Updatable 一个意思。(如果不明白什么意思,记住就可以,后面讲
EntityManager 就知道了)
注意,我比较常犯的错误,就是常常忘记了 length,那么生成的字段就会为最大长度,这样会照成浪费。
2.3 使用@Enumerated
枚举就不用多说了吧,C 和 Pascal 语言里面已经有枚举类型几十年了。
@Enumerated(EnumType.ORDINAL)以数字编码形式映射
@Enumerated(EnumType.STRING)以字符串形式映射
2.4 映射 CLOB 和 BLOB 二进制大型对象:BLOB 字符大型对象:CLOB
@Basic(fetch=FetchType.LAZY)
private byte[] picture;
生成:LONGBLOB
@Basic(fetch=FetchType.LAZY)
privateString content; 生成:LONGTEXT
记住如果是大文本一定要使用@Basic 标注为懒加载,因为大文本是非常耗内存资源的。Mysql
不建议把图片等附件保存在数据库里面,会严重影响数据库的性能。(不幸运的是,EJB3 规 范把 LOB 类型的延迟加载作为选项留给厂商完成,因此不能确保确实进行了延迟加载)
2.5 把实体映射到多个表 在一些非常少见的情况中,这是非常有意义的策略。
2.6 生成主键 标识为主键时,在本质上要求数据库强制实现唯一性。
1》 身份列作为生成器
@GeneratedValue(strategy=GenerationType.IDENTITY),如果我没记错的话,
mysql 和 mssql 都是用这种生产方式,也就是自增长
在实体数据保持到数据库之前,身份字段的值可以不可用,通常在提交记录时才生成它。
Hibernate: insert into USERS (city, street, content,CREATE_DATE, FIRST_NAME,USER_TYPE, USER_NAME) values(?, ?, ?, ?, ?, ?, ?)
看到了实现产品也不是那么傻,id 它不会插入的。
2》 数据库序列作为生成器
@GeneratedValue(strategy=GenerationType.SEQUENCE)
自动方式,会给你生成序列,有可能是统一一个名称为“HIBERNATE_SEQUENCE”序列,并且手工给你插入id。
@GeneratedValue(strategy=GenerationType.SEQUENCE,generator="CMS_PL_Guarantee_Type_SEQ")
@SequenceGenerator(sequenceName="CMS_PL_Guarantee_Type_SEQ",name="CMS_PL_Guarantee_Type_SEQ",allocationSize=1)
明确指定方式,它会给你创建这个名称的序列。
如果你 mysql 非这么设置,会报 not support sequences。
3.序列表作为生成器 这种方式我印象中没有见这么用的,大体含义就是搞出来一个表专门负责生产主键和 oracle 的序列生成器差不多的含义,不过这里是单独创建一个表。
@GeneratedValue(strategy=GenerationType.TABLE,generator="USER_GENERATOR_TABLE")
4.默认主键生成策略
讲过了上面主键生成策略,当然你如果想,假如数据库可能变更,如果通过这种硬编译的方 式指定策略,往往会带来附加的工作。那么很庆幸,我们使用的产品并不会那么傻,hibernate和 openjpa 都会很好的支持默认自动生成策略。不过请记住,当你使用 TopLink 时,很抱歉, 它会默认都是用表生成器。(我都是使用 Hibernate,所以一般都不设置,呵呵)
2.7 映射可嵌入对象
可嵌入对象没有自己的身份,与包含它的实体共享身份。EJB3 不允许把可嵌入的对象映射到与包含它的实体不通过的表中。如果你有特殊需求,需要映射到不同表中,就可以使用上 面 2.5 的生成策略:
@SecondaryTable(name="USER_PICTURES",pkJoinColumns=@PrimaryKeyJoinCol umn(name="USER_ID"))
@Column(name="PICTURE",table="USER_PICTURES")
3.映射实体关系
(我们上面说了不包含域对象关系模式的实体,现在我们就详细讲解一下最开始,我们说过 的域对象—一对一、一对多、多对多关系是如何映射成实体的 ----我很多地方都说了此类的一些废话,意图就在于,很多朋友经常不知道目前自己到底在学习的具体哪部分知识,很不便于整理思路)
3.1 映射一对一关系
public class BillingInfo {
private Long billingId;
private String bankName;
// @OneToOne(mappedBy="billingInfo",optional=false)
// private User user;
}
public class User {
private String userId;
private String email;
@OneToOne(optional=false)
private BillingInfo billingInfo;
} 去掉注解为双向引用,这一点我们上面已经介绍过了,在被维护端,指定关系维护端的维护字段。(维护端:生成维护字段)
?如果你想改变维护字段的表字段生成名字该如何做?
@JoinColumn(name="BillingInfo_Id",referencedColumnName="billingId",up datable=false)
Name:生成字段的名字
referencedColumnName、updable 等都省略就可以
(你有没有发现,我上面的两端都设置为不允许为空,你要是没发现就大头了)
?有时我被维护端不想生存主键,而是引用维护端的主键 。用:
@PrimaryKeyJoinColumn(name="userId",referencedColumnName="userId")
不过这种做法,我搞了很久没搞明白到底如何进行保存,所以我也就没资格多说了。
3.2 一对多和多对一
只要记住多的一端为关系的维护端,在一的一端的标记 mappedBy,在多的一端标记
@JoinColumn(name=""),因为一的一端生成相应的维护字段。
3.3 多对多
多对多是特殊的多对一,根绝业务需求在一端定义被维护
@ManyToMany(mappedBy="items"),在维护端定义中间表生成方式
@JoinTable(name="CA_IT",joinColumns=@JoinColumn(name="categoryId"),in
verseJoinColumns=@JoinColumn(name="itemId")),当然@JoinTable可以省略。
? 当 主 键 对 应 字 段 名 字 使 用 @Column(name="category_Id") 进行了类似更改,
@JoinTable
里的@JoinColumn 是否必须进行 referencedColumnName="category_Id", 标识呢? 答案是,不是必须的,因为既然是主键,并且是唯一主键,我们的框架就不会那么傻。当然以防万一最好写上。如果你指定了,并且指定错了,那么框架不会聪明到如此程度给你自动 更正。
4.映射继承
三种策略:
A. 单表:就是第一部分我们讲解的嵌入对象的使用方式,我实在没感觉对表结构生产上多么明智,仅是实体 bean定义时,更对象化
B. 连接表:比单表查询性能还要差劲
C.每个类一个表:性能更差劲
第三部分 使用 EntityManager 操作实体
我们前面介绍了如何创建一个实体,当然创建完之后,我们就需要对实体进行 CURD 操 作,EntityManager 就是提供该操作的接口。
本部分你将了解 EntityManager 接口、实体的生命周期、持久化上下文的概念,以及如 何获取 EntityManager 的实体。然后,讨论生命周期回调监听器。
1 . 介绍 EntityManager
1.1 EntityManager 接口
如果你用过 Hibernate,必然知道 Hibernate 的 SessionFactory,EntityManager 作用也基本如 此:作为面向对象和(数据库的)关系领域之间的桥梁。它解释实体指定的 O/R 映射并且 把实体保存到数据库中。
由于 JPA 实体独立于容器,因为由 EntityManager 而不是由容器管理实体,所以它们不
能直接使用容器服务,比如依赖注入、定义方法级别事物和声明式安全、远程功能等等。所
以,如果有人称 JPA 为实体“bean”,你可以礼貌的纠正下。当然我就喜欢这么叫
尽管 JPA 不像会话 bean 或 MDB 那样是以容器为中心的,但是实体仍然具有生命周期。
1.2 实体的生命周期
EntityManager 简言之就是对实体进行管理,进行数据库的 CURD 映射,EntityManager 可以
想象是使用它本身的容器--一个大水池对实体进行管理。所以就会有 EntityManager 和实体
之间的管理关系,或者说实体与 EntityManager 的大水池之间的存在关系。如:创建的实体是否已经放进 EntityManager 的大水池进行管理等
分为:
1.新建状态:如当实体第一次被创建时,尚未拥有持久化的主键,没有和一个持久化的上下 文关联起来。可以理解为这个实体还没有身份,所以无法也没有被 EntityManager 映射成对 应的数据库字段。
2.托管状态:只要我们要求 EntityManager 开始管理实体,当然这个实体必须具有身份也就是主键值,EntityManager 就使数据库和实体状态同步。其次,在实体不再受管理之前, EntityManager 确保对实体书库的改动被反映到数据库中。(当然你会问我们怎么样才能要求 EntityManager 开始管理我们创建的实体?如:当你调用 EntityManager 的方法,或者说当你 把实体传递给 persist、merge 活 refresh 方法时。这些方法的含义我们后面就会介绍。之所
以调用 EntityManager 的这些方法会被管理,一是因为 EntityManager 主动给实体赋予了身份,或者实体本身具有身份,二是 EntityManager 主动把实体放进了自己的水池,进行了监控)
3.游离状态:和新建状态最大的区别就是,游离状态的实体有身份,只是因为一些原因被拿 出了 EntityManager 的水池,或者还没有放进 EntityManager 的水池。自然而然的,游离状态的实体就无法和数据库进行同步。(如:调用 EntityManager 的 clear 方法,就会强制把水池 里面的实体都拿出来,变为游离状态)
4.删除状态:最常见的也就是实体同步的数据库字段,被实体主动或被动删除了。实体就没有对应的数据库字段了。但是它还具有身份,并删除前已经放进了 EntityManager 水池。
2 获取 EntityManager
我们既然已经知道了 EntityManager 的作用,那么该如何得到 EntityManager 呢?
2.1 应用程序管理的 EntityManager
1.容器中获取
如在java EE环境中,可以使用@PersistenceUnit注解注解 EntityManager 实例,sping
中我们是@PersistenceContext protected EntityManager em; 当然无论是 ejb 还是 sping 或者 J2ee 环境,你都要先事先把 EntityManager 放进这 里容器里。
2.容器之外的应用程序获取 EntityManager
也就是我们正常通过类获取的方式:
//因为我们目前使用的是hibernate作为JPA的实现产品,EntityManagerFactory背后的
实现为sessionFactory,
//当我们创建EntityManagerFactory的时候,背后也会创建sessionFactory,我们学习
hibernate知道,当sessionFactory被创建的时候,就会根据映射信息创建数据库表. EntityManagerFactory factory =
Persistence.createEntityManagerFactory("partner4java");//sessionFactory ---获取创建EntityManager的工厂,传入参数为我们配置<persistence-unit name, EntityManager em = factory.createEntityManager();//session,使用工厂创建
EntityManager em.getTransaction().begin();//开启事务
。。。。CURD操作
em.getTransaction().commit();//提交事物
em.close();
factory.close();
3.在 Web 容器和 ThreadLocal 模式中使用 JPA
一些持久化提供器(如 Hibernate)将建议你使用 ThreadLocal 模式。它把 EntityManager 的 单 一 实 例 和 特 定 请 求 相 关 联 , 必 须 把 EntityManager 绑 定 到 本 地 线 程 变 量 并 且 把 EntityManager 实例设置到相关的线程。
package cn.partner4java.service.base;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class EntityManagerTool {
private static EntityManagerFactory entityManagerFactory;
private static final ThreadLocal<EntityManager>
threadLocal = new
ThreadLocal<EntityManager>();
static{
if(entityManagerFactory == null){
entityManagerFactory = Persistence.createEntityManagerFactory("partner4java");
}
}
/**
* 获取当前线程对应的EntityManager,如果没有,就创建一个,并与当前线程对应
* @return
*/
public static EntityManager getEntityManager(){ EntityManager entityManager =
threadLocal.get(); if(entityManager== null){
entityManager = entityManagerFactory.createEntityManager();
threadLocal.set(entityManager);
}
}
/**
return entityManager;
* 关闭当前线程对应的EntityManager,并且移除他们之间的对应关系
*/
public static void closeEntityManager(){ EntityManager entityManager =
threadLocal.get(); if(entityManager!= null){
entityManager.close();
threadLocal.remove();
}
}
}
3.管理持久化操作
3.1 持久化实体 persist
让实体处理托管状态,且保存实体。
(从现在开始的内容会比较繁琐,希望您能够保持清晰的思路,能够随时理清对象间的包含 和被包含关系,能够清晰的知道各种状态间是如何转换的)
1.持久化实体关系 例 1.
EntityManager em = EntityManagerTool.getEntityManager();
em.getTransaction().begin();//开启事务
Item item = new Item(); item.setTitle("商品名称"); Bid bid =
new Bid();
bid.setAmount(2D);
item.getBids().add(bid);
em.persist(item);
em.persist(bid);
em.getTransaction().commit();//提交事物