??xml version="1.0" encoding="utf-8" standalone="yes"?>
板桥里h http://www.jdon.com 2004/03/08 首先Q我们必L,Z么要使用J2EEQJ2EE优点是什么?使用J2EE的主要原因是多层l构Q传l的两层C/Sl构难于l护Q稳定性极差,界面代码和数据库代码h在一P牵一动百Q多层结构得界面和数据库完全分,q且诞生了中间gq样的技术,如下图:
Z么用EJB我原先认不是一个讨论的话题Q因为EJB是J2EE重要的组成部分,可以说没有EJB的J2EE只是一UWebpȝQ这Lpȝ非常Ҏ(gu)丧失了多层结构的大部分优点(仔细x那些混合多种层次功能JavaBeans和传l两层结构有什么区别?Q?/p>
当然Q可以h为地在Javabeans之间q行层次划分Q例如Hibernate数据持久层Q某些JavaBeans是业务核心层Q但是因为都是普?JavaBeansQ这U划分没有一U强制性和明显标志性,q样的系l更换了d人员或设计师Q可能就会被新的E序员修改得非常混ؕ?/p>
我们先看看一个包含EJB的J2EEpȝ是如何清晰地表达层次。如下图Q?/p>
Web完全只是一个MVC模式的实玎ͼ关键业务核心是在EJB的服务层实现Q这样做的优Ҏ(gu)QWeb只负责界面相关部分,因ؓQ如果是一个智能客LQ如Swing或J2MEQ在不需要修改Q何业务核心的情况下能够方便地更换。同P提供Web Services功能Q也只是?Web层修改,不会涉及EJB斚w的修改,同样保证了系l的E_性,保证了系l升U和未来的扩展性?/p>
如果不用EJBQ在EJB服务层实现的业务核心?yu)由普通JavaBeans实现Q用何U架构或设计能够保证负责MVC的JavaBeans和负责业务核心的JavaBeans清晰地分开Q又如何保证在新的程序员不会破坏和打׃_ֿ布局的JavaBeans架构Q?/p>
最主要的是性能问题Q由于以前国内中文Java|站有些人弯曲EJBQ认为EJB性能低,其实q是一U非常肤错误的认识Q我们首先看看在一般Java环境中是如何提高性能?/p>
假定一个JavaBeans为AQ那么一般用这个JavaBeans命o如下Q?/p>
A a = new A(); 但是Q在高访问量的环境中Qnew A()其实是很Ҏ(gu)消耗系l性能的,因此Q能不能在Y件系l启动时候就预先建立一些对象,q样Q系l运行时Q从q些已经生成的对象池中借用一个,q样Q就 无需在用时q行NewQ节U了开销Q提高了性能Q因此,真正成熟性能解决Ҏ(gu)都是需要对象池{支持?/p>
在一个纯Webl构的系l(也就是只能运行在Tomat环境中)Q例如Struts + Hibernate{这LpȝQ除非自己动手做Q一般是没有对象池技术支持的Q因此他们的性能只能是Demo演示版本的性能Q根本无法承受大定wq发 讉KQ也无法UCؓ一个成熟的pȝQ所以,我们研究成熟的开源WebpȝQ如Jive、OFBizeQLifeRay{,他们都在Web层拥有自q对象?和缓存池?/p>
对象池和~存机制是J2EE必须的吗Q当Ӟ是所有成熟系l必ȝQWindowspȝ如果L~存会变得怎样Q?/p>
自己动手开发对象池和缓存机制ƈ不是一件简单的事情Q需要对多线E以及同步锁{底层原理有深层ơ的把握Q这其实也是一门非常深入的Java研究分支Q所以,你可以抛开你的客户焦急的催促Q精心研I开发自q对象池和~存池?/p>
但是QEJB容器Q如JBossQ已l提供了对象池和~存机制Q所以,没有事务机制的无状态Session Bean的性能肯定要强于普通JavaBeans。EJB容器不但在单Z提供了对象池和缓存,而且可以跨服务器实现动态负载^衡,q些都无需开发者自?开发Q何Y件代码,l构如下Q?/p>
每一个jar包代表一个EJBlgQ一个系l可以由多个可重用的EJBlg构成Q例如:树Şl构EJBlgQ自增序号EJBlgQ用戯料EJBlg{,q样的EJBlg可以象积木一h配在大部分应用系l中Q提高了pȝ的开发效率,保证了开发质量?/p>
下图是某个新的具体系l时应用到的EJBlg图,在这个新的应用中Q由于用了以前大量可重用的EJBlgQ新的开发工作基本集中在界面设计和流E安排上Q?/p>
事务机制对于一些关键事务是很重要的Q例如ATM机提ƾ,提款有多个动作:修改数据库以及数qQ如果这其中有Q何一个环节出错,那么其它已经实现的操作必还原,否则Q就会出玎ͼ提款人没有拿到钱Q但是卡上已l扣Ƅ不可思议的事情发生?/p>
EJB提供的事务机刉常周全,但事务机制带来的~点是性能的降低,因此Q有些h认ؓEJB很重Q因为在实际应用中,有的用户pȝ可能不需要事务机Ӟ 只是需要EJB提供的性能优化机制Q这P如果使用EJBQ就象叫一个h来背东西Q他除了背着我要的东西外Q还背着我不要的东西?/p>
除非你是一个完主义,在一般企业应用或数据库系l应用中QEJB不会对你构成很重的包袱?/p>
开源以及一些数据库持久层技术崇拜者,一直抨击CMPQ认为CMP慢无用,实际最大的问题是他们的设计和用问题?/p>
׃EJB容器Q如JBossQ对CMP实现有事务机制的~存优化Q因此,CMP特别适合多个用户同时更新同一个数据源的情况,CMPq种严格的事务完 整性保证多个用户同时操作一个数据记录时Q能够保证性能优化和数据的完整性,如果q个数据记录是是软gpȝ的状态标志,它的状态会影响pȝ中很多的环节Q?那么状态更改的重要性不a而喻?/p>
如果没有事务完整性支持,你的软gpȝ在用戯问量变大Q就会变得发生各U不可能发生的逻辑错误Q查看程序逻辑是正的Q那么问题出在哪里?出在数据完整性上?/p>
׃每个CMP在内存中都有一个缓存,在实际应用中Q如果用CMP扚wL据库数据Q几万条查询完毕Q内存中充满了几万条CMP~存Q如果这时你?EJB容器讄不当Q如使用JBoss~省配置Q,那么JVM的垃圑֛收机制就会频J启动,D你的pȝ变慢甚至LQ这也是一些h抨击CMP慢的原因所 在,其实他们使用Ҏ(gu)不当Q或者没有正配|EJB容器CMP~存?/p>
对于q种情况Q根据J2EE核心模式Q推荐用DAO+JDBC方式?/p>
除非你对设计模式非常_深Q能够将自己pȝ中的JavaBeans使用模式或某U框架进行固定分层,同时Q你孜孜不倦研发出对象池,又熟l于JTA{事 务机Ӟ你可以选择没有EJB的纯Webl构Q就象Jive、OFBiz那样。当然还有一个前提,老板不懂或者非常有挑战性(做与IBM SUN 微Y齐名的公司和技术)?/p>
不要再被TSS那些狂热的开源先生误|他们有时间有保障可以做他们喜Ƣ的事情Q作Z业的J2EEE序员,按照J2EE标准d习去行动Q也不要认ؓQ只要用了J2EE其中某个技术如Jsp或JavaBeans心安理得认qpȝ是J2EE了?/p>
当然Q我q不是说UWebpȝ不能实现多层l构Q但是至在很多斚w没有Web+EJBl构完善和清晎ͼ所以,EJB不是J2EE可以忽视的部分,而是主要的重要的部分Q重要业务功能核心都装在EJB中,相反Web层是一U次要的、和界面相关的层ơ?/p>
补充Q什么情况下不需要EJBQ在SUN的SECA架构师试卷中回答Q小型系l和不需要事务。另外过去那U认为“EJB有性能问题”根本是一U缪误,具体可参考下面有关问题?/p>
Web+EJB能组成真正的多层l构
EJB提供性能优化支持
EJBlg能提供真正的可重用框?/h3>
EJB提供了事务机?/h3>
CMP独特的优?/h3>
结
后就可以开发最单的Session Bean了,我感兴趣的还是Entity BeanQ所以接下来
我想先试验一下Entity Bean?br />
一、在JBoss中配|好Data Source
我用的是MySQL数据库,所以首先将MySQL的JDBC驱动复制?br />jboss-4.0.3SP1\server\all\lib目录Q然后将jboss-4.0.3SP1\docs\examples\jca
下的mysql-ds.xml作出适当修改后复制到jboss-4.0.3SP1\server\all\deploy目录
下,q是我修改后的mysql-ds.xml文gQ?/p>
<?xml version="1.0" encoding="UTF-8"?>
<!-- $Id: mysql-ds.xml,v 1.3.2.1 2004/12/01 11:46:00 schrouf Exp $ -->
<!-- Datasource config for MySQL using 3.0.9 available from:
http://www.mysql.com/downloads/api-jdbc-stable.html
-->
<datasources>
<local-tx-datasource>
<jndi-name>MySqlDS</jndi-name>
<connection-url>jdbc:mysql://localhost:3306/test</connection-url>
<driver-class>com.mysql.jdbc.Driver</driver-class>
<user-name>test</user-name>
<password></password>
<exception-sorter-class-
name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</excepti
on-sorter-class-name>
<!-- sql to call when connection is created
<new-connection-sql>some arbitrary sql</new-connection-sql>
-->
<!-- sql to call on an existing pooled connection when it is obtained
from pool
<check-valid-connection-sql>some arbitrary sql</check-valid-
connection-sql>
-->
<!-- corresponding type-mapping in the standardjbosscmp-jdbc.xml
(optional) -->
<metadata>
<type-mapping>mySQL</type-mapping>
</metadata>
</local-tx-datasource>
</datasources>
q样之后QJBoss下的MySQL Data Source配置完成?/p>
二、创建数据库q编写Entity Bean代码
create table book(
id int not null auto_increment primary key,
title varchar(20) not null,
author varchar(40) not null
);
新徏Java ProjectQ导入User LibraryQEJB3_JBossQ以下是cM码?/p>
//Book.java
package ejb.bean.entity;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name="book")
public class Book implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private Integer id;
private String title;
private String author;
public Book() {
super();
}
public Book(Integer id, String title, String author) {
super();
this.id = id;
this.title = title;
this.author = author;
}
@Override
public String toString() {
return "Book: " + getId() + " Title " + getTitle() + "
Author " + getAuthor();
}
public String getAuthor() {
return author;
}
@Id @GeneratedValue(strategy=GenerationType.AUTO)
public Integer getId() {
return id;
}
public String getTitle() {
return title;
}
public void setAuthor(String author) {
this.author = author;
}
public void setId(Integer id) {
this.id = id;
}
public void setTitle(String title) {
this.title = title;
}
}
三、编写一个简单的Stateless Session Bean q进行测?/p>
//BookTestLocal.java
package ejb.bean.entity;
import javax.ejb.Local;
@Local
public interface BookTestLocal {
public void test();
}
//BookTestRemote.java
package ejb.bean.entity;
import javax.ejb.Remote;
@Remote
public interface BookTestRemote {
public void test();
}
//BookTestBean.java
package ejb.bean.entity;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
@Stateless
public class BookTestBean implements BookTestLocal, BookTestRemote {
@PersistenceContext
EntityManager em;
public void test() {
// TODO Auto-generated method stub
Book book = new Book(null, "My first bean book",
"Sebastian");
em.persist(book);
}
}
//Client.java
package ejb.client.entity;
import ejb.bean.entity.*;
import javax.naming.InitialContext;
/**
* Comment
*
* @author <a href="mailto:bill@jboss.org">Bill Burke</a>
* @version $Revision: 1.1.6.7 $
*/
public class Client
{
public static void main(String[] args) throws Exception
{
InitialContext ctx = new InitialContext();
BookTestRemote book = (BookTestRemote) ctx.lookup
("BookTestBean/remote");
book.test();
System.out.println("test successful! ");
}
}
三、其他文?br />jboss-EJB-3.0_RC5-PFD\docs\tutorial中的扑ֈ的jndi.properties、log4j.xml
、builder.xml{复制到当前工程中,其中builder.xml需要修攏V?br />//builder.xml
<?xml version="1.0"?>
<!--
=======================================================================
-->
<!-- JBoss build file
-->
<!--
=======================================================================
-->
<project name="JBoss" default="ejbjar" basedir=".">
<property file="../local.properties" />
<property environment="env"/>
<property name="src.dir" value="${basedir}/src"/>
<property name="jboss.home" value="E:/Programming/Servers/jboss-
4.0.3SP1/"/>
<property name="jboss.server.config" value="all"/>
<property name="build.dir" value="${basedir}/build"/>
<property name="build.classes.dir" value="${build.dir}/classes"/>
<!-- Build classpath -->
<path id="classpath">
<!-- So that we can get jndi.properties for InitialContext -->
<pathelement location="${basedir}"/>
<fileset dir="${jboss.home}/lib">
<include name="**/*.jar"/>
</fileset>
<fileset dir="${jboss.home}/server/${jboss.server.config}/lib">
<include name="**/*.jar"/>
</fileset>
<fileset dir="${jboss.home}/server/
${jboss.server.config}/deploy/ejb3.deployer">
<include name="*.jar"/>
</fileset>
<fileset dir="${jboss.home}/server/
${jboss.server.config}/deploy/jboss-aop-jdk50.deployer">
<include name="*.jar"/>
</fileset>
<pathelement location="${build.classes.dir}"/>
</path>
<property name="build.classpath" refid="classpath"/>
<!--
=================================================================== -->
<!-- Prepares the build directory
-->
<!--
=================================================================== -->
<target name="prepare">
<mkdir dir="${build.dir}"/>
<mkdir dir="${build.classes.dir}"/>
</target>
<!--
=================================================================== -->
<!-- Compiles the source code
-->
<!--
=================================================================== -->
<target name="compile" depends="prepare">
<javac srcdir="${src.dir}"
destdir="${build.classes.dir}"
debug="on"
deprecation="on"
optimize="off"
includes="**">
<classpath refid="classpath"/>
</javac>
</target>
<target name="ejbjar" depends="compile">
<jar jarfile="build/tutorial.jar">
<fileset dir="${build.classes.dir}">
<include name="**/*.class"/>
</fileset>
<fileset dir=".">
<include name="META-INF/persistence.xml"/>
</fileset>
</jar>
<copy file="build/tutorial.jar" todir="${jboss.home}/server/
${jboss.server.config}/deploy"/>
</target>
<target name="run.stateless" depends="ejbjar">
<java classname="ejb.client.stateless.Client" fork="yes" dir=".">
<classpath refid="classpath"/>
</java>
</target>
<target name="run.stateful" depends="ejbjar">
<java classname="ejb.client.stateful.Client" fork="yes"
dir=".">
<classpath refid="classpath"/>
</java>
</target>
<target name="run.timer" depends="ejbjar">
<java classname="ejb.client.timer.Client" fork="yes"
dir=".">
<classpath refid="classpath"/>
</java>
</target>
<target name="run.entity" depends="ejbjar">
<java classname="ejb.client.entity.Client"
fork="yes" dir=".">
<classpath refid="classpath"/>
</java>
</target>
<!--
=================================================================== -->
<!-- Cleans up generated stuff
-->
<!--
=================================================================== -->
<target name="clean.db">
<delete dir="${jboss.home}/server/
${jboss.server.config}/data/hypersonic"/>
</target>
<target name="clean">
<delete dir="${build.dir}"/>
<delete file="${jboss.home}/server/
${jboss.server.config}/deploy/tutorial.jar"/>
</target>
</project>
最后,在工E目录下新徏目录META-INFQ在目录META-INF新徏persistence.xml文g
Q以下是文g内容Q?br /><?xml version="1.0" encoding="UTF-8"?>
<persistence>
<persistence-unit name="test">
<jta-data-source>java:/MySqlDS</jta-data-source>
<properties>
<property name="hibernate.dialect"
value="org.hibernate.dialect.MySQLDialect"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
</properties>
</persistence-unit>
</persistence>
四、运行测?br />run as->ant build后,选择q行目标为run.entity->run?br />q行l果Q?br />Buildfile: D:\Programs\Java\EclipseWork\EJB3\build.xml
prepare:
compile:
ejbjar:
run.entity:
[java] test successful!
BUILD SUCCESSFUL
Total time: 9 seconds
MySQL中select * from book;
id title author
1 My first bean book Sebastian
已经成功写入?/p>
@Id(generate = GeneratorType.AUTO) public int getId() { return id; } |
package com.kuaff.ejb3.composite; import java.io.Serializable; import javax.ejb.AccessType; import javax.ejb.DependentObject; @DependentObject(access = AccessType.PROPERTY) public class Name implements java.io.Serializable { private String first; private String last; public Name() { } public Name(String first, String last) { this.first = first; this.last = last; } public String getFirst() { return first; } public void setFirst(String first) { this.first = first; } public String getLast() { return last; } public void setLast(String last) { this.last = last; } public int hashCode() { return (first+last).hashCode(); } public boolean equals(Object object) { if (this == object) return true; if (object == null) return false; if (! (object instanceof Name)) return false; Name name = (Name)object; if ((name.first.equals(first)) && (name.last.equals(last))) return true; else return false; } } |
package com.kuaff.ejb3.composite; import javax.ejb.Dependent; import javax.ejb.DependentAttribute; import javax.ejb.Column; import javax.ejb.Entity; import javax.ejb.GeneratorType; import javax.ejb.Id; import javax.ejb.Table; @Entity @Table(name = "STUDENT") public class Student implements java.io.Serializable { private Name name; private String grade; private String email; public void setName(Name name) { this.name = name; } @Id(generate = GeneratorType.NONE) @Dependent( { @DependentAttribute(name = "first", column ={ @Column(name = "FIRST") }), @DependentAttribute(name = "last", column ={ @Column(name = "LAST") }) }) public Name getName() { return name; } public void setGrade(String grade) { this.grade = grade; } @Column(name = "GRADE") public String getGrade() { return grade; } public void setEmail(String email) { this.email = email; } @Column(name = "EMAIL") public String getEmail() { return email; |
package com.kuaff.ejb3.composite; import javax.ejb.Remote; import java.util.List; @Remote public interface StudentDAO { void create(String first, String last, String grade, String email); Student find(Name name); List findByFirstName(String name); List findByLastName(String name); List findByEmail(String email); void merge(Student s); } |
package com.kuaff.ejb3.composite; import java.util.List; import javax.ejb.EntityManager; import javax.ejb.Inject; import javax.ejb.Stateless; @Stateless public class StudentDAOBean implements StudentDAO { @Inject private EntityManager manager; public void create(String first, String last, String grade, String email) { Student student = new Student(); student.setName(new Name(first,last)); student.setGrade(grade); student.setEmail(email); manager.create(student); } public Student find(Name name) { return manager.find(Student.class, name); } public List findByFirstName(String name) { return manager.createQuery("from Student s where s.name.last = :name").setParameter("name", name).listResults(); } public List findByLastName(String name) { return manager.createQuery("from Student s where s.name.first = :name").setParameter("name", name).listResults(); } public List findByEmail(String email) { return manager.createQuery("from Student s where s.email = :email").setParameter("email", email).listResults(); } public void merge(Student s) { manager.merge(s); } } |
package com.kuaff.ejb3.composite; import javax.naming.InitialContext; import javax.naming.NamingException; import java.util.List; public class Client { public static void main(String[] args) throws NamingException { InitialContext ctx = new InitialContext(); StudentDAO dao = (StudentDAO) ctx.lookup(StudentDAO.class.getName()); dao.create("?,"x","8","smallnest@kuaff.com"); dao.create("?,"立焕","6","zhuzhu@kuaff.com"); Name name = new Name("?,"立焕"); //List list = dao.findByEmail("zhuzhu@kuaff.com"); Student s = dao.find(name); System.out.printf("%s %s的email:%s%n",s.getName().getFirst(),s.getName().getLast(),s.getEmail()); /* for(Object o:list) { Student s = (Student)o; System.out.printf("%s %s的email:%s%n",s.getName().getFirst(),s.getName().getLast(),s.getEmail()); } */ } } |
@ManyToMany(cascade = {CascadeType.CREATE, CascadeType.MERGE}, fetch = FetchType.EAGER, isInverse = true) @AssociationTable(table = @Table(name = "STUDENT_TEACHER"), joinColumns = {@JoinColumn(name = "TEACHER_ID")},inverseJoinColumns = {@JoinColumn(name = "STUDENT_ID")}) @ AssociationTable的注释声明如下: @Target({METHOD, FIELD}) public @interface AssociationTable { Table table() default @Table(specified=false); JoinColumn[] joinColumns() default {}; JoinColumn[] inverseJoinColumns() default {}; } |
@OneToOne(cascade = {CascadeType.ALL}) @JoinColumn(name = "DOSSIER_ID") public Dossier getDossier() { return dossier; } |
@ OneToOne的注释声明如下: @Target({METHOD, FIELD}) @Retention(RUNTIME) public @interface OneToOne { String targetEntity() default ""; CascadeType[] cascade() default {}; FetchType fetch() default EAGER; boolean optional() default true; } |
package com.kuaff.ejb3.relationships; import javax.ejb.CascadeType; import javax.ejb.Entity; import javax.ejb.FetchType; import javax.ejb.GeneratorType; import javax.ejb.Id; import javax.ejb.JoinColumn; import javax.ejb.OneToOne; import javax.ejb.ManyToMany; import javax.ejb.Table; import javax.ejb.AssociationTable; import java.util.ArrayList; import java.util.Set; import java.util.Collection; import java.io.Serializable; @Entity @Table(name = "STUDENT") public class Student implements Serializable { private int id; private String first; private String last; private Dossier dossier; private Set<Teacher> teachers; @Id(generate = GeneratorType.AUTO) public int getId() { return id; } public void setId(int id) { this.id = id; } public void setFirst(String first) { this.first = first; } public String getFirst() { return first; } public void setLast(String last) { this.last = last; } public String getLast() { return last; } public void setDossier(Dossier dossier) { this.dossier = dossier; } @OneToOne(cascade = {CascadeType.ALL}) @JoinColumn(name = "DOSSIER_ID") public Dossier getDossier() { return dossier; } public void setTeacher(Set<Teacher> teachers) { this.teachers = teachers; } @ManyToMany(cascade = {CascadeType.CREATE, CascadeType.MERGE}, fetch = FetchType.EAGER, isInverse = true) @AssociationTable(table = @Table(name = "STUDENT_TEACHER"), joinColumns = {@JoinColumn(name = "TEACHER_ID")},inverseJoinColumns = {@JoinColumn(name = "STUDENT_ID")}) public Set<Teacher> getTeacher() { return teachers; } } Dossier.java package com.kuaff.ejb3.relationships; import javax.ejb.Entity; import javax.ejb.GeneratorType; import javax.ejb.Id; @Entity public class Dossier implements java.io.Serializable { private Long id; private String resume; @Id(generate = GeneratorType.AUTO) public Long getId() { return id; } public void setId(Long id) { this.id = id; } public void setResume(String resume) { this.resume = resume; } public String getResume() { return resume; } } |
|
@Target({TYPE}) @Retention(RUNTIME) public @interface SecondaryTable { String name(); String catalog() default ""; String schema() default ""; JoinColumn[] join() default {}; UniqueConstraint[] uniqueConstraints() default {}; } |
@SecondaryTable @Target({TYPE}) @Retention(RUNTIME) public @interface SecondaryTables { SecondaryTable[] value() default {}; } |
Student.java package com.kuaff.ejb3.secondary; import javax.ejb.Dependent; import javax.ejb.DependentAttribute; import javax.ejb.Column; import javax.ejb.Entity; import javax.ejb.GeneratorType; import javax.ejb.Id; import javax.ejb.Table; import javax.ejb.SecondaryTables; import javax.ejb.SecondaryTable; import javax.ejb.JoinColumn; @Entity @Table(name = "STUDENT") @SecondaryTables({ @SecondaryTable(name = "GENDER", join = {@JoinColumn(name = "GENDER_ID")}) }) public class Student implements java.io.Serializable { private int id; private Name name; private String grade; private String email; private String gender; @Id(generate = GeneratorType.AUTO) public int getId() { return id; } public void setId(int id) { this.id = id; } public void setName(Name name) { this.name = name; } @Dependent({ @DependentAttribute(name = "first", column ={ @Column(name = "FIRST") }), @DependentAttribute(name = "last", column ={ @Column(name = "LAST") }) }) public Name getName() { return name; } public void setGrade(String grade) { this.grade = grade; } @Column(name = "GRADE") public String getGrade() { return grade; } public void setEmail(String email) { this.email = email; } @Column(name = "EMAIL") public String getEmail() { return email; } public void setGender(String gender) { this.gender = gender; } @Column(name = "gender", secondaryTable = "GENDER") public String getGender() { return gender; } } |
@SecondaryTables({ @SecondaryTable(name = "GENDER", join = {@JoinColumn(name = "GENDER_ID")}) }) |
@Column(name = "gender", secondaryTable = "GENDER") Client.java package com.kuaff.ejb3.secondary; import javax.naming.InitialContext; import javax.naming.NamingException; import java.util.List; public class Client { public static void main(String[] args) throws NamingException { InitialContext ctx = new InitialContext(); StudentDAO dao = (StudentDAO) ctx.lookup(StudentDAO.class.getName()); int id = dao.create("?,"x","8","smallnest@kuaff.com","?); dao.create("?,"立焕","6","zhuzhu@kuaff.com","?); List list = dao.findAll(); for(Object o:list) { Student s = (Student)o; System.out.printf("%s%s的性别:%s%n",s.getName().getFirst(),s.getName().getLast(),s.getGender()); dao.evict(s); } |
@Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) public @interface Column { String name() default ""; boolean primaryKey() default false; boolean unique() default false; boolean nullable() default true; boolean insertable() default true; boolean updatable() default true; String columnDefinition() default ""; String secondaryTable() default ""; int length() default 255; int precision() default 0; int scale() default 0; boolean specified() default true; // For internal use only } |
package javax.ejb; import java.sql.Connection; /** * 用来和持久化上下文交互的接口 */ public interface EntityManager { /** * 使实体bean受持久化理 * @param entity */ public void create(Object entity); /** * 给定的实体Bean的状态和持久化上下文l合。类似数据库的更新操作?br /> * @param entity * @return 被结合的实体实例 */ public <T> T merge(T entity); /** * 删除实例 * @param entity */ public void remove(Object entity); /** * Ҏ(gu)主键查找. * @param entityName * @param primaryKey * @return 查询实例 */ public Object find(String entityName, Object primaryKey); /** * Ҏ(gu)主键查找 * @param primaryKey * @return 查询实例 */ public <T> T find(Class<T> entityClass, Object primaryKey); /** * 持久化上下文与底层数据库的同?br /> */ public void flush(); /** * 执行一个EJBQL查询 * @param ejbqlString EJBQL查询语句 * @return the new query instance */ public Query createQuery(String ejbqlString); /** * 执行命名的查?br /> * @param name 预定义的查询名称 * @return 查询实例 */ public Query createNamedQuery(String name); /** * 执行一个本地SQL查询语句 * @param sqlString 本地查询语句 * @return q回查询实例 */ public Query createNativeQuery(String sqlString); /** * 更新到数据库?br /> * @param entity */ public void refresh(Object entity); /** * 持久化上下文中删除实?br /> * @param entity */ public void evict(Object entity); /** * 查当前上下文中是否包含此实体 * context. * @param entity * @return */ public boolean contains(Object entity); } |
package com.kuaff.ejb3.entity; import javax.ejb.CascadeType; import javax.ejb.Entity; import javax.ejb.FetchType; import javax.ejb.GeneratorType; import javax.ejb.Id; import javax.ejb.JoinColumn; import javax.ejb.OneToMany; import javax.ejb.Table; import java.util.ArrayList; import java.util.Collection; import java.io.Serializable; @Entity @Table(name = "STUDENT") public class Student implements Serializable { //主键 private int id; //学生?br /> private String name; //学生的分?br /> private Collection<Score> scores; //主键自动产生 @Id(generate = GeneratorType.AUTO) public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public void addScores(String name,int number) { if (scores == null) { scores = new ArrayList<Score>(); } Score score = new Score(); score.setName(name); score.setNumber(number); score.setStudent(this); scores.add(score); } @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) @JoinColumn(name = "student_id") public Collection<Score> getScores() { return scores; } public void setScores(Collection<Score> scores) { this.scores = scores; } } |
package com.kuaff.ejb3.entity; import java.io.Serializable; import javax.ejb.Entity; import javax.ejb.GeneratorType; import javax.ejb.Id; import javax.ejb.JoinColumn; import javax.ejb.ManyToOne; import javax.ejb.Table; @Entity @Table(name = "Score") public class Score implements Serializable { private int id; private String name; private int number; private Student student; //主键自动产生 @Id(generate = GeneratorType.AUTO) public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } @ManyToOne @JoinColumn(name = "student_id") public Student getStudent() { return student; } public void setStudent(Student student) { this.student = student; } } |
package com.kuaff.ejb3.entity; import javax.ejb.Remote; import javax.ejb.Remove; import java.util.Map; @Remote public interface Teacher { public void addScore(String studentName,Map<String,Integer> map); public Student getStudent(); @Remove public void leave(); } |
package com.kuaff.ejb3.entity; import javax.ejb.EntityManager; import javax.ejb.Inject; import javax.ejb.Remove; import javax.ejb.Stateful; import java.util.Map; import java.util.Set; @Stateful public class TeacherBean implements Teacher { @Inject private EntityManager manager; private Student student; public Student getStudent() { return student; } public void addScore(String studentName, Map<String,Integer> map) { if (student == null) { student = new Student(); } student.setName(studentName); Set<String> set = map.keySet(); for (String sname:set) { student.addScores(sname,map.get(sname).intValue()); } } @Remove public void leave() { manager.create(student); } } |
package com.kuaff.ejb3.entity; import java.util.Map; import java.util.HashMap; import java.util.Collection; import javax.naming.InitialContext; import javax.naming.NamingException; public class Client { public static void main(String[] args) throws NamingException { InitialContext ctx = new InitialContext(); Teacher teacher = (Teacher) ctx.lookup(Teacher.class.getName()); Map<String,Integer> map = new HashMap<String,Integer>(); map.put("语文",new Integer(98)); map.put("化学",new Integer(149)); map.put("物理",new Integer(143)); teacher.addScore("smallnest",map); Student student = teacher.getStudent(); String name = student.getName(); System.out.printf("昄%s的分?%n",name); Collection<Score> c = student.getScores(); for (Score score:c) { System.out.printf("%s:%s%n",score.getName(),score.getNumber()+""); } } } |
@Target({TYPE}) @Retention(RUNTIME) public @interface DependentObject { AccessType access() default PROPERTY; } |
@Target({METHOD, FIELD}) @Retention(RUNTIME) public @interface Dependent { DependentAttribute[] value() default {}; } |
@Target({}) @Retention(RUNTIME) public @interface DependentAttribute { String name(); Column[] column() default {}; } |
package com.kuaff.ejb3.dependent; import javax.ejb.Dependent; import javax.ejb.DependentAttribute; import javax.ejb.Column; import javax.ejb.Entity; import javax.ejb.GeneratorType; import javax.ejb.Id; import javax.ejb.Table; @Entity @Table(name = "STUDENT") public class Student implements java.io.Serializable { private int id; private Name name; private String grade; private String email; @Id(generate = GeneratorType.AUTO) public int getId() { return id; } public void setId(int id) { this.id = id; } public void setName(Name name) { this.name = name; } @Dependent( { @DependentAttribute(name = "first", column ={ @Column(name = "FIRST") }), @DependentAttribute(name = "last", column ={ @Column(name = "LAST") }) }) public Name getName() { return name; } public void setGrade(String grade) { this.grade = grade; } @Column(name = "GRADE") public String getGrade() { return grade; } public void setEmail(String email) { this.email = email; } @Column(name = "EMAIL") public String getEmail() { return email; } } |
package com.kuaff.ejb3.dependent; import java.io.Serializable; import javax.ejb.AccessType; import javax.ejb.DependentObject; @DependentObject(access = AccessType.PROPERTY) public class Name implements java.io.Serializable { private String first; private String last; public Name() {} public Name(String first, String last) { this.first = first; this.last = last; } public String getFirst() { return first; } public void setFirst(String first) { this.first = first; } public String getLast() { return last; } public void setLast(String last) { this.last = last; } } |
package com.kuaff.ejb3.dependent; import javax.ejb.Remote; import java.util.List; @Remote public interface StudentDAO { int create(String first, String last, String grade, String email); Student find(int id); List findByFirstName(String name); List findByLastName(String name); List findByEmail(String email); void merge(Student s); } |
package com.kuaff.ejb3.dependent; import java.util.List; import javax.ejb.EntityManager; import javax.ejb.Inject; import javax.ejb.Stateless; @Stateless public class StudentDAOBean implements StudentDAO { @Inject private EntityManager manager; public int create(String first, String last, String grade, String email) { Student student = new Student(); student.setName(new Name(first,last)); student.setGrade(grade); student.setEmail(email); manager.create(student); return student.getId(); } public Student find(int id) { return manager.find(Student.class, id); } public List findByFirstName(String name) { return manager.createQuery("from Student s where s.name.last = :name").setParameter("name", name).listResults(); } public List findByLastName(String name) { return manager.createQuery("from Student s where s.name.first = :name").setParameter("name", name).listResults(); } public List findByEmail(String email) { return manager.createQuery("from Student s where s.email = :email").setParameter("email", email).listResults(); } public void merge(Student s) { manager.merge(s); } } |
package com.kuaff.ejb3.dependent; import javax.naming.InitialContext; import javax.naming.NamingException; import java.util.List; public class Client { public static void main(String[] args) throws NamingException { InitialContext ctx = new InitialContext(); StudentDAO dao = (StudentDAO) ctx.lookup(StudentDAO.class.getName()); int id = dao.create("?,"x","8","smallnest@kuaff.com"); dao.create("?,"立焕","6","zhuzhu@kuaff.com"); List list = dao.findByEmail("zhuzhu@kuaff.com"); for(Object o:list) { Student s = (Student)o; System.out.printf("%s %s的email:%s%n",s.getName().getFirst(),s.getName().getLast(),s.getEmail()); } } } |
NewsTimer.java package com.kuaff.ejb3.schedule; import javax.ejb.Remote; @Remote public interface NewsTimer { public void fiveNews(); } |
NewsTimerBean.java package com.kuaff.ejb3.schedule; import java.util.Date; import javax.ejb.Inject; import javax.ejb.SessionContext; import javax.ejb.Stateless; import javax.ejb.Timer; @Stateless public class NewsTimerBean implements NewsTimer { private @Inject SessionContext ctx; public void fiveNews() { ctx.getTimerService().createTimer(new Date(new Date().getTime() + 300000), "子虚乌有?sh)视?分钟新闻栏目:现在q?分钟Q又到即时新闻节目的旉了?); } public void ejbTimeout(Timer timer) { System.out.printf("旉?%n%s%n" , timer.getInfo()); timer.cancel(); } } Client.java package com.kuaff.ejb3.schedule; import javax.naming.InitialContext; import javax.naming.NamingException; public class Client { public static void main(String[] args) throws NamingException { InitialContext ctx = new InitialContext(); NewsTimer timer = (NewsTimer) ctx.lookup(NewsTimer.class.getName()); timer.fiveNews(); } } |
ctx = new InitialContext(); |
@Resource(jndiName="java:/DefaultDS") public DataSource customerDB; Resource注释的声?br />@Target({TYPE, METHOD, FIELD, PARAMETER}) @Retention(RUNTIME) public @interface Resource { String name() default ""; String resourceType() default ""; AuthenticationType authenticationType() default CONTAINER; boolean shareable() default true; String jndiName() default ""; } public enum Authentication Type { CONTAINER, APPLICATION } @Target(TYPE) @Retention(RUNTIME) public @interface Resources { Resource[] value(); } |
@Inject(jndiName="java:/DefaultDS") public DataSource customerDB; |
@Inject javax.ejb.SessionContext ctx; @Inject javax.ejb.TimerService timer; @Inject javax.ejb.UserTransaction ut; @Inject javax.ejb.EntityManager manager; |
JmsUsers.java package com.kuaff.ejb3.di; import java.util.List; import javax.ejb.Remote; import javax.sql.*; @Remote public interface JmsUsers { public List<String> getUsers(); } |
CounterBean.java package com.kuaff.ejb3.di; import java.util.List; import java.util.ArrayList; import javax.ejb.Stateless; import javax.ejb.Resource; import javax.sql.*; import java.sql.*; @Stateless public class JmsUsersBean implements JmsUsers { @Resource(jndiName="java:/DefaultDS",resourceType="javax.sql.DataSource") public DataSource customerDB; public List<String> getUsers() { List<String> list = new ArrayList<String>(); try { Connection conn = customerDB.getConnection(); Statement st = conn.createStatement(); ResultSet rs = st.executeQuery("select * from jms_users"); while(rs.next()) { list.add(rs.getString("userid")); } } catch(SQLException e) {} return list; } } |
Client.java package com.kuaff.ejb3.di; import java.util.List; import javax.naming.InitialContext; import javax.naming.NamingException; public class Client { public static void main(String[] args) { InitialContext ctx; try { ctx = new InitialContext(); JmsUsers users = (JmsUsers) ctx.lookup(JmsUsers.class.getName()); List<String> jmsUsers = users.getUsers(); for(String user:jmsUsers) { System.out.printf("用户?%s%n",user); } } catch (NamingException e) { e.printStackTrace(); } } } |
@ConnectionConfig(destinationType = javax.jms.Queue.class, destinationJndiName = "queue/kuaffejb3/sample", durable = true, subscriptionId = "kuaffMessage") |
Messager.java package com.kuaff.ejb3.messager; import org.jboss.ejb3.mdb.ConnectionConfig; import javax.ejb.MessageDriven; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.TextMessage; import javax.jms.MessageListener; @MessageDriven @ConnectionConfig(destinationType = javax.jms.Queue.class, destinationJndiName = "queue/kuaffejb3/sample", durable = true, subscriptionId = "kuaffMessage") public class Messager implements MessageListener { public void onMessage(Message recvMsg) { System.out.println("接收到的消息:"); try { TextMessage message = (TextMessage)recvMsg; System.out.println(message.getText()); } catch (JMSException e) { e.printStackTrace(); } } } |
Client.java package com.kuaff.ejb3.messager; import javax.jms.Queue; import javax.jms.QueueConnection; import javax.jms.QueueConnectionFactory; import javax.jms.QueueSender; import javax.jms.QueueSession; import javax.jms.TextMessage; import javax.naming.InitialContext; public class Client { public static void main(String[] args) throws Exception { QueueConnection cnn = null; QueueSender sender = null; QueueSession session = null; InitialContext ctx = new InitialContext(); Queue queue = (Queue) ctx.lookup("queue/kuaffejb3/sample"); QueueConnectionFactory factory = (QueueConnectionFactory) ctx.lookup("ConnectionFactory"); cnn = factory.createQueueConnection(); session = cnn.createQueueSession(false, QueueSession.AUTO_ACKNOWLEDGE); TextMessage msg = session.createTextMessage("江湖快报:玉树临风风流倜傥的公子小巢又出现了?); sender = session.createSender(queue); sender.send(msg); System.out.println("消息已经发出"); } } |
queue-example-service.xml Q?xml version="1.0" encoding="UTF-8"?Q?br />QserverQ?br />Qmbean code="org.jboss.mq.server.jmx.Queue" name="jboss.mq.destination:service=Queue,name=kuaffMessage"Q?br /> Qattribute name="JNDIName"Qqueue/kuaffejb3/sampleQ?attributeQ?br /> Qdepends optional-attribute-name="DestinationManager"Qjboss.mq:service=DestinationManagerQ?dependsQ?br />Q?mbeanQ?br />Q?serverQ?/td> |
Counter.java …?br />import javax.ejb.Remove; …?br />@Remote public interface Counter { …?br /> @Remove public void clean(); } |
CounterBean.java …?br />import javax.ejb.Stateful; …?br />@Stateful public class CounterBean implements Counter { //增加事务支持 @Tx(TxType.REQUIRESNEW) public int getNumber() { return number; } @Remove public void clean() { System.out.println("我,被删除了!"); } } |
Client.java package com.kuaff.ejb3.stateful; import javax.ejb.EJBException; import java.rmi.NoSuchObjectException; import javax.naming.InitialContext; import javax.naming.NamingException; public class Client { public static void main(String[] args) { InitialContext ctx; try { ctx = new InitialContext(); Counter counter = (Counter) ctx.lookup(Counter.class.getName()); counter.add(10); System.out.println("当前的number:" + counter.getNumber()); counter.add(10); System.out.println("当前的number:" + counter.getNumber()); Counter counter2 = (Counter) ctx.lookup(Counter.class.getName()); counter2.add(10); System.out.println("当前的number:" + counter2.getNumber()); //删除 counter2.clean(); //下面如果再用counter2,出?br /> try { System.out.println("当前的number:" + counter2.getNumber()); } catch(EJBException ex) { if (ex.getCausedByException() instanceof NoSuchObjectException) { System.out.println("我都被删除啦Q还找我Q?); } else { throw ex; } } } catch (NamingException e) { e.printStackTrace(); } } } |
本文关键字:EJB, 设计模式, pattern, session bean, entity bean, message driven bean, command, dto, factory |
|
二. 常见EJB设计模式
Session Facade Pattern
通常目中,客户端往往需要频J的Ҏ(gu)务器端数据进行操作。当采用实体QJQ作为数据的抽象层时Q如果直接让客户端程序与实体QJQ交互,会生实C个业务需求便需要大量的QJQ属性操作(如下图1Q。这直接D如下问题Q网l负载大Q远E客LӞ、ƈ发性能低、客L与服务器端关联度大、可重用性和可维护性差、性能
因此有必要在客户端与实体QJQ层间加入Session QJQ层Q在Sessino EJB中实现商业逻辑q封装对实体QJQ的操作。(如下图2Q?br />
图1Q客L直接与实体IQ交互
图2Q通过SessionEJB层实?br />Session Fa?ade模式的好处是Q降低了|络负蝲QSessionEjb可以调用实体QJQ的本地接口Q将商业逻辑与商业数据隔;l护与开发方便;显著提高性能?br />Session Fa?ade模式因其单用,是目前用很q的模式。但具体应用q程中应注意Q避免将所有的操作装C个很大的SessionEJB内;服务器端数据l构应由实体EJB实现,除非特例否则避免直接的数据库操作QSessionEjb内某些系l通用操作的代码容易重复(比如权限查等Q解军_法是系l通用服务装在Java Class内)?/p>
Message Facade Pattern
很多时候,一ơRequest需要操作多个IQ又不需要得到即时返回。对q种异步调用Q通常应用Message Fa?ade Pattern.
q种时候,如采用Session Fa?ade Pattern存在如下问题Q?br />1. 客户端等待返回的旉q长。一个SessionEjb的实例在完成客户hq程中中涉及到的每一ơ对其他实体Ejb的调用过E中都会被锁定直到得到实体EJBq回信息后才能进行下一步操作。这样造成客户不必要的{待Qƈ很容易因旉D整个事务p|?br />2. pȝ可靠性和定w性低。如果需要调用不同系l或服务器上或多个异构数据源的多个IQӞM一个环节出错,均导致客戯求失败?br />以Message-Driven Bean为基的Message Facade Pattern则可以解决上q异步请求需求。具体架构见下图Q?
图3Q用Message Facade Pattern
Message Facade Pattern的不之处在于:
Q. Message-Driven Bean没有q回倹{这样通知客户执行l果只能依赖于Imail或h工等其他手段?br />Q. Message-Driven Bean执行q程中无法将捕获的异常直接返回给客户端,x法客户端直接直到错误信息?br />Q. Message-Driven Bean通过接收Message响应客户hQ对Message内容的合法性(比如对象的类型等Q依赖与客户?Ҏ(gu)产生q行旉误?br />Message Facade Patternl常与Session Facade Pattern在同一个项目里共同使用?/p>
EJB Command Pattern
Session Facade Pattern中将商业逻辑实现装在Session EJB中,q种做法带来诸多益处之外也带来如下问题:
Q. ׃业务l常的变化,Dl常需要更新Session EJB代码?br />Q. 客户端代码不得不包含大量EJB相关的AQ;Q不利于后期目l护?br />Q. 目开发测试需要经常的EJB重部|过E?br />引v上述问题的重要根l就是Session EJB本n重量U组Ӟ其开发测试部|工作量较大Q开发周期较ѝ以上不_以通过EJB Command Pattern克服?br />EJB Command Pattern中将商业逻辑实现装在普通的Java ClassQ称之ؓCommand BeanQ中。该模式的具体实现有很多U,通常的框枉包括三部分:
Q. Command Bean.由应用开发者写的具体实现某商业操作的Java Class.主要包含getXXX(),setXXX(),execute()Ҏ(gu)?br />Q. Client-Side Routing Logic.由多个Classl成Q用于将h转发至Command SeverQ这个过E对客户是透明的。这部分代码可以跨项目用。\p则中可以考虑用QL技术?br />Q. Remote Command Server.实际执行商业操作h。通常可以用Session EJB层实现?/p>
整个框架见下图4Q?br />
图4QCommand的基本框?br />EJB Command Patternh如下好处Q?br />Q. 适应与需要快速开发环境。因Command Bean是轻量的Java ClassQ其~译和调试比较方ѝ?br />Q. 表现层与商业实现层隔离Q同时将客户端代码与EJB层隔R?br />Q. 客L代码开发与服务器端代码开发相Ҏ(gu)晰。早期可以创建空的Command Bean方便客户端代码调试?br /> EJB Command Pattern的弱处在于:
Q. Command Bean中对事务的控制不如Session EJB中?br />Q. Command Bean是无状态的?br />Q. 无法异常直接返回给客户?br />Q. 在大目中,׃商业逻辑复杂Q常D大数量的Command Bean存在.
Q. 作ؓCommand Server的Session EJB打包时必d含Command Bean以致存在l护上的不便?br /> EJB Command Pattern的一个实际实现可以参考IBM's Command Framework.
Data Transfer Object Factory
ZEJB的J2EE目Q经帔R要在客户端与服务器端传输大量数据。数据的l织形式常用的是DTO(Data Transfer ObjectQ服务器端数据对象的抽象)。但因ؓ客户端表现层l常是变化的Q所需要服务器端数据也变动频繁Q换句话_DTO的数量和属性经常要更改。因此如何以及在何处生成和维护DTO便是需要考虑的问题?br />一U解x案是直接在Entity EJB中直接处理,卛_Entity EJB的BeancM加入getXXXDTO()、setXXXDTO(){。但q样做导致EJB与DTO层紧紧绑定。一旦DTO更改Q与该DTO相关的EJB即需要重~译打包。EJB层与客户端层相关联不仅ɾl护困难而且DEJB的重用性大大降低?br />更好的解x案是利用Data Transfer Object Factory装对DTO的操作逻辑Q如下图Q)?br />
图6QDTO FactoryCZ
DTO Factory具体实现方式通常有两U:
Q. 普通Java Class实现Q用于Session Facade Pattern使用DTO环境下?br />Q. Stateless Session EJB实现Q用于非EJB客户端用DTO环境下(见图Q)?br />
图7QSessionEJB实现DTOFactory
DTO Factory带来如下好处Q?br />Q. 使Entity EJB的重用成为可能。由于不含DTO处理逻辑QEntity EJB功能单一化,只作为数据源。不通客L通过各自的DTO Factory可以从同一个Entity EJB得到各自所需的个性化数据Q自定义DTOQ?br />Q. 提高可维护性和性能?br />Q. 可以Ҏ(gu)在DTO Factory层生成很复杂的DTOl构Q诸如ѝ关联关pȝQ而对客户端提供一个透明、细化的数据接口?br /> 使用DTO Factory旉要注意的是:不需为每个Entity EJB定义一个Factory。可以ؓ一pd相关的Entity EJB创徏一个FactoryQ或者只创徏一个Factory?/p>
Generic Attribute Access
使用Entity EJB作ؓ商业数据层时Q我们首先需要从数据库加载数据,创徏对应的Entity EJB实例Q之后对内存中Entity EJB实例的属性进行相应操作。对属性的操作比较直接的做法是Q直接调用Entity EJB的getXXX()/setXXX()Q通常利用EJB2.0的本地接口;通过DTO Factory生成DTO。但q两U做法都存在如下问题Q?br />Q. 当Entity EJB的属性特别多时候,以上做法会带来复杂罗嗦的代码QEJB变的庞大无比?br />Q. 使Entity EJB的客LQ比如Session EJBQ和Entity EJB的接口紧密关联。Entity EJB属性的增删都需要更改客L代码Q给目开发和l护带来不便?br />事实上可以利用更通用的方式访问Entity EJB的属性,卛_义Generic Attribute Access Interface。见下图Q:
图8QGeneric Attribute Access InterfaceCZ
Generic Attribute Access Interface由Entity EJB的本地或q程接口实现Qƈ利用Hash Maps传输数据。实现方式常见如下:
Q. BMPcd实体EJB可以在BeancM定义包含所有属性的U有成员变量HashMap?br />Q. CMPcd实体EJB可以在BeancM可以适用Java Reflection API实现?br />Q. 建立一个父c,在不同的情况下定义子c重载父cL法?br />使用Generic Attribute Access Interface需要在客户端与服务器端对属性以及对应的关键字徏立统一的命名习惯。常见的做法如下Q?br />Q. 建立q保持良好的文档记录和命名约定?br />Q. 在实体EJB的实现类中定义静态成员映属性?br />Q. 创徏׃n静态类Q通过成员变量映射实体EJB属性?br />Q. 通过JNDI在服务器端保存属性映关pR?br />Generic Attribute Access Interface的运用带来一下益处:
Q. 接口实现后对不通实体EJB都适用?br />Q. 对属性较多实体EJB能精代码Qƈ更具l护性?br />Q. 使运行中动态增删实体EJB属性成为可能?br />Generic Attribute Access Interface的缺点在于:
Q. 讉KEJB属性时增加了额外的操作。需要通过关键字映属性,最后还需q行cd转换?br />Q. 需要徏立客L与服务器端的命名U定?
Q. 因ؓ通过HashMap操作时候需要进行类型{换,Ҏ(gu)产生q行时类型不匚w异常?/p>
Business Interface
EJB规范要求Bean实现cdd现所有在q程Q或本地Q接口中定义的所有方法,同时不允许Bean实现cȝ接承远E(或本圎ͼ接口。这导致编译时候很Ҏ(gu)产生两者不一致的问题Q即q程Q或本地Q接口中定义的某Ҏ(gu)为在Bean实现cM被实现等错误。ؓ避免上诉错误Q可以利用应用服务器厂商所提供的工兗但也可以应用EJB的设计架构来实现Q定义商业接口?br />Business Interface卛_定义商业接口Q在接口中定义所有EJB提供的商业方法,q让Bean实现cdq程Q或本地Q接口都实现该商业接口。其l承关系见下图9Q?br />
图9Q商业接口的使用
Business Interface是个普通的Java Class。依赖于使用本地接口与远E接口的不通,Business Interface的定义略有不同:应用与远E接口时Q在接口中的Ҏ(gu)需要抛出java.rmi.RemoteExceptionQ而应用与本地接口时候则不需要作M特别处理?br />应用Business Interface时候必L意一点:EJB规范不允许直接EJB的实例将对自q引用Qthis对象Q返回给客户端,否则~译时候即报错。但使用Business Interface后,~译时候无法检查出有无this对象q回l客L。这一炚w要程序员自己保证?/p>
三. 内部数据转换{略
Data Transfer Object
ZEJB的J2EE多层架构应用中,l常涉及的一个问题就是如何在各层之间传递批量数据,比如客户端对服务器端数据的批量读写操作等。比如需要得到实体EJB的属性,直接的方法是多次调用不通的属性,如下图1Q:
图1Q:低效的数据传递方?br /> 但这U方法容易导致许多问题,比如性能以及代码的复杂度{,更有效的办法是在一个调用中得到所有需要的属性。所以可以引入Data Transfer Object来封装所需要的属性,q在客户与服务器端通过传递该对象一ơ实现对数据的操作。如下图Q1Q?br />
图1Q:通过DTO传递数?br />
DTO为普通的Java ClassQ通常是服务器端数据的快照。由于网l传输的需要,DTO应该实现java.io.Serializable接口?br />DTO的设计有两种模型QDomain DTO以及Custom DTO?br />Domain DTO仅仅实现Ҏ(gu)务器数据的拷贝,通常与实体EJBZ对一的关p?也存在ؓ多个相关联的实体EJB对应一个Domain DTO)。Domain DTO通常除用于读取更改实体EJB属性外也可用于创徏实体EJB时候。实体EJB与Domain DTO对应关系如下图1Q:
图1Q:Account EJB 与 Account DomainDTO
Domain DTO的应用除了DTO所h的一般优点外Q还有别的益处:
Q. 开发迅速。因Z旦实体EJB设计好后Q很Ҏ(gu)转换得到Domain DTO?br />Q. 可以利用Domain DTO的setXXX()Ҏ(gu)在客Lq行属性有效性效验?br />Domain DTO的缺Ҏ(gu)Q?br />Q. 客户端绑定了服务器端数据模型Q不利于l护?br />Q. 不够灉|Q无法处理客L的多样化数据要求。对一个数百个属性的实体EJBh一个属性时候却q回一个包含所有属性值的Domain DTO明显是笨重的实现?br />Q. D代码的重复?br />Q. Domain DTO中如果嵌套包含了别的Domain DTOӞ一旦需服务器端数据的更改而需要重定义Domain DTO模型时候异常困难?/p>
Custom DTO则可以克服上q的一些缺炏VCustomer DTO仅仅装用户感兴的服务器数据集卛_以根据客L需求创建Customer DTO。这样作的优Ҏ(gu)灉|高效Q缺Ҏ(gu)大项目中可能D大量的Customer DTO存在?br />通常Domain DTO可以用于数据的更C创徏QCustomer DTO可以用于客户用于表现层的数据d。两者可以相辅相成。而且使用DTO一般与DTO Factory同时使用?/p>
Domain Transfer Hash Map
DTO的用往往~Z通用性。不通的用户案例需要创Z同的DTO。当目很复杂时Q从l护性考虑需要更好的数据传输的实现方式?
Domain Transfer Hash Map卛_用HashMap作ؓ客户所需数据集的装。好处是Q?br />Q. 良好的维护性?br />Q. 较大的通用性。不同的客户端可以用相同的数据传递方式?br />~点是:
Q. 需要维护客L与服务器端在属性及其对应关键字的映关pR?br />Q. 当需要用基本类型的数据时候,因ؓHash Map的限制必d基本cd先{换成对象?br />Q. 使用得到的数据时Q需要进行类型强制{换?/p>
Data Transfer RowSet
当需要处理直接的JDBC调用得到的结果集Ӟ昄用DTO/Hash Map已经不合适,因ؓ需要对大量数据q行cd转换{额外操作是很费资源和不必要的,而且最l用户常需要以表格式样昄数据?
所以对二维表式数据Q更好的处理方式是利用Data Transfer RowSet。Data Transfer RowSet通过ResultSet直接转换为RowSet传递给客户端?br />在Session EJB中用RowSet的一D늤例代码如下图Q3Q?br />
图1Q:使用RowSet
使用RowSet的好处很多:
Q. 接口通用于各L数据库查询操作?br />Q. 当需要表格式数据昄Ӟ因ؓ直接从ResultSet得到Q所以不需要额外的数据cd转换?br />~点是:
Q. 数据库结构暴露给客户端?br />Q. 不符合面向对象设计思想?br />Q. 依赖于SQL?br />Data Transfer RowSet通常用于只读式数据的昄操作Q经常和JDBC for Reading Patternq用?/p>
四.事务和数据持久机?/p>
JDBC for Reading Pattern
ZEJB的J2EE应用中,通过EJBҎ(gu)据库的操作可以有两种方式Q实体EJB或者Session EJB中直接利用JDBC讉K?br />客户很多时候取出数据库中数据ƈ以表格方式显C。这U情形如果用实体EJB会导致如下问题:
Q. 引用服务器端频繁的数据库查询和加载操作。因为加载N个实体EJB总需要进行一ơfind()操作 Nơ数据加载?br />Q. 如果使用Remote接口Q引起频J的额外|络操作?br />Q. 对关联关pL较复杂的数据库表l构Q很隄接通过Entity EJB表现?br />因此在只需Ҏ(gu)据库表数据进行只读访问时候,应该采用JDBC for Reading PatternQ即通过JDBC直接讉K数据库。除了避免上qC用实体EJB的缺点还带来一下好处:
Q. 充分利用数据库能力,比如数据库的~存机制?br />Q. 减少了对事务控制的资源?br />Q. 利用自定义SQL可以按需要比较灵zȝd数据?br />Q. 只需要一ơ数据查询,减少了数据库操作?br />~点是:
Q. 于J2EE应用的面向对象设计相q背?br />Q. 因ؓSession EJB代码中包含了自定义SQLQ维护性差?br />Q. Session EJB中不得不包含JDBC的APIQƈ且需要了解数据库l构?/p>
EJB作ؓ企业U的数据讉K/持久化标准在1999q作为J2EE规范的核心规范出玎ͼ极大的{变了java企业U开发的模式Qؓjava软g开发提供了一个良好的架构?EJB?.0?.1在J2EE架构中,都是作ؓ一个服务器端的(Server side)的数据访问中间g。开发h员通过EJB标准的API接口来访问操作数据,避免直接用JDBC和Sql操作底层的数据库?
采用EJB架构的目标在于:
在旧的EJB模型中(2.1以前Q?EJB实现了大部分的目标,但一个巨大的~陷是原有的模型在试囑ևL据访问工作量的同时也引入了更多的复杂开发需求?例如EJB核心的的Entity Bean必须特定的Home,Remote,Business接口Q发布前需要预~译Q只能实现单表映操作,静态的EJB-QL(EJB查询语言Q都不能满化数据库操作的目标?EJB 2.1的复杂度Q开发成本和性能问题使得EJB在问?q后仍然{不到广泛的应用?
C2004q_随着POJO( Plain Old Java Object )模型的出玎ͼ动态代码操作,IOC模式{先q,单实用技术的发展和它们在各种独立产品中的表现Q都证明POJO,IOC的技术比原有的EJB 2.1模型更适合作ؓ数据讉K中间Ӟ开发的隑ֺ和成本远q小于目前的EJB模型Q确有更灉|和可扩展?2004q?月J2EEq_规范集众家所长,推出了跨式的Java EE 5.0规范Q最核心的是全面引入新的ZPOJO和IOC技术的EJB3模型。到此,J2EE 5规范( Java EE 5 )成ؓ一个集大成者,U百家之长,成ؓjava企业开发统一的标准规范?
EJB3 规范中引入了元数据批?Annotation)。Annotation是从J2SE 1.5开始称为java语言的一部分。Annotationq不是一个新的事物,在J2SE 1.5以前Qh们已l自行引入了象著名的XDoclet{外挂式的元数据Ҏ(gu)Ҏ(gu)。而在.NET中,元数据批注也早已l是C#语言的成分了?
在以往Q我们都是采用xml作ؓ配置Ҏ(gu)Q但采用文本的xml配置存在一些缺P
采用元数据可以很好的解决q些问题:
EJB3中的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来处理?/p>
@Table( name="helloTable" ) :思义Q定义当前Entity对应数据库中的表?/p>
@Id(generate=GeneratorType.NONE) :我们把HelloEntityBean的id属性定义ؓ主键。主键生策略ؓGeneratorType.NONE,意味q是一个业务主键?/p>
p么简单!
从EJB3.0开始,Entity Bean持久化规范与EJB的其他规范如Session Bean, Message Driven Bean和EJB3容器规范开始分,单独作ؓ一个持久化API。而在来Q该持久化API很可能会从EJB3.0规范中脱d来成Z个独立的规范Java Persistence API。作为Javaq_上的通用数据讉K标准接口?Z跟规范一_我们在开发手册中Entity BeanUCؓEntity?/p>
虽然EJB3 Entity可以是很单的java beanQ只要批注了@Entity或者在xml配置中作了说明,p做一个可持久化的Entity处理?但还是需要遵行一定的规则Q?/p>
在绝大部分的商业应用Q开发h员都可以忽略q部分无需兛_。但如果你需要编写复杂的Entitycȝ话,你需要了解这个部分。复杂的EntitycL指在Entitycȝgetter/setter和商业方法中包含比较复杂的业务逻辑而不是仅仅返?W值某个属性?/p>
在大部分的情况下Q我们都使EntitycMsetter/getter中的逻辑可能简单,除了必要的校验符值外Q不要包含复杂的业务逻辑Q例如对兌的其他Entityc进行操作。但有些情况下,我们q是需要在Entitycȝsetter/getterҎ(gu)中包含商业逻辑。这时候,采用何种属性访问方式就可能会媄响代码的性能甚至是逻辑正确产生影响?/p>
EJB3持久化规范中,在默认情况下所有的属性都会自动的被持久化Q除非属性变量用@Transient元数据进行了标注。针对可持久化属性定义了两种属性访问方?access): FIELD和PROPERTY?/p>
规范中access方式q有多一层含义。就是采用access=FIELDӞ元数据应该批注在属性上?/p>
@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上?/p>
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; }
Z方便开发,Liberator EJB3实现对元数据Ҏ(gu)的位|比规范规定的宽松,针对属性变量或它的getterq行Ҏ(gu)都可以,不受accesscd的媄响?/p>
对EntitycMQgetter/setter的命名规则遵从java bean规范?/p>
EntitycM的属性变量可以是以下数据cdQ?/p>
q可以是以下集合cd:
每个Entityc都必须有一个主键。在EJB3中定义了两种主键Q?/p>
单主键必d应Entity中的一个属性变量(Instance Variable)Q而该属性对应数据库表中的一列。用简单主键,我们只需要用@Id元数据对一个属性变量或者她的getterҎ(gu)q行Ҏ(gu)?/p>
当我们需要用一个或多个属性变量(表中的一列或多列Q联合v来作Z键,我们需要用复合主键。复合主键要求我们编写一个复合主键类( Composite Primary Key Class )。复合主键类需要符合以下一些要求:
@Id private String firstName; @Id private String lastName; public 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; } }
对Entityq行操作的API都设计在javax.persistence.EntityManager接口上。EntityManagerQ顾名思义是管理所有EJB 3q行环境中的所有Entity?EntityManagerҎ(gu)q行的环境不同分为容器管理的EntityManager和应用管理的EntityManager。Liberator EJB3是一个可以运行在J2SE环境中的 嵌入式的EJB3 Persistence实现Q因此在Liberator EJB3中的 EntityManager都是指应用管理的EntityManager?/p>
// 获得默认当前的EntityManagerFactory final EntityManagerFactory emf = Persistence.createEntityManagerFactory(); final EntityManager entityManager = emf.createEntityManager();
当调用Persistence.createEntityManagerFactory()的时候,Persistence会做以下的步骤:
<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的名字,q样当应用中需要多个EntityManagerFactory的时候可以区别。通常情况下,一个EntityManagerFactory对应一个数据源?/p>
<provider></provider> 定义采用的什么EJB3 Persistenceq行环境Q也是EJB3 Persistence产品)Q例子中采用的的是Liberator EJB3的运行环境。正如前面说的,在EJB3规范? EJB3 Persistenceq行环境( Runtime )是作Z个独立的嵌入式模块,你可以随意的更换不同的EJB3 Persistenceq行环境而不会媄响你的应用程序?strong>甚至在J2EE服务器内q行你也可以不用服务器本w提供的EJB3 Persistenceq行环境Q而采用第三方的EJB3 Persistenceq行环境?/p>
<class></class> 定义哪些cLEntity持久化类。如果是试环境或者你不采用ear/war的方式发布你的应用,而只是把~译后的cȝ接拷贝到web容器中,在添加一个新的EntitycLQ需要在q里d一行。要求这个步骤是因ؓ在J2EE 5架构中EJB3q行环境的嵌入式设计。采用这U松耦合设计使EJB3 Persistenceq行环境不能控制持久化类的加?由J2EE服务器负责加?, q样EJB3 Persistenceq行环境?yu)无法分辨jvm中哪个类是持久化Entityc,哪个不是。因此需要一个额外的信息来让EJB3 Persistence知道那些是持久化Entity?/p>
<property></property> 定义了了EJB3 Persistenceq行环境需要的其他参数Q这些参C是标准的Q而是由EJB3 Persistenceq行环境的厂商自行定义?/p>详细的配|方法参看persistence.xml的语法?
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 ); // q时hello的状态变为managed trans.commit(); entityManager.close(); // q时hellow的状态变为detached.
不配|cascade的情况下QEJB3 Persistenceq行环境默认不会采用Persistence by reachability?/p>
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; } }
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();
public class Father{ @Id int id String name; // OneToOne配置cascade=CascadeType.ALL,配置cascade=CascadeType.PERSIT也对persist操作也可以获得同L效果? // 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; } }
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();
Father father = manager.find( Father.class, new Integer( 1 ) ); // ׃JDK1.5支持自动转型Q也可以如下使用 Father father = manager.find( Father.class, 1 ); /* * 或?可以用Entity名字作ؓ查找。但无法利用JDK 1.5的自动{型功能, * 需要用对象作为查找主键,q要对获得Entityq行转型 */ Father father = (Father)manager.find( "com.redsoft.samples.Father", new Integer( 1 ) );
transaction.begin(); Father father = manager.find( Father.class, 1 ); // 更新原始数据cd father.setName( "newName" ); // 更新对象引用 Son newSon = new Son(); father.setSon( newSon ); // 提交事务Q刚才的更新同步到数据库 transaction.commit();
transaction.begin(); Father father = manager.find( Father.class, 1 ); // 如果father/son的@OneToOne的cascade=CascadeType.ALLQ在删除father时候,也会把son删除? // 把cascade属性设为cascade=CascadeType.REMOVE有同L效果? manager.remove( father ); // 提交事务Q刚才的更新同步到数据库 transaction.commit();
在三层或者分布式应用中,我们很多时候需要Entity能脱EntityManagerQ避免长旉保持EntityManager打开占用资源和可以在不同的JVM之间传递Entity?/p>
在脱EJB3 Persistence Runtime(EntityManager)的管理后,我们仍然可以d或者修改Entity中的内容。而在E后的时_我们又可以将Entity重新和原有或者新的EntityManager附合Q如果附合前Entity被改动过Q更改的数据可以自动的被发现q和数据库同步?/p>
EntityManager entityManager = emf.createEntityManager(); // q时Fatherq是被EntityManager理? Father father = manager.find( Father.class, 1 ); // 当entityManger关闭的时候,当前被entityManager理的Entity都会自动的脱EntityManagerQ状态{变ؓdetached entityManager.close(); // qEntityManager后,我们仍然可以修改Father的属? father.setName( "newName" ); // 在稍后的Q我们可以将father重新附和C个新的或者原来的EntityManager? EntityManager newEntityManager = emf.createEntityManager(); // 附合( merge )需要在事务中进? newEntityManager.getTransaction().begin(); newEntityManager.merge( father ); // commit后father中的被修改的内容会同步到数据库? newEntityManager.getTransaction().commit();
Liberator EJB3 Persistneceq行环境׃支持Persistence-by-reachablity。我们徏议采用cascade=CascadeType.ALLQ这样当需要附合的是一个对象图的时候,我们只需要merge根对象即可,整个对象图,对象图中被修改过的对象都会被自动识别和同步?/p>
需要注意的是在qEJB3 Persistence Runtime的管理后Q如果对象中有定义ؓlazy-load的属性将无法讉K?/p>
Table用来定义entity主表的nameQcatalogQschema{属性?/p>
元数据属性说明:
@Entity @Table(name="CUST") public class Customer { ... }
一个entity class可以映射到多表,SecondaryTable用来定义单个从表的名字,主键名字{属性?/p>
元数据属性说明:
下面的代码说明CustomercL到两个表,主表名是CUSTOMERQ从表名是CUST_DETAILQ从表的主键列和主表的主键列cd相同Q列名ؓCUST_ID?/p>
@Entity @Table(name="CUSTOMER") @SecondaryTable(name="CUST_DETAIL",pkJoin=@PrimaryKeyJoinColumn(name="CUST_ID")) public class Customer { ... }
当一个entity class映射C个主表和多个从表Ӟ用SecondaryTables来定义各个从表的属性?/p>
元数据属性说明:
@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 {}
UniqueConstraint定义在Table或SecondaryTable元数据里Q用来指定徏表时需要徏唯一U束的列?/p>
元数据属性说明:
@Entity @Table(name="EMPLOYEE", uniqueConstraints={@UniqueConstraint(columnNames={"EMP_ID", "EMP_NAME"})} ) public class Employee { ... }
Column元数据定义了映射到数据库的列的所有属性:列名Q是否唯一Q是否允ؓI,是否允许更新{?/p>
元数据属性说明:
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;
如果在entity class的field上定义了关系Qone2one或one2many{)Q我们通过JoinColumn来定义关pȝ属性。JoinColumn的大部分属性和ColumncM?/p>
元数据属性说明:
下面的代码说明Custom和Order是一对一关系。在Order对应的映表Z个名为CUST_ID的列Q该列作为外键指向Custom对应表中名ؓID的列?/p>
public class Custom { @OneToOne @JoinColumn( name="CUST_ID", referencedColumnName="ID", unique=true, nullable=true, updatable=true) public Order getOrder() { return order; }
如果在entity class的field上定义了关系Qone2one或one2many{)Qƈ且关pd在多个JoinColumnQ用JoinColumns定义多个JoinColumn的属性?/p>
元数据属性说明:
下面的代码说明Custom和Order是一对一关系。在Order对应的映表Z列,一列名为CUST_IDQ该列作为外键指向Custom对应表中名ؓID的列,另一列名为CUST_NAMEQ该列作为外键指向Custom对应表中名ؓNAME的列?/p>
public class Custom { @OneToOne @JoinColumns({ @JoinColumn(name="CUST_ID", referencedColumnName="ID"), @JoinColumn(name="CUST_NAME", referencedColumnName="NAME") }) public Order getOrder() { return order; }
声明当前field为映表中的主键列。id值的获取方式有五U:TABLE, SEQUENCE, IDENTITY, AUTO, NONE。Oracle和DB2支持SEQUENCEQSQL Server和Sybase支持IDENTITY,mysql支持AUTO。所有的数据库都可以指定为AUTOQ我们会Ҏ(gu)不同数据库做转换。NONE(默认)需要用戯己指定Id的倹{Tablecd详见TableGenerator的介l?/p>
元数据属性说明:
下面的代码声明Task的主键列id是自动增长的?Oracle和DB2从默认的SEQUENCE取|SQL Server和Sybase该列建成IDENTITYQmysql该列建成auto increment?
@Entity @Table(name = "OTASK") public class Task { @Id(generate = GeneratorType.AUTO) public Integer getId() { return id; } }
当entity class使用复合主键Ӟ需要定义一个类作ؓid class。id class必须W合以下要求:cdd明ؓpublicQƈ提供一个声明ؓpublic的空构造函数。必d现Serializable接,覆写equals()和hashCodeQ)Ҏ(gu)。entity class的所有id field在id class都要定义Q且cd一栗?/p>
元数据属性说明:
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; }
在一对多Q多对多关系中,我们可以用Map来保存集合对象。默认用主键值做keyQ如果用复合主键,则用id class的实例做keyQ如果指定了name属性,q指定的field的值做key?/p>
元数据属性说明:
下面的代码说明Person和Book之间是一对多关系。Person的books字段是MapcdQ用Book的isbn字段的g为Map的key?/p>
@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>(); }
在一对多Q多对多关系中,有时我们希望从数据库加蝲出来的集合对象是按一定方式排序的Q这可以通过OrderBy来实玎ͼ默认是按对象的主键升序排列?/p>
元数据属性说明:
下面的代码说明Person和Book之间是一对多关系。集合books按照Book的isbn升序Qname降序排列?/p>
@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>(); }
在三U情况下会用到PrimaryKeyJoinColumn?
元数据属性说明:
下面的代码说明Customer映射C个表Q主表CUSTOMER,从表CUST_DETAILQ从表需要徏立主键列CUST_IDQ该列和主表的主键列id除了列名不同Q其他定义一栗?
@Entity @Table(name="CUSTOMER") @SecondaryTable(name="CUST_DETAIL",pkJoin=@PrimaryKeyJoinColumn(name="CUST_ID"QreferencedColumnName="id")) public class Customer { @Id(generate = GeneratorType.AUTO) public Integer getId() { return id; } }
下面的代码说明Employee和EmployeeInfo是一对一关系QEmployee的主键列id作ؓ外键指向EmployeeInfo的主键列INFO_ID?
@Table(name = "Employee") public class Employee { @OneToOne @PrimaryKeyJoinColumn(name = "id", referencedColumnName="INFO_ID") EmployeeInfo info; }
如果entity class使用了复合主键,指定单个PrimaryKeyJoinColumn不能满要求Ӟ可以用PrimaryKeyJoinColumns来定义多个PrimaryKeyJoinColumn?
元数据属性说明:
下面的代码说明了Employee和EmployeeInfo是一对一关系。他们都使用复合主键Q徏表时需要在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; }
Version指定实体cd乐观事务中的version属性。在实体c重新由EntityManager理q且加入C观事务中Ӟ保证完整性。每一个类只能有一个属性被指定为versionQversion属性应该映到实体cȝ主表上?/p>
下面的代码说明versionNum属性作个类的versionQ映到数据库中主表的列名是OPTLOCK?/p>
@Version @Column("OPTLOCK") protected int getVersionNum() { return versionNum; }
Lob指定一个属性作为数据库支持的大对象cd在数据库中存储。用LobTypeq个枚D来定义Lob是二q制cdq是字符cd?/p>
LobType枚Dcd说明Q?/p>
元数据属性说明:
下面的代码定义了一个BLOBcd的属性和一个CLOBcd的属性?/p>
@Lob @Column(name="PHOTO" columnDefinition="BLOB NOT NULL") protected JPEGImage picture; @Lob(fetch=EAGER, type=CLOB) @Column(name="REPORT") protected String report;
JoinTable在many-to-many关系的所有者一边定义。如果没有定义JoinTableQ用JoinTable的默认倹{?/p>
元数据属性说明:
下面的代码定义了一个连接表CUST和PHONE的join table。join table的表名是CUST_PHONEQ包含两个外键,一个外键是CUST_IDQ指向表CUST的主键IDQ另一个外键是PHONE_IDQ指向表PHONE的主键ID?/p>
@JoinTable( table=@Table(name=CUST_PHONE), joinColumns=@JoinColumn(name="CUST_ID", referencedColumnName="ID"), inverseJoinColumns=@JoinColumn(name="PHONE_ID", referencedColumnName="ID") )
TableGenerator定义一个主键值生成器Q在Idq个元数据的generateQTABLEӞgenerator属性中可以使用生成器的名字。生成器可以在类、方法或者属性上定义?/p>
生成器是为多个实体类提供q箋的ID值的表,每一行ؓ一个类提供ID|ID值通常是整数?/p>
元数据属性说明:
下面的代码定义了两个生成器empGen和addressGenQ生成器的表是ID_GEN?/p>
@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; ... }
SequenceGenerator定义一个主键值生成器Q在Idq个元数据的generator属性中可以使用生成器的名字。生成器可以在类、方法或者属性上定义。生成器是数据库支持的sequence对象?/p>
元数据属性说明:
下面的代码定义了一个用提供商默认名称的sequence生成器?/p>
@SequenceGenerator(name="EMP_SEQ", allocationSize=25)
DiscriminatorColumn定义在用SINGLE_TABLE或JOINEDl承{略的表中区别不l承层次的列?/p>
元数据属性说明:
下面的代码定义了一个列名ؓDISCQ长度ؓ20的Stringcd的区别列?/p>
@Entity @Table(name="CUST") @Inheritance(strategy=SINGLE_TABLE, discriminatorType=STRING, discriminatorValue="CUSTOMER") @DiscriminatorColumn(name="DISC", length=20) public class Customer { ... }
双向一对一关系需要在关系l护端(owner sideQ的one2one Annotition定义mappedBy属性。徏表时在关p被l护端(inverse sideQ徏立外键列指向关系l护端的主键列?/p>
假设Country ?Capital 是双向一对一的关p,具体元数据声明如?
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;
代码中元数据的说明:
元数据描q?
@OneToOne(optional = true,cascade = CascadeType.ALL, mappedBy = "country")
optional声明关系是否是必d在的Q即是否允许其中一端ؓnull?/p>
cascade声明U联操作?/p>
@JoinColumn(name = "COUNTRY_ID", referencedColumnName = "id")
name声明外键列的名字QreferencedColumnName声明外键指向列的列名?br />
双向一对多关系Q一是关pȝ护端Qowner sideQ,多是关系被维护端Qinverse sideQ?时在关系被维护端建立外键列指向关pȝ护端的主键列?/p>
假设Father ?Child 是双向一对多的关p,具体元数据声明如?
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; } }
代码中元数据的说明:
元数据描q?
@OneToMany(targetEntity = Child.class, cascade = CascadeType.ALL, mappedBy = "father")
targetEntity = Child.class表明关系另一端的实体cd
cascade声明U联操作?/p>
mappedBy声明关系l护端的字段QfieldQ名?/p>
@ManyToOne
@JoinColumn(name = "FATHER_ID", referencedColumnName = "id")
name声明外键列的名字QreferencedColumnName声明外键指向列的列名?br />
多对多映采取中间表q接的映策略,建立的中间表分别引入两边的主键作ؓ外键?/p>
EJB3对于中间表的元数据提供了可配|的方式Q用户可以自定义中间表的表名Q列名?/p>
假设Teacher ?Student是多对多的关p,具体元数据声明如?
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; } }
代码中元数据的说明:
元数据描q?
@ManyToMany(targetEntity = Student.class, cascade = CascadeType.PERSIST)
targetEntity = Student.class表明关系另一端的实体cd。cascade声明U联操作?/p>
@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") ,声明中间表的名字
2Q?joinColumns Q定义中间表与关pȝ护端的外键关pR?/p>
3Q?inverseJoinColumnsQ定义中间表与inverse端的外键关系.
EJB3规定了三U基本的l承映射{略Q?/p>
.每个cd层结构一张表(table per class hierarchy)
.每个子类一张表(table per subclass)
.每个具体cM张表(table per concrete class)
在我们提供的Alpha版本中仅支持W一U映策略,x个类层次一个表。我们将在下一个版本中提供每个具体cM张表的支持, 考虑到性能Q这两个映射{略也是推荐的映策?
@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表示l承映射采用W一U映策略?/p>
discriminatorType=DiscriminatorType.STRING表示l承层次中类型识别列cd为String.
discriminatorValue="employee" 表示此类对应的类型识别码为employee.
EJB3的查询语a是一U和SQL非常cM的中间性和对象化查询语a。它可以被编译成不同的底层数据库能接受的SQLQ从而屏蔽不同数据库的差异,保用EJB3 QL查询语言~写的代码可在不同的数据库上q行。比起EJB 2.1的查询语aQEJB3可以q行期构造,支持多态,q远比EJB 2.1的查询更灉|和功能强大。在E序中用EJB3 QL可以使用大写(SELECT)或者小?select)Q但不要大小?比如:Select)混合使用?/p>
javax.persistence.Query是EJB3查询操作的接口。进行查询,首先要通过EntityManager 获得Query对象?/p>
public Query createQuery(String ejbqlString);
下面我们做一个最单的查询Q查询所有的com.redsoft.samples.OrdercR?/p>
final Query query = entityManager.createQuery( "select o from Order o"); final List result = query.getResultList(); final Iterator iterator = result.iterator(); while( iterator.hasNext() ){ // 处理Order }
注意"from Order"?Order"在EJB3查询中称为com.redsoft.samples.Ordercȝabstract schema Type。查询Entity在EJB3 QL中都是针对Entity的Abstract Schema Typeq行查询?在同一个EntityManagerFactory中,不允许同时有两个Abstract Schema Type相同的EntitycR比如不允许同时有com.redsoft.samples.Order和com.redsoft.foo.Order。
Queryq回一个List的集合结果,我们可以用Iterator或者List.get( int )的方法来获得每个W合条g的Entity。Liberator EJB3 Persistenceq行环境的Query查询 在构造Query的时候的只是把EJB3 QL~译成相应的SQLQ但q不执行。只有当应用代码W一ơ调用Iterator.next(),Iterator.hasNext()或者List.get( int )Ҏ(gu)的时?~译后的SQL才会被真正的执行?
在Liberator EJB3 Persistenceq行环境q回的结果集合中Qƈ不保存所有的l果Q而只是保持一个指向JDBC ResultSet或者缓存ResultSet的一个行(row)指针。只有当用户实需要获得Entity实例的时候,才会从ResultSet中获取数据ƈ填充到Entity实例中返回给应用?
如果查询l果l合中包含所有符合条件的Entity, Liberator EJB3 Persistenceq行环境默认会自动缓存每ơ查询的l果。这样下ơ同L查询操作无需讉K数据库,而直接从~存中返回结果集合。但如果在下ơ查询操作之前,有针对被~存的Entityc进行update/insert/delete操作Q则~存的结果集合会自动被清I,q样下次查询׃从数据库获得数据Q?保查询L获得正确的结果,避免~存脏数据?/p>
有时候查询会q回量的数据。Liberator EJB3q行环境采用了自适应的弱引用POJO理机制Q可以处理v量的数据。在我们的测试中和客L环境可以处千万别的数据量。但在处理大数据量的时候,注意关闭寚w合结果的~存?/p>
// 假设q回的结果数量巨? final Query query = entityManager.createQuery( "select o from Order o"); // 关闭Ҏ(gu)询结果的~存 query.setHint( Constants.QUERY_RESULT_CACHE, "false"); final List result = query.getResultList(); final Iterator iterator = result.iterator(); // q里我们可以处理量的数? while( iterator.hasNext() ){ // 处理Order }
参数查询也和SQL中的参数查询cM。EJB3 QL支持两种方式的参数定义方? 命名参数和位|参数。在同一个查询中只允怋用一U参数定义方式?/p>命名参数:
final Query query = entityManager.createQuery( "select o from Order o where o.id = :myId"); // 讄查询中的参数 query.setParameter( "myId", 2 ); // 可以使用多个参数 final Query query = entityManager.createQuery( "select o from Order o where o.id = :myId and o.customer = :customerName" ); // 讄查询中的参数 query.setParameter( "myId", 2 ); query.setParameter( "customerName", "foo" );
位置参数Q?
final Query query = entityManager.createQuery( "select o from Order o where o.id = ?1"); // 讄查询中的参数 query.setParameter( 1, 2 );// 1表示W一个参敎ͼ2是参数的? //或? final Query query = entityManager.createQuery( "select o from Order o where o.id = ?1").setParameter( 1, 2 ); // 可以使用多个参数 final Query query = entityManager.createQuery( "select o from Order o where o.id = ?1 and o.customer = ?2" ); // 讄查询中的参数 query.setParameter( 1, 2 ); query.setParameter( 2, "foo" );
// 不注明的话,默认为asc为升? final Query query = entityManager.createQuery( "select o from Order o order by o.id"); final Query query = entityManager.createQuery( "select o from Order o order by o.address.streetNumber desc");// desc为降? final Query query = entityManager.createQuery( "select o from Order o order by o.id, o.address.streetNumber");
// 我们把需要的三个属性作Z个class( OrderHolder )的构造器参数Qƈ使用new函数? Query query = entityManager.createQuery("select new com.redsoft.ejb3.dummy.OrderHolder ( o.id, o.vender, o.partNumber ) FROM Order AS o"); // 集合中的l果是OrderHolder List result = query.getResultList();
象大部分的SQL一?EJB3 QL也支持查询中的聚合函数。目前EJB QL支持的聚合函数包括:
final Query query = entityManager.createQuery( "select MAX( o.id ) from Order where o.customerName='foo'"); // 如果我们知道l果是单个,我们可以用getSingleResult()获得l果 final Object result = query.getSingleResult(); // ׃Order中id的类型ؓlong, final Long max = (Long)result; // 在一些数据库中max函数q回的结果的cd不一定于id对应的列的类型相W,更安全的方式可以采用string来{? fina long max = Long.parseLong( result.toString() );
// q回所有的订单的生产厂商和他们的订单h(hun)值总额 final Query query = entityManager.createQuery( "select o.vender, sum(o.amount) FROM Order o group by o.vender");");
和SQL一P如果聚合函数不是select...from的唯一一个返回列Q需要?GROUP BY"语句?GROUP BY"应该包含select语句中除了聚合函数外的所有属性?/p>
// q回所有的订单的生产厂商的的名字,货物L和每U货物的订单价值总额 // 注意group by后面必须包含o.vender和o.partNumber final Query query = entityManager.createQuery( "select o.vender, o.partNumber, sum(o.amount) FROM Order o group by o.venderQo.partNumber");
如果q需要加上查询条Ӟ需要?HAVING"条g语句而不?WHERE"语句?/p>
// q回所有的订单的生产厂商是"foo"的货物号码和每种货物的订单h(hun)值总额 // q里"having o.vender = 'foo'为条? final Query query = entityManager.createQuery( "select o.vender, o.partNumber, sum(o.amount) FROM Order o group by o.venderQo.partNumber having o.vender='foo'");
?HAVING"语句里可以跟"WHERE"语句一样用参数?/p>
// q回所有的订单的生产厂商是"foo"的货物号码和每种货物的订单h(hun)值总额 // q里"having o.vender = 'foo'为条? final Query query = entityManager.createQuery( "select o.vender, o.partNumber, sum(o.amount) FROM Order o group by o.venderQo.partNumber having o.vender=?1"); query.setParameter( 1, "foo" ); final List result = query.getResultList();
在EJB3 QL中,大部分的情况下,使用对象属性都隐含了关?join)。例如在以下查询中:
final Query query = entityManager.createQuery( "select o from Order o where o.address.streetNumber=2000 order by o.id");
select o.id, o.vender, o.partNumber, o.amount, addressTable.id, addressTable.streetNumber from orderTable as o left join addressTable where addressTable.streetNumber = 2000
但在一些情况下Q我们仍焉要对兌做精的控制。因此EJB3 QL仍然支持和SQL中类似的兌语法Q?/p>
left join, left out join{义Q都是允许符合条件的双表达式中的Entiies为空?/p>
// q回所有地址?000的OrderU录Q不Order中是否有OrderItem final Query query = entityManager.createQuery( "select o from Order o left join o.orderItems where o.address.streetNumber=2000 order by o.id");
׃EJB3 QL默认采用left join。这L查询和以下的EJB3 QL其实是等L?/p>
// q回所有地址?000的OrderU录Q不Order中是否有OrderItem final Query query = entityManager.createQuery( "select o from Order o where o.address.streetNumber=2000 order by o.id");
需要显式用left join/left outer join的情况会比较?yu)?/p>
inner join要求双的表辑ּ必须q回Entities?/p>
// q回所有地址?000的OrderU录QOrder中必LOrderItem final Query query = entityManager.createQuery( "select o from Order o inner join o.orderItems where o.address.streetNumber=2000 order by o.id");
left/left out/inner join fetch提供了一U灵zȝ查询加蝲方式来提高查询的性能。在默认的查询中QEntity中的集合属性默认不会被兌Q集合属性默认是~加? lazy-load )?/p>
``` // 默认EJB3 QL~译后不兌集合属性变?orderItems)对应的表 final Query query = entityManager.createQuery( "select o from Order o inner join o.orderItems where o.address.streetNumber=2000 order by o.id"); final List result = query.getResultList(); // q时获得Order实体中orderItems( 集合属性变?)为空 final Order order = (Order)result.get( 0 ) // 当应用需要时QEJB3 Runtime才会执行一条SQL语句来加载属于当前Order的OrderItems Collection orderItems = order.getOrderItems();
q样的查询性能上有不的地斏Vؓ了查询N个OrderQ我们需要一条SQL语句获得所有的Order的原?对象属性, 但需要另外N条语句获得每个Order的orderItems集合属性。ؓ了避免N+1的性能问题Q我们可以利用join fetch一ơ过用一条SQL语句把Order的所有信息查询出来?/p>
// q回所有地址?000的OrderU录QOrder中必LOrderItem final Query query = entityManager.createQuery( "select o from Order o inner join fetch o.orderItems where o.address.streetNumber=2000 order by o.id");
׃使用了fetch,q个查询只会产生一条SQL语句Q比原来需要N+1条SQL语句在性能上有了极大的提升?/p>
在查询中使用参数查询Ӟ参数cd除了String, 原始数据cd( int, double{?和它们的对象cd( Integer, Double{?,也可以是Entity的实例?/p>
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 );
EJB3 QL支持扚w更新?/p>
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();
EJB3 QL支持扚w删除?/p>
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();
// 查询所有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();
// 查询所有h(hun)值amount在5?0之间?包含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();
// 查询所有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();
// 查询所有vender以字W串"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以字W串"foo"l尾的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(); // 可以l合NOT一起用,比如查询所有vender不以以字W串"foo"l尾的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(); // 可以l合escape使用Q比如查询所有vender?foo"开始的Orderq忽?3'字符? // 如果vender?foo1", "foo2", "foo3"W合q个条g, 另外"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();
// 查询所有没有地址的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();
IS EMPTY是针寚w合属?Collection)的操作符。可以和NOT一起用?/p>
// 查询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();
[NOT]EXISTS需要和子查询配合用?/p>
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");
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();
EJB3 QL中定义的计算函数包括Q?/p>
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");
JBoss4中对EJB3.0支持的Hibernate3和数据源的配|?/strong>
JBoss EJB3.0
建立?span lang="EN-US">Hibernate 3.0之上。配|数据源你的实体bean需要创?span lang="EN-US">hibernate. Properties配置文g。在EJB 3.0部v包下有一个默认的hibernate配置文gejb3.deployer/META-INF/hibernate.properties?br />
请参考我们附带的Hibernate 3.0的文档来了解如何配置hibernate.properties文g中的各个倹{这里只做一些说明?br />
hibernate.transaction.manager_lookup_class=org.hibernate.transaction.JBossTransactionManagerLookup
hibernate.hbm2ddl.auto=create-drop
hibernate.cache.provider_class=org.hibernate.cache.HashtableCacheProvider
hibernate.connection.datasource=java:/DefaultDS
hibernate.dialect=org.hibernate.dialect.HSQLDialect
上面?span lang="EN-US">EJB3.0应用E序?span lang="EN-US">ejb3.deployer/META-INF/hibernate.properties默认的配|文?br />
hibernate.connection.datasource
指向JBoss数据源的JNDI?span lang="EN-US">
hibernate.dialect
默认?span lang="EN-US">Hypersonic SQLQ但是你可以改变为其他数据库方言?span lang="EN-US">Oracle。可以参?span lang="EN-US">Hibernate 3.0的文档?span lang="EN-US">
hibernate.hbm2ddl.auto=create-drop
建立部v中的数据库图表,q在取消部v的时候删除他们?span lang="EN-US">
hibernate.transaction.manager_lookup_class
定义?span lang="EN-US">Hibernates的事务管理。它一般不能改变?span lang="EN-US">
hibernate.cache.provider_class
定义?span lang="EN-US">Hibernate要使用的缓存结构。如果你在集中使用你也许要考虑JBoss的缓存?br />
每个部v?span lang="EN-US">Hibernate.properties文g
你或讔R要替换默认的hibernate.properties文g。那么用上面提到的变量定义一个最配|来建立一个你自己?span lang="EN-US">hibernate.properties文g。然后将文g攑֜你得.ejb3 jar 目录下的META-INF/下或打入JAR包?/span>
默认的会?span lang="EN-US">bean会以路径或远E接口的全名l定?span lang="EN-US">JNDI。你可以通过定义你自q@org.jboss.ejb3.LocalBinding?span lang="EN-US">@org.jboss.ejb3.remoting.RemoteBinding来修?span lang="EN-US">
本地接口?span lang="EN-US">JNDIl定
使用org.jboss.ejb3.LocalBinding annotation来改变你本地接口?span lang="EN-US">JNDI名字?span lang="EN-US">
@Stateless
@LocalBinding(jndiBinding="custom/MySession")
public class MySessionBean implements MySession
{
}
q程接口?span lang="EN-US">JNDIl定
使用org.jboss.ejb3.RemoteBindings annotation来改变你q程接口?span lang="EN-US">JNDI名字?span lang="EN-US">
@Stateless
@RemoteBindings({@RemoteBinding(jndiName="custom/remote/MySession")})
public class MySessionBean implements MySession
{
}
多通\传输和客L拦截Q?span lang="EN-US">Multiple transports and Client Interceptors
Q?span lang="EN-US">
你可以通过JBoss Remoting架构来展C个会?span lang="EN-US">bean通过多通\传输的远E调用。现在仅仅一些插件支持。可以查?span lang="EN-US">JBoss文档中怎样定义传?span lang="EN-US">MBean。要展现一个会?span lang="EN-US">bean通过多通\传输你需要用远E绑定注解?span lang="EN-US">
public @interface RemoteBinding
{
String jndiBinding() default "";
String interceptorStack() default "SessionBeanClientInterceptors";
String clientBindUrl();
Class factory() default org.jboss.ejb3.remoting.RemoteProxyFactory.class;
}
q里是一个例子:
@Stateless
@RemoteBindings({
@RemoteBinding(jndiName="custom/remote/MySession",
interceptorStack="MyInterceptorStack",
clientBindUrl="socket://foo.jboss.org:2222")
})
public class MySessionBean implements MySession
{
}
参?Enterprise JavaBeans 4
q篇文章 讲述了何时用EJBQ何时不用EJBQ以及用哪些可以替代EJBQ?/p>
1 When to Use EJBs
Here's a list of situations where EJBs are strong; we haven't distinguished between
different types of EJBs.
Single and multisystem business transactions
The ability to maintain transactional integrity for complex business entities is one of
an EJB's key strengths. EJBs aren't alone in providing straightforward transactional
control over a single data repository. However, EJBs shine where multiple resources
(relational databases, messaging systems, etc.) are involved because they allow
transactions to spread across as many different resources as you like, so long as the
resources support distributed transactions.
Distributed functionality
Business services often live on a remote server. For example, a business enterprise will
have many different systems, ranging in degrees of inflexibility and entrenchment. One of
these systems may need to access another; EJBs, which are inherently distributed, are
often the simplest way to distribute remote services. EJB also allows you to provide
business services to remote clients more easily than some alternatives. Remote access
through components is easier to maintain than direct database access, because the
component code can shield the client from database schema changes.
Portable components (not classes)
Until recently, if you wanted to share your business services with another application
developer, you were forced to share classes or at least packages. Java did not allow for
the easy creation of enterprise components, reusable software building blocks that can be
assembled with other components to form an application. EJBs allow you to package your
business logic into a tidy, distributable unit that can be shared in a loosely coupled
fashion. The user of your component need only tweak a descriptor file for her
environment.
Applications relying on asynchronous messaging
EJBs (specifically MDBs) provide a strong technology for handling asynchronous
communication such as JMS-based messaging or web services.
Security roles
If your application's business operations can be mapped to specific business roles in
your enterprise, then EJBs may be a good choice. So much is made of the transaction
management capability of EJBs that their deployment-descriptor-based security management
features are overlooked. This capability is very powerful; if your application's users
fit into distinct roles and the rules for those roles dictate which users can write what
data, EJBs are a good choice.
2 When Not to Use EJBs
There are several situations in building a software application—even an "enterprise"
software application—in which using EJBs may actually be a barrier to meeting your
business goals. The following list represents places where you might not want to use
EJBs:
Read-mostly applications
If your application requires only (or even mostly) database reads (as opposed to writes),
then the added complexity and performance overhead of EJBs may be unwarranted. If your
application is only reading and presenting data, you should go with straight JDBC (see
below) or another persistence mechanism. That said, if your application's writes
(database update and inserts) require transactional support (especially if those
transactions go over multiple systems), then EJBs may be the way to go—at least for the
write portion of the application.
Applications requiring thread control
If your application design requires extensive use of threads, then the EJB spec actually
prevents you from using EJBs (although some EJB container vendors may provide nonportable
ways around this restriction). Container systems manage resources, transactions,
security, and other qualities of service using threads; threads you create are outside of
the container's control and can potentially cause system failures. Also, EJB containers
may distribute EJBs across multiple JVMs, preventing the synchronization of threads.
Performance
Because EJBs do so much more than plain Java classes, they are slower than plain Java
classes. The EJB container has to do a lot: maintain transactional integrity, manage bean
instances and the bean pools, enforce security roles, manage resources and resource
pools, coordinate distributed operations, synchronize shared services (if the vendor
offers clustering capabilities), and so on. The security and transactional management
operations can have a significant impact on the performance of method calls (on both
local and remote interfaces). If you require real-time or near-real-time performance
characteristics, EJB may not be your best choice.
3 Alternatives to EJB
There are several alternatives to EJB; some of them are growing in popularity and
maturity. EJBs still rank as the de facto standard for enterprise transactional needs,
but some of the alternatives, like JDO, are also available.
3.1 JDBC
3.2 Java Data Objects
3.3 Others
Castor JDO (http://www.exolab.org)
Hibernate (http://www.hibernate.org)
Prevayler (http://www.prevayler.org)
As you can see, there are several alternatives to EJB. If your application doesn't need
the complexity or some of the features of EJB, take a look around. Data persistence with
Java has been around for some time and there is a wide assortment of approaches.
The goal of this project is to try out various features of Java EE 5.0, such as EJB 3.0, JAX-WS, JAXB, etc., and identify design/code patterns that result in:
A few experimental projects are available from the download area. These are:
I require help to create the user interface programs that will exercise the EJBs. I would like to have a complete Web application implemented in tpcc-web project, and also an alternative J2SE GUI application implemented in tpcc-client and tpcc-j2se projects. The aim is to share the same Business Logic/Data Access Layer and Entities across these projects, thus demonstrating the benefits of the new EJB 3.0 technolgies.
I would also welcome essays and articles that describe design patterns that will lead to portability, simplicity, and reusability of code.
It would be nice if someone could take these projects and make them work under JBOSS.
A test of the EJB 3.0 technology will be to deploy the Business Logic and DAO layer as Beans using the Spring Framework.
If you would like to participate in this project, please request an appropriate role and email me describing what you would like to work on.
The latest code can be obtained here. Note that this is work in progress, and therefore changing frequently.
Todo.