名词:
注解方式:
@persistenceContext:持续、存留;环境、上下文;
@Stateless: 无状态(无权的)
@Remote: 远程接口
一、EJB接口
remote和local的?
二、(Enterprice JavaBeans )EJB基础知识:
①EJB是一个用于分布式业务应用的标准服务端组件模型。采用EJB架构编写的应用是可伸的、事务性的、多用户安全的。一次编写这些应用,
然后部署在任何支持EJB规范的服务器平台,如:JBOSS/WEBLOGIC。
②EJB定义了三种企业Bean,分别是会话Bean(Session Bean)、实体Bean(Entity Bean)和消息驱动Bean(MessageDriven Bean)。
Session Bean:Session Bean用于实现业务逻辑,分为有状态Bean 和 无状态Bean。
每当客户端请求时,容器就会选择一个Session Bean来客户端服务。
Session Bean可以直接访问数据库,但是更多,会通过Entity Bean
实现数据访问。
@persistenceContext 声明进行操作的Session Bean,对象;
实体Bean: 从名字上我们就能猜到,实体Bean代表真实物体的数据;但是这里可以把
实体Bean看做是用来存放数据的JavaBean,比普通JavaBean多一个功能,不仅
可以存放数据的角色,还要跟数据库进行对象和关系的映射。
@Entity @Table(name=“表名”)
消息驱动Bean(MDB):是设计用来专门处理基于消息请求的组件。它能收发异步JMS消息,
并能轻易的与其他EJB进行交互。所以它特别适合用于当一个业务
执行的时间特别长,而执行的结果无需实时向用户反馈的场合。
三、会话Bean(Session bean)
用于实现业务逻辑,分为有状态和无状态两种;每当客户端请求时,容器就会选择一个Session Bean来为客户端服务。
Session Bean作为业务处理对象出现在各种应用体系中;
1、JNDI==JNDI===JNDI↓
客户需要通过JNDI查找EJB(JSP---》EJB)
JNDI:(The Java Naming and Directory InterFace)
Java命名和目录的端口,是一组在Java应用中访问命名和目录服务的API。为开发人员提供了查找和访问各种命名和目录服务的通用、统一的形式。借助于
JNDI提供的接口,能够通过名字定为用户、机器、网络、对象服务等。
命名服务:就像DNS一样,通过命名服务器提供服务,大部分的J2EE服务器都含有命名服务器。
目录服务:一种简化的RDBMS系统,通过目录具有的属性保存一些简单的信息。目录服务通过目录服务器实现,比如:微软ACTIVE DIRECTORY等。
JNDI 的好处:
(1)包含大量命名和目录服务,可以使用相同API 调用访问任何命名或目录服务。
(2)可以同时连接多个命名和目录服务。
(3)允许把名称同JAVA 对象或资源关联起来,不必知道对象或资源的物理ID。
(4)使用通用接口访问不同种类的目录服务
(5)使得开发人员能够集中使用和实现一种类型的命名或目录服务客户API 上。
什么是上下文:由0或多个绑定构成。比如:java/MySql,java为上下文(context),MySql为命名
什么是子上下文(subContext):上下文下的上下文。比如:MyJNDITree/ejb/helloBean,ejb为子上下文。
JNDI编程过程
因为JNDI是一组接口,所以只需根据接口规范编程就可以。要通过JNDI进行资源访问,必须设置初始化上下文的参数,
主要是设置JNDI驱动的类名(java.naming.factory.initial)和提供命名服务的URL(java.naming.provider.url)。
因Jndi的实现产品有很多。所以java.naming.factory.initial的值因提供JNDI服务器的不同而不同。java.naming.provider.url
的值包括提供命名服务的主机地址和端口号。
下面为访问JBOSS服务器的例子代码:
Properties props = new Properties();
props.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory");
props.setProperty("java.naming.provider.url", "localhost:1099");
InitialContext = new InitialContext(props); //设置JNDI 访问的环境,如果客户端运行在jboss,不需要传入props;
HelloWorld helloworld = (HelloWorld) ctx.lookup("HelloWorldBean/remote");
下面为访问Sun应用服务器的例子代码:
Properties props = new Properties();
props.setProperty("java.naming.factory.initial","com.sun.enterprise.naming.SerialInitContextFactory");
props.setProperty("java.naming.provider.url", "localhost:3700");
InitialContext = new InitialContext(props);
HelloWorld helloworld = (HelloWorld) ctx.lookup("com.foshanshop.ejb3.HelloWorld");
下面为访问Webblogic10应用服务器的例子代码:
Properties props = new Properties();
props.setProperty("java.naming.factory.initial", "weblogic.jndi.WLInitialContextFactory");
props.setProperty("java.naming.provider.url", "t3://localhost:7001");
InitialContext = new InitialContext(props);
HelloWorld helloworld = (HelloWorld) ctx.lookup("HelloWorldBean
#com.foshanshop.ejb3.HelloWorld");
JBOSS环境下JNDI树命名约定:
① java:copm
这个上下文环境和其子上下文环境仅能被与之相关的特定组件访问和使用
② java:
子上下文环境和绑定的对象只能被Jboss服务器虚拟机内的应用访问
③ 其他上下文环境
只要实现序列化就可以被远程用户调用。
2、无状态bean开发(Stateless Session Beans)
无状态会话bean主要用来实现单次使用的服务,该服务能被启用很多次,但是由于无状态会话Bean并不保留任何有关状态的信息,其效果是每次
调用提供单独的使用,在很多情况下,无状态会话Bean 提供可重用的单次使用服务。
尽管无状态会话Bean 并不为特定的客户维持会话状态,但会有一个以其成员变量形式表示的过度状态。当一个客户调用无状态会话Bean 的方法时,
Bean 的成员变量的值只表示调用期间的一个过度状态。当该方法完成时,这个状态不再保留。
除了在方法调用期间,所有的无状态会话Bean 的实例都是相同的,允许EJB 容器给任何一个客户赋予一个实例。许多应用服务器利用这个特点,
共享无状态会话Bean 以获得更好的性能。
由于无状态会话Bean 能够支持多个客户,并且通常在EJB 容器中共享,可以为需要大量客户的应用提供更好的扩充能力。无状态会话Bean 比有状态
会话Bean 更具优势的是其性能,在条件允许的情况下开发人员应该首先考虑使用无状态会话Bean。
@Stateless , @Remote,第一个注释定义这是一个无状态会话Bean,第二个注释指明这个无状态Bean 的remote 接口,指明实现的接口是远程接口,
在使用这两个注释时需要使用一些EJB 的类包,这些类包都可以在jboss 安装目录的client,/server/default/deploy/jboss-aop-jdk50.deployer,
/server/default/deploy/ejb3.deployer,/lib/endorsed 等文件夹下找到,或者在源代码的Lib 文件夹下获得。
接口的定义如下: HelloWorld.java
实现类的命名规则是:接口+Bean ,如: HelloWorldBean
***************************************************************************************************************************************
在这里作者要重点说明一下Jboss EJB JNDI 名称默认的命名规则,命名规则如下:
1> 如果EJB 打包进后缀为*.ear 的J2EE 发布文件,默认的JNDI 路径名称是
访问本地接口:EAR-FILE-BASE-NAME/EJB-CLASS-NAME/local
访问远程接口:EAR-FILE-BASE-NAME/EJB-CLASS-NAME/remote
例:EJB HelloWorld 打包进名为HelloWorld.ear 的J2EE 应用,访问她远程接口的JNDI 名是:
HelloWorld/HelloWorldBean/remote
2> 如果EJB 应用打包成后缀为*.jar 的发布文件, 默认的JNDI 路径名称是
访问本地接口:EJB-CLASS-NAME/local
访问远程接口:EJB-CLASS-NAME/remote
例: HelloWorld 应用打包成HelloWorld.jar 文件,访问她远程接口的JNDI 名称是:HelloWorldBean/remote
另外有一点要注意:EJB-CLASS-NAME 是不带包名的,如com.foshanshop.ejb3.impl.HelloWorldBean 只需取HelloWorldBean。
目前网上很多教材获取JNDI 路径名的方式不适用在jboss 下,如:
HelloWorld helloworld = (HelloWorld) ctx.lookup(HelloWorld.class.getName());这种方式适用于Sun ApplicationServer 及glassfish
我们把上面的客户端应用打成war 文件。然后把她拷贝到“[jboss 安装目录]\server\default\deploy”目录下。如果
war文件的文件名为EJBTest.war ,我们可以通过http://localhost:8080/EJBTest/Test.jsp 访问客户端。
****************************************************************************************************************************************
@Local 注释指明实现的接口是本地接口。当@Local 和@Remote 注释都不存在时,会话Bean 实现的接口默认为Local 接口。
如果在本机调用EJB(确保客户端与EJB 容器运行在同一个JVM),采用Local 接口访问EJB 优于Remote 接口,因为Remote
接口访问EJB需要经过远程方法调用(RPCs)环节,而Local接口访问EJB直接从JVM中返回EJB的引用。
****************************************************************************************************************************************
如果你试图在独立的Tomcat 服务器中执行客户端代码(如何在独立的Tomcat 环境中调用EJB 请考照第二章:在独立的Tomcat 中调用EJB),你将获得如下例外:
java.lang.NullPointerExceptionorg.jboss.ejb3.stateless.StatelessLocalProxy.invoke(StatelessLocalProxy.java:74)产生此例外的原因是,调用Local接口
的客户端与EJB 容器不在同一个VM(虚拟内存堆)。相对于发布到jboss deploy 目录下的客户端应用而言,他与EJB 容器运行在同一个VM。如果客户端与EJB容器
在不同的VM,只能通过其Remote 接口进行访问。
调用Local 接口时,两次累加的结果都不一样,一个是2,一个是4。
这是因为Stateless Session Bean 不负责记录使用者状态,Stateless Session Bean 一旦实例化就被加进会话池中,各个用户都可以共用。即使用户已经消亡,
Stateless Session Bean 的生命期也不一定结束,它可能依然存在于会话池中,供其他用户调用。如果它有自己的属性(变量),那么这些变量就会受到所有
调用它的用户的影响。
在Jboss 网站看到,在EJB3.0 RC9 以上版本中Remote 及Local 接口可以指向同一个业务接口,这样客户端就不会因调用接口的不同而来回切换业务接口类。
当然这种使用场合是在Remote 和Local 的接口方法相同的情况下。
3、有状态Bean开发(Stateful Session Beans)
有状态Bean是一个可以维持自身状态的会话Bean,每个用户都有自己的一个实例,在用户的生命周期内,Stateful Session Bean 保持了用户的信息,即“有状态”
一旦用户灭亡(调用结束或实例结束),Stateful Session Bean的生命周期也告结束。每个用户最初都会得到一个初始的Stateful Session Bean。
stateful session bean 必须实现Serializable 接口,这样EJB 容器才能在她们不再使用时序列化存储她们的状态信息。
@Stateful 注释定义这是一个有状态会话Bean,@Remote注释指明有状态Bean 的remote 接口。
@SuppressWarnings("serial") 注释屏蔽缺少serialVersionUID 定义的警告。
因为stateful session bean 的每个用户都有自己的一个实例,所以两者对stateful session bean 的操作不会影响对方。
另外注意:如果后面需要操作某个用户的实例,你必须在客户端缓存Bean 的Stub 对象(JSP 通常的做法是用Session缓存),
这样在后面每次调用中,容器才知道要提供相同的bean 实例。
4、如何改变Session Bean的JNDI名称
在Jboss 中要自定义JNDI 名称,可以使用@LocalBinding 和@RemoteBinding 注释,@LocalBinding 注释指定Session Bean 的Local 接口的JNDI 名称,
@RemoteBinding 注释指定Session Bean 的Remote 接口的JNDI名称,例子:
@Remote ({Operation.class})
@RemoteBinding (jndiBinding="foshanshop/RemoteOperation")
@Local ({LocalOperation.class})
@LocalBinding (jndiBinding="foshanshop/LocalOperation")
在weblogic10 中,你可以通过@Stateless.mappedName()设置全局JNDI名称
@Stateless(mappedName="OperationBeanRemote")
客户端调用EJB 的代码片断如下:
InitialContext ctx = new InitialContext(props);
Operation operation = (Operation) ctx.lookup("OperationBeanRemote#com.foshanshop.ejb3.Operation");
5、Session Bean的生命周期:
6、拦截器(Interceptor)
拦截器可以监听程序的一个或所有方法。拦截器对方法调用流提供了细粒度控制。可以在无状态会话bean、有状态会话bean 和消息驱动bean 上使用它们。
拦截器可以是同一bean 类中的方法或是一个外部类。
@Interceptors({HelloInterceptor.class})
public class HelloChinaBean implements HelloChina,HelloChinaRemote {
@Interceptors 注释指定一个或多个在外部类中定义的拦截器。上面拦截器HelloInterceptor 对HelloChinaBean 中的所有方法进行监听。
拦截器HelloInterceptor.java:
public class HelloInterceptor {
@AroundInvoke
public Object log(InvocationContext ctx) throws Exception {
System.out.println("*** HelloInterceptor intercepting");
long start = System.currentTimeMillis();
try{
if (ctx.getMethod().getName().equals("SayHello")){
System.out.println("*** SayHello 已经被调用! *** " );
}
if (ctx.getMethod().getName().equals("Myname")){
System.out.println("*** Myname 已经被调用! *** " );
}
return ctx.proceed();
}catch (Exception e) {
throw e;
}finally {
long time = System.currentTimeMillis() - start;
System.out.println("用时:"+ time + "ms");
}
}
}
@AroundInvoke 注释指定了要用作拦截器的方法。用@AroundInvoke 注释指定的方法必须遵守以下格式:
public Object XXX(InvocationContext ctx) throws Exception XXX 代表方法名可以任意。
除了可以在外部定义拦截器之外,还可以将Session Bean 中的一个或多个方法定义为拦截器。下面以前面的HelloChinaBean 为例,
介绍在Session Bean 中如何定义拦截器。
@Stateless
@Remote ({HelloChinaRemote.class})
@Local(HelloChina.class)
public class HelloChinaBean implements HelloChina,HelloChinaRemote {
public String SayHello(String name) {
return name +"说:你好!中国.";
}
public String Myname() {
return "我是佛山人";
}
@AroundInvoke
public Object log(InvocationContext ctx) throws Exception {
try{
if (ctx.getMethod().getName().equals("SayHello")){
System.out.println("*** HelloChinaBean.SayHello() 已经被调用! *** " );
}
if (ctx.getMethod().getName().equals("Myname")){
System.out.println("*** HelloChinaBean.Myname() 已经被调用! *** " );
}
return ctx.proceed();
}catch (Exception e) {
throw e;
}
}
}
上面只需一个@AroundInvoke 注释就指定了要用作拦截器的方法。
7、依赖注入(DI)
①
使用@EJB 注释,你可以将EJB存根对象注入到任何EJB 3.0 容器管理的POJO 中。如果注释用在一个属性变量上,容器将会在它被第一次访问之前赋值给它。
依赖注入只工作在本地命名服务中,因此你不能注入远程服务器的对象。
@Stateless
@Remote ({Injection.class})
public class InjectionBean implements Injection {
@EJB (beanName="HelloWorldBean")
HelloWorld helloworld;
public String SayHello() {
return helloworld.SayHello("注入者");
}
@EJB 注释的beanName 属性指定EJB 的名称(如果没有设置过@Stateless 或@Stateful 的name 属性,默认为不带包名的类名),
他的另一个属性mappedName 指定EJB 的全局JNDI 名。
②
下面的片断演示了如何使用beanName 或mappedName 属性查找HelloWorldBean 会话bean
public class InjectionBean implements Injection {
@EJB (beanName="HelloWorldBean")
//@EJB (mappedName="HelloWorldBean/remote")
HelloWorld helloworld;
@EJB 注释如果被用在JavaBean 风格的setter 方法上时,容器会在属性第一次使用之前,自动地用正确的参数调用bean 的setter 方法。
public class InjectionBean implements Injection {
HelloWorld helloworld;
@EJB (beanName="HelloWorldBean")
public void setHelloworld(HelloWorld helloworld) {
this.helloworld = helloworld;
}
③
@EJB 注释只能注入EJB 存根对象,除@EJB 注释之外,EJB 3.0 也支持@Resource 注释来注入来自JNDI 的任何资源。
下面的例子中演示了如何注入数据源。"java:/DefaultMySqlDS"是数据源DefaultMySqlDS 的全局JNDI 名。
public class InjectionBean implements Injection {
@EJB(beanName = "HelloWorldBean")
HelloWorld helloworld;
@Resource(mappedName = "java:/DefaultMySqlDS")
DataSource myDb;
public String SayHello() {
String str = "";
try {
Connection conn = myDb.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT studentName FROM student");
if (rs.next()) {
str = rs.getString(1);
}
rs.close();
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
return helloworld.SayHello(str);
}
8、定时服务(Timer Service):(60页以后)
定时服务用作在一段特定的时间后执行某段程序,估计各位在不同的场合中已经使用过。
定时服务的开发过程。定时服务的开发过程与会话Bean 的开发过程大致相同,但比会话Bean 多了几个操作,
那就是使用容器对象SessionContext 创建定时器,并使用@Timeout 注释声明定时器方法。
9、安全服务开发:
10、自定义安全域:
四、JMS(Java Message Service)( P75)
Java 消息服务(Java Message Service,简称JMS)是企业级消息传递系统,紧密集成于Jboss Server 平台之中。
企业消息传递系统使得应用程序能够通过消息的交换与其他系统之间进行通信。
1、消息组成
消息传递系统的中心是消息。一条Message分为三个组成部分。
① 头:是个标准字段集,客户机和供应商都用它来标识和路由信息。
② 属性(property):支持把可选头字段添加到消息。如果您的应用程序需要不使用标准头字段对消息编目和分类,您就可以添加一个属性
到消息以实现这个编目和分类。提供set<Type>Property(...)和get<Type>Property(...)方法设置和获取各种Java类型的属性,包括Object。
③ 消息的主体:
2、消息的传递模式
点对点(PTP):一条消息只能传递给一个接收方;
发布/订阅(pub/sub):一条消息传递给多个接收方;
1、消息驱动Bean(Message Driven Bean):
消息驱动Bean(MDB)是设计来专门处理基于消息请求的组件。它是一个异步的无状态Session Bean,客户端调用MDB后无需等待,立刻返回,
MDB将异步处理客户的请求。一个MDB类必须实现MessageListener接口。当容器检测到bean守候的队列一条信息时,就调用onMessage()方法,
将消息作为参数传入。MDB在OnMessage()中决定如何处理该消息。
也可用注释来配置MDB监听哪一队列。当MDB部署时,容器将会用到其中的注释消息。
当一个业务执行的时间很长,而执行结果无需实时向用户反馈时,很适合使用消息驱动Bean。如:订单成功后会给用户发一条短信等。
2、实体Bean(Entity Bean)
持久化是位于JDBC之上的一个更高层抽象。持久层将对象映射到数据库,以便在查询、装载、更新、或删除对象的时候,无需使用像
JDBC那样繁琐的API。在EJB得到早期版本中,持久化是EJB平台的一部分。从EJB 3.0开始,持久化已经自成规范,被称为Java Persistence API。
Java Persistence API 定义了一种方法,可以将常规的普通Java对象(有时被称作POJO)映射到数据库。这些普通Java对象被称作
entity bean.除了是用Java Persistence元数据将其映射到数据库外,entity bean与其他Java类没有任何区别。事实上,创建一个
Entity Bean 对象相当于新建一条记录,删除一个Entity Bean 会同时从数据库中删除对应记录,修改一个Entity Bean 时,容器会
自动将Entity Bean 的状态和数据库同步。
Java Persistence API还定义了一种查询语言(JPQL),具有与SQL相类似的特征,只不过做了裁减,以便处理Java对象而非原始的关系schema。
注释:
@Entity 注释指明这是一个实体Bean,@Table注释指定了entity 所要映射的数据库表,其中@Table.name()用来指定映射表的表名。如果
缺省@Table注释,系统默认采用类名作为映射表的表名。实体Bean的每个实例代表数据表中的一行数据,行中的一列对应实例中的一个属性。
@javax.persistence.Column 注释定义了将成员属性映射到关系表中的哪一列和该列的一些结构信息(如列名是否唯一,是否允许为空,是否允许更新等),
他的属性介绍如下:
·name: 映射的列名。如:映射Person 表的PersonName 列,可以在name 属性的getName 方法上面加入
@Column(name = "PersonName"),如果不指定映射列名,容器将属性名称作为默认的映射列名。
·unique: 是否唯一
·nullable: 是否允许为空
·length: 对于字符型列,length 属性指定列的最大字符长度
·insertable: 是否允许插入
·updatable: 是否允许更新
·columnDefinition: 定义建表时创建此列的DDL
·secondaryTable: 从表名。如果此列不建在主表上 (默认建在主表),该属性定义该列所在从表的名字。
@Id 注释指定personid 属性为表的主键,它可以有多种生成方式:
① ·TABLE:容器指定用底层的数据表确保唯一。
@TableGenerator(name="Person_GENERATOR",//为该生成方式取个名称
table= "Person_IDGenerator",//生成ID的表
pkColumnName= "PRIMARY_KEY_COLUMN",//主键列的名称
valueColumnName= "VALUE_COLUMN",//存放生成ID值的列的名称
pkColumnValue= "personid",//主键列的值(定位某条记录)
allocationSize=1)//递增值
@Id
@GeneratedValue(strategy=GenerationType.TABLE, generator "Person_GENERATOR")
public Integer getPersonid() {
return personid;
}
② ·SEQUENCE:使用数据库的SEQUENCE 列来保证唯一(Oralce 数据库通过序列来生成唯一ID)
@SequenceGenerator(name ="Person_SEQUENCE",//为该生成方式取个名称
sequenceName= "Person_SEQ")//sequence的名称(如果不存在,会自动生成)
public classPerson implementsSerializable{
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator ="Person_SEQ")
public Integer getPersonid() {
return personid;
}
③ ·IDENTITY:使用数据库的INDENTIT 列来保证唯一(像mysql,sqlserver 数据库通过自增长来生成唯一ID)
·AUTO:由容器挑选一个合适的方式来保证唯一(由容器决定采用何种方式生成唯一主键,hibernate 会根据
数据库类型选择适合的生成方式,相反toplink 就不是很近人情)
·NONE :容器不负责主键的生成,由调用程序来完成。
@GeneratedValue 注释定义了标识字段的生成方式。
注:实体bean需要在网络上传送时必须实现Serializable接口,否则将引发java.io.InvalidClassException例外。
@Temporal 注释用来指定java.util.Date或java.util.Calendar属性与数据库类型date,time 或timestamp中的那一种类型进行映射。
@Temporal(value=TemporalType.DATE)
public Date getBirthday() {
return birthday;
}
Session Bean 我觉得就是dao层(接口有方法),而实现session bean 就是daoImpl(实现接口,实现方法),其中,实现Session Bean其中
加入了一个对象EntityManager em,EntityManager是由EJB容器自动地管理和配置的,不需要用户自己创建,他用作操作实体 Bean。
@PersistenceContext
protected EntityManager em;
查找用户用em.find()方法,更新用em.merge(),添加用em.persist(),查所有人按id排序:
public List getPersonList() {
Query query em.createQuery("from Person order by personid asc");
List list =query.getResultList();
return list;
}
在类中并没有看到对 EntityManager em进行赋值,后面却可以直接使用他。这是因为容器在实例化SessionBean 后,就通过@PersistenceContext
注释动态注入 EntityManager 对象。
如果 persistence.xml文件中配置了多个不同的持久化内容。在注入 EntityManager 对象时必须指定持久化名称,可以通过@PersistenceContext
注释的 unitName 属性进行指定,如果只有一个持久化内容配置,不需要明确指定。
@PersistenceContext(unitName="foshanshop")
EntityManager em;
persistence.xml文件的配置:↓↓↓
2.1 持久化persistence.xml配置文件
一个实体Bean应用由实体类和persistence.xml文件组成.persisence.xml文件的META-INF目录。persistence.xml文件指定实体Bean
使用的数据源及EntityManager对象的默认行为。persistence.xml文件的配置说明如下:
<persistence>
<persistence-unit name="foshanshop">
<jta-data-source>java:/DefaultMySqlDS</jta-data-source>
<properties>
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
</properties>
</persistence-unit>
</persistence>
persistence-unit节点可以有一个或多个,每个persistence-unit节点定义了持久化内容名称、使用的数据源及持久化产品专有属性。name属性
定义持久化名称。jta-data-source节点指定实体Bean使用的数据源JNDI 名称(如何配置数据源请参考下节 “Jboss数据源的配置”),如果应用
发布在jboss下数据源名称必须带有java:/前缀,数据源名称大小写敏感。properties节点用作指定持久化产品的各项属性,各个应用服务器使用
的持久化产品都不一样如Jboss使用Hibernate,weblogic10 使用Kodo,glassfish/sun application server/Oralce 使用Toplink。因为jboss采
用Hibernate,Hibernate有一项属性hibernate.hbm2ddl.auto,该属性指定实体Bean发布时是否同步数据库结构,如果hibernate.hbm2ddl.auto的
值设为create-drop,在实体Bean 发布及卸载时将自动创建及删除相应数据库表(注意:Jboss服务器启动或关闭时也会引发实体Bean的发布及卸载)。
TopLink 产品的toplink.ddl-generation 属性也起到同样的作用。关于hibernate的可用属性及默认值你可以在 [Jboss 安装目录]
\server\default\deploy\ejb3.deployer\META-INF/persistence.properties 文件中看见.
小提示:如果你的表已经存在,并且想保留数据,发布实体bean 时可以把hibernate.hbm2ddl.auto 的值设为none或update, 以后为了实体bean的
改动能反应到数据表,建议使用update,这样实体Bean 添加一个属性时能同时在数据表增加相应字段。
属性映射:
如果不想让一些成员属性映射成数据库字段,我们可以使用@Transient注释进行标注。
如果你想映射枚举对象到数据库就需要使用@Enumerated 注释进行标注。
@Enumerated(EnumType.STRING)
public CommentType getType() {
return type;
}
@Lob 注释用作映射这些大数据类型,当属性的类型为 byte[], Byte[]或 java.io.Serializable 时,@Lob 注释将映射为数据库的 Blob 类型,
当属性的类型为 char[],Character[]或 java.lang.String 时,@Lob 注释将映射为数据库的 Clob 类型。
@Lob 注释的大数据类型,为了避免每次加载实体时占用大量内存,我们有必要对该属性进行延时加载,这时我们需要用到@Basic注释。
public @interface Basic{
FetchType fetch( ) default EAGER;
boolean optional( ) default true;
}
FetchType 属性指定是否延时加载,默认为立即加载,optional属性指定在生成数据库结构时字段能否为 null
@Lob
@Basic(fetch=FetchType.LAZY)
public String getContent() {
return content;
}
持久化管理器EntityManager:
EntityManager 常用的 API:
① Entity获取 find()或 getReference()
当在数据库中没有找到记录时,getReference()和 find()是有区别的,find()方法会返回 null,而 getReference()方法
会抛出 javax.persistence.EntityNotFoundException 例外,另外 getReference()方法不保证实体 Bean 已被初始化。
如果传递进 getReference()或 find()方法的参数不是实体 Bean,都会引发 IllegalArgumentException 例外。
② 添加persist()
如果传递进 persist()方法的参数不是实体 Bean,会引发 IllegalArgumentException 例外。
③ 更新实体
当实体正在被容器管理时,你可以调用实体的 set方法对数据进行修改,在容器决定 flush 时,更新的数据才会同
步到数据库。如果你希望修改后的数据实时同步到数据库,你可以执行 EntityManager.flush()方法。
④ 合并Merge()
merge ()方法是在实体Bean 已经脱离了EntityManager 的管理时使用,当容器决定 flush 时,数据将会同步到数据库中。
执行 em.merge(person)方法时,容器的工作规则:
1.如果此时容器中已经存在一个受容器管理的具有相同 ID 的 person 实例,容器将会把参数 person 的内容拷贝
进这个受管理的实例, merge()方法返回受管理的实例, 但参数 person仍然是分离的不受管理的。 容器在决定 Flush
时把实例同步到数据库中。
2.容器中不存在具有相同 ID 的 person 实例。容器根据传进的 person 参数 Copy出一个受容器管理的 person 实
例,同时 merge()方法会返回出这个受管理的实例,但参数 person 仍然是分离的不受管理的。容器在决定 Flush
时把实例同步到数据库中。
如果传递进 merge ()方法的参数不是实体 Bean,会引发一个 IllegalArgumentException 例外。
⑤ 删除 Remove()
em.remove (person);
//如果级联关系cascade=CascadeType.ALL, 在删除 person 时候, 也会把级联对象删除。 把 cascade属性设为
cascade=CascadeType.REMOVE 有同样的效果。
如果传递进 remove ()方法的参数不是实体 Bean,会引发一个 IllegalArgumentException 例外。
⑥ 执行 JPQL 操作 createQuery()
通过JPQL得到实体Bean。要执行 JPQL语句,你必须通过 EntityManager的createQuery()或 createNamedQuery()方法
创建一个 Query对象。
Query query = em.createQuery("select p from Person p where p. name=’ 黎明’");
List result = query.getResultList();
Iterator iterator = result.iterator();
while( iterator.hasNext() ){
//处理 Person
}
Query query = em.createQuery("update Person as p set p.name =?1 where p. personid=?2");
query.setParameter(1, “ 黎明”);
query.setParameter(2, new Integer(1) );
int result = query.executeUpdate(); //影响的记录数
// 执行更新语句
Query query = em.createQuery("delete from Person");
int result = query.executeUpdate(); //影响的记录数
⑦ 执行 SQL 操作 createNativeQuery()
注意这里操作的是 SQL语句,并非 JPQL,千万别搞晕了。
Query query = em.createNativeQuery("select * from person", Person.class);
List result = query.getResultList();
if (result!=null){
Iterator iterator = result.iterator();
while( iterator.hasNext() ){
Person person= (Person)iterator.next();
… ..
}
// 直接通过 SQL执行更新语句
Query query = em.createNativeQuery("update person set age=age+2");
query.executeUpdate();
⑧ 刷新实体 refresh()
当前被管理的实体已经不是数据库中最新的数据,你可以通过 refresh()方法刷新实体,容器会把数据
库中的新值重写进实体。这种情况一般发生在你获取了实体之后,有人更新了数据库中的记录,这时你需要得到
最新的数据。当然你再次调用 find()或 getReference()方法也可以得到最新数据,但这种做法并不优雅。
如果传递进 refresh ()方法的参数不是实体 Bean,会引发一个 IllegalArgumentException 例外。
⑨ 检测实体当前是否被管理中 contains()
contains()方法使用一个实体作为参数,如果这个实体对象当前正被持久化内容管理,返回值为 true,否则为 false。
如果传递的参数不是实体 Bean,将会引发一个 IllegalArgumentException 例外。
⑩ 分离所有当前正在被管理的实体 clear()
在处理大量实体的时候,如果你不把已经处理过的实体从 EntityManager 中分离出来,将会消耗你大量的内存。
调用 EntityManager 的 clear()方法后,所有正在被管理的实体将会从持久化内容中分离出来。
在事务没有提交前(事务默认在调用堆栈的最后提交,如:方法的返回),如果调用 clear()方法,
之前对实体所作的任何改变将会掉失,所以建议你在调用 clear()方法之前先调用 flush()方法保存更改。
em.flush();//手动将更新立刻刷新进数据库;
(11)改变实体管理器的 Flush模式 setFlushMode()
对 Flush 模式进行修改需要使用到 javax.persistence.FlushModeType
默认情况下,实体管理器的 Flush 模式为 AUTO,你可以改变他的值,如下:entityManager.setFlushMode(FlushModeType.COMMIT);
FlushModeType.AUTO: 刷新在查询语句执行前(除了find()和 getreference()查询)或事务提交时才发生,使用场合:
在大量更新数据的过程中没有任何查询语句(除了 find()和 getreference()查询)的执行。
FlushModeType.COMMIT:刷新只有在事务提交时才发生,使用场合:在大量更新数据的过程中存在查询语句(除
了 find()和 getreference()查询)的执行。
JDBC驱动跟数据库交互的次数。JDBC性能最大的增进是减少JDBC驱动与数据库之间的网络通讯。
FlushModeType.COMMIT 模式使更新只在一次的网络交互中完成,而
FlushModeType.AUTO 模式可能需要多次交互(触发了多少次 Flush 就产生了多少次网络交互).
(12)获取持久化实现者的引用 getDelegate( )
用过 getDelegate( )方法,你可以获取 EntityManager 持久化实现者的引用,如 Jboss EJB3 的持久化产品采用
Hibernate,可以通过 getDelegate( ) 方法获取对他的访问,如:
@PersistenceContext
protected EntityManager em;
HibernateEntityManager manager = (HibernateEntityManager)em.getDelegate();
获得对 Hibernate 的引用后,可以直接面对 Hibernate 进行编码,不过这种方法并不可取,强烈建议不要使用。
在 Weblogic 中,你也可以通过此方法获取对 Kodo 的访问。
关系对象映射
① 映射的表名或列名与数据库保留字同名时的处理
如果应用采用的数据库是Mysql,当映射的表名或列名与数据库保留字同名时,持久化引掣转绎后的 SQL在执行
时将会出错。
采用了一种变通的方法来解决此问题。该方法针对具体数据库,不利于数据库移植。建议大家在不得已的情况下使用。
可以用``字符把 Order括起来。@Table(name = "`Order`");如果数据库是 Sqlserver 可以用 [] 把表名或列名括起来。
Sqlserver不加[]也能执行成功,建议在出错的情况下使用。
② 一对多及多对一映射
双向一对多关系,一是关系维护端(owner side),多是关系被维护端(inverse side)。在关系被维护端建立外键列
指向关系维护端的主键列。
@OneToMany(mappedBy="order",cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@OrderBy(value = "id ASC")
public Set<OrderItem> getOrderItems() {
return orderItems;
}
下面是@OneToMany注释的属性介绍:
(1) targetEntity
Class类型的属性。
定义关系类的类型,默认是该成员属性对应的类类型,所以通常不需要提供定义。
(2) mappedBy
String 类型的属性。
定义类之间的双向关系。如果类之间是单向关系,不需要提供定义,如果类和类之间形成双向关系,我们就需要
使用这个属性进行定义,否则可能引起数据一致性的问题。
(3) cascade
CascadeType[]类型。
该属性定义类和类之间的级联关系。定义的级联关系将被容器视为对当前类对象及其关联类对象采取相同的操
作,而且这种关系是递归调用的。
cascade的值只能从 CascadeType.PERSIST (级联新建)、 CascadeType.REMOVE (级联删除)、 CascadeType.REFRESH
(级联刷新)、CascadeType.MERGE(级联更新)中选择一个或多个。还有一个选择是使用CascadeType.ALL,表
示选择全部四项。
(4) fetch
FetchType 类型的属性。
可选择项包括: FetchType.EAGER和 FetchType.LAZY。 前者表示关系类(本例是OrderItem类)在主类(本例是 Order
类)加载的时候同时加载,后者表示关系类在被访问时才加载。默认值是 FetchType. LAZY。
@OrderBy(value = "id ASC")注释指明加载 OrderItem时按 id 的升序排序
@ManyToOne(cascade=CascadeType.REFRESH,optional=false)
@JoinColumn(name = "order_id")
public Order getOrder() {
return order;
}
@ManyToOne 注释有四个属性:targetEntity、cascade、fetch 和 optional,前三个属性的具体含义和@OneToMany
注释的同名属性相同,但@ManyToOne 注释的 fetch 属性默认值是 FetchType.EAGER。
optional属性是定义该关联类是否必须存在.
值为false时,关联类双方都必须存在,如果关系被维护端不存在,查询的结果为 null。
值为 true 时, 关系被维护端可以不存在,查询的结果仍然会返回关系维护端,在关系维护端中指向关系被维护端的属性为 null。
optional属性的默认值是true。optional属性实际上指定关联类与被关联类的 join 查询关系,
如:optional=false时,join查询关系为inner join; optional=true时,join查询关系为left join。
public class OrderItem implements Serializable {
@ManyToOne(cascade=CascadeType.REFRESH,optional=false)
@JoinColumn(name = "order_id")
public Order getOrder() {
return order;
}
//获取OrderItem时的SQL为:select * from OrderItem item inner join Orders o on o.order_id=item.id,
OrderItem表与orders表都必须有关联记录时,查询结果才有记录。
@ManyToOne(cascade=CascadeType.REFRESH,optional=true)
@JoinColumn(name = "order_id")
public Order getOrder() {
return order;
}
//获取OrderItem时的SQL为:select * from OrderItem item left outer join Orders o on o.order_id=item.id
如果orders表没有记录,OrderItem表有记录,查询结果仍有记录。
@JoinColumn(name = "order_id")注释指定 OrderItem 映射表的 order_id 列作为外键与 Order 映射表的主键列关联。
注意:当业务方法需要把一个实体Bean作为参数返回给客户端时,除了实体 Bean 本身需要实现Serializable 接口之外,
如果关联类(OrderItem)是延迟加载,还需在返回实体Bean 之前通过访问关联类的方式加载关联类。否则在客户端
访问关联类时将会抛出加载例外。另外不管是否延迟加载,通过 join fetch 关联语句都可显式加载关联类,如业务方法 getAllOrder。
③ 一对一映射
一对一关系需要在关系维护端(owner side)的@OneToOne注释中定义 mappedBy属性。在关系被维护端(inverse
side)建立外键列指向关系维护端的主键列。
关系维护端:
@OneToOne(optional = true,cascade = CascadeType.ALL, mappedBy = "person")
public IDCard getIdcard() {
return idcard;
}
public void setIdcard(IDCard idcard) {
this.idcard = idcard;
}
@OneToOne注释五个属性:targetEntity、cascade、fetch、optional和 mappedBy, 前四个属性的具体含义与@ManyToOne注释的同名属性一一对应,
fetch 属性默认值是 FetchType.EAGER。 mappedBy属性的具体含义与@OneToMany注释的同名属性相同。
关系被维护端:
optional = true 设置 idcard 属性可以为 null,也就是允讦没有身份证,未成年人就是没有身份证的。
@OneToOne(optional = false, cascade = CascadeType.REFRESH)
@JoinColumn(name = "Person_ID", referencedColumnName = "personid",unique = true)
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
IDCard 是关系被维护端,optional = false设置person 属性值不能为null,也就是身份证必须有对应的主人。
@JoinColumn(name = "Person_ID", referencedColumnName="personid",unique = true)指明IDCard对应表的
Person_ID列作为外键与Person对应表的personid列进行关联, unique= true 指明 Person_ID 列的值不可重复。
④ 多对多映射:
多对多映射采取中间表连接的映射策略,建立的中间表将分别引入两边的主键作为外键。
EJB3 对于中间表的元数据提供了可配置的方式,用户可以自定义中间表的表名,列名。
Student端:
@ManyToMany(mappedBy = "students")
public Set<Teacher> getTeachers() {
return teachers;
}
public void setTeachers(Set<Teacher> teachers) {
this.teachers = teachers;
}
@ManyToMany 注释表示 Student 是多对多关系的一边,mappedBy 属性定义了 Student 为双向关系的维护端(owning side)。
Teacher端:
@ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
@JoinTable(name = "Teacher_Student",
joinColumns = {@JoinColumn(name = "Teacher_ID", referencedColumnName = "teacherid")},
inverseJoinColumns = {@JoinColumn(name = "Student_ID", referencedColumnName ="studentid")})
public Set<Student> getStudents() {
return students;
}
@ManyToMany 注释表示 Teacher是多对多关系的一端。
@JoinTable 描述了多对多关系的数据表关系。name 属性指定中间表名称,joinColumns 定义中间表与 Teacher表的外键关系。
上面的代码中,中间表 Teacher_Student的Teacher_ID列是Teacher表的主键列对应的外键列
inverseJoinColumns 属性定义了中间表与另外一端(Student)的外键关系。
⑤ 使用参数查询
参数查询也和 SQL中的参数查询类似。EJB3 QL支持两种方式的参数定义方式: 命名参数和位置参数。在同一个
查询中只允许使用一种参数定义方式。
a、命名参数查询
//获取指定 personid 的人员
Query query = em.createQuery("select p from Person p where p.personid=:Id");
query.setParameter("Id",new Integer(1));
List result = query.getResultList();
b、位置参数查询
//获取指定 personid 的人员
Query query = em.createQuery("select p from Person p where p.personid=?1");
query.setParameter(1,new Integer(1));
List result = query.getResultList();
c、Date参数
如果你需要传递 java.util.Date 或 java.util.Calendar 参数进一个参数查询,你需要使用一个特殊的 setParameter()
方法,相关的 setParameter方法定义如下:
//命名参数查询时使用,参数类型为 java.util.Date
Query setParameter(String name, java.util.Date value, TemporalType temporalType);
//命名参数查询时使用,参数类型为 java.util.Calendar
Query setParameter(String name, Calendar value, TemporalType temporalType);
//位置参数查询时使用,参数类型为 java.util.Date
Query setParameter(int position, Date value, TemporalType temporalType);
//位置参数查询时使用,参数类型为 java.util.Calendar
Query setParameter(int position, Calendar value, TemporalType temporalType);
因为一个Date 或 Calendar对象能够描述一个真实的日期、 时间或时间戳.所以我们需要告诉Query对象怎么使用
这些参数,我们把 javax.persistence.TemporalType 作为参数传递进 setParameter方法,告诉查询接口在转换
java.util.Date 或 java.util.Calendar 参数到本地 SQL时使用什么数据库类型。
⑥ JPQL语言
Java Persistence API定义了一种查询语言,具有与SQL相类似的特征,JPQL是完全面向对象的,具备继承、多态和关联等特性。
a、大小写敏感性
除了Java 类和属性名称外,查询都是大小写不敏感的。所以,SeLeCT 和 sELEct 以及SELECT相同的,
但是 com.foshanshop.ejb3.bean.Person 和 com.foshanshop.ejb3.bean.PERSon 是不同的, person.name 和
person.NAME 也是不同的。
b、命名查询
在实体 bean 上预先定义一个或多个查询语句,减少每次因书写错误而引起的 BUG。通常把经常使用的查
询语句定义成命名查询,代码如下:
@NamedQuery(name="getPerson", query= "FROM Person WHERE personid=?1")
@Entity
@Table(name = "Person")
public class Person implements Serializable{
定义多个命名查询,应在@javax.persistence.NamedQueries注释里定义@NamedQuery,代码如下:
@NamedQueries({
@NamedQuery(name="getPerson", query= "FROM Person WHERE personid=?1"),
@NamedQuery(name="getPersonList", query= "FROM Person WHERE age>?1")
})
@Entity
@Table(name = "Person")
public class Person implements Serializable{
当命名查询定义好了之后,我们就可以通过名称执行其查询。代码如下:
Query query = em.createNamedQuery("getPerson");
query.setParameter(1, 1);
c、排序(order by)
EJB3 QL中默认为 asc 升序。
//先按年龄降序排序,然后按出生日期升序排序
Query query = em.createQuery("select p from Person p order by p.age desc, p.birthday asc");
d、查询部分属性
在前面的例子中,都是对针对Entity类的查询,返回的也是被查询的Entity类的实体。EJB3 QL也允许我们直接
查询返回我们需要的属性,而不是返回整个 Entity。在一些 Entity 中属性特别多的情况,这样的查询可以提高性能。
//直接查询我们感兴趣的属性(列)
Query query = em.createQuery("select p.personid, p.name from Person p order by p.personid desc ");
//集合中的元素不再是 Person,而是一个 Object[]对象数组
List result = query.getResultList();
StringBuffer out = new StringBuffer("*************** QueryPartAttribute 结果打印****************<BR>");
if (result!=null){
Iterator iterator = result.iterator();
while( iterator.hasNext() ){
//取每一行
Object[] row = ( Object[]) iterator.next();
//数组中的第一个值是 personid
int personid = Integer.parseInt(row[0].toString());
String PersonName = row[1].toString();
out.append("personid="+ personid+ "; Person Name="+PersonName+ "<BR>");
e、查询中使用构造器(Constructor)
EJB3 QL支持将查询的属性结果直接作为一个 java class 的构造器参数,并产生实体作为结果返回。
f、聚合查询(Aggregation)
目前 EJB3 QL支持的聚合函数包括:
1. AVG()
2. SUM()
3. COUNT() ,返回类型为 Long,注意 count(*)语法在 hibernate 中可用,但在 toplink 其它产品中并不可用
4. MAX()
5. MIN()
和 SQL一样,如果聚合函数不是 select...from的唯一一个返回列,需要使用"GROUP BY"语句。"GROUP BY"应
该包含 select语句中除了聚合函数外的所有属性。
//返回男女生各自的总人数
Query query = em.createQuery("select p.sex, count(p) from Person p group by p.sex");
//集合中的元素不再是 Person,而是一个 Object[]对象数组
List result = query.getResultList();
StringBuffer out = new StringBuffer("*************** QueryGroupBy 结果打印*********<BR>");
if (result!=null){
Iterator iterator = result.iterator();
while( iterator.hasNext() ){
//取每一行
Object[] row = (Object[]) iterator.next();
//数组中的第一个值是 sex
boolean sex = Boolean.parseBoolean(row[0].toString());
//数组中的第二个值是聚合函数 COUNT 返回值
String sextotal = row[1].toString();
out.append((sex ? "男生":"女生")+ "总共有"+ sextotal+ "人<BR>");
}
}
return out.toString();
注意:如果还需要加上查询条件,需要使用"HAVING"条件语句而不是"WHERE"语句。
//返回人数超过 1 人的性别
Query query = em.createQuery("select p.sex, count(p) from Person p group by p.sex having count(*)>?1");
//设置查询中的参数
query.setParameter(1, new Long(1));
//集合中的元素不再是 Person,而是一个 Object[]对象数组
g、关联(join)
left out join/left join:left out join/left join 等,都是允许符合条件的右边表达式中的 Entiies为空。
inner join:inner join 要求右边的表达式必须返回 Entities。
left join/inner join fetch:left/left out/inner join fetch提供了一种灵活的查询加载方式来提高查询的性能。在默认的查询中,
Entity中的集合属性默认不会被关联,集合属性默认是缓加载( lazy-load )。
h、排除相同的记录 DISTINCT
Query query = em.createQuery("select DISTINCT o from Order o inner join fetch o.orderItems order by o.orderid");
i、比较 Entity
在查询中使用参数查询时,参数类型除了 String, 原始数据类型( int, double 等)和它们的对象类型( Integer, Double
等),也可以是 Entity的实例。
j、批量更新(Batch Update)
private String QueryBatchUpdate(){
//把所有订单的金额加 10
Query query = em.createQuery("update Order as o set o.amount=o.amount+10");
//update 的记录数
int result = query.executeUpdate();
k、批量删除(Batch Remove)
Query query = em.createQuery("delete from OrderItem item where item.order in(from Order as
o where o.amount<100)");
query.executeUpdate();
query = em.createQuery("delete from Order as o where o.amount<100");
int result = query.executeUpdate();//delete的记录数
l、使用操作符 NOT
//查询除了指定人之外的所有订单
Query query = em.createQuery("select o from Order o where not(o.ower =?1) order by o.orderid");
Person person = new Person();
person.setPersonid(new Integer(2));
//设置查询中的参数
query.setParameter(1,person);
List result = query.getResultList();
m、使用操作符 BETWEEN
//查询金额在 300 到 1000 之间的订单
Query query = em.createQuery("select o from Order as o where o.amount between 300 and 1000");
List result = query.getResultList();
n、使用操作符 IN
//查找年龄为 26,21 的 Person
Query query = em.createQuery("select p from Person as p where p.age in(26,21)");
List result = query.getResultList();
o、使用操作符 LIKE
//查找以字符串"li"开头的 Person
Query query = em.createQuery("select p from Person as p where p.name like ‘li%‘");
List result = query.getResultList();
//可以结合 NOT 一起使用,比如查询所有 name 不以字符串"ming"结尾的 Person
query = em.createQuery("select p from Person as p where p.name not like ‘%ming‘");
result = query.getResultList();
p、使用操作符 IS NULL
//查询含有购买者的所有 Order
Query query = em.createQuery("select o from Order as o where o.ower is not null order by
o.orderid");
List result = query.getResultList();
//查询没有购买者的所有 Order
query = em.createQuery("select o from Order as o where o.ower is null order by o.orderid");
result = query.getResultList();
q、使用操作符 IS EMPTY
IS EMPTY 是针对集合属性(Collection)的操作符。可以和 NOT 一起使用。低版权的 Mysql不支持 IS EMPTY。
//查询含有订单项的所有 Order
Query query = em.createQuery("select o from Order as o where o.orderItems is not empty order by
o.orderid");
List result = query.getResultList();
//查询没有订单项的所有 Order
query = em.createQuery("select o from Order as o where o.orderItems is empty order by o.orderid");
r、使用操作符 EXISTS
[NOT]EXISTS 需要和子查询配合使用。注:低版权的 Mysql不支持 EXISTS
//如果存在订单号为 1 的订单,就获取所有 OrderItem
Query query = em.createQuery("select oi from OrderItem as oi where exists (select o from Order o where o.orderid=1)");
//如果不存在订单号为 10 的订单,就获取 id 为 1 的 OrderItem
query = em.createQuery("select oi from OrderItem as oi where oi.id=1 and not exists (select o from
Order o where o.orderid=10)");
result = query.getResultList();
s、字符串函数
EJB3 QL定义了内置函数方便使用。这些函数的使用方法和 SQL中相应的函数方法类似。
符串函数包括:
1. CONCAT 字符串拼接
2. SUBSTRING 字符串截取
3. TRIM 去掉空格
4. LOWER 转换成小写
5. UPPER 装换成大写
6. LENGTH 字符串长度
7. LOCATE 字符串定位
//查询所有人员,并在姓名后面加上字符串"_foshan"
Query query = em.createQuery("select p.personid, concat(p.name, ‘_foshan‘) from Person as p");
List result = query.getResultList();
//查询所有人员,并在姓名后面加上字符串"_foshan"
Query query = em.createQuery("select p.personid, concat(p.name, ‘_foshan‘) from Person as p");
List result = query.getResultList();
t、计算函数
ABS 绝对值
SQRT 平方根
MOD 取余数
SIZE 取集合的数量
//查询所有 Order的订单号及其订单项的数量
Query query = em.createQuery("select o.orderid, size(o.orderItems) from Order as o group by o.orderid");
List result = query.getResultList();
//查询所有 Order的订单号及其总金额/10 的余数
query = em.createQuery("select o.orderid, mod(o.amount, 10) from Order as o");
result = query.getResultList();
u、子查询
子查询可以用于 WHERE和 HAVING 条件语句中。注:低版权的 Mysql不支持子查询。
//查询年龄为 26 岁的购买者的所有 Order
Query query = em.createQuery("select o from Order as o where o.ower in(select p from Person as p
where p.age =26) order by o.orderid");
List result = query.getResultList();
分***页 v、结果集分页
QueryAPI有两个接口方法可以解决这个问题:setMaxResults( ) 和 setFirstResult( )
setMaxResults 方法设置获取多少条记录
setFirstResult 方法设置从结果集中的那个索引开始获取(假如返回的记录有3 条,容器会自动为记录编上索引,
索引从 0 开始,依次为 0,1,2)
public List getPersonList(int max,int whichpage) {
try {
int index = (whichpage-1) * max;
Query query = em.createQuery("from Person p order by personid asc");
List list = query.setMaxResults(max).setFirstResult(index).getResultList();
em.clear();//分离内存中受EntityManager管理的实体bean,让VM进行垃圾回收
return list;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
JSP 客户端调用代码片断:
<%@ page contentType="text/html; charset=GBK"%>
<%@ page import="com.foshanshop.ejb3.PersonDAO,
com.foshanshop.ejb3.bean.Person,
javax.naming.*,
java.util.Properties,
java.util.List,
java.util.Iterator"%>
<%
Properties props = new Properties();
props.setProperty("java.naming.factory.initial","org.jnp.interfaces.NamingContextFactory");
props.setProperty("java.naming.provider.url", "localhost:1099");
props.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming");
InitialContext ctx = new InitialContext(props);
try {
PersonDAO persondao = (PersonDAO) ctx.lookup("PersonDAOBean/remote");
out.println("<br>============ 分页显示,每页记录数为2 =========<BR>");
String index = request.getParameter("index");
if (index==null || "".equals(index.trim())) index = "1";
int max = 2; //每页记录数为2
int whichpage = Integer.parseInt(index); //第几页
List list = persondao.getPersonList(max, whichpage);
if (list!=null){
Iterator it = list.iterator();
while (it.hasNext()) {
Person p = (Person)it.next();
out.println("人员编号:"+ p.getPersonid() + " 姓名:"+ p.getName() + "<Br>");
}
}
} catch (Exception e) {
out.println(e.getMessage());
}
%>
w、调用存储过程(使用MySql数据库)
要调用存储过程,我们可以通过 EntityManager 对象的 createNativeQuery()方法执行 SQL 语句(注意:这里说的是
SQL语句,不是 EJB3 QL), 调用存储过程的 SQL格式如下:
{call 存储过程名称(参数 1, 参数 2, … )}
在 EJB3 中你可以调用的存储过程有两种
1.无返回值的存储过程。
2.返回值为 ResultSet(以 select形式返回的值)的存储过程,EJB3 不能调用以 OUT 参数返回值的存储过程。
① 调用无返回值的存储过程
CREATE PROCEDURE `AddPerson`()
NOT DETERMINISTIC
SQL SECURITY DEFINER
COMMENT ‘‘
BEGIN
INSERT into person(`PersonName`,`sex`,`age`) values(‘存储过程‘,1,25);
END;
调用:
private String QueryNoneReturnValueStoreProcedure(){
//调用无返回参数的存储过程
Query query = em.createNativeQuery("{call AddPerson()}");
query.executeUpdate();
② 调用返回单值的存储过程
CREATE PROCEDURE `GetPersonName`(IN Pid INTEGER(11))
NOT DETERMINISTIC
SQL SECURITY DEFINER
COMMENT ‘‘
BEGIN
select personname from person where `personid`=Pid;
END;
调用:
//调用返回单个值的存储过程
Query query = em.createNativeQuery("{call GetPersonName(?)}");
query.setParameter(1, new Integer(1));
String result = query.getSingleResult().toString();
③ 调用返回表全部列的存储过程
CREATE PROCEDURE `GetPersonList`()
NOT DETERMINISTIC
SQL SECURITY DEFINER
COMMENT ‘‘
BEGIN
select * from person;
END;
调用:
//调用返回 Person 全部列的存储过程
Query query = em.createNativeQuery("{call GetPersonList()}", Person.class);
List result = query.getResultList();
④ 调用返回部分列的存储过程
CREATE PROCEDURE `GetPersonPartProperties`()
NOT DETERMINISTIC
SQL SECURITY DEFINER
COMMENT ‘‘
BEGIN
SELECT personid, personname from person;
END;
调用:
//调用返回部分列的存储过程
Query query = em.createNativeQuery("{call GetPersonPartProperties()}");
List result = query.getResultList();
StringBuffer out = new StringBuffer("*************** QueryPartColumnStoreProcedure 结果打印*********<BR>");
if (result!=null){
Iterator iterator = result.iterator();
while( iterator.hasNext() ){
//取每一行
Object[] row = ( Object[]) iterator.next();
//数组中的第一个值是 personid
int personid = Integer.parseInt(row[0].toString());
String PersonName = row[1].toString();
out.append("人员 ID="+ personid+ "; 姓名="+PersonName+ "<BR>");
⑦ 事务管理服务
当应用出现失败或异常时,它保证了数据库的完整性。你可以简单地将为一个POJO方法申明它的事务属性。
这样容器就可以在合适的上下文中运行这个方法。最常见的事务是定义在 session bean 的方法上,方法中
所有的数据库操作只有在方法正常退出时才会提交,如果方法抛出未捕获的异常,事务管理将回滚所有的变更。
@TransactionAttribute 注释用作定义一个需要事务的方法。它可以有以下参数:
1.REQUIRED:方法在一个事务中执行,如果调用的方法已经在一个事务中,则使用该事务,否则将创建一个新的事务。
2.MANDATORY:如果运行于事务中的客户调用了该方法,方法在客户的事务中执行。如果客户没有关联到
事务中, 容器就会抛出TransactionRequiredException。 如果企业 bean 方法必须用客户事务则采用 Mandatory属性。
3.REQUIRESNEW:方法将在一个新的事务中执行,如果调用的方法已经在一个事务中,则暂停旧的事务。在调用结束后恢复旧的事务。
4.SUPPORTS:如果方法在一个事务中被调用,则使用该事务,否则不使用事务。
5.NOT_SUPPORTED:如果方法在一个事务中被调用,容器会在调用之前中止该事务。在调用结束后,容器
会恢复客户事务。如果客户没有关联到一个事务中,容器不会在运行入该方法前启动一个新的事务。用
NotSupported 属性标识不需要事务的方法。因为事务会带来更高的性能支出,所以这个属性可以提高性能。
6.Never:如果在一个事务中调用该方法,容器会抛出 RemoteException。如果客户没有关联到一个事务中,
容器不会在运行入该方法前启动一个新的事务。
如果没有指定参数,@TransactionAttribute 注释使用 REQUIRED 作为默认参数。
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void insertProduct(String name, Float price, boolean error) {
??????
⑧ Entity的生命周期和状态
在 EJB3 中定义了四种 Entity的状态:
1. 新实体(new)。Entity由应用产生,和 EJB3 Persistence 运行环境没有联系,也没有唯一的标示符(Identity)。
2. 持久化实体(managed)。新实体和 EJB3 Persistence 运行环境产生关联 (通过 persist(), merge()等方法),在EJB3
Persistence 运行环境中存在和被管理,标志是在 EJB3 Persistence 运行环境中有一个唯一的标示(Identity)。
3. 分离的实体(detached)。Entity有唯一标示符,但它的标示符不被 EJB3 Persistence 运行环境管理, 同样的该
Entity也不被 EJB3 Persistence 运行环境管理。
4. 删除的实体(removed)。Entity被 remove()方法删除,对应的纪录将会在当前事务提交的时候从数据库中删除。
----------------------看到173页-----------------------------------------------------明天继续---------------------------------------------------------
2.2 JBoss 数据源的配置(见图片)
Jboss有一个默认的数据源DefaultDS,他使用Jboss内置的HSQLDB数据库。
实际应用中你可能使用不同的数据库,如MySql、MsSqlServer、Oracle 等。
各种数据库的数据源配置模版你可以在[Jboss 安装目录]\docs\examples\jca 目录中找到,默认名称为:数据库名+ -ds.xml。
不管你使用那种数据库都需要把他的驱动类Jar包放置在[Jboss安装目录]\server\default\lib目录下,放置后需要启动Jboss服务器。
五、Web 服务(Web Service)
1、Web Service的创建
开发一个JSR-181 POJO Endpoint的 Web Service 应遵守下面几个步骤:
1> 建立一个 POJO endpoint
2> 把 endpoint 定义成一个 servlet
3> 把 endpoint打包成一个 Web 应用(war 文件)
建立一个 POJO endpoint.
package com.foshanshop.ws;
import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
@WebService(name = "HelloWorld",
targetNamespace = "http://com.foshanshop.ws", serviceName = "HelloWorldService")
@SOAPBinding(style = SOAPBinding.Style.RPC)
public class HelloWorldService {
@WebMethod
public String SayHello(String name) {
return name+ "说:这是我的第一个 web 服务";
}
}
@WebService 这个注释放置在 Java 类的前面,声明这个类的部分方法可以被发布为 Web 服务。
@WebService 的属性用于设置 Web 服务被发布时的一些配置信息,常用的属性说明如下:
1. name
Web 服务的名字,WSDL中 wsdl:portType 元素的 name 属性和它保持一致,默认是 Java 类或者接口的名字。
2. serviceName
Web 服务的服务名,WSDL 中 wsdl:service 元素的 name 属性和它保持一致,默认是Java 类的名字+”Service” 。
3. targetNamespace
WSDL文件所使用的 namespace,该 Web 服务中所产生的其他 XML文档同样采用这个作为 namespace 。
@SOAPBinding()表示这个服务可以映射到一个 SOAP 消息中。 Style 用于指定SOAP 消息请求和回应的编码方式。
@WebMethod 这个注释放在需要被发布成 Web 服务的方法前面。
把 POJO endpoint 定义成一个 servlet.
Web.xml
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"version="2.4">
<servlet>
<servlet-name>HelloWorldService</servlet-name>
<servlet-class>com.foshanshop.ws.HelloWorldService</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloWorldService</servlet-name>
<url-pattern>/HelloWorldService/*</url-pattern>
</servlet-mapping>
</web-app>
把 endpoint打包成一个 web 应用(*.war),下面是 Ant配置文件 build.xml的片断:
<target name="war" depends="compile" description="创建 WS 发布包">
<war warfile="${app.dir}/Services.war" webxml="${app.dir}/WEB-INF/web.xml">
<classes dir="${build.classes.dir}">
<include name="com/foshanshop/ws/HelloWorldService.class" />
</classes>
</war>
</target>
六、使用 EJB3.0 构建轻量级应用框架
1、在 WEB中使用 EJB3.0框架