- 浏览: 151330 次
- 性别:
- 来自: 北京
最新评论
-
pandengzhegt:
好牛!正需要!谢谢了!
JPA 2.0 中的动态类型安全查询 -
yanlp:
万分的感谢。
仿google 的输入提示框 -
huangwenji6111:
可谓良师,在此拜谢!受益匪浅!
hibernate lazy -
jwx0925:
不错!mark!
hibernate对象状态 -
leftstick:
大有裨益,谢了!
hibernate lazy
转自:http://hi.baidu.com/yaolihui/blog/category/Ejb
第一部分 EJB3介绍:OverviewEJB作为企业级的数据访问/持久化标准在1999年作为J2EE规范的核心规范出现,极大的转变了java企业级开发的模式,为java软件开发提供了一个良好的架构。 EJB从1.0到2.1在J2EE架构中,都是作为一个服务器端的(Server side)的数据访问中间件。开发人员通过EJB标准的API接口来访问操作数据,避免直接用JDBC和Sql操作底层的数据库。 采用EJB架构的目标在于:
减轻直接操作底层数据库的工作量
为企业级开发引入了面向对象/面向服务的开发架构
数据对象生命周期的自动管理
分布式能力
集成/声明式的安全/事务管理
在旧的EJB模型中(2.1以前),EJB实现了大部分的目标,但一个巨大的缺陷是原有的模型在试图减轻数据访问工作量的同时也引入了更多的复杂开发需求。例如EJB核心的的Entity Bean必须特定的Home,Remote,Business接口,发布前需要预编译,只能实现单表映射操作,静态的EJB-QL(EJB查询语言)都不能满足简化数据库操作的目标。 EJB 2.1的复杂度,开发成本和性能问题使得EJB在问世4年后仍然等不到广泛的应用。 到了2004年,随着POJO( Plain Old Java Object )模型的出现,动态代码操作,IOC模式等先进,简单实用技术的发展和它们在各种独立产品中的表现,都证明POJO,IOC的技术比原有的EJB 2.1模型更适合作为数据访问中间件,开发的难度和成本远远小于目前的EJB模型,确有更灵活和可扩展。 2004年9月J2EE平台规范集众家所长,推出了跨越式的Java EE 5.0规范,最核心的是全面引入新的基于POJO和IOC技术的EJB3模型。到此,J2EE 5规范( Java EE 5 )成为一个集大成者,纳百家之长,成为java企业开发统一的标准规范。 1.1 EJB 3和EJB 2.1的区别从整个EJB规范的角度来说,EJB 3和EJB 2.1最大变更在Entity Bean持久化API上。在EJB3中,Entity Bean持久化已经单独作为一个Persistence API规范和其他的EJB部分分离开来。下面我们主要讨论EJB 3和EJB 2.1在持久化API上的区别。 EJB 2.1模型存在复杂度高的缺陷:
EJB 2.0 模型要求创建多个组件接口并实现多个不必要的回调方法
组件接口要求实现 EJBObject 或 EJBLocalObject 以及处理许多不必要的异常
基于XML的EJB 2.0 部署描述符比较复杂并容易出错
基于 EJB 模型的容器管理持久性在开发和管理方面过于复杂,并且失去了几个基本特性--如使用数据库序列定义主键的标准方法
EJBQL 语法非常有限,而且是静态的,无法做到运行期间的动态查询
EJB 2.0 组件并非是真正面向对象的,因为它们在继承和多态性方面的有使用限制
开发人员无法在 EJB 容器外部测试 EJB 模块,而在容器内部调试 EJB非常复杂和耗时
查找和调用 EJB 2.0 是一项复杂的任务。即使是在应用程序中使用最基本的 EJB 也需要对 JNDI 有一个详细的了解
对容器的依赖使得EJB 2.0只能用于服务器组件的开发,无法实现一次编写,四处运行的面向构件的开发
所有这些复杂度和缺陷,都导致EJB 2.0的采用无法真正简化开发并提高生产力。 EJB 3.0 旨在解决以往EJB 2.0 模型的复杂性和提高灵活性,具体体现在:
消除了不必要的接口Remote, Home, EJB以及回调方法实现
实体Bean采用了POJO模型,一个简单的java bean就可以是一个Entity Bean。无需依赖容器运行和测试
全面采用O/R Mapping技术来实现数据库操作
实体Bean可以运用在所有需要持久化的应用,不管是客户端还是服务器端。从而真正实现面向构件的开发
实体 bean 现在支持继承和多态性
灵活丰富的EJB3查询语言
SQL支持
使用元数据批注代替部署描述符,减少复杂配置和提高可维护性
将常规 Java 类用作 EJB 并将常规业务接口用于 EJB
1.2 EJB 3中的元数据批注:AnnotationEJB3 规范中引入了元数据批注(Annotation)。Annotation是从J2SE 1.5开始称为java语言的一部分。Annotation并不是一个新的事物,在J2SE 1.5以前,人们已经自行引入了象著名的XDoclet等外挂式的元数据批注方法。而在.NET中,元数据批注也早已经是C#语言的成分了。 在以往,我们都是采用xml作为配置批注,但采用文本的xml配置存在一些缺陷:
描述符多,不容易记忆和掌握
无法做自动的校验,需要人工排错
当系统变大时,大量的xml配置难以管理
读取和解析xml配置非常耗时,导致应用启动缓慢,不利于测试和维护
做O/R Mapping的时候需要在java文件和xml配置文件之间交替,增大了工作量
运行中保存xml配置需要消耗额外的内存
采用元数据可以很好的解决这些问题:
描述符大量减少。以往在xml配置中往往需要描述java属性的类型,关系等等。而元数据本身就是java语言,从而省略了大量的描述符
编译期校验。错误的批注在编译期间就会报错。
元数据批注在java代码中,避免了额外的文件维护工作
元数据被编译成java bytecode,消耗小的多内存,读取也非常迅速,往往比xml配置解析快几个数据量级,利于测试和维护
第二部分 Entity介绍2.1 第一个Entity Bean:HelloWorldEJB3中的Entity Bean是如此的简单,就是一个普通的java bean加上一些精炼的元数据批注。 @Entity@Table( name="helloTable" )public class HelloEntityBean { private int id; private String foo; /** * The entity class must have a no-arg constructor. */ public HelloEntityBean() { } public HelloEntityBean(int id, String foo) { this.id = id; this.foo = foo; } @Id(generate=GeneratorType.NONE) public int getId() { return id; } public void setId(int id) { this.id = id; } public String getFoo() { return foo; } public void setFoo(String foo) { this.foo = foo; } public String toString(){ return "HelloEntityBean: id=" + id + ", foo=" + foo; }} 代码中元数据的说明:@Entity :EJB3 Entity Bean的批注,表明当前的java bean作为一个Entity Bean来处理。@Table( name="helloTable" ) :顾名思义,定义当前Entity对应数据库中的表。@Id(generate=GeneratorType.NONE) :我们把HelloEntityBean的id属性定义为主键。主键产生策略为GeneratorType.NONE,意味这是一个业务主键。就这么简单!2.2 解说Entity从EJB3.0开始,Entity Bean持久化规范与EJB的其他规范如Session Bean, Message Driven Bean和EJB3容器规范开始分离,单独作为一个持久化API。而在将来,该持久化API很可能会从EJB3.0规范中脱离出来成为一个独立的规范Java Persistence API。作为Java平台上的通用数据访问标准接口。 为了跟规范一致,我们将在开发手册中将Entity Bean称为Entity。虽然EJB3 Entity可以是很简单的java bean,只要批注了@Entity或者在xml配置中作了说明,就被做一个可持久化的Entity处理。 但还是需要遵行一定的规则:
Entity类必须要有一个无参数的public或者protected的Constructor。
如果在应用中需要将该Entity类分离出来在分布式环境中作为参数传递,该Entity Class需要实现java.io.Serialzable接口。
Entity类不可以是final,也不可有final的方法。
abstract类和Concrete实体类都可以作为Entity类。
Entity类中的属性变量不可以是public。Entity类的属性必须通过getter/setter或者其他的商业方法获得。
2.3 定义对Entity中属性变量的访问在绝大部分的商业应用,开发人员都可以忽略这部分无需关心。但如果你需要编写复杂的Entity类的话,你需要了解这个部分。复杂的Entity类是指在Entity类的getter/setter和商业方法中包含比较复杂的业务逻辑而不是仅仅返回/符值某个属性。在大部分的情况下,我们都建议使Entity类中setter/getter中的逻辑尽可能简单,除了必要的校验符值外,不要包含复杂的业务逻辑,例如对关联的其他Entity类进行操作。但有些情况下,我们还是需要在Entity类的setter/getter方法中包含商业逻辑。这时候,采用何种属性访问方式就可能会影响代码的性能甚至是逻辑正确产生影响。EJB3持久化规范中,在默认情况下所有的属性都会自动的被持久化,除非属性变量用@Transient元数据进行了标注。针对可持久化属性定义了两种属性访问方式(access): FIELD和PROPERTY。
如果采用access=FIELD, EJB3 Persistence运行环境(EJB3持久化产品,如Liberator EJB3)直接访问对象的属性变量,而不是通过getter。这种访问方式也不要求每个属性必须有getter/setter。如果需要在getter中包含商业逻辑,应该采用access=FIELD的方式。
如果采用access=PROPERTY, EJB3 Persistence运行环境将通过Entity类上的getter来访问对象的属性变量,这就要求每个属性变量要有getter/setter方法。在EJB3中,默认的属性访问方式是PROPERTY。access=PROPERTY时getter/setter的逻辑应该尽量简单。
规范中access方式还有多一层含义。就是采用access=FIELD时,元数据应该批注在属性上。 @Id(generate=GeneratorType.NONE) private int id; private String foo; /** * The entity class must have a no-arg constructor. */ public HelloEntityBean() { } public int getId() { return id; } 采用access=PROPERTY(默认方式)时,元数据应该批注在对应属性变量的getter上。 private int id; private String foo; /** * The entity class must have a no-arg constructor. */ public HelloEntityBean() { } @Id(generate=GeneratorType.NONE) public int getId() { return id; } 为了方便开发,Liberator EJB3实现对元数据批注的位置比规范规定的宽松,针对属性变量或它的getter进行批注都可以,不受access类型的影响。对Entity类中,getter/setter的命名规则遵从java bean规范。Entity类中的属性变量可以是以下数据类型:
原始数据类型和他们的对象类型
java.lang.String
java.math.BigInteger
java.math.BigDecimal
java.util.Date
java.util.Calendar
java.sql.Date
java.sql.Time
java.sql.Timestamp
byte[]
Byte[]
char[]
Character[]
enums
Entity类
嵌入实体类(embeddable classes)
还可以是以下集合类型:
java.util.Collection和它的实体类
java.util.Set和它的实体类
java.util.List和它的实体类
java.util.Map和它的实体类
2.3 主键和实体标识(Primary Key and Entity Identity)每个Entity类都必须有一个主键。在EJB3中定义了两种主键:
键单主键
复合主键
简单主键必须对应Entity中的一个属性变量(Instance Variable),而该属性对应数据库表中的一列。使用简单主键,我们只需要用@Id元数据对一个属性变量或者她的getter方法进行批注。当我们需要使用一个或多个属性变量(表中的一列或多列)联合起来作为主键,我们需要使用复合主键。复合主键要求我们编写一个复合主键类( Composite Primary Key Class )。复合主键类需要符合以下一些要求:
复合主键类必须是public和具备一个没有参数的constructor
复合主键类的每个属性变量必须有getter/setter,如果没有,每个属性变量则必须是public或者protected
复合主键类必须实现java.io.serializable
复合主键类必须实现equals()和hashcode()方法
复合主键类中的主键属性变量的名字必须和对应的Entity中主键属性变量的名字相同
一旦主键值设定后,不要修改主键属性变量的值
一起看一个复合主键的例子。Entity类Person,它的主键属性变量是firstName和lastName。 @Id private String firstName; @Id private String lastName; public Person() {} Person的复合主键类: public class PersonPK implements java.io.Serializable{ private String firstName; private String lastName; public PersonPK() { } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof PersonPK)) return false; final PersonPK personPK = (PersonPK) o; if (!firstName.equals(personPK.firstName)) return false; if (!lastName.equals(personPK.lastName)) return false; return true; } public int hashCode() { int result; result = firstName.hashCode(); result = 29 * result + lastName.hashCode(); return result; }}
第三部分 持久化 3.1 操作持久化Entity对Entity进行操作的API都设计在javax.persistence.EntityManager接口上。EntityManager,顾名思义是管理所有EJB 3运行环境中的所有Entity。 EntityManager根据运行的环境不同分为容器管理的EntityManager和应用管理的EntityManager。Liberator EJB3是一个可以运行在J2SE环境中的 嵌入式的EJB3 Persistence实现,因此在Liberator EJB3中的 EntityManager都是指应用管理的EntityManager。3.2 配置和获得EntityManager在J2SE环境中,EJB3定义了一个javax.persistence.Persistence类用于启动EJB3运行环境。要获得EntityManager,首先需要通过javax.persistence.Persistence获得EntityManagerFactory,然后调用EntityManagerFactory.createEntityManager()方法获得。 // 获得默认当前的EntityManagerFactory final EntityManagerFactory emf = Persistence.createEntityManagerFactory(); final EntityManager entityManager = emf.createEntityManager(); 当调用Persistence.createEntityManagerFactory()的时候,Persistence会做以下的步骤:
搜索当前jar包的META-INFO/persistence.xml配置文件
如果没有在META-INFO下找到persistence.xml,搜索当前线程的ContextClassLoader中的persistence.xml
根据获得的persistence.xml初始化EntityManagerFactory
每个EntityManagerFactory都必须有一个persistence.xml配置文件。下面是一个例子: <entity-manager> <name>myEntityManager</name> <provider>com.redsoft.ejb3.PersistenceProviderImpl</provider> <class>com.redsoft.samples.HelloEntityBean</class> <properties> <property name="ConnectionDriverName" value="com.mysql.jdbc.Driver"/> <property name="ConnectionURL" value="jdbc:mysql://localhost/EJB3Test"/> <property name="ConnectionUserName" value="ejb3"/> <property name="ConnectionPassword" value="ejb3"/> <property name="RetainValues" value="true"/> <property name="RestoreValues" value="false"/> <property name="IgnoreCache" value="false"/> <property name="NontransactionalRead" value="true"/> <property name="NontransactionalWrite" value="false"/> <property name="Multithreaded" value="false"/> <property name="Liberator.option.action" value="create"/> <property name="Liberator.option.cache" value="com.redsoft.jdo.enterprise.OscacheAdapter"/> <property name="Liberator.option.JMXEnabled" value="false"/> <property name="Liberator.option.NamingPort" value="3012"/> <property name="Liberator.option.JMXPort" value="3011"/> <property name="Liberator.option.JDBC.scrollable" value="false"/> <property name="Liberator.option.JDBC.fetchSize" value="500"/> <property name="cache.algorithm" value="LRUCache"/> <property name="cache.capacity" value="5000"/> <property name="log4j.logger.com.redsoft" value="FATAL"/> </properties></entity-manager> <name></name> 定义一个EntityManagerFactory的名字,这样当应用中需要多个EntityManagerFactory的时候可以区别。通常情况下,一个EntityManagerFactory对应一个数据源。<provider></provider> 定义采用的什么EJB3 Persistence运行环境(也就是EJB3 Persistence产品),例子中采用的的是Liberator EJB3的运行环境。正如前面说的,在EJB3规范中, EJB3 Persistence运行环境( Runtime )是作为一个独立的嵌入式模块,你可以随意的更换不同的EJB3 Persistence运行环境而不会影响你的应用程序。甚至在J2EE服务器内运行你也可以不用服务器本身提供的EJB3 Persistence运行环境,而采用第三方的EJB3 Persistence运行环境。<class></class> 定义哪些类是Entity持久化类。如果是测试环境或者你不采用ear/war的方式发布你的应用,而只是把编译后的类直接拷贝到web容器中,在添加一个新的Entity类时,需要在这里添加一行。要求这个步骤是因为在J2EE 5架构中EJB3运行环境的嵌入式设计。采用这种松耦合设计使EJB3 Persistence运行环境不能控制持久化类的加载(由J2EE服务器负责加载), 这样EJB3 Persistence运行环境就无法分辨jvm中哪个类是持久化Entity类,哪个不是。因此需要一个额外的信息来让EJB3 Persistence知道那些是持久化Entity。<property></property> 定义了了EJB3 Persistence运行环境需要的其他参数,这些参数不是标准的,而是由EJB3 Persistence运行环境的厂商自行定义。详细的配置方法参看persistence.xml的语法。 3.3 Entity的生命周期和状态在EJB3中定义了四种Entity的状态:
新实体(new)。Entity由应用产生,和EJB3 Persistence运行环境没有联系,也没有唯一的标示符(Identity)。
持久化实体(managed)。新实体和EJB3 Persistence运行环境产生关联(通过persist(), merge()等方法),在EJB3 Persistence运行环境中存在和被管理,标志是在EJB3 Persistence运行环境中有一个唯一的标示(Identity)。
分离的实体(detached)。Entity有唯一标示符,但它的标示符不被EJB3 Persistence运行环境管理, 同样的该Entity也不被EJB3 Persistence运行环境管理。
删除的实体(removed)。Entity被remove()方法删除,对应的纪录将会在当前事务提交的时候从数据库中删除。
3.4 持久化Entity(Persist) final EntityManagerFactory emf = Persistence.createEntityManagerFactory(); final EntityManager entityManager = emf.createEntityManager(); final HelloEntityBean hello = new HelloEntityBean( 1, "foo" ); EntityTransaction trans = entityManager.getTransaction(); trans.begin(); // 持久化hello,在此操作之前hello的状态为new entityManager.persist( hello ); // 这时hello的状态变为managed trans.commit(); entityManager.close(); // 这时hellow的状态变为detached. Liberator EJB3支持Persistence by reachability。当保存一个Entity时,以该对象为根对象的整个对象图都会自动的被保存。但在EJB3中,我们仍然可以通过关系元数据(比如OneToOne,OneToMany)的cascade属性来精确定义保存的级联行为。 下面我们来看看不同的cascade属性的区别。 不配置cascade的情况下,EJB3 Persistence运行环境默认不会采用Persistence by reachability。 public class Father{ @Id int id String name; // OneToOne没有配置cascade属性,因此默认不 // 会使用Persistence by reachablity @OneToOne Son mySon public Father( int id, String name, Son mySon ){ this.id = id; this.name = name; this.mySon = mySon; } } 现在来保存一个Father和Son。 final EntityManager manager = emf.createEntityManager(); manager.getTransaction().begin; Son mySon = new Son(); Father = new Father( 1, "father" mySon ); // 保存Father manager.persist( father ); // 由于OneToOne关系中没有配置casacade属性,father 关联的mySon// 不会被自动保存,需要分别保存 manager.persist( mySon ); manager.getTransaction().commit(); manager.close(); 现在我们配置casacde=CascadeType.ALL public class Father{ @Id int id String name; // OneToOne配置cascade=CascadeType.ALL,配置 // cascade=CascadeType.PERSIT也对persist操作也可以获得同样// 的效果。 // CascadeType.ALL包含CascadeType.PERSIST。 @OneToOne(cascade=CascadeType.ALL) Son mySon public Father( int id, String name, Son mySon ){ this.id = id; this.mySon = mySon; this.name = name; } } 在代码中同样持久化Father和mySon。 final EntityManager manager = emf.createEntityManager(); manager.getTransaction().begin; Son mySon = new Son(); Father = new Father( 1, mySon ); // 保存Father。由于OneToOne关系中配置casacade=CascadeType.ALL属// 性,关联的mySon会自动地被持久化 manager.persist( father ); manager.getTransaction().commit(); manager.close(); 由于Liberator EJB3运行环境采用了弱引用POJO管理技术,能很好的支持Persistence by reachablity而不会有其他产品有的潜在内存回收的问题, 因此建议在应用中尽可能使用cascade=CascadeType.ALL来减少持久化操作的复杂性和代码量,特别是在有复杂对象关系图的时候。 3.5 获取Entity如果知道Entity的唯一标示符,我们可以用find()方法来获得Entity。 Father father = manager.find( Father.class, new Integer( 1 ) ); // 由于JDK1.5支持自动转型,也可以如下使用 Father father = manager.find( Father.class, 1 ); /* * 或者,可以用Entity名字作为查找。但无法利用JDK 1.5的自动转型功能, * 需要使用对象作为查找主键,并需要对获得Entity进行转型 */ Father father = (Father)manager.find( "com.redsoft.samples.Father", new Integer( 1 ) ); 3.6 新Entity对Entity的更新必须在事物内完成。和persist中一样,关系元数据的cascade属性对是否集联删除有影响。 transaction.begin(); Father father = manager.find( Father.class, 1 ); // 更新原始数据类型 father.setName( "newName" ); // 更新对象引用 Son newSon = new Son(); father.setSon( newSon ); // 提交事务,刚才的更新同步到数据库 transaction.commit(); 3.7 删除Entity对Entity的删除必须在事物内完成。 transaction.begin(); Father father = manager.find( Father.class, 1 ); // 如果father/son的@OneToOne的cascade=CascadeType.ALL,在删除father时候,也会把son删除。 // 把cascade属性设为cascade=CascadeType.REMOVE有同样的效果。 manager.remove( father ); // 提交事务,刚才的更新同步到数据库 transaction.commit(); 3.8 脱离/附合(Detach/Merge)在三层或者分布式应用中,我们很多时候需要Entity能脱离EntityManager,避免长时间保持EntityManager打开占用资源和可以在不同的JVM之间传递Entity。在脱离EJB3 Persistence Runtime(EntityManager)的管理后,我们仍然可以读取或者修改Entity中的内容。而在稍后的时间,我们又可以将Entity重新和原有或者新的EntityManager附合,如果附合前Entity被改动过,更改的数据可以自动的被发现并和数据库同步。 EntityManager entityManager = emf.createEntityManager(); // 这时Father还是被EntityManager管理的 Father father = manager.find( Father.class, 1 ); // 当entityManger关闭的时候,当前被entityManager管理的Entity都会自动的脱离EntityManager,状态转变为detached entityManager.close(); // 脱离EntityManager后,我们仍然可以修改Father的属性 father.setName( "newName" ); // 在稍后的,我们可以将father重新附和到一个新的或者原来的EntityManager中 EntityManager newEntityManager = emf.createEntityManager(); // 附合( merge )需要在事务中进行 newEntityManager.getTransaction().begin(); newEntityManager.merge( father ); // commit后father中的被修改的内容会同步到数据库。 newEntityManager.getTransaction().commit(); Liberator EJB3 Persistnece运行环境由于支持Persistence-by-reachablity。我们建议采用cascade=CascadeType.ALL,这样当需要附合的是一个对象图的时候,我们只需要merge根对象即可,整个对象图,对象图中被修改过的对象都会被自动识别和同步。需要注意的是在脱离EJB3 Persistence Runtime的管理后,如果对象中有定义为lazy-load的属性将无法访问。
4.9 比较Entity在查询中使用参数查询时,参数类型除了String, 原始数据类型( int, double等)和它们的对象类型( Integer, Double等),也可以是Entity的实例。 final Query query = entityManager.createQuery( "select o from Order o where o.address = ?1 order by o.id"); final Address address = new Address( 2001, "foo street", "foo city", "foo province" ); // 直接把address对象作为参数。 query.setParameter( 1, address );4.10 批量更新(Batch Update)EJB3 QL支持批量更新。 Query query = managerNew.createQuery("update Order as o set o.vender=:newvender, o.partNumber='fooPart' where o.vender = 'foo'"); query.setParameter("newvender", "barVender"); // update的记录数 int result = query.executeUpdate(); 4.11 批量删除(Batch Remove)EJB3 QL支持批量删除。 Query query = managerNew.createQuery("DELETE FROM Order"); int result = query.executeUpdate(); Query query = managerNew.createQuery("DELETE FROM Order AS o WHERE o.vender='redsoft'"); int result = query.executeUpdate(); 4.12 使用操作符NOT // 查询所有vender不等于"foo"的Order Query query = managerNew.createQuery("SELECT FROM Order AS o WHERE not(o.vender='foo')"); List result = query.getResultList(); // 删除所有vender不等于"foo"的Order Query query = managerNew.createQuery("DELETE FROM Order AS o WHERE not(o.vender='foo')"); int result = query.executeUpdate(); 4.13 使用操作符BETWEEN // 查询所有价值amount在5和10之间的(包含5,10)的Order Query query = managerNew.createQuery("select o FROM Order AS o left join o.orderItems ot where o.amount BETWEEN 5 AND 10 order by o.vender desc"); List result = query.getResultList(); 4.14 使用操作符IN // 查询所有vender是"foo1", "foo2"或者"foo3"的Order Query query = managerNew.createQuery("select o FROM Order AS o left join o.orderItems ot where o.vender in ( 'foo1', 'foo2', 'foo3' ) order by o.vender desc"); List result = query.getResultList(); 4.15 使用操作符LIKE // 查询所有vender以字符串"foo"开头的Order Query query = managerNew.createQuery("select o FROM Order as o where o.vender like 'foo%' order by o.vender desc"); List result = query.getResultList(); // 查询所有vender以字符串"foo"结尾的Order Query query = managerNew.createQuery("select o FROM Order as o where o.vender like '%foo' order by o.vender desc"); List result = query.getResultList(); // 可以结合NOT一起使用,比如查询所有vender不以以字符串"foo"结尾的Order Query query = managerNew.createQuery("select o FROM Order as o where o.vender not like '%foo' order by o.vender desc"); List result = query.getResultList(); // 可以结合escape使用,比如查询所有vender以"foo"开始的Order并忽略'3'字符。 // 如果vender是"foo1", "foo2", "foo3"符合这个条件, 另外"3foo1", "f3oo4"也符合条件。 Query query = managerNew.createQuery("select o FROM Order as o where o.vender like '%foo' escape '3' order by o.vender desc"); List result = query.getResultList(); 4.16 使用操作符IS NULL // 查询所有没有地址的Order Query query = managerNew.createQuery("select o FROM Order as o where o.address is null"); List result = query.getResultList(); // 查询所有地址非空的Order Query query = managerNew.createQuery("select o FROM Order as o where o.address is not null"); List result = query.getResultList(); 4.17 使用操作符IS EMPTYIS EMPTY是针对集合属性(Collection)的操作符。可以和NOT一起使用。 // 查询orderItems集合为空的Order Query query = managerNew.createQuery("select o FROM Order o where o.orderItems is empty by o.vender desc"); List result = query.getResultList(); // 查询orderItems集合非空的Order Query query = managerNew.createQuery("select o FROM Order o where o.orderItems is not empty by o.vender desc"); List result = query.getResultList();
4.18 使用操作符EXISTS[NOT]EXISTS需要和子查询配合使用。 Query query = manager.createQuery("select o FROM Order o where exists (select o from Order o where o.partNumber=?1) order by o.vender desc"); query.setParameter(1, "partNumber"); Query query = manager.createQuery("select o FROM Order o where o.vender='partNumber' and not exists (select o from Order o where o.partNumber=?1) order by o.vender desc"); query.setParameter(1, "partNumber"); 4.19 使用操作符ALL/SOME/ANY Query query = managerNew.createQuery("select emp from EmployeeA emp where emp.salary > all ( select m.salary from Manager m where m.department = emp.department)"); List result = query.getResultList(); Query query = managerNew.createQuery("select emp from EmployeeA emp where emp.salary > any ( select m.salary from Manager m where m.department = emp.department)"); List result = query.getResultList(); Query query = managerNew.createQuery("select emp from EmployeeA emp where emp.salary > some ( select m.salary from Manager m where m.department = emp.department)"); List result = query.getResultList(); 4.20 字符串函数EJB3 QL定义了内置函数方便使用。这些函数的使用方法和SQL中相应的函数方法类似。EJB3 QL中定义的字符串函数包括:
CONCAT 字符串拼接
SUBSTRING 字符串截取
TRIM 去掉空格
LOWER 转换成小写
UPPER 装换成大写
LENGTH 字符串长度
LOCATE 字符串定位
// concat将参数中的两个字符串并结成一个字符串,这里firstName是"foo", lastName是"bar" Query query = entityManager.createQuery("select concat( o.owner.firstName, o.owner.lastName ) FROM Order AS o left outer join o.orderItems as oi where o.owner.firstName='foo'"); List result = query.getResultList(); assertEquals("foobar", result.get(0).toString()); // firstName是"fooBar",结果应该返回"oo" Query query = entityManager.createQuery("select o.vender,substring( o.owner.firstName, 1, 3 ), o.owner.info.age FROM Order AS o left outer join o.orderItems as oi where o.owner.firstName='charles'"); List result = query.getResultList(); Object[] row1 = (Object[]) result.get(0); assertEquals("oo", row1[1].toString()); // 获得"ar"在firstName中地起始位置 Query query = managerNew.createQuery("SELECT emp.firstName , emp.salary , locate( emp.firstName, 'ar') FROM EmployeeA as emp where emp.firstName='charles1111'"); List result = query.getResultList(); 4.21 计算函数EJB3 QL中定义的计算函数包括:
ABS 绝对值
SQRT 平方根
MOD 取余数
SIZE 取集合的数量
Query query = entityManager.createQuery("select o.vender, size( o.orderItems ) FROM Order o where o.owner.firstName = 'charles' group by o.vender order by o.vender desc"); List result = query.getResultList(); // 函数也可以用在条件中 Query query = managerNew.createQuery("select o.vender, sum(o.amount) FROM Order AS o left join o.orderItems ot group by o.vender having size(o.orderItems) = 0 or lower( o.vender ) = 'foo' order by o.vender desc"); List result = query.getResultList(); // 取余数 Query query = managerNew.createQuery("select mod( o.owner.info.age, 10 ) FROM Order o where exists ( select o from Order o where o.partNumber= :name ) and o.vender='order1' and exists ( select o from Order o where o.amount= :name1 ) order by o.vender desc"); 4.22 子查询子查询可以用于WHERE和HAVING条件语句中。 Query query = managerNew.createQuery("select emp from EmployeeA as emp where ( select count(m) from Manager as m where m.department = emp.department) > 0 "); List result = query.getResultList(); 4.23 原生SQL查询EJB3 QL对原生SQL做了非常好的支持。采用原生SQL做查询结果不但可以是象SQL中的返回列值,也可以是Entity类,甚至可以是两者的混合。EJB3 EntityManager接口定义了多个原生SQL的产生方法。
public Query createNativeQuery(String sqlString);
public Query createNativeQuery(String sqlString, Class resultClass);
public Query createNativeQuery(String sqlString, String resultSetMapping);
一个简单的SQL查询: Query query = entityManager.createNativeQuery("select o.id, o.vender as vender from OrderTable"); // 集合中的每个元素是一个数组,代表一行返回列值,每一个数具有2个元素,分别是id列,vender列 List result = query.getResultList(); Object[] row1 = (Object[])result.get(0); 在原生SQL中使用参数和EJB3 QL中使用参数的方法一致,但只支持位置参数。 Query query = entityManager.createNativeQuery("select o.id, o.vender as vender from OrderTable where o.id = ?1"); query.setParameter( 1, 200 ); // 集合中的每个元素是一个数组,代表一行返回列值,每一个数具有2个元素,分别是id列,vender列 List result = query.getResultList(); Object[] row1 = (Object[])result.get(0); 除了象上面例子中直接返回查询结果的列值,我们可以让EJB3 Persistence运行环境将列值直接填充入一个Entity的实例,并将实例作为结果返回。 // 我们这里将结果定义为Entity类Order的实例 Query query = entityManager.createNativeQuery("select o.id, o.vender, o.partId from OrderTable o order by o.amount desc", Order.class); // 结果集合中是Order类的实例,但Order实例只有id, vender, partId三列对应的属性变量有值,其他的变量属性为空。 List result = query.getResultList(); Order order1 = (Order)result.get( 0 );
5.1 一对一映射双向一对一关系需要在关系维护端(owner side)的one2one Annotition定义mappedBy属性。建表时在关系被维护端(inverse side)建立外键列指向关系维护端的主键列。假设Country 和 Capital 是双向一对一的关系,具体元数据声明如下:
public class Country {
@OneToOne(optional = true,cascade = CascadeType.ALL, mappedBy = "country")
private Capital capital;
}
public class Capital {
@OneToOne(optional = false, cascade = CascadeType.ALL)
@JoinColumn(name = "COUNTRY_ID", referencedColumnName = "id")
private Country country;
代码中元数据的说明:元数据描述:@OneToOne(optional = true,cascade = CascadeType.ALL, mappedBy = "country")optional声明关系是否是必须存在的,即是否允许其中一端为null。cascade声明级联操作。@JoinColumn(name = "COUNTRY_ID", referencedColumnName = "id")name声明外键列的名字,referencedColumnName声明外键指向列的列名。5.2 一对多映射双向一对多关系,一是关系维护端(owner side),多是关系被维护端(inverse side)。 建表时在关系被维护端建立外键列指向关系维护端的主键列。假设Father 和 Child 是双向一对多的关系,具体元数据声明如下:
public class Father {
@OneToMany(targetEntity = Child.class, cascade = CascadeType.ALL, mappedBy = "father")
public List<Child> getChildren() {
return children;
}
}
public class Child {
@ManyToOne
@JoinColumn(name = "FATHER_ID", referencedColumnName = "id")
public Father getFather() {
return father;
}
}
代码中元数据的说明:
元数据描述:@OneToMany(targetEntity = Child.class, cascade = CascadeType.ALL, mappedBy = "father")targetEntity = Child.class表明关系另一端的实体类型cascade声明级联操作。mappedBy声明关系维护端的字段(field)名。@ManyToOne@JoinColumn(name = "FATHER_ID", referencedColumnName = "id")name声明外键列的名字,referencedColumnName声明外键指向列的列名。5.3 多对多映射多对多映射采取中间表连接的映射策略,建立的中间表将分别引入两边的主键作为外键。EJB3对于中间表的元数据提供了可配置的方式,用户可以自定义中间表的表名,列名。假设Teacher 和 Student是多对多的关系,具体元数据声明如下:
pubic class Teacher{
@ManyToMany(targetEntity = Student.class, cascade = CascadeType.PERSIST)
@JoinTable(table = @Table(name = "M2M_TEACHER_STUDENT"),
joinColumns = @JoinColumn(name = "TEACHER_ID", referencedColumnName = "ID"),
inverseJoinColumns = @JoinColumn(name = "STUDENT_ID", referencedColumnName = "ID"))
public List<Student> getStudents() {return students;}
}
public class Student{
@ManyToMany(targetEntity = Teacher.class, mappedBy = "students")
public List<Teacher> getTeachers() {
return teachers;
}
}
代码中元数据的说明:元数据描述:@ManyToMany(targetEntity = Student.class, cascade = CascadeType.PERSIST)targetEntity = Student.class表明关系另一端的实体类型。cascade声明级联操作。@JoinTable(table = @Table(name = "M2M_TEACHER_STUDENT"),joinColumns = @JoinColumn(name = "TEACHER_ID", referencedColumnName = "ID"),inverseJoinColumns = @JoinColumn(name = "STUDENT_ID", referencedColumnName = "ID"))JoinTable配置中间表信息,它由3个部分组成:1) table = @Table(name = "M2M_TEACHER_STUDENT") ,声明中间表的名字2) joinColumns ,定义中间表与关系维护端的外键关系。3) inverseJoinColumns,定义中间表与inverse端的外键关系.
第六部分 继承(Inheritance strategy)EJB3规定了三种基本的继承映射策略:.每个类分层结构一张表(table per class hierarchy) .每个子类一张表(table per subclass) .每个具体类一张表(table per concrete class)在我们提供的Alpha版本中仅支持第一种映射策略,即每个类层次一个表。我们将在下一个版本中提供每个具体类一张表的支持, 考虑到性能,这两个映射策略也是推荐的映射策略. 每个类分层结构一张表(Table per class hierarchy)假设有这么一个继承类层次:Employee,两个子类FullTimeEmployee,PartTimeEmployee 源代码如下所示: @Entity@Table( name="inheritance_Employee" )@Inheritance(strategy=InheritanceType.SINGLE_TABLE, discriminatorType=DiscriminatorType.STRING, discriminatorValue="employee")public class Employee {...} @Entity@Inheritance(discriminatorValue="fullTimeEmp")public class FullTimeEmployee extends Employee {...}@Entity@Inheritance(discriminatorValue="partTimeEmp")public class PartTimeEmployee extends Employee {...} 代码中元数据的说明:基类中元数据描述:@Inheritance(strategy=InheritanceType.SINGLE_TABLE,discriminatorType=DiscriminatorType.STRING,discriminatorValue="employee")strategy=InheritanceType.SINGLE_TABLE表示继承映射采用第一种映射策略。discriminatorType=DiscriminatorType.STRING表示继承层次中类型识别列类型为String.discriminatorValue="employee" 表示此类对应的类型识别码为employee.
第七部分 一些重要的关键字和元数据7.1 TableTable用来定义entity主表的name,catalog,schema等属性。元数据属性说明:
name: 表名
catalog: 对应关系数据库中的catalog
schema:对应关系数据库中的schema
UniqueConstraints:定义一个UniqueConstraint数组,指定需要建唯一约束的列(具体参看UniqueConstraint)
@Entity @Table(name="CUST") public class Customer { ... }7.2 SecondaryTable一个entity class可以映射到多表,SecondaryTable用来定义单个从表的名字,主键名字等属性。元数据属性说明:
name: 表名
catalog: 对应关系数据库中的catalog
schema:对应关系数据库中的schema
pkJoin: 定义一个PrimaryKeyJoinColumn数组,指定从表的主键列(具体参看PrimaryKeyJoinColumn)
UniqueConstraints:定义一个UniqueConstraint数组,指定需要建唯一约束的列(具体参看UniqueConstraint)
下面的代码说明Customer类映射到两个表,主表名是CUSTOMER,从表名是CUST_DETAIL,从表的主键列和主表的主键列类型相同,列名为CUST_ID。 @Entity @Table(name="CUSTOMER") @SecondaryTable(name="CUST_DETAIL",pkJoin=@PrimaryKeyJoinColumn(name="CUST_ID")) public class Customer { ... } 7.3 SecondaryTables当一个entity class映射到一个主表和多个从表时,用SecondaryTables来定义各个从表的属性。元数据属性说明:
value: 定义一个SecondaryTable数组,指定每个从表的属性。
@Table(name = "CUSTOMER") @SecondaryTables( value = { @SecondaryTable(name = "CUST_NAME", pkJoin = { @PrimaryKeyJoinColumn(name = "STMO_ID", referencedColumnName = "id") }), @SecondaryTable(name = "CUST_ADDRESS", pkJoin = { @PrimaryKeyJoinColumn(name = "STMO_ID", referencedColumnName = "id") }) }) public class Customer {} 7.4 UniqueConstraintUniqueConstraint定义在Table或SecondaryTable元数据里,用来指定建表时需要建唯一约束的列。元数据属性说明:
columnNames:定义一个字符串数组,指定要建唯一约束的列名。
@Entity @Table(name="EMPLOYEE", uniqueConstraints={@UniqueConstraint(columnNames={"EMP_ID", "EMP_NAME"})} ) public class Employee { ... } 7.5 ColumnColumn元数据定义了映射到数据库的列的所有属性:列名,是否唯一,是否允许为空,是否允许更新等。元数据属性说明:
name:列名。
unique: 是否唯一
nullable: 是否允许为空
insertable: 是否允许插入
updatable: 是否允许更新
columnDefinition: 定义建表时创建此列的DDL
secondaryTable: 从表名。如果此列不建在主表上(默认建在主表),该属性定义该列所在从表的名字。
public class Person { @Column(name = "PERSONNAME", unique = true, nullable = false, updatable = true) private String name; @Column(name = "PHOTO", columnDefinition = "BLOB NOT NULL", secondaryTable="PER_PHOTO") private byte[] picture; 7.6 JoinColumn如果在entity class的field上定义了关系(one2one或one2many等),我们通过JoinColumn来定义关系的属性。JoinColumn的大部分属性和Column类似。元数据属性说明:
name:列名。
referencedColumnName:该列指向列的列名(建表时该列作为外键列指向关系另一端的指定列)
unique: 是否唯一
nullable: 是否允许为空
insertable: 是否允许插入
updatable: 是否允许更新
columnDefinition: 定义建表时创建此列的DDL
secondaryTable: 从表名。如果此列不建在主表上(默认建在主表),该属性定义该列所在从表的名字。
下面的代码说明Custom和Order是一对一关系。在Order对应的映射表建一个名为CUST_ID的列,该列作为外键指向Custom对应表中名为ID的列。 public class Custom { @OneToOne @JoinColumn( name="CUST_ID", referencedColumnName="ID", unique=true, nullable=true, updatable=true) public Order getOrder() { return order; } 7.7 JoinColumns如果在entity class的field上定义了关系(one2one或one2many等),并且关系存在多个JoinColumn,用JoinColumns定义多个JoinColumn的属性。元数据属性说明:
value: 定义JoinColumn数组,指定每个JoinColumn的属性。
下面的代码说明Custom和Order是一对一关系。在Order对应的映射表建两列,一列名为CUST_ID,该列作为外键指向Custom对应表中名为ID的列,另一列名为CUST_NAME,该列作为外键指向Custom对应表中名为NAME的列。 public class Custom { @OneToOne @JoinColumns({ @JoinColumn(name="CUST_ID", referencedColumnName="ID"), @JoinColumn(name="CUST_NAME", referencedColumnName="NAME") }) public Order getOrder() { return order; } 7.8 Id声明当前field为映射表中的主键列。id值的获取方式有五种:TABLE, SEQUENCE, IDENTITY, AUTO, NONE。Oracle和DB2支持SEQUENCE,SQL Server和Sybase支持IDENTITY,mysql支持AUTO。所有的数据库都可以指定为AUTO,我们会根据不同数据库做转换。NONE(默认)需要用户自己指定Id的值。Table类型详见TableGenerator的介绍。元数据属性说明:
generate():主键值的获取类型
generator():TableGenerator的名字(当generate=GeneratorType.TABLE才需要指定该属性)
下面的代码声明Task的主键列id是自动增长的。(Oracle和DB2从默认的SEQUENCE取值,SQL Server和Sybase该列建成IDENTITY,mysql该列建成auto increment。) @Entity @Table(name = "OTASK") public class Task { @Id(generate = GeneratorType.AUTO) public Integer getId() { return id; } } 7.9 IdClass当entity class使用复合主键时,需要定义一个类作为id class。id class必须符合以下要求:类必须声明为public,并提供一个声明为public的空构造函数。必须实现Serializable接,覆写equals()和hashCode()方法。entity class的所有id field在id class都要定义,且类型一样。元数据属性说明:
value: id class的类名
public class EmployeePK implements java.io.Serializable{ String empName; Integer empAge; public EmployeePK(){} public boolean equals(Object obj){ ......} public int hashCode(){......} } @IdClass(value=com.acme.EmployeePK.class) @Entity(access=FIELD) public class Employee { @Id String empName; @Id Integer empAge; }
7.10 MapKey在一对多,多对多关系中,我们可以用Map来保存集合对象。默认用主键值做key,如果使用复合主键,则用id class的实例做key,如果指定了name属性,就用指定的field的值做key。元数据属性说明:
name: 用来做key的field名字
下面的代码说明Person和Book之间是一对多关系。Person的books字段是Map类型,用Book的isbn字段的值作为Map的key。 @Table(name = "PERSON") public class Person { @OneToMany(targetEntity = Book.class, cascade = CascadeType.ALL, mappedBy = "person") @MapKey(name = "isbn") private Map<String, Book> books = new HashMap<String, Book>(); } 7.11 OrderBy在一对多,多对多关系中,有时我们希望从数据库加载出来的集合对象是按一定方式排序的,这可以通过OrderBy来实现,默认是按对象的主键升序排列。元数据属性说明:
value: 字符串类型,指定排序方式。格式为"fieldName1 [ASC|DESC],fieldName2 [ASC|DESC],......",排序类型可以不指定,默认是ASC。
下面的代码说明Person和Book之间是一对多关系。集合books按照Book的isbn升序,name降序排列。 @Table(name = "MAPKEY_PERSON") public class Person { @OneToMany(targetEntity = Book.class, cascade = CascadeType.ALL, mappedBy = "person") @OrderBy(name = "isbn ASC, name DESC") private List<Book> books = new ArrayList<Book>(); } 7.12 PrimaryKeyJoinColumn在三种情况下会用到PrimaryKeyJoinColumn。
继承(详见Inheritance)。
entity class映射到一个或多个从表。从表根据主表的主键列(列名为referencedColumnName值的列),建立一个类型一样的主键列,列名由name属性定义。
one2one关系,关系维护端的主键作为外键指向关系被维护端的主键,不再新建一个外键列(与JoinColumn不同)。
元数据属性说明:
name:列名。
referencedColumnName:该列引用列的列名
columnDefinition: 定义建表时创建此列的DDL
下面的代码说明Customer映射到两个表,主表CUSTOMER,从表CUST_DETAIL,从表需要建立主键列CUST_ID,该列和主表的主键列id除了列名不同,其他定义一样。 @Entity @Table(name="CUSTOMER") @SecondaryTable(name="CUST_DETAIL",pkJoin=@PrimaryKeyJoinColumn(name="CUST_ID",referencedColumnName="id")) public class Customer { @Id(generate = GeneratorType.AUTO) public Integer getId() { return id; } } 下面的代码说明Employee和EmployeeInfo是一对一关系,Employee的主键列id作为外键指向EmployeeInfo的主键列INFO_ID。 @Table(name = "Employee") public class Employee { @OneToOne @PrimaryKeyJoinColumn(name = "id", referencedColumnName="INFO_ID") EmployeeInfo info; } 7.13 PrimaryKeyJoinColumns如果entity class使用了复合主键,指定单个PrimaryKeyJoinColumn不能满足要求时,可以用PrimaryKeyJoinColumns来定义多个PrimaryKeyJoinColumn。 元数据属性说明:
value: 一个PrimaryKeyJoinColumn数组,包含所有PrimaryKeyJoinColumn。
下面的代码说明了Employee和EmployeeInfo是一对一关系。他们都使用复合主键,建表时需要在Employee表建立一个外键,从Employee的主键列id,name指向EmployeeInfo的主键列INFO_ID和INFO_NAME. @Entity @IdClass(EmpPK.class) @Table(name = "EMPLOYEE") public class Employee { private int id; private String name; private String address; @OneToOne(cascade = CascadeType.ALL) @PrimaryKeyJoinColumns({ @PrimaryKeyJoinColumn(name="id", referencedColumnName="INFO_ID"), @PrimaryKeyJoinColumn(name="name" , referencedColumnName="INFO_NAME")}) EmployeeInfo info; } @Entity @IdClass(EmpPK.class) @Table(name = "EMPLOYEE_INFO") public class EmployeeInfo { @Id @Column(name = "INFO_ID") private int id; @Id @Column(name = "INFO_NAME") private String name; } 7.14 TransientTransient用来注释entity的属性,指定的这些属性不会被持久化,也不会为这些属性建表。 @Transient private String name; 7.15 VersionVersion指定实体类在乐观事务中的version属性。在实体类重新由EntityManager管理并且加入到乐观事务中时,保证完整性。每一个类只能有一个属性被指定为version,version属性应该映射到实体类的主表上。下面的代码说明versionNum属性作为这个类的version,映射到数据库中主表的列名是OPTLOCK。 @Version @Column("OPTLOCK") protected int getVersionNum() { return versionNum; } 7.16 LobLob指定一个属性作为数据库支持的大对象类型在数据库中存储。使用LobType这个枚举来定义Lob是二进制类型还是字符类型。LobType枚举类型说明:
BLOB 二进制大对象,Byte[]或者Serializable的类型可以指定为BLOB。
CLOB 字符型大对象,char[]、Character[]或String类型可以指定为CLOB。
元数据属性说明:
fetch: 定义这个字段是lazy loaded还是eagerly fetched。数据类型是FetchType枚举,默认为LAZY,即lazy loaded.
type: 定义这个字段在数据库中的JDBC数据类型。数据类型是LobType枚举,默认为BLOB。
下面的代码定义了一个BLOB类型的属性和一个CLOB类型的属性。 @Lob @Column(name="PHOTO" columnDefinition="BLOB NOT NULL") protected JPEGImage picture; @Lob(fetch=EAGER, type=CLOB) @Column(name="REPORT") protected String report; 7.17 JoinTableJoinTable在many-to-many关系的所有者一边定义。如果没有定义JoinTable,使用JoinTable的默认值。元数据属性说明:
table:这个join table的Table定义。
joinColumns:定义指向所有者主表的外键列,数据类型是JoinColumn数组。
inverseJoinColumns:定义指向非所有者主表的外键列,数据类型是JoinColumn数组。
下面的代码定义了一个连接表CUST和PHONE的join table。join table的表名是CUST_PHONE,包含两个外键,一个外键是CUST_ID,指向表CUST的主键ID,另一个外键是PHONE_ID,指向表PHONE的主键ID。 @JoinTable( table=@Table(name=CUST_PHONE), joinColumns=@JoinColumn(name="CUST_ID", referencedColumnName="ID"), inverseJoinColumns=@JoinColumn(name="PHONE_ID", referencedColumnName="ID") ) 7.18 TableGeneratorTableGenerator定义一个主键值生成器,在Id这个元数据的generate=TABLE时,generator属性中可以使用生成器的名字。生成器可以在类、方法或者属性上定义。生成器是为多个实体类提供连续的ID值的表,每一行为一个类提供ID值,ID值通常是整数。元数据属性说明:
name:生成器的唯一名字,可以被Id元数据使用。
table:生成器用来存储id值的Table定义。
pkColumnName:生成器表的主键名称。
valueColumnName:生成器表的ID值的列名称。
pkColumnValue:生成器表中的一行数据的主键值。
initialValue:id值的初始值。
allocationSize:id值的增量。
下面的代码定义了两个生成器empGen和addressGen,生成器的表是ID_GEN。 @Entity public class Employee { ... @TableGenerator(name="empGen", table=@Table(name="ID_GEN"), pkColumnName="GEN_KEY", valueColumnName="GEN_VALUE", pkColumnValue="EMP_ID", allocationSize=1) @Id(generate=TABLE, generator="empGen") public int id; ... } @Entity public class Address { ... @TableGenerator(name="addressGen", table=@Table(name="ID_GEN"), pkColumnValue="ADDR_ID") @Id(generate=TABLE, generator="addressGen") public int id; ... } 7.19 SequenceGeneratorSequenceGenerator定义一个主键值生成器,在Id这个元数据的generator属性中可以使用生成器的名字。生成器可以在类、方法或者属性上定义。生成器是数据库支持的sequence对象。元数据属性说明:
name:生成器的唯一名字,可以被Id元数据使用。
sequenceName:数据库中,sequence对象的名称。如果不指定,会使用提供商指定的默认名称。
initialValue:id值的初始值。
allocationSize:id值的增量。
下面的代码定义了一个使用提供商默认名称的sequence生成器。 @SequenceGenerator(name="EMP_SEQ", allocationSize=25) 7.20 DiscriminatorColumnDiscriminatorColumn定义在使用SINGLE_TABLE或JOINED继承策略的表中区别不继承层次的列。元数据属性说明:
name:column的名字。默认值为TYPE。
columnDefinition:生成DDL的sql片断。
length:String类型的column的长度,其他类型使用默认值10。
下面的代码定义了一个列名为DISC,长度为20的String类型的区别列。 @Entity @Table(name="CUST") @Inheritance(strategy=SINGLE_TABLE, discriminatorType=STRING, discriminatorValue="CUSTOMER") @DiscriminatorColumn(name="DISC", length=20) public class Customer { ... }
第一部分 EJB3介绍:OverviewEJB作为企业级的数据访问/持久化标准在1999年作为J2EE规范的核心规范出现,极大的转变了java企业级开发的模式,为java软件开发提供了一个良好的架构。 EJB从1.0到2.1在J2EE架构中,都是作为一个服务器端的(Server side)的数据访问中间件。开发人员通过EJB标准的API接口来访问操作数据,避免直接用JDBC和Sql操作底层的数据库。 采用EJB架构的目标在于:
减轻直接操作底层数据库的工作量
为企业级开发引入了面向对象/面向服务的开发架构
数据对象生命周期的自动管理
分布式能力
集成/声明式的安全/事务管理
在旧的EJB模型中(2.1以前),EJB实现了大部分的目标,但一个巨大的缺陷是原有的模型在试图减轻数据访问工作量的同时也引入了更多的复杂开发需求。例如EJB核心的的Entity Bean必须特定的Home,Remote,Business接口,发布前需要预编译,只能实现单表映射操作,静态的EJB-QL(EJB查询语言)都不能满足简化数据库操作的目标。 EJB 2.1的复杂度,开发成本和性能问题使得EJB在问世4年后仍然等不到广泛的应用。 到了2004年,随着POJO( Plain Old Java Object )模型的出现,动态代码操作,IOC模式等先进,简单实用技术的发展和它们在各种独立产品中的表现,都证明POJO,IOC的技术比原有的EJB 2.1模型更适合作为数据访问中间件,开发的难度和成本远远小于目前的EJB模型,确有更灵活和可扩展。 2004年9月J2EE平台规范集众家所长,推出了跨越式的Java EE 5.0规范,最核心的是全面引入新的基于POJO和IOC技术的EJB3模型。到此,J2EE 5规范( Java EE 5 )成为一个集大成者,纳百家之长,成为java企业开发统一的标准规范。 1.1 EJB 3和EJB 2.1的区别从整个EJB规范的角度来说,EJB 3和EJB 2.1最大变更在Entity Bean持久化API上。在EJB3中,Entity Bean持久化已经单独作为一个Persistence API规范和其他的EJB部分分离开来。下面我们主要讨论EJB 3和EJB 2.1在持久化API上的区别。 EJB 2.1模型存在复杂度高的缺陷:
EJB 2.0 模型要求创建多个组件接口并实现多个不必要的回调方法
组件接口要求实现 EJBObject 或 EJBLocalObject 以及处理许多不必要的异常
基于XML的EJB 2.0 部署描述符比较复杂并容易出错
基于 EJB 模型的容器管理持久性在开发和管理方面过于复杂,并且失去了几个基本特性--如使用数据库序列定义主键的标准方法
EJBQL 语法非常有限,而且是静态的,无法做到运行期间的动态查询
EJB 2.0 组件并非是真正面向对象的,因为它们在继承和多态性方面的有使用限制
开发人员无法在 EJB 容器外部测试 EJB 模块,而在容器内部调试 EJB非常复杂和耗时
查找和调用 EJB 2.0 是一项复杂的任务。即使是在应用程序中使用最基本的 EJB 也需要对 JNDI 有一个详细的了解
对容器的依赖使得EJB 2.0只能用于服务器组件的开发,无法实现一次编写,四处运行的面向构件的开发
所有这些复杂度和缺陷,都导致EJB 2.0的采用无法真正简化开发并提高生产力。 EJB 3.0 旨在解决以往EJB 2.0 模型的复杂性和提高灵活性,具体体现在:
消除了不必要的接口Remote, Home, EJB以及回调方法实现
实体Bean采用了POJO模型,一个简单的java bean就可以是一个Entity Bean。无需依赖容器运行和测试
全面采用O/R Mapping技术来实现数据库操作
实体Bean可以运用在所有需要持久化的应用,不管是客户端还是服务器端。从而真正实现面向构件的开发
实体 bean 现在支持继承和多态性
灵活丰富的EJB3查询语言
SQL支持
使用元数据批注代替部署描述符,减少复杂配置和提高可维护性
将常规 Java 类用作 EJB 并将常规业务接口用于 EJB
1.2 EJB 3中的元数据批注:AnnotationEJB3 规范中引入了元数据批注(Annotation)。Annotation是从J2SE 1.5开始称为java语言的一部分。Annotation并不是一个新的事物,在J2SE 1.5以前,人们已经自行引入了象著名的XDoclet等外挂式的元数据批注方法。而在.NET中,元数据批注也早已经是C#语言的成分了。 在以往,我们都是采用xml作为配置批注,但采用文本的xml配置存在一些缺陷:
描述符多,不容易记忆和掌握
无法做自动的校验,需要人工排错
当系统变大时,大量的xml配置难以管理
读取和解析xml配置非常耗时,导致应用启动缓慢,不利于测试和维护
做O/R Mapping的时候需要在java文件和xml配置文件之间交替,增大了工作量
运行中保存xml配置需要消耗额外的内存
采用元数据可以很好的解决这些问题:
描述符大量减少。以往在xml配置中往往需要描述java属性的类型,关系等等。而元数据本身就是java语言,从而省略了大量的描述符
编译期校验。错误的批注在编译期间就会报错。
元数据批注在java代码中,避免了额外的文件维护工作
元数据被编译成java bytecode,消耗小的多内存,读取也非常迅速,往往比xml配置解析快几个数据量级,利于测试和维护
第二部分 Entity介绍2.1 第一个Entity Bean:HelloWorldEJB3中的Entity Bean是如此的简单,就是一个普通的java bean加上一些精炼的元数据批注。 @Entity@Table( name="helloTable" )public class HelloEntityBean { private int id; private String foo; /** * The entity class must have a no-arg constructor. */ public HelloEntityBean() { } public HelloEntityBean(int id, String foo) { this.id = id; this.foo = foo; } @Id(generate=GeneratorType.NONE) public int getId() { return id; } public void setId(int id) { this.id = id; } public String getFoo() { return foo; } public void setFoo(String foo) { this.foo = foo; } public String toString(){ return "HelloEntityBean: id=" + id + ", foo=" + foo; }} 代码中元数据的说明:@Entity :EJB3 Entity Bean的批注,表明当前的java bean作为一个Entity Bean来处理。@Table( name="helloTable" ) :顾名思义,定义当前Entity对应数据库中的表。@Id(generate=GeneratorType.NONE) :我们把HelloEntityBean的id属性定义为主键。主键产生策略为GeneratorType.NONE,意味这是一个业务主键。就这么简单!2.2 解说Entity从EJB3.0开始,Entity Bean持久化规范与EJB的其他规范如Session Bean, Message Driven Bean和EJB3容器规范开始分离,单独作为一个持久化API。而在将来,该持久化API很可能会从EJB3.0规范中脱离出来成为一个独立的规范Java Persistence API。作为Java平台上的通用数据访问标准接口。 为了跟规范一致,我们将在开发手册中将Entity Bean称为Entity。虽然EJB3 Entity可以是很简单的java bean,只要批注了@Entity或者在xml配置中作了说明,就被做一个可持久化的Entity处理。 但还是需要遵行一定的规则:
Entity类必须要有一个无参数的public或者protected的Constructor。
如果在应用中需要将该Entity类分离出来在分布式环境中作为参数传递,该Entity Class需要实现java.io.Serialzable接口。
Entity类不可以是final,也不可有final的方法。
abstract类和Concrete实体类都可以作为Entity类。
Entity类中的属性变量不可以是public。Entity类的属性必须通过getter/setter或者其他的商业方法获得。
2.3 定义对Entity中属性变量的访问在绝大部分的商业应用,开发人员都可以忽略这部分无需关心。但如果你需要编写复杂的Entity类的话,你需要了解这个部分。复杂的Entity类是指在Entity类的getter/setter和商业方法中包含比较复杂的业务逻辑而不是仅仅返回/符值某个属性。在大部分的情况下,我们都建议使Entity类中setter/getter中的逻辑尽可能简单,除了必要的校验符值外,不要包含复杂的业务逻辑,例如对关联的其他Entity类进行操作。但有些情况下,我们还是需要在Entity类的setter/getter方法中包含商业逻辑。这时候,采用何种属性访问方式就可能会影响代码的性能甚至是逻辑正确产生影响。EJB3持久化规范中,在默认情况下所有的属性都会自动的被持久化,除非属性变量用@Transient元数据进行了标注。针对可持久化属性定义了两种属性访问方式(access): FIELD和PROPERTY。
如果采用access=FIELD, EJB3 Persistence运行环境(EJB3持久化产品,如Liberator EJB3)直接访问对象的属性变量,而不是通过getter。这种访问方式也不要求每个属性必须有getter/setter。如果需要在getter中包含商业逻辑,应该采用access=FIELD的方式。
如果采用access=PROPERTY, EJB3 Persistence运行环境将通过Entity类上的getter来访问对象的属性变量,这就要求每个属性变量要有getter/setter方法。在EJB3中,默认的属性访问方式是PROPERTY。access=PROPERTY时getter/setter的逻辑应该尽量简单。
规范中access方式还有多一层含义。就是采用access=FIELD时,元数据应该批注在属性上。 @Id(generate=GeneratorType.NONE) private int id; private String foo; /** * The entity class must have a no-arg constructor. */ public HelloEntityBean() { } public int getId() { return id; } 采用access=PROPERTY(默认方式)时,元数据应该批注在对应属性变量的getter上。 private int id; private String foo; /** * The entity class must have a no-arg constructor. */ public HelloEntityBean() { } @Id(generate=GeneratorType.NONE) public int getId() { return id; } 为了方便开发,Liberator EJB3实现对元数据批注的位置比规范规定的宽松,针对属性变量或它的getter进行批注都可以,不受access类型的影响。对Entity类中,getter/setter的命名规则遵从java bean规范。Entity类中的属性变量可以是以下数据类型:
原始数据类型和他们的对象类型
java.lang.String
java.math.BigInteger
java.math.BigDecimal
java.util.Date
java.util.Calendar
java.sql.Date
java.sql.Time
java.sql.Timestamp
byte[]
Byte[]
char[]
Character[]
enums
Entity类
嵌入实体类(embeddable classes)
还可以是以下集合类型:
java.util.Collection和它的实体类
java.util.Set和它的实体类
java.util.List和它的实体类
java.util.Map和它的实体类
2.3 主键和实体标识(Primary Key and Entity Identity)每个Entity类都必须有一个主键。在EJB3中定义了两种主键:
键单主键
复合主键
简单主键必须对应Entity中的一个属性变量(Instance Variable),而该属性对应数据库表中的一列。使用简单主键,我们只需要用@Id元数据对一个属性变量或者她的getter方法进行批注。当我们需要使用一个或多个属性变量(表中的一列或多列)联合起来作为主键,我们需要使用复合主键。复合主键要求我们编写一个复合主键类( Composite Primary Key Class )。复合主键类需要符合以下一些要求:
复合主键类必须是public和具备一个没有参数的constructor
复合主键类的每个属性变量必须有getter/setter,如果没有,每个属性变量则必须是public或者protected
复合主键类必须实现java.io.serializable
复合主键类必须实现equals()和hashcode()方法
复合主键类中的主键属性变量的名字必须和对应的Entity中主键属性变量的名字相同
一旦主键值设定后,不要修改主键属性变量的值
一起看一个复合主键的例子。Entity类Person,它的主键属性变量是firstName和lastName。 @Id private String firstName; @Id private String lastName; public Person() {} Person的复合主键类: public class PersonPK implements java.io.Serializable{ private String firstName; private String lastName; public PersonPK() { } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof PersonPK)) return false; final PersonPK personPK = (PersonPK) o; if (!firstName.equals(personPK.firstName)) return false; if (!lastName.equals(personPK.lastName)) return false; return true; } public int hashCode() { int result; result = firstName.hashCode(); result = 29 * result + lastName.hashCode(); return result; }}
第三部分 持久化 3.1 操作持久化Entity对Entity进行操作的API都设计在javax.persistence.EntityManager接口上。EntityManager,顾名思义是管理所有EJB 3运行环境中的所有Entity。 EntityManager根据运行的环境不同分为容器管理的EntityManager和应用管理的EntityManager。Liberator EJB3是一个可以运行在J2SE环境中的 嵌入式的EJB3 Persistence实现,因此在Liberator EJB3中的 EntityManager都是指应用管理的EntityManager。3.2 配置和获得EntityManager在J2SE环境中,EJB3定义了一个javax.persistence.Persistence类用于启动EJB3运行环境。要获得EntityManager,首先需要通过javax.persistence.Persistence获得EntityManagerFactory,然后调用EntityManagerFactory.createEntityManager()方法获得。 // 获得默认当前的EntityManagerFactory final EntityManagerFactory emf = Persistence.createEntityManagerFactory(); final EntityManager entityManager = emf.createEntityManager(); 当调用Persistence.createEntityManagerFactory()的时候,Persistence会做以下的步骤:
搜索当前jar包的META-INFO/persistence.xml配置文件
如果没有在META-INFO下找到persistence.xml,搜索当前线程的ContextClassLoader中的persistence.xml
根据获得的persistence.xml初始化EntityManagerFactory
每个EntityManagerFactory都必须有一个persistence.xml配置文件。下面是一个例子: <entity-manager> <name>myEntityManager</name> <provider>com.redsoft.ejb3.PersistenceProviderImpl</provider> <class>com.redsoft.samples.HelloEntityBean</class> <properties> <property name="ConnectionDriverName" value="com.mysql.jdbc.Driver"/> <property name="ConnectionURL" value="jdbc:mysql://localhost/EJB3Test"/> <property name="ConnectionUserName" value="ejb3"/> <property name="ConnectionPassword" value="ejb3"/> <property name="RetainValues" value="true"/> <property name="RestoreValues" value="false"/> <property name="IgnoreCache" value="false"/> <property name="NontransactionalRead" value="true"/> <property name="NontransactionalWrite" value="false"/> <property name="Multithreaded" value="false"/> <property name="Liberator.option.action" value="create"/> <property name="Liberator.option.cache" value="com.redsoft.jdo.enterprise.OscacheAdapter"/> <property name="Liberator.option.JMXEnabled" value="false"/> <property name="Liberator.option.NamingPort" value="3012"/> <property name="Liberator.option.JMXPort" value="3011"/> <property name="Liberator.option.JDBC.scrollable" value="false"/> <property name="Liberator.option.JDBC.fetchSize" value="500"/> <property name="cache.algorithm" value="LRUCache"/> <property name="cache.capacity" value="5000"/> <property name="log4j.logger.com.redsoft" value="FATAL"/> </properties></entity-manager> <name></name> 定义一个EntityManagerFactory的名字,这样当应用中需要多个EntityManagerFactory的时候可以区别。通常情况下,一个EntityManagerFactory对应一个数据源。<provider></provider> 定义采用的什么EJB3 Persistence运行环境(也就是EJB3 Persistence产品),例子中采用的的是Liberator EJB3的运行环境。正如前面说的,在EJB3规范中, EJB3 Persistence运行环境( Runtime )是作为一个独立的嵌入式模块,你可以随意的更换不同的EJB3 Persistence运行环境而不会影响你的应用程序。甚至在J2EE服务器内运行你也可以不用服务器本身提供的EJB3 Persistence运行环境,而采用第三方的EJB3 Persistence运行环境。<class></class> 定义哪些类是Entity持久化类。如果是测试环境或者你不采用ear/war的方式发布你的应用,而只是把编译后的类直接拷贝到web容器中,在添加一个新的Entity类时,需要在这里添加一行。要求这个步骤是因为在J2EE 5架构中EJB3运行环境的嵌入式设计。采用这种松耦合设计使EJB3 Persistence运行环境不能控制持久化类的加载(由J2EE服务器负责加载), 这样EJB3 Persistence运行环境就无法分辨jvm中哪个类是持久化Entity类,哪个不是。因此需要一个额外的信息来让EJB3 Persistence知道那些是持久化Entity。<property></property> 定义了了EJB3 Persistence运行环境需要的其他参数,这些参数不是标准的,而是由EJB3 Persistence运行环境的厂商自行定义。详细的配置方法参看persistence.xml的语法。 3.3 Entity的生命周期和状态在EJB3中定义了四种Entity的状态:
新实体(new)。Entity由应用产生,和EJB3 Persistence运行环境没有联系,也没有唯一的标示符(Identity)。
持久化实体(managed)。新实体和EJB3 Persistence运行环境产生关联(通过persist(), merge()等方法),在EJB3 Persistence运行环境中存在和被管理,标志是在EJB3 Persistence运行环境中有一个唯一的标示(Identity)。
分离的实体(detached)。Entity有唯一标示符,但它的标示符不被EJB3 Persistence运行环境管理, 同样的该Entity也不被EJB3 Persistence运行环境管理。
删除的实体(removed)。Entity被remove()方法删除,对应的纪录将会在当前事务提交的时候从数据库中删除。
3.4 持久化Entity(Persist) final EntityManagerFactory emf = Persistence.createEntityManagerFactory(); final EntityManager entityManager = emf.createEntityManager(); final HelloEntityBean hello = new HelloEntityBean( 1, "foo" ); EntityTransaction trans = entityManager.getTransaction(); trans.begin(); // 持久化hello,在此操作之前hello的状态为new entityManager.persist( hello ); // 这时hello的状态变为managed trans.commit(); entityManager.close(); // 这时hellow的状态变为detached. Liberator EJB3支持Persistence by reachability。当保存一个Entity时,以该对象为根对象的整个对象图都会自动的被保存。但在EJB3中,我们仍然可以通过关系元数据(比如OneToOne,OneToMany)的cascade属性来精确定义保存的级联行为。 下面我们来看看不同的cascade属性的区别。 不配置cascade的情况下,EJB3 Persistence运行环境默认不会采用Persistence by reachability。 public class Father{ @Id int id String name; // OneToOne没有配置cascade属性,因此默认不 // 会使用Persistence by reachablity @OneToOne Son mySon public Father( int id, String name, Son mySon ){ this.id = id; this.name = name; this.mySon = mySon; } } 现在来保存一个Father和Son。 final EntityManager manager = emf.createEntityManager(); manager.getTransaction().begin; Son mySon = new Son(); Father = new Father( 1, "father" mySon ); // 保存Father manager.persist( father ); // 由于OneToOne关系中没有配置casacade属性,father 关联的mySon// 不会被自动保存,需要分别保存 manager.persist( mySon ); manager.getTransaction().commit(); manager.close(); 现在我们配置casacde=CascadeType.ALL public class Father{ @Id int id String name; // OneToOne配置cascade=CascadeType.ALL,配置 // cascade=CascadeType.PERSIT也对persist操作也可以获得同样// 的效果。 // CascadeType.ALL包含CascadeType.PERSIST。 @OneToOne(cascade=CascadeType.ALL) Son mySon public Father( int id, String name, Son mySon ){ this.id = id; this.mySon = mySon; this.name = name; } } 在代码中同样持久化Father和mySon。 final EntityManager manager = emf.createEntityManager(); manager.getTransaction().begin; Son mySon = new Son(); Father = new Father( 1, mySon ); // 保存Father。由于OneToOne关系中配置casacade=CascadeType.ALL属// 性,关联的mySon会自动地被持久化 manager.persist( father ); manager.getTransaction().commit(); manager.close(); 由于Liberator EJB3运行环境采用了弱引用POJO管理技术,能很好的支持Persistence by reachablity而不会有其他产品有的潜在内存回收的问题, 因此建议在应用中尽可能使用cascade=CascadeType.ALL来减少持久化操作的复杂性和代码量,特别是在有复杂对象关系图的时候。 3.5 获取Entity如果知道Entity的唯一标示符,我们可以用find()方法来获得Entity。 Father father = manager.find( Father.class, new Integer( 1 ) ); // 由于JDK1.5支持自动转型,也可以如下使用 Father father = manager.find( Father.class, 1 ); /* * 或者,可以用Entity名字作为查找。但无法利用JDK 1.5的自动转型功能, * 需要使用对象作为查找主键,并需要对获得Entity进行转型 */ Father father = (Father)manager.find( "com.redsoft.samples.Father", new Integer( 1 ) ); 3.6 新Entity对Entity的更新必须在事物内完成。和persist中一样,关系元数据的cascade属性对是否集联删除有影响。 transaction.begin(); Father father = manager.find( Father.class, 1 ); // 更新原始数据类型 father.setName( "newName" ); // 更新对象引用 Son newSon = new Son(); father.setSon( newSon ); // 提交事务,刚才的更新同步到数据库 transaction.commit(); 3.7 删除Entity对Entity的删除必须在事物内完成。 transaction.begin(); Father father = manager.find( Father.class, 1 ); // 如果father/son的@OneToOne的cascade=CascadeType.ALL,在删除father时候,也会把son删除。 // 把cascade属性设为cascade=CascadeType.REMOVE有同样的效果。 manager.remove( father ); // 提交事务,刚才的更新同步到数据库 transaction.commit(); 3.8 脱离/附合(Detach/Merge)在三层或者分布式应用中,我们很多时候需要Entity能脱离EntityManager,避免长时间保持EntityManager打开占用资源和可以在不同的JVM之间传递Entity。在脱离EJB3 Persistence Runtime(EntityManager)的管理后,我们仍然可以读取或者修改Entity中的内容。而在稍后的时间,我们又可以将Entity重新和原有或者新的EntityManager附合,如果附合前Entity被改动过,更改的数据可以自动的被发现并和数据库同步。 EntityManager entityManager = emf.createEntityManager(); // 这时Father还是被EntityManager管理的 Father father = manager.find( Father.class, 1 ); // 当entityManger关闭的时候,当前被entityManager管理的Entity都会自动的脱离EntityManager,状态转变为detached entityManager.close(); // 脱离EntityManager后,我们仍然可以修改Father的属性 father.setName( "newName" ); // 在稍后的,我们可以将father重新附和到一个新的或者原来的EntityManager中 EntityManager newEntityManager = emf.createEntityManager(); // 附合( merge )需要在事务中进行 newEntityManager.getTransaction().begin(); newEntityManager.merge( father ); // commit后father中的被修改的内容会同步到数据库。 newEntityManager.getTransaction().commit(); Liberator EJB3 Persistnece运行环境由于支持Persistence-by-reachablity。我们建议采用cascade=CascadeType.ALL,这样当需要附合的是一个对象图的时候,我们只需要merge根对象即可,整个对象图,对象图中被修改过的对象都会被自动识别和同步。需要注意的是在脱离EJB3 Persistence Runtime的管理后,如果对象中有定义为lazy-load的属性将无法访问。
4.9 比较Entity在查询中使用参数查询时,参数类型除了String, 原始数据类型( int, double等)和它们的对象类型( Integer, Double等),也可以是Entity的实例。 final Query query = entityManager.createQuery( "select o from Order o where o.address = ?1 order by o.id"); final Address address = new Address( 2001, "foo street", "foo city", "foo province" ); // 直接把address对象作为参数。 query.setParameter( 1, address );4.10 批量更新(Batch Update)EJB3 QL支持批量更新。 Query query = managerNew.createQuery("update Order as o set o.vender=:newvender, o.partNumber='fooPart' where o.vender = 'foo'"); query.setParameter("newvender", "barVender"); // update的记录数 int result = query.executeUpdate(); 4.11 批量删除(Batch Remove)EJB3 QL支持批量删除。 Query query = managerNew.createQuery("DELETE FROM Order"); int result = query.executeUpdate(); Query query = managerNew.createQuery("DELETE FROM Order AS o WHERE o.vender='redsoft'"); int result = query.executeUpdate(); 4.12 使用操作符NOT // 查询所有vender不等于"foo"的Order Query query = managerNew.createQuery("SELECT FROM Order AS o WHERE not(o.vender='foo')"); List result = query.getResultList(); // 删除所有vender不等于"foo"的Order Query query = managerNew.createQuery("DELETE FROM Order AS o WHERE not(o.vender='foo')"); int result = query.executeUpdate(); 4.13 使用操作符BETWEEN // 查询所有价值amount在5和10之间的(包含5,10)的Order Query query = managerNew.createQuery("select o FROM Order AS o left join o.orderItems ot where o.amount BETWEEN 5 AND 10 order by o.vender desc"); List result = query.getResultList(); 4.14 使用操作符IN // 查询所有vender是"foo1", "foo2"或者"foo3"的Order Query query = managerNew.createQuery("select o FROM Order AS o left join o.orderItems ot where o.vender in ( 'foo1', 'foo2', 'foo3' ) order by o.vender desc"); List result = query.getResultList(); 4.15 使用操作符LIKE // 查询所有vender以字符串"foo"开头的Order Query query = managerNew.createQuery("select o FROM Order as o where o.vender like 'foo%' order by o.vender desc"); List result = query.getResultList(); // 查询所有vender以字符串"foo"结尾的Order Query query = managerNew.createQuery("select o FROM Order as o where o.vender like '%foo' order by o.vender desc"); List result = query.getResultList(); // 可以结合NOT一起使用,比如查询所有vender不以以字符串"foo"结尾的Order Query query = managerNew.createQuery("select o FROM Order as o where o.vender not like '%foo' order by o.vender desc"); List result = query.getResultList(); // 可以结合escape使用,比如查询所有vender以"foo"开始的Order并忽略'3'字符。 // 如果vender是"foo1", "foo2", "foo3"符合这个条件, 另外"3foo1", "f3oo4"也符合条件。 Query query = managerNew.createQuery("select o FROM Order as o where o.vender like '%foo' escape '3' order by o.vender desc"); List result = query.getResultList(); 4.16 使用操作符IS NULL // 查询所有没有地址的Order Query query = managerNew.createQuery("select o FROM Order as o where o.address is null"); List result = query.getResultList(); // 查询所有地址非空的Order Query query = managerNew.createQuery("select o FROM Order as o where o.address is not null"); List result = query.getResultList(); 4.17 使用操作符IS EMPTYIS EMPTY是针对集合属性(Collection)的操作符。可以和NOT一起使用。 // 查询orderItems集合为空的Order Query query = managerNew.createQuery("select o FROM Order o where o.orderItems is empty by o.vender desc"); List result = query.getResultList(); // 查询orderItems集合非空的Order Query query = managerNew.createQuery("select o FROM Order o where o.orderItems is not empty by o.vender desc"); List result = query.getResultList();
4.18 使用操作符EXISTS[NOT]EXISTS需要和子查询配合使用。 Query query = manager.createQuery("select o FROM Order o where exists (select o from Order o where o.partNumber=?1) order by o.vender desc"); query.setParameter(1, "partNumber"); Query query = manager.createQuery("select o FROM Order o where o.vender='partNumber' and not exists (select o from Order o where o.partNumber=?1) order by o.vender desc"); query.setParameter(1, "partNumber"); 4.19 使用操作符ALL/SOME/ANY Query query = managerNew.createQuery("select emp from EmployeeA emp where emp.salary > all ( select m.salary from Manager m where m.department = emp.department)"); List result = query.getResultList(); Query query = managerNew.createQuery("select emp from EmployeeA emp where emp.salary > any ( select m.salary from Manager m where m.department = emp.department)"); List result = query.getResultList(); Query query = managerNew.createQuery("select emp from EmployeeA emp where emp.salary > some ( select m.salary from Manager m where m.department = emp.department)"); List result = query.getResultList(); 4.20 字符串函数EJB3 QL定义了内置函数方便使用。这些函数的使用方法和SQL中相应的函数方法类似。EJB3 QL中定义的字符串函数包括:
CONCAT 字符串拼接
SUBSTRING 字符串截取
TRIM 去掉空格
LOWER 转换成小写
UPPER 装换成大写
LENGTH 字符串长度
LOCATE 字符串定位
// concat将参数中的两个字符串并结成一个字符串,这里firstName是"foo", lastName是"bar" Query query = entityManager.createQuery("select concat( o.owner.firstName, o.owner.lastName ) FROM Order AS o left outer join o.orderItems as oi where o.owner.firstName='foo'"); List result = query.getResultList(); assertEquals("foobar", result.get(0).toString()); // firstName是"fooBar",结果应该返回"oo" Query query = entityManager.createQuery("select o.vender,substring( o.owner.firstName, 1, 3 ), o.owner.info.age FROM Order AS o left outer join o.orderItems as oi where o.owner.firstName='charles'"); List result = query.getResultList(); Object[] row1 = (Object[]) result.get(0); assertEquals("oo", row1[1].toString()); // 获得"ar"在firstName中地起始位置 Query query = managerNew.createQuery("SELECT emp.firstName , emp.salary , locate( emp.firstName, 'ar') FROM EmployeeA as emp where emp.firstName='charles1111'"); List result = query.getResultList(); 4.21 计算函数EJB3 QL中定义的计算函数包括:
ABS 绝对值
SQRT 平方根
MOD 取余数
SIZE 取集合的数量
Query query = entityManager.createQuery("select o.vender, size( o.orderItems ) FROM Order o where o.owner.firstName = 'charles' group by o.vender order by o.vender desc"); List result = query.getResultList(); // 函数也可以用在条件中 Query query = managerNew.createQuery("select o.vender, sum(o.amount) FROM Order AS o left join o.orderItems ot group by o.vender having size(o.orderItems) = 0 or lower( o.vender ) = 'foo' order by o.vender desc"); List result = query.getResultList(); // 取余数 Query query = managerNew.createQuery("select mod( o.owner.info.age, 10 ) FROM Order o where exists ( select o from Order o where o.partNumber= :name ) and o.vender='order1' and exists ( select o from Order o where o.amount= :name1 ) order by o.vender desc"); 4.22 子查询子查询可以用于WHERE和HAVING条件语句中。 Query query = managerNew.createQuery("select emp from EmployeeA as emp where ( select count(m) from Manager as m where m.department = emp.department) > 0 "); List result = query.getResultList(); 4.23 原生SQL查询EJB3 QL对原生SQL做了非常好的支持。采用原生SQL做查询结果不但可以是象SQL中的返回列值,也可以是Entity类,甚至可以是两者的混合。EJB3 EntityManager接口定义了多个原生SQL的产生方法。
public Query createNativeQuery(String sqlString);
public Query createNativeQuery(String sqlString, Class resultClass);
public Query createNativeQuery(String sqlString, String resultSetMapping);
一个简单的SQL查询: Query query = entityManager.createNativeQuery("select o.id, o.vender as vender from OrderTable"); // 集合中的每个元素是一个数组,代表一行返回列值,每一个数具有2个元素,分别是id列,vender列 List result = query.getResultList(); Object[] row1 = (Object[])result.get(0); 在原生SQL中使用参数和EJB3 QL中使用参数的方法一致,但只支持位置参数。 Query query = entityManager.createNativeQuery("select o.id, o.vender as vender from OrderTable where o.id = ?1"); query.setParameter( 1, 200 ); // 集合中的每个元素是一个数组,代表一行返回列值,每一个数具有2个元素,分别是id列,vender列 List result = query.getResultList(); Object[] row1 = (Object[])result.get(0); 除了象上面例子中直接返回查询结果的列值,我们可以让EJB3 Persistence运行环境将列值直接填充入一个Entity的实例,并将实例作为结果返回。 // 我们这里将结果定义为Entity类Order的实例 Query query = entityManager.createNativeQuery("select o.id, o.vender, o.partId from OrderTable o order by o.amount desc", Order.class); // 结果集合中是Order类的实例,但Order实例只有id, vender, partId三列对应的属性变量有值,其他的变量属性为空。 List result = query.getResultList(); Order order1 = (Order)result.get( 0 );
5.1 一对一映射双向一对一关系需要在关系维护端(owner side)的one2one Annotition定义mappedBy属性。建表时在关系被维护端(inverse side)建立外键列指向关系维护端的主键列。假设Country 和 Capital 是双向一对一的关系,具体元数据声明如下:
public class Country {
@OneToOne(optional = true,cascade = CascadeType.ALL, mappedBy = "country")
private Capital capital;
}
public class Capital {
@OneToOne(optional = false, cascade = CascadeType.ALL)
@JoinColumn(name = "COUNTRY_ID", referencedColumnName = "id")
private Country country;
代码中元数据的说明:元数据描述:@OneToOne(optional = true,cascade = CascadeType.ALL, mappedBy = "country")optional声明关系是否是必须存在的,即是否允许其中一端为null。cascade声明级联操作。@JoinColumn(name = "COUNTRY_ID", referencedColumnName = "id")name声明外键列的名字,referencedColumnName声明外键指向列的列名。5.2 一对多映射双向一对多关系,一是关系维护端(owner side),多是关系被维护端(inverse side)。 建表时在关系被维护端建立外键列指向关系维护端的主键列。假设Father 和 Child 是双向一对多的关系,具体元数据声明如下:
public class Father {
@OneToMany(targetEntity = Child.class, cascade = CascadeType.ALL, mappedBy = "father")
public List<Child> getChildren() {
return children;
}
}
public class Child {
@ManyToOne
@JoinColumn(name = "FATHER_ID", referencedColumnName = "id")
public Father getFather() {
return father;
}
}
代码中元数据的说明:
元数据描述:@OneToMany(targetEntity = Child.class, cascade = CascadeType.ALL, mappedBy = "father")targetEntity = Child.class表明关系另一端的实体类型cascade声明级联操作。mappedBy声明关系维护端的字段(field)名。@ManyToOne@JoinColumn(name = "FATHER_ID", referencedColumnName = "id")name声明外键列的名字,referencedColumnName声明外键指向列的列名。5.3 多对多映射多对多映射采取中间表连接的映射策略,建立的中间表将分别引入两边的主键作为外键。EJB3对于中间表的元数据提供了可配置的方式,用户可以自定义中间表的表名,列名。假设Teacher 和 Student是多对多的关系,具体元数据声明如下:
pubic class Teacher{
@ManyToMany(targetEntity = Student.class, cascade = CascadeType.PERSIST)
@JoinTable(table = @Table(name = "M2M_TEACHER_STUDENT"),
joinColumns = @JoinColumn(name = "TEACHER_ID", referencedColumnName = "ID"),
inverseJoinColumns = @JoinColumn(name = "STUDENT_ID", referencedColumnName = "ID"))
public List<Student> getStudents() {return students;}
}
public class Student{
@ManyToMany(targetEntity = Teacher.class, mappedBy = "students")
public List<Teacher> getTeachers() {
return teachers;
}
}
代码中元数据的说明:元数据描述:@ManyToMany(targetEntity = Student.class, cascade = CascadeType.PERSIST)targetEntity = Student.class表明关系另一端的实体类型。cascade声明级联操作。@JoinTable(table = @Table(name = "M2M_TEACHER_STUDENT"),joinColumns = @JoinColumn(name = "TEACHER_ID", referencedColumnName = "ID"),inverseJoinColumns = @JoinColumn(name = "STUDENT_ID", referencedColumnName = "ID"))JoinTable配置中间表信息,它由3个部分组成:1) table = @Table(name = "M2M_TEACHER_STUDENT") ,声明中间表的名字2) joinColumns ,定义中间表与关系维护端的外键关系。3) inverseJoinColumns,定义中间表与inverse端的外键关系.
第六部分 继承(Inheritance strategy)EJB3规定了三种基本的继承映射策略:.每个类分层结构一张表(table per class hierarchy) .每个子类一张表(table per subclass) .每个具体类一张表(table per concrete class)在我们提供的Alpha版本中仅支持第一种映射策略,即每个类层次一个表。我们将在下一个版本中提供每个具体类一张表的支持, 考虑到性能,这两个映射策略也是推荐的映射策略. 每个类分层结构一张表(Table per class hierarchy)假设有这么一个继承类层次:Employee,两个子类FullTimeEmployee,PartTimeEmployee 源代码如下所示: @Entity@Table( name="inheritance_Employee" )@Inheritance(strategy=InheritanceType.SINGLE_TABLE, discriminatorType=DiscriminatorType.STRING, discriminatorValue="employee")public class Employee {...} @Entity@Inheritance(discriminatorValue="fullTimeEmp")public class FullTimeEmployee extends Employee {...}@Entity@Inheritance(discriminatorValue="partTimeEmp")public class PartTimeEmployee extends Employee {...} 代码中元数据的说明:基类中元数据描述:@Inheritance(strategy=InheritanceType.SINGLE_TABLE,discriminatorType=DiscriminatorType.STRING,discriminatorValue="employee")strategy=InheritanceType.SINGLE_TABLE表示继承映射采用第一种映射策略。discriminatorType=DiscriminatorType.STRING表示继承层次中类型识别列类型为String.discriminatorValue="employee" 表示此类对应的类型识别码为employee.
第七部分 一些重要的关键字和元数据7.1 TableTable用来定义entity主表的name,catalog,schema等属性。元数据属性说明:
name: 表名
catalog: 对应关系数据库中的catalog
schema:对应关系数据库中的schema
UniqueConstraints:定义一个UniqueConstraint数组,指定需要建唯一约束的列(具体参看UniqueConstraint)
@Entity @Table(name="CUST") public class Customer { ... }7.2 SecondaryTable一个entity class可以映射到多表,SecondaryTable用来定义单个从表的名字,主键名字等属性。元数据属性说明:
name: 表名
catalog: 对应关系数据库中的catalog
schema:对应关系数据库中的schema
pkJoin: 定义一个PrimaryKeyJoinColumn数组,指定从表的主键列(具体参看PrimaryKeyJoinColumn)
UniqueConstraints:定义一个UniqueConstraint数组,指定需要建唯一约束的列(具体参看UniqueConstraint)
下面的代码说明Customer类映射到两个表,主表名是CUSTOMER,从表名是CUST_DETAIL,从表的主键列和主表的主键列类型相同,列名为CUST_ID。 @Entity @Table(name="CUSTOMER") @SecondaryTable(name="CUST_DETAIL",pkJoin=@PrimaryKeyJoinColumn(name="CUST_ID")) public class Customer { ... } 7.3 SecondaryTables当一个entity class映射到一个主表和多个从表时,用SecondaryTables来定义各个从表的属性。元数据属性说明:
value: 定义一个SecondaryTable数组,指定每个从表的属性。
@Table(name = "CUSTOMER") @SecondaryTables( value = { @SecondaryTable(name = "CUST_NAME", pkJoin = { @PrimaryKeyJoinColumn(name = "STMO_ID", referencedColumnName = "id") }), @SecondaryTable(name = "CUST_ADDRESS", pkJoin = { @PrimaryKeyJoinColumn(name = "STMO_ID", referencedColumnName = "id") }) }) public class Customer {} 7.4 UniqueConstraintUniqueConstraint定义在Table或SecondaryTable元数据里,用来指定建表时需要建唯一约束的列。元数据属性说明:
columnNames:定义一个字符串数组,指定要建唯一约束的列名。
@Entity @Table(name="EMPLOYEE", uniqueConstraints={@UniqueConstraint(columnNames={"EMP_ID", "EMP_NAME"})} ) public class Employee { ... } 7.5 ColumnColumn元数据定义了映射到数据库的列的所有属性:列名,是否唯一,是否允许为空,是否允许更新等。元数据属性说明:
name:列名。
unique: 是否唯一
nullable: 是否允许为空
insertable: 是否允许插入
updatable: 是否允许更新
columnDefinition: 定义建表时创建此列的DDL
secondaryTable: 从表名。如果此列不建在主表上(默认建在主表),该属性定义该列所在从表的名字。
public class Person { @Column(name = "PERSONNAME", unique = true, nullable = false, updatable = true) private String name; @Column(name = "PHOTO", columnDefinition = "BLOB NOT NULL", secondaryTable="PER_PHOTO") private byte[] picture; 7.6 JoinColumn如果在entity class的field上定义了关系(one2one或one2many等),我们通过JoinColumn来定义关系的属性。JoinColumn的大部分属性和Column类似。元数据属性说明:
name:列名。
referencedColumnName:该列指向列的列名(建表时该列作为外键列指向关系另一端的指定列)
unique: 是否唯一
nullable: 是否允许为空
insertable: 是否允许插入
updatable: 是否允许更新
columnDefinition: 定义建表时创建此列的DDL
secondaryTable: 从表名。如果此列不建在主表上(默认建在主表),该属性定义该列所在从表的名字。
下面的代码说明Custom和Order是一对一关系。在Order对应的映射表建一个名为CUST_ID的列,该列作为外键指向Custom对应表中名为ID的列。 public class Custom { @OneToOne @JoinColumn( name="CUST_ID", referencedColumnName="ID", unique=true, nullable=true, updatable=true) public Order getOrder() { return order; } 7.7 JoinColumns如果在entity class的field上定义了关系(one2one或one2many等),并且关系存在多个JoinColumn,用JoinColumns定义多个JoinColumn的属性。元数据属性说明:
value: 定义JoinColumn数组,指定每个JoinColumn的属性。
下面的代码说明Custom和Order是一对一关系。在Order对应的映射表建两列,一列名为CUST_ID,该列作为外键指向Custom对应表中名为ID的列,另一列名为CUST_NAME,该列作为外键指向Custom对应表中名为NAME的列。 public class Custom { @OneToOne @JoinColumns({ @JoinColumn(name="CUST_ID", referencedColumnName="ID"), @JoinColumn(name="CUST_NAME", referencedColumnName="NAME") }) public Order getOrder() { return order; } 7.8 Id声明当前field为映射表中的主键列。id值的获取方式有五种:TABLE, SEQUENCE, IDENTITY, AUTO, NONE。Oracle和DB2支持SEQUENCE,SQL Server和Sybase支持IDENTITY,mysql支持AUTO。所有的数据库都可以指定为AUTO,我们会根据不同数据库做转换。NONE(默认)需要用户自己指定Id的值。Table类型详见TableGenerator的介绍。元数据属性说明:
generate():主键值的获取类型
generator():TableGenerator的名字(当generate=GeneratorType.TABLE才需要指定该属性)
下面的代码声明Task的主键列id是自动增长的。(Oracle和DB2从默认的SEQUENCE取值,SQL Server和Sybase该列建成IDENTITY,mysql该列建成auto increment。) @Entity @Table(name = "OTASK") public class Task { @Id(generate = GeneratorType.AUTO) public Integer getId() { return id; } } 7.9 IdClass当entity class使用复合主键时,需要定义一个类作为id class。id class必须符合以下要求:类必须声明为public,并提供一个声明为public的空构造函数。必须实现Serializable接,覆写equals()和hashCode()方法。entity class的所有id field在id class都要定义,且类型一样。元数据属性说明:
value: id class的类名
public class EmployeePK implements java.io.Serializable{ String empName; Integer empAge; public EmployeePK(){} public boolean equals(Object obj){ ......} public int hashCode(){......} } @IdClass(value=com.acme.EmployeePK.class) @Entity(access=FIELD) public class Employee { @Id String empName; @Id Integer empAge; }
7.10 MapKey在一对多,多对多关系中,我们可以用Map来保存集合对象。默认用主键值做key,如果使用复合主键,则用id class的实例做key,如果指定了name属性,就用指定的field的值做key。元数据属性说明:
name: 用来做key的field名字
下面的代码说明Person和Book之间是一对多关系。Person的books字段是Map类型,用Book的isbn字段的值作为Map的key。 @Table(name = "PERSON") public class Person { @OneToMany(targetEntity = Book.class, cascade = CascadeType.ALL, mappedBy = "person") @MapKey(name = "isbn") private Map<String, Book> books = new HashMap<String, Book>(); } 7.11 OrderBy在一对多,多对多关系中,有时我们希望从数据库加载出来的集合对象是按一定方式排序的,这可以通过OrderBy来实现,默认是按对象的主键升序排列。元数据属性说明:
value: 字符串类型,指定排序方式。格式为"fieldName1 [ASC|DESC],fieldName2 [ASC|DESC],......",排序类型可以不指定,默认是ASC。
下面的代码说明Person和Book之间是一对多关系。集合books按照Book的isbn升序,name降序排列。 @Table(name = "MAPKEY_PERSON") public class Person { @OneToMany(targetEntity = Book.class, cascade = CascadeType.ALL, mappedBy = "person") @OrderBy(name = "isbn ASC, name DESC") private List<Book> books = new ArrayList<Book>(); } 7.12 PrimaryKeyJoinColumn在三种情况下会用到PrimaryKeyJoinColumn。
继承(详见Inheritance)。
entity class映射到一个或多个从表。从表根据主表的主键列(列名为referencedColumnName值的列),建立一个类型一样的主键列,列名由name属性定义。
one2one关系,关系维护端的主键作为外键指向关系被维护端的主键,不再新建一个外键列(与JoinColumn不同)。
元数据属性说明:
name:列名。
referencedColumnName:该列引用列的列名
columnDefinition: 定义建表时创建此列的DDL
下面的代码说明Customer映射到两个表,主表CUSTOMER,从表CUST_DETAIL,从表需要建立主键列CUST_ID,该列和主表的主键列id除了列名不同,其他定义一样。 @Entity @Table(name="CUSTOMER") @SecondaryTable(name="CUST_DETAIL",pkJoin=@PrimaryKeyJoinColumn(name="CUST_ID",referencedColumnName="id")) public class Customer { @Id(generate = GeneratorType.AUTO) public Integer getId() { return id; } } 下面的代码说明Employee和EmployeeInfo是一对一关系,Employee的主键列id作为外键指向EmployeeInfo的主键列INFO_ID。 @Table(name = "Employee") public class Employee { @OneToOne @PrimaryKeyJoinColumn(name = "id", referencedColumnName="INFO_ID") EmployeeInfo info; } 7.13 PrimaryKeyJoinColumns如果entity class使用了复合主键,指定单个PrimaryKeyJoinColumn不能满足要求时,可以用PrimaryKeyJoinColumns来定义多个PrimaryKeyJoinColumn。 元数据属性说明:
value: 一个PrimaryKeyJoinColumn数组,包含所有PrimaryKeyJoinColumn。
下面的代码说明了Employee和EmployeeInfo是一对一关系。他们都使用复合主键,建表时需要在Employee表建立一个外键,从Employee的主键列id,name指向EmployeeInfo的主键列INFO_ID和INFO_NAME. @Entity @IdClass(EmpPK.class) @Table(name = "EMPLOYEE") public class Employee { private int id; private String name; private String address; @OneToOne(cascade = CascadeType.ALL) @PrimaryKeyJoinColumns({ @PrimaryKeyJoinColumn(name="id", referencedColumnName="INFO_ID"), @PrimaryKeyJoinColumn(name="name" , referencedColumnName="INFO_NAME")}) EmployeeInfo info; } @Entity @IdClass(EmpPK.class) @Table(name = "EMPLOYEE_INFO") public class EmployeeInfo { @Id @Column(name = "INFO_ID") private int id; @Id @Column(name = "INFO_NAME") private String name; } 7.14 TransientTransient用来注释entity的属性,指定的这些属性不会被持久化,也不会为这些属性建表。 @Transient private String name; 7.15 VersionVersion指定实体类在乐观事务中的version属性。在实体类重新由EntityManager管理并且加入到乐观事务中时,保证完整性。每一个类只能有一个属性被指定为version,version属性应该映射到实体类的主表上。下面的代码说明versionNum属性作为这个类的version,映射到数据库中主表的列名是OPTLOCK。 @Version @Column("OPTLOCK") protected int getVersionNum() { return versionNum; } 7.16 LobLob指定一个属性作为数据库支持的大对象类型在数据库中存储。使用LobType这个枚举来定义Lob是二进制类型还是字符类型。LobType枚举类型说明:
BLOB 二进制大对象,Byte[]或者Serializable的类型可以指定为BLOB。
CLOB 字符型大对象,char[]、Character[]或String类型可以指定为CLOB。
元数据属性说明:
fetch: 定义这个字段是lazy loaded还是eagerly fetched。数据类型是FetchType枚举,默认为LAZY,即lazy loaded.
type: 定义这个字段在数据库中的JDBC数据类型。数据类型是LobType枚举,默认为BLOB。
下面的代码定义了一个BLOB类型的属性和一个CLOB类型的属性。 @Lob @Column(name="PHOTO" columnDefinition="BLOB NOT NULL") protected JPEGImage picture; @Lob(fetch=EAGER, type=CLOB) @Column(name="REPORT") protected String report; 7.17 JoinTableJoinTable在many-to-many关系的所有者一边定义。如果没有定义JoinTable,使用JoinTable的默认值。元数据属性说明:
table:这个join table的Table定义。
joinColumns:定义指向所有者主表的外键列,数据类型是JoinColumn数组。
inverseJoinColumns:定义指向非所有者主表的外键列,数据类型是JoinColumn数组。
下面的代码定义了一个连接表CUST和PHONE的join table。join table的表名是CUST_PHONE,包含两个外键,一个外键是CUST_ID,指向表CUST的主键ID,另一个外键是PHONE_ID,指向表PHONE的主键ID。 @JoinTable( table=@Table(name=CUST_PHONE), joinColumns=@JoinColumn(name="CUST_ID", referencedColumnName="ID"), inverseJoinColumns=@JoinColumn(name="PHONE_ID", referencedColumnName="ID") ) 7.18 TableGeneratorTableGenerator定义一个主键值生成器,在Id这个元数据的generate=TABLE时,generator属性中可以使用生成器的名字。生成器可以在类、方法或者属性上定义。生成器是为多个实体类提供连续的ID值的表,每一行为一个类提供ID值,ID值通常是整数。元数据属性说明:
name:生成器的唯一名字,可以被Id元数据使用。
table:生成器用来存储id值的Table定义。
pkColumnName:生成器表的主键名称。
valueColumnName:生成器表的ID值的列名称。
pkColumnValue:生成器表中的一行数据的主键值。
initialValue:id值的初始值。
allocationSize:id值的增量。
下面的代码定义了两个生成器empGen和addressGen,生成器的表是ID_GEN。 @Entity public class Employee { ... @TableGenerator(name="empGen", table=@Table(name="ID_GEN"), pkColumnName="GEN_KEY", valueColumnName="GEN_VALUE", pkColumnValue="EMP_ID", allocationSize=1) @Id(generate=TABLE, generator="empGen") public int id; ... } @Entity public class Address { ... @TableGenerator(name="addressGen", table=@Table(name="ID_GEN"), pkColumnValue="ADDR_ID") @Id(generate=TABLE, generator="addressGen") public int id; ... } 7.19 SequenceGeneratorSequenceGenerator定义一个主键值生成器,在Id这个元数据的generator属性中可以使用生成器的名字。生成器可以在类、方法或者属性上定义。生成器是数据库支持的sequence对象。元数据属性说明:
name:生成器的唯一名字,可以被Id元数据使用。
sequenceName:数据库中,sequence对象的名称。如果不指定,会使用提供商指定的默认名称。
initialValue:id值的初始值。
allocationSize:id值的增量。
下面的代码定义了一个使用提供商默认名称的sequence生成器。 @SequenceGenerator(name="EMP_SEQ", allocationSize=25) 7.20 DiscriminatorColumnDiscriminatorColumn定义在使用SINGLE_TABLE或JOINED继承策略的表中区别不继承层次的列。元数据属性说明:
name:column的名字。默认值为TYPE。
columnDefinition:生成DDL的sql片断。
length:String类型的column的长度,其他类型使用默认值10。
下面的代码定义了一个列名为DISC,长度为20的String类型的区别列。 @Entity @Table(name="CUST") @Inheritance(strategy=SINGLE_TABLE, discriminatorType=STRING, discriminatorValue="CUSTOMER") @DiscriminatorColumn(name="DISC", length=20) public class Customer { ... }
发表评论
-
HBase技术介绍
2012-05-17 15:58 748http://www.searchtb.com/2011/01 ... -
使用JRockit Mission Control监控Java程序运行性能
2011-05-24 10:57 1523BEA的JRockit JDK是BEA公司自己开发的JDK。使 ... -
Myeclipse8.5 反编译插件 jad 安装
2010-09-25 15:11 1329准备工作 1.下载jad.exe文件:http://www. ... -
何谓系统架构师
2010-01-30 10:11 826首先,何谓系统架构师? IBM工程师的说明是: ... -
构建高级用户界面 使用 Tree 控件
2010-01-05 16:32 1479原文地址:http://www.airia.cn/FLEX_D ... -
Flex开发RIA和WEB应用的5个建议
2009-12-31 16:02 11411.避免容器内嵌套其它容器,减少使用相对大小和相对位置 如果容 ... -
JPA 2.0 中的动态类型安全查询
2009-12-21 13:15 1710如果编译器能够对查询执行语法正确性检查,那么对于 Java 对 ... -
一个简单的java Mail发送邮件程序
2009-12-06 12:01 1696package com.wisentsoft.manageme ... -
java Mail发送附件邮件
2009-12-04 13:25 3452利用Sun公司提供的JavaMail API可以很方便的开发邮 ... -
JMS服务器openJms入门
2009-11-25 10:46 1191首先可以到网站上下载 ...
相关推荐
在这个"ejb3.0开发实例(java工程)"中,我们将深入探讨EJB 3.0的主要特性和开发实践。 1. **注解驱动**:EJB 3.0最大的变革之一就是大量使用注解(Annotation),减少了XML配置文件的使用。例如,@Stateless、@...
EJB(Enterprise JavaBeans)3.0是Java企业级应用开发的一个重要标准,它定义了如何在Java EE(Java Platform, Enterprise Edition)环境中构建可复用的、组件化的服务器端应用程序。EJB 3.0的发布极大地简化了EJB的...
《EJB3.0入门经典》是关于EJB 3.0的专业技术教程,从实用的角度出发,理论联系实际,用9章的篇幅详细讲解了EJB 3.0开发的方法和技巧。《EJB3.0入门经典》内容丰富,讲解由浅入深,全面系统,在讲解EJB 3.0最新开发...
《精通EJB3.0》是一本深入探讨企业级JavaBeans(EJB)3.0技术的专业书籍,由Rima Patel、Sriganesh、Gerald Brose和Micah Silverman共同编写,由Wiley Publishing出版。该书为读者提供了全面且深入的EJB3.0知识体系...
首先,我们来看《EJB3.0开发Entity.pdf》。在EJB 3.0中,Entity Bean代表持久化对象,它存储在数据库中并与数据库进行交互。这个部分将介绍如何定义实体bean,包括使用JPA(Java Persistence API)来注解实体类,...
在"ejb3.0入门经典教程-source"这个压缩包中,包含了书中各个章节的示例代码,覆盖了EJB 3.0的各个方面,例如实体Bean的创建、会话Bean的使用、事务管理、安全性设置以及JPA的持久化操作等。这些源码对于初学者来说...
**企业级JavaBeans(EJB)3.0详解** 企业级JavaBeans(Enterprise JavaBeans,简称EJB)是Java平台上用于构建分布式企业级应用的重要组件模型。EJB 3.0是EJB规范的一个重大革新,它极大地简化了EJB的开发过程,引入...
### Java之精通EJB3.0 #### 一、EJB3.0简介与改进 企业Java Beans(Enterprise JavaBeans,简称EJB)是Java平台为企业级应用开发提供的一种组件模型。EJB3.0是EJB规范的一个重大版本更新,它在EJB2.0的基础上进行...
在"**EJB3.0+JBOSS+MyEclipse初体验(完整代码和过程).txt**"文件中,你将找到一个完整的示例,涵盖了上述所有步骤,包括具体的代码片段和执行过程,这对于初学者来说是一个很好的起点,可以快速理解并实践EJB 3.0在...
"李腾飞EJB3.0 源码 源代码"可能包含的是李腾飞教授或团队关于EJB 3.0技术的实践示例或教学资源。通过分析这些源码,开发者可以深入理解EJB 3.0的实现细节,例如如何使用注解定义Bean、如何处理持久化、如何进行依赖...
罗时飞精通EJB3.0.zip.001 罗时飞精通EJB3.0.zip.002 罗时飞精通EJB3.0.zip.003 《精通EJB3.0》共分为4个部分:第一部分对EJB编程基础进行介绍,概要性地对EJB进行了阐述;第二部分重点关注EJB编程的具体内容和...
### EJB2.0与EJB3.0的主要区别 #### 一、简介 企业Java Beans(EJB)是Java平台为企业级应用提供的一种组件模型。随着技术的发展,EJB经历了多个版本的迭代,其中EJB 2.0和EJB 3.0是两个重要的里程碑版本。本文将...
罗时飞精通EJB3.0.zip.001 罗时飞精通EJB3.0.zip.002 罗时飞精通EJB3.0.zip.003 《精通EJB3.0》共分为4个部分:第一部分对EJB编程基础进行介绍,概要性地对EJB进行了阐述;第二部分重点关注EJB编程的具体内容和...
EJB(Enterprise JavaBeans)3.0规范是Java EE(Enterprise Edition)平台中核心的组件模型,用于构建可扩展、安全且易于管理的企业级应用程序。EJB 3.0是EJB规范的一个重要里程碑,因为它引入了许多重大的改进,...
EJB3.0是EJB规范的一个重要版本,它在EJB2.x的基础上进行了大量简化,提高了开发效率,并引入了注解驱动的开发方式,使得EJB更易于理解和使用。 在EJB3.0中,主要包含以下关键知识点: 1. **实体Bean(Entity ...
**EJB3.0实例教程**是一份详细指导开发者如何使用Enterprise JavaBeans 3.0(EJB3.0)技术进行企业级应用开发的电子文档。EJB3.0是Java EE(Java Platform, Enterprise Edition)规范的一部分,旨在简化企业级组件的...
**企业级JavaBeans (EJB) 3.0 规范** EJB 3.0是Java Enterprise Edition (Java EE)中的一个关键组件,它定义了如何在服务器端创建可复用、模块化的业务组件。这个规范的主要目标是简化EJB的开发过程,使其更加轻量...
总的来说,"EJB3.0源代码"的压缩包很可能包含了使用EJB 3.0规范编写的各类Bean的源码,包括实体Bean、无状态会话Bean、有状态会话Bean和消息驱动Bean,以及相关的配置文件。通过分析这些源代码,我们可以深入理解EJB...
### 精通EJB3.0经典书目解析 #### 一、书籍基本信息 - **书名**:《精通企业级Java Beans (EJB) 3.0》 - **作者**:Rima Patel Sriganesh, Gerald Brose, Micah Silverman - **出版社**:Wiley Publishing, Inc. -...
**电子书-EJB3.0实例教程** EJB(Enterprise JavaBeans)是Java平台企业版(Java EE)的一部分,主要用于构建可复用的、模块化的、面向服务的企业级应用程序。EJB 3.0是其重要的一个版本,它在EJB 2.x的基础上进行...