EJB3.0笔记

来源:互联网 发布:上海医院排名 知乎 编辑:程序博客网 时间:2024/06/05 18:14

转帖自 http://blog.sina.com.cn/s/blog_5016113a0100asqq.html

 

EJB3.0笔记-1 Entity回调和监听器

在你执行EntityManager的persist()、merge、remove和find方法时,或者在执行EJB QL查询时,一系列预先定义好的生命周期事件会被触发。Java Persistence规范允许在entity class上设置回调方法,当这些事件发生时,该entity会收到相应的通知。也可以注册一些单独的监听类来拦截这些事件

回调事件
它们代表了entity生命周期中的一个阶段(pre表示之前,post表示之后)
@javax.persistence.PrePersist; @javax.persistence.PostPersist;
@javax.persistence.PreUpdate; @javax.persistence.PostUpdate;
@javax.persistence.PreRemove; @javax.persistence.PostRemove; @javax.persistence.PostLoad(在查询数据或refresh()时会触发)

Entity Class上的回调方法
通过为entity bean 的任何一个public、private、protected或package-protected方法添加注解,当某个接受托管的entity实例触发事件时,entity manager会调用entity bean上被添加了注解的相应方法。回调方法必须返回void,不抛出checked exception,并无传入参数。如:
@PostPersist void afterInsert(){………}
    等价的ORM映射文件
    <entity class=”com.titan.domain.Cabin”>
        <post-persist name=” afterInsert”/>
    </entity>

Entity 监听器
Entity监听器是一种能够拦截entity回调事件的类。它本身不是entity class,它必须有一个公有无参构造函数,监听方法必须返回void,并接受一个Object类型的参数,它就是触发事件的entity实例
    public class TitanAuditLogger{
        @PostPersist void postInsert(Object entity){……..}
    }
    通过@javax.persistence.EntityListeners注解把监听器应用于某个entity class
    @Entity
    @EntityListeners{ TitanAuditLogger.class,其它监听器}
    public class Cabin{….}
    等价XML
    <entity class=”com.titan.domain.Cabin”>
        <entity-listeners>
            <entity-listener class=”com.titan.listeners.TitanAuditLogger”>
                <post-resist name=” postInsert”/>
            </ entity-listener >
        </entity-listeners >
    </entity>
Entity监听器的执行顺序是它们在@EntityListeners注解或ORM XML映射文件中的声明顺序,而任何在entity class上声明的回调方法则会在其后被调用

默认的Entity监听器
可以在ORM映射文件中,通过顶层元素<entity-mappings>下的<entity-listeners>元素,为persistence unit中的每一个entitty class指定一组默认的entity监听器,如:
<entity-mappings>
    <entity-listeners>
            <entity-listeners>
                <entity-listener class=”com.titan.listeners.TitanAuditLogger”>
                    <post-resist name=” postInsert”/>
                </ entity-listener >
            </entity-listeners >
    </entity-listeners>
</entity-mappings>
如果想关闭默认的entity监听器,可以使用@javax.persistence.ExcludeDefaultListeners注解
    @Entity
    @ExcludeDefaultListeners
    public class Cabin{….}
    等价的XML
    <entity class=”com.titan.domain.Cabin”>
        <exclude-default-listeners/>
    </entity>

继承与监听器的关系
如果在一个实体的继承层次中,entity监听器被用于基类,则所有子类都会继承基类的监听器。基类的监听器会先于子类的监听器执行,如果想关闭从父类继承下来的entity监听器,可以使用@javax.persistence.ExcludeSupperclassListeners,如:
@Entity
@ExcludeSupperclassListeners
public class Customer extends Person{……}

 

 

EJB 3.0笔记-3资源管理和基本服务

EJB服务器
EJB服务器支持6种基本服务:并发(concurrency)、事务管理(transaction management)、持久化(persistence)、对象分布(object distribution)、命名(naming)和安全(security)。它还提供两个额外的服务:异步消息服务(asynchronous messaging)和定时服务(timer service)

资源管理
    EJB提供了两种管理大量bean实例的机制:实例池化(instance pooling)和激活(activation)
    
    实例池化
    通过建立数据库连接池使系统中的业务对象可以共享数据库访问是一种常见的技术。实例池之所以可         行,是因为客户端从来不会直接访问bean实例,服务器只需维护足以完成工作的少量bean实例,并重复    使用这些实例为不同的请求提供服务就可以了。

    Stateless session bean的生命周期
    stateless session bean存在以下三种状态
    无状态(No state):尚未被初始化
    池状态(Pooled state):已经被容器初绐化
    就绪状态(Ready state):已经与一个EJB请求关联,做好响应方法调用的准备
    EJB服务器为已经部署在其中的每个stateless session bean都维护了一个实例池,池中的每个实例都是        等价的。容器接收到一个来自客户端的请求,就从池中挑选一个实例为其服务,一旦完成请求,容器就        会将此实例重新放回到实例池中

    Message-driven bean和实例池化
    由于MDB和stateless session bean一样,不为特定的客户端请求维护状态,所以MDB也可以使用实例池    技术。在大多数EJB容器中,每个MDB都有自己的实例池

    激活机制
    stateful session bean会在不同的方法调用之间维护会话状态,它使用激活而不是实例池来降低资源的消    耗。当EJB服务器需要节省资源时,收回stateful session bean,释放它所占有的内在资源,并将其所保    持的会话状态序列化到辅助存储器中,如果客户端对该EJB对象再次发出请求,EJB容器会重新实例化一    个stateful session bean,并从辅助存储器中将之前的状态恢复。

    钝化(passivation)
    解除stateful bean实例与相关EJB对象间的关联,并保存其状态的一种操作,当bean被钝化后,就可以将    它从EJB对象和内在中移除。而激活是将bean实例的状态重新恢复到一个新的bean中。在EJB中,当        bean被激活之后,其非持久数据成员并不一定会被设置为对应类型的默认值,有可能保留原值,也可能    是任意值,这取决于EJB的实现,在stateful bean中使用非持久数据成员需格外小心。可以使用
    @javax.ejb.PostActivate注解,当bean被成功激活后,用它来重置非持久数据成员的初始值

Java EE 连接器体系结构
    Java EE连接器体系结构定义了java EE容器和企业信息系统(EIS)之间的接口。EJB3.0要求容器支持推送    (客户端没有发送请求,容器向客户端推送数据)模式的Java EE连接体系架构,即JCA1.5

基本服务
    并发
    并发控制是通过用锁来帮助保护数据而实现的。锁控制着多个用户如何同时访问和更改共享数据而不会        彼此冲突。不可能并发访问到session bean,所以容器不用对他们做并发控制。EJB规范禁止在bean中        使用synchronized关键字控制并发,以提高bean的性能,同时禁止bean创建自己的线程,因为这会影响    到容器跟踪控制bean的能力。Entity bean代表可被共享和并发访问的数据,EJB通过事务隔离设置来控        制对entity bean的并发访问。
   
    Message-driven bean的并发控制
    对于MDB,并发意味着同时处理多条信息,EJB容器使用MDB实例池,并发处理接收到的信息
   
    事务
    事务是一个工作单元,它是原子性的,即事务中的所有任务必须全部完成,才认为事务是成功的。EJB容    器是自动管理事务的。事务从一个方法调用开始,并会传播到该方法内的其他方法
   
    持久化
    entity bean是持久化的,亦即它的状态是保存到数据库中的,bean通过EntityManager服务与持久存储器    关联。java Persistence模型是基于普通java模型的,它提供了丰富的关系数据库映射、继承映射、多表    映射来达到从对象到关系数据库的持久化,它还为entity bean定义了关系数据成员,用来描述bean之间    的关系,比如:一对一、一对多和多对多
   
    分布式对象
    即可以处理分布在不同地点的客户端请求
    EJB3.0要求所有服务器都必须支持java RMI-IIOP协议(使用CORBA IIOP协议的java RMI API),规范还要    求EJB服务器通过JAX-RPC API支持SOAP1.2协议。服务器必须支持java客户端使用java EJB客户端API对    EJB的访问,EJB还允许服务器支持非JAVA的客户端对bean的访问(通过基于JAX-RPC的EJB-SOAP映        射)。
   
    异步企业消息服务
    支持企业级消息机制,要求EJB容器经过规定路线将消息可靠地从JMS客户端发送到JMS-MDB。所谓的    异步是指客户端向服务器发送信息后,不要求必须得到服务器的响应才进行下一步的工作。
        
    EJB定时服务
    EJB定时服务可以用于安排在特定的时刻向enterprise bean发送通知,它可以用于entity bean、stateless
     session bean 和message-driven bean
   
    命名服务
    命名服务本质是为客户端提供一种定位分布式对象和资源的机制。命名服务必须支持对象绑定,并提供        查找API。Enterprise JavaBeans规定java客户端使用JNDI作为查找API
   
    安全
    EJB服务器支持三种类型的安全
   认证(authentication):检验用身份。最常见的就是要求输入用户名和密码。
    授权(authorization):授权也叫访问控制,用于管理用户可以访问的资源
    安全通信(secure communication):对客户端和服务器之间的通信内容进行加密。
   
    基本服务和互操作性
    EJB规范要求容器支持java rmi-iiop远程调用,还要求支持JAX-RPC远程调用,使用JAX-RPC需要支持    SOAP和WSDL,这是web service的行业规范。这些远程调用期间,要保证对事务处理、命名服务和安全    服务之些基本服务的支持。IIOP提供了可行的操作能力,但并没有获得很大成功,目前业界更倾向于使用    基于SOAP和WSDL的web service
    事务提交互操作可以确保java ee web组件发起的事务可以被正确传播到其他容器的enterprise bean上。
    EJB规范指定容器使用CORBA CosNaming作为可互操作的命名服务。
    EJB为安全服务提供了互操作能力,可以指定容器间如何建立信任关系,以及如何交换安全凭证。

 

 

 

EJB 3.0笔记-4编写你的第一组bean

开发Entity bean
在Java Persistence中,实体是一个标注了O/R映射元数据的普通java对象。Bean class实现了java.io.Serializable接口,但这并非是必须的,如果实体实现了Serializable,就可以用作session bean中远程接口方法的参数和返回值。注解@Table和@Column这样的表映射并非是必需的,如果省略,其默认值分别为类的非限定名称和成员属性名称,但是主键标识是必需的(使用@Id或使用XML声明)。

persistence.xml文件
配置与EntityManager相关的信息。位于METAINF目录下。session bean使用注解@javax.persistence.PersistenceContext来获得对EntityManager服务的访问

开发session bean
如果要bean允许客户端远程调用,可以先定义一个远程接口,标注@javax.ejb.Remote,然后再实现这个接口。

titan.jar:JAR文件
JAR(java Archive)是基于zip文件格式和zlib压缩标准的,他将组件和其他软件进行“压缩-包装”以便发布给第三方。打包命令:jar cf titan.jar com/tian/domain
                      Object ref = jndiContext.lookup("TravelAgentRemote");
                      TravelAgentRemote dao = (TravelAgentRemote)
                      PortableRemoteObject.narrow(ref,TravelAgentRemote.class);
                       //远程接口要使用narrow()方法。得到这个引用后,就可以使用它的业务方法。
              } catch (javax.naming.NamingException ne){
                       ne.printStackTrace();
            }
        }
        //使用jndi.properties文件,不用显式设置属性
       public static Context getInitialContext()throws javax.naming.NamingException {
            return new javax.naming.InitialContext();
       }
}

 

 

EJB 3.0笔记-5持久化服务EntityManager

在旧版本的Java EE中,EJB规范涵盖了持久层的相关内容,而在Java EE 5中,持久层已经被剥离出来了,成为一个独立的规范:java Persisence 1.0。它可以自动完成Java对象与关系数据库的映射。所有的持久化操作现在都要借助 javax.persistence.Entity.Manager服务来完成。EntityManager在一组固定的实体类与底层数据源之间进行O/R映射的管理,它提供了创建、查找、同步对象以及将对象插入数据库的API,java Persisence可以与java EE及EJB结合在一起,也可以在普通的java程序中使用。

实体即POJO
实体即普通的Java对象。当它与EntityManager关联时,entity bean class的实例才会变成持久对象。

托管与非托管实体
一个entity bean要么受entity manager托管(也称attached),要么不受托管。一旦对象解除了与entity manager的关联,entity manager就不再跟踪entity class的状态。

持久上下文
Persistence context是由一组受托管的实体对象实例所构成的集合。Entity manager追踪persistence context中所有对象的修改和更新情况。一旦对象从persistence context中脱离,或persistence context关闭,对象就不再受entity Manager管理了。persistence context有两种类型,分别是transaction-scoped persistence context和extended persistence context。transaction-scoped persistence context只在事务范围内存在,它们会在事务结束后被关闭。可以使用@PersistenceContext注解标注EntityManager实例,表示该entity manager管理事务规范的persistence context。extended persistence context表示超出事务范围的持久化上下文,与此关联的对象会在多个事务间保持托管状态。

游离实体(datached entities)
当transacton scope persistence context或extended persistence context结束之后,实体的就会不受托管而处于游离状态。

Persistence Unit
每个Entity Manager负责管理一组类映射到数据库中,这组类就是persistence unit。它是通过persesence.xml文件定义的,persesence.xml文件位于META-INF目录中。下面是一个persistence.xml各元素解释:
<?xml version="1.0" encoding="UTF-8"?>
<persistence>
<!—name定义了unit的引用名; transaction-type 表示事务类型,可以取值JTA(java transaction api,Java EE环境默认取值)或RESOURCE_LOCAL(java SE环境使用javax.persistence.EntityTransaction API管理事务)
-->
   <persistence-unit name="titan" transaction-type="RESOURCE_LOCAL">
    <description>PU的描述
    <provider>指定实现javax.persistence.PersistenceProvider接口的类
    <jta-data-source>事务类型为JTA时的数据源
    <non-jta-data-source>事务类型为RESOURCE_LOCAL时的数据源
           <properties>persistence provider厂商的专有属性,可以配置数据源等
                <property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver"/>
                <property name="hibernate.connection.username" value="sa"/>
                <property name="hibernate.connection.password" value=""/
           </properties>
    <mapping-file>映射文件
   </persistence-unit>
</persistence>

Persistence Unit中类的集合
 PU管理的类的集合。集合中的类由以下因素决定:
1.包含persistence.xml的JAR文件中标有@Entity注解的类。如果指定<exclude-unlisted-classes/>persistence provider将不扫描此jar包
2.<jar-file>../lib/customer.jar</jar-file>指定JAR包中标有@Entity注解的类
3.在META-INF/orm.xml文件中映射的类
4<mapping-file>指定xml文件中映射的类
5.<class>com.tian.domain.Customer</class>列出的类

获取Entity Manager
得到EM,就可以使用它来完成对PU中类的数据库操作。
 
EntityManagerFactory
在java SE中,可以使用它来获得EM。它使用方法EntityManager createEntityManager();或EntityManager createEntityManager(java.util.Map map);来创建EM,其中map用来覆盖或扩展persistence.xml中的声明的厂商专有属性。
       
在java SE中获取EntityManagerFactory
使用javax.persistence.Persistence来创建EMF。它使用方法public static createEntityManagerFactory(String unitName)或public static createEntityManagerFactory(String unitName,java.util.Map properites)来创建EMF,其中properites用于覆盖或添加在persistence.xml的<properties>元素中定义的厂商专有属性

在Java EE中获取EntityManagerFactory
可以使用@javax.persistence.PersistenceUnit标注EntityManagerFactory成员变量,由容器把EMF实例注入到成员变量。
@javax.persistence.PersistenceUnit(unitName=”CRM”)
private EntityManagerFactory factory;

获取persistence context
使用EntityManagerFactory. createEntityManager()方法,得到一个extended persistence context。如果使用JTA管理事务,要调用EntityManager.jonTransaction()方法把EM参与事务,如果使用EJB容器管理的persistence context,就不用这一步了。可以使用@javax.persistence.PersistenceContext注解,把EM实例注入EJB中。
@PersistenceContext(unitName=”titan” type=PersistenceContextType.EXTENDED)
private EntityManager manager;

操作EntityManager
使用EntityManager API包含了有关实体的一系列数据库操作方法。
   
持久化实体
使用entityManager.persist(cust);方法持久化一个对象,当它被调用时,java persistence可以自动生成主键。在transaction-acoped persistence context里,事务范围外调用persist()会引起TransactionRequiredException异常

查找实体
使用find()和getReference()方法。在无法从数据库中找到指定的实体时,find()方法会返回null;而getReference()会抛出javax.persistence.EntityNotFoundException异常

查询
可以使用EJB QL语句来查询,这时要调用EntityManager的createQuery()、createNamedQuery()或createNativeQuery()来创建查询

更新实体
在实体处于托管状态时,任何更改都将被自动(取决于flush模式)或手工(通过调用flush()方法)地同步到数据库中。

合并实体
使用EntityManager的merge()方法,可以将游离实体合并到当前EntityManager管理的persistence context中。如果EM没有管理过与传入的实体主键相同的entity,则merger()会返回这个传入实体的拷贝,这份拷贝受EM的管理;如果EM管理着与传入参数相同的ID的实体,则把传入的参数内容复制到托管的对象实例中。这两种情况,传入的参数都不受托管。

删除实体
 调用EntityManager.remove()可以将实体从数据库中删除,实例将不受entity manager托管而成为游离对象

刷新实体
调用refresh()方法根据数据库的情况刷新内存中实体的状态,如果entity bean有关联的实体,根据设置的级联策略,决定关联的实体是否刷新

contains()方法与clear()方法
contains()方法判断persistence context中是否包含该实体对象。EntityManager.clear()方法,把persistence context中所有的托管实体变成游离对象

flush()方法和FlushModeType
调用persist()、merge()和remove方法之后,这些更改操作只有执行flush操作时才同步到数据库中。flush操作会在相关查询执行之前和事务提交之时自动执行,也可以手工调用flush()方法强行同步。但是通过主键来查询实体(如find()和getReference())并不会引起flush。FlushModeType默认行为为AUTO,表示自己flush,COMMIT则表示,仅当事务提交时才对更改做flush操作,有时这个方式会减少数据库访问量。可以调用setFlushMode()方法来指定EntityManager的FlushModeType

锁定lock()
EentiyManager API同时支持读锁和写锁,锁定行为与事务的概念紧密关联。

getDelegate()
该方法允许你获得一个指向底层persistence provider对象的引用,该persistence provider实现了EntityManager接口。该方法提供了一种获取并使用厂商专有API的途径

Resource Local事务
在非java EE环境中无法使用JTA,因此Java Persistence API规范通过EntityTransaction接口提供了与JTA事务类似的编程接口,它可以使用EntityManager.getTransaction()获得

 

 

 

EJB 3.0笔记-6映射持久对象

实体代表了数据库中的数据,因而对entity bean的更改会导致对数据库的更改,这便是entity bean的根本目的:为程序员提供一种访问和更改数据的更为简单的机制(相对使用SQL而言)。

保持bean实例所代表的数据与数据库中的数据协调一致的过程被称为持久化。

编程模型
在java Persistence中,实体就是普通的Java类

Bean Class
一个映射到关系数据库的普通Java对象,它必须至少拥有一个无参的构造函数,还要求有两段元数据:注解@javax.persistence.Entity表示将该类映射到数据库,注解@javax.persistence.Id则表示在类中哪个成员属性会被用作主键。
Persistence provider会假定,类中所有其余的成员属性都将映射到具有相同名称相同类型的数据库字段上。如果将@Id注解置于getter成员函数上,那么必须将任何用于映射的其他注解都置于类中的gettert和setter成员函数之上(有getter方法的成员才认为是要持久化的成员);如果将@Id注解置于类的数据成员之上,Persistence provider会假定,类中的任何其他数据成员也是持久化成员属性,并根据它们的名称和类型自动对其进行映射

XML映射文件
XML映射文件可以替代类中的注解。缺省情况下,persistence provide会在META-INF目录下查找名为orm.xml的映射文件。也可以使用persistence provider的<mapping-file>元素声明映射文件
<entity-mapping>
    <entity class=”com.tian.domain.Customer” access=”PROPERTY”>
        <attributes>
            <id name=”id”/>
        </attributes>
    </entity>
</entity-mapping>
persistence provider会假定,在类中的任何其他成员属性都是持久化成员属性,不必都显式定义。

基本的Schema映射
如果不能使用原有的默认映射方法,比如类成员名称与表的名称不相同,或者要为类成员添加约束,就要到更为详细的注解。
  
     @Table
        @Table(name=”CUSTOMER_TABLE”,catalog=”TITAN” ,schema=”TITAN” ,UniqueConstraint={“ATTRIBUTE”})
        XML方式
        <table name=”CUSTOMER_TABLE” catalog=”TITAN”  schema=”TITAN” >
            <unique-constraint>
                <column-name>ATTRIBUTE</column_name>
            </unique-constraint>
        <table/>
其中:name表示表名,catalog表示关系数据库的catalog,shema表示关系表所属的schema(方案),UniqueConstraint表示唯一性约束
   
    @Column
        以XML方式为例
        <column  name=”CUST_ID” 列名
                unique=”true”唯一性
                nullable=”true”是否可以为空
                insertable=”true”允许插入该字段
                updateable=”false”不允许更新
        column-definition=”integer”字段类型
        table=””用于多表映射
        length=””string类型的成员长度
        precision=””数值的总位数
        scale=””小数位数
/>

主键
        每个entity bean必须有一个主键,并且是唯一的。
        @Id
        id可以手工生成,也可以让persistence provider自动生成,这时要使用以下注解:
        @GeneratedValue
        以XML为例
        <attributes>
            <id name=”id”>
                <generated-value strategy=”AUTO”  >
                <!--
                strategy有四种取值
                策略AUTO指明由persistence provider自动生成主键
                IDENTITY来创建主键,不过要数据库的支持
                TABLE这种类型还要指定generator属性
                SEQUENCE这种类型还要指定generator属性
                -->
            </id>
        </attributes>

        表生成器
            策略TABLE要先指定一张用户定义的关系表。如:
            create table GENERATOR_TABLE(
                PRIMARY_KEY_COLUMN VARCHAR not null,要生成值的主键名
                VALUE_COLUMN long not null用于保存计数器的值
            )
            然后使用@javax.persistence.TableGenerator注解定义一个表生成器,如
            @TableGenerator(name=”CUST_GENERATOR”
                    table=” GENERATOR_TABLE”生成器依赖的表名
                    pkColumnName=” PRIMARY_KEY_COLUMN” 对应表生成器的字段
                    valueColumnValue=” VALUE_COLUMN”  对应表生成器的字段
                    pkColumnValue=”CUST_ID”要自动生成主键的字段名
                    allocationSize=10    每次生成10个id,缓存起来使用
            )
            使用方式
            @Id
            @TableGenerator(strategy=GenerationType.TABLE,generator=” CUST_GENERATOR”)
       
        Sequence生成器
            Oracle数据库可以使用这各生成策略。
            @SequenceGenerator(name=”CUSTOMER_SEQUENCE”,生成器的名称
                    sequenceName=”CUST_SEQ”,数据库中的Sequence对象
                    initialValue=1,主键初始值
                    allocationSize=50递增值
            )
            使用方式
            @Id
            @TableGenerator(strategy=GenerationType.SEQUENCE,generator=”
                 CUSTOMER_SEQUENCE”)
            以上两种生成器都是定义在bean class上。即@Table之下。主键在执行persist()方法时自动生成。
   
主键类和复合键
 映射复合主键,java persistence提供了两种方式:@javax.persistence.IdClass注解和@javax.persistence.EmbeddedId

@IdClass
它是一个类级别的注解,指定了当与entity manager进行交互时,使用什么主键类。假如客户由姓氏和社会保险号码组成的复合主键,那么主键类如:
1.
public class CustomerPK implements java.io.Serializable {  //1. 主键类必须是可以序列化的
       private String lastName; //lastName 和ssn组成复合主键
       private long ssn;
       public CustomerPK() {}    //2. 主键类必须有公有无参构造函数
       public CustomerPK(String lastName, long ssn){
             this.lastName = lastName;
              this.ssn = ssn;
       }
       public String getLastName() { return this.lastName; }
       public void setLastName(String lastName) { this.lastName = lastName; }

       public long getSsn() { return ssn; }
       public void setSsn(long ssn) { this.ssn = ssn; }
    
       public boolean equals(Object obj){//3主键类必须实现equals()方法
              if (obj == this) return true;
              if (!(obj instanceof CustomerPK)) return false;
              CustomerPK pk = (CustomerPK)obj;
              if (!lastName.equals(pk.lastName)) return false;
              if (ssn != pk.ssn) return false;
              return true;
       }

      public int hashCode(){//4主键类必须实现hashCode ()方法
             return lastName.hashCode() + (int)ssn;
       }
}
2.
Customer类
@Entity
@IdClass(CustomerPK.class)
public class Customer implements java.io.Serializable {
       private String firstName;
       private String lastName;
       private long ssn;

       public String getFirstName() { return firstName; }
       public void setFirstName(String firstName) { this.firstName = firstName; }

       @Id
       public String getLastName() { return lastName; }
       public void setLastName(String lastName) { this.lastName = lastName; }

       @Id
       public long getSsn() { return ssn; }
       public void setSsn(long ssn) { this.ssn = ssn; }
}
3.
        映射方式
            @IdClass(CustomerPK.class)
            public class……..
            @Id
            public String getLastName()……
            @Id
            public String getSsn()……..
        或者
            <id-class>…….CustomerPK</id-class>
            <attributes>
                <id name=”lastName”/>
                <id name=”ssn”/>
            </attributes>
4.
        查询方法
        CustomerPK pk=new CustomerPK(“Burke”,9999999);
        Customer cust=entityManager.find(Customer.class,pk);
       
@EmbeddedId
 此方法将主键类直接嵌入到bean class中。
 1.
@ EmbeddedId
首先声明主键类(青色部分表示和上面的CustomerPK不同的地方)
@Embeddable
public class CustomerPK implements java.io.Serializable { 
       …….
    @Column(name=”CUSTOMER_LAST_NAME”)
   public String getLastName() { return this.lastName; }
   public void setLastName(String lastName) { this.lastName = lastName; }
   @Column(name=”CUSTOMER_SSN”)
   public long getSsn() { return ssn; }
   public void setSsn(long ssn) { this.ssn = ssn; }
……
}
2.在Customer 类中
@Entity
public class Customer implements java.io.Serializable {
         private CustomerPK pk;
        ……….
         @ EmbeddedId
        public CustomerPK pk getPk(){……..}
        public void setPK(CustomerPk pk){….}
}
如果要覆盖原有的@Column定义,可以使用@AttributeOverrides,这时的Customer类如:
@Entity
public class Customer implements java.io.Serializable {
        private CustomerPK pk;
         ……….
        @ EmbeddedId
        @ AttributeOverrides{
            @ AttributeOverride(name=”lastName”,column=@Column(name=”LAST_NAME”))
        }
    public CustomerPK pk getPk(){……..}
    public void setPK(CustomerPk pk){….}
}

成员属性映射
Java Persistence针对Blob和Clob、可序列化对象、可内嵌对象、以及带有成员属性version的乐观并发控制,都提供了映射

@Transient(瞬时成员)
 对于不打算持久化的成员属性,可以使用@ Transient注解标准成员属性,等价的XML文件如:
 <transient name=”lastName”/>
   
@Basic和FetchType
 这是成员属性的默认映射方式,可以映射基本数据类型及其封装类型的成员属性。
@Basic(fetch=FetchType.LAZY,optional=false) LAZY表示延迟加载该属性,还可以使用EAGER表示立即加载。optional=false表示在persistence provider为你生成数据库schema时,允许字段为null。等价的XML为:
<basic name=”firstName” fetch=”LAZY” optional=”false”/>

@Temportal(表示时间的)
用于映射java.util.Date或java.util.Calendar类型的成员属性的附加信息(@Basic也可以),它可以将这些类型映射到数据库中date,time或timestamp类型的字段上。
@ Temportal(TemporalType.TIME)还有两种类型DATE、TIMESTAMP,等价的XML格式为
<temporal>TIME</temporal>

@Lob
类型java.sql.Blob代表二进制数据,而java.sql.Clob代表字符串数据。注解@Lob用来映射这两种大对象。如果java类型是 byte[]、Byte[]或java.io.Serializable可以看作是Blob类型的;如果java类型是char[]、 Character[]或java.lang.String可以看作是Clob的。
@Lob
@Basic(fetch=FetchType.LAZY)与@Basic结合使用,以提醒该字段延迟加载
public JPEG getPicture(){…}
        等价的XML
        <basic name=”picture”><lob/></basic>

@Enumerated
用于映射java的java.lang.Enum枚举类型。
public enum CustomerType{
            UNREGISTERED,
            REGISTERED,
            BIG_SPENDAH
}
@ Enumerated(EnumType.STRING)表示映射成字符串型式,还有ORDINAL(枚举值的数据序号)选项 public CustomerType getCustomerType(){…}
等价的XML
<enumerated>STRING</enumerated>

@SecondaryTable 进行多表映射
使用注解@javax.persistence.SecondaryTable可以将一个entity bean class映射到一张或多张表中。比如顾客信息由顾客、地址和信用卡三张表保存
@Table(name=”CUSTOMER_TABLE’)第一张表
@SecondaryTable(name=”ADDRESS_TABLE” 第二张表
pkJoinColums{@PrimaryKeyJoinColumn(name=”ADDRESS_ID”,referenceedColumnName=”CUST_ID”)}) 分别表示第二张表和第一张表join的字段,referenceedColumnName默认为第一张表的主键,可以不写
    ………..
    @Column(name=”STREET” table=”ADDRESS_TABLE”)这个字段写到第二张表中。
    如果是多张表,使用以下注解
    @SecondaryTable({
        @SecondaryTable(name=”ADDRESS_TABLE”
pkJoinColums{@PrimaryKeyJoinColumn(name=”ADDRESS_ID”,referenceedColumnName=”CUST_ID”)}),
        @SecondaryTable(name=”CREDIT_CARD_TABLE”
pkJoinColums{@PrimaryKeyJoinColumn(name=”ADDRESS_ID”,referenceedColumnName=”CC_ID”)})
    })
    等价的XML为
    <secondary-table name=” ADDRESS_TABLE”>
        <primary-key-join-column name=”ADDRESS_ID”>
    </secondary-table>
    <column name=”STREET” name=”ADDRESS_TABLE”/>

@Embedded对象
如果entity bean包含一个自定义javabean,并要将该内嵌值对象的成员属性映射到实体表的字段上,使用@Embedded注解。如果顾客中包含地址对象
    @Embeddable
    public class Address implements java.io.Serializable{…….}
    @entity
    @Table(name=”CUSTOMER_TABLE”)
    public class Customer implements java.io.Serializable{
        private Address address;
        ………..
        @Embedded
        @AttributeOverrides({@AttributeOverrides(name=”street”
                 column=@Column(name=”SUST_STREET”)).....}) //@AttributeOverrides用于覆盖字段定义
        public Address getAddress(){}
    }
等价的XML
<embeddable class=”” access=”PROPERTY”/>
<entity class=””>
    <attributes>
        <id name=”id”/>
        <enbedded name=”address”>
            <attribute-override name=”street”>
                <column name=”CUST_STREET”/>
            </attribute-override>
        </enbedded>
    </attributes>
……..
</entity>

 

 

 

EJB3.0笔记-7关联实体

实体与实体之间的关系,实体之间的关系是由系统要实现的业务来决定,而不一定要反映真实的世界,比如可以让一个人只有一个住址,也可以让一个人有多个住址。所以确定实体与实体的关系可以通过要实现的表形式确定,比如一条A实体记录可以被多少条B实体记录使用,反过来一条B实体记录可以被多少条A实体记录使用,回答这两个问题后,就可以解释实体与实体的关系。
   
7种关联类型
one-to-one、one-to-many、many-to-one和many-to-many。每一种关联又分为双向的或单向的,似乎有8种组合,但是双向ont-to-many和双向many-to-one关联是一回事。

单向one-to-one关联
假设Customer(顾客)和Address(地址)是一对一关联
数据库实现:CUSTOMER表中有一个外键ADDRESS_ID引用ADDRESS表的主键ID,我认为还应同时定义这个字段是唯一的。
类的实现:Customer中包含Address对象
    注解:
    @OneToOne(cascade={CascadeType.ALL})
    @JoinColumn(name=” ADDRESS_ID”)如果不是引用主键,可以使用referencedColumnName指定
    public Address getAddress(){……}
    等价XML:
    <one-to-one name=”address”
        target-entity=”com.titan.domain.Address” 目标类
        fetch=”LAZY”获取方式
        optional=”true”>关联两端是否可以为null
        <cascade-all/>
        <join-column name=” ADDRESS_ID”/>
    </one-to-one>
   
    主键连接字段
    Customer主键连接Address的主键,可以实现one-to-one
    @OneToOne(cascade={CascadeType.ALL})
    @PrimaryKeyJoinColumn(name=”ID”, referencedColumnName=”ID”)
    分别代表两个实体的主键,可以使用默认
    public Address getAddress(){……}

双向one-to-one关联
假设Customer和CreditCard(信用卡)一对一双向关联
双向的作用是关联的两个实体,可以从任意一个对象访问到另一个对象
数据库实现:Customer表中有引用CreditCard的外键。在关系数据库模型中,关系是没有方向的,所以不用两个表相互引用
类的实现:Customer实体中包含CreditCard类型的成员变量;CreditCard包含Customer类型的成员变量
    @Entity
    public class CreditCard implements java.io.Serializable{
        private Customer customer;
        @OneToOne(mappedBy=”creditCard”)
        mappedBy用于设置双向关联,对应于Customer的creditCard成员
        public Customer getCustomer(){……..}
    }
    @Entity
    public class Customer implements java.io.Serializable{
        private CreditCard creditCard;
        @OneToOne
        @JoinColumn(name=”CREDIT_CARD_ID”)
        public CreditCard get CreditCard (){……..}
    }
等价的XML
Customer类XML一对一映射部分:
    <one-to-one name=”creditCard”
        target-entity=”com.titan.domain.CreditCard”
        fetch=”LAZY”
        optional=”true”>
        <cascade-all/>
        <join-column name=”CREDIT_CARD_ID”/>
    </one-to-one>
    CreditCard类XML映射部分:
    <one-to-one name=”customer” target-entity=”com.titan.domain.Customer”
        mapped-by=” creditCard”/>
    </one-to-one>

one-to-Many关联
假设一个顾客可以拥有多个电话Phone
关系数据库schema:PHONE中有一个指向CUSTOMER的非唯一外键。
程序模型:Customer中有Phone的集合,可以使用java.util包中的一些数据结构,像Collection、List、Map和Set
@Entity
public class Customer implements java.io.Serializable{
    private Collection<Phone> phoneNumbers=new ArrayList<Phone>();
    @OneToMany(cascade={CascadeType.ALL,targetEntity=”com.titan.domain.Phone”})
    如果使用泛型,targetEntity可以不用。
    @JoinColumn(name=”CUSTOMER_ID”)
    public Collection<Phone> getPhoneNumbers(){….}
}
等价XML
<one-to-many name=”phones” target-entity=”com.titan.domain.Phone” >
        <join-column name=”CUSTOMER_ID”>
    </one-to- many >
使用:删除Phone
cust.getPhones().remove(phone);//从集体中删除,释放内存
em.remove(phone);//从数据库中删除

关联表映射(Join table mapping)

映射one-to-many的另一种方法是使用关联表(association table),它包含指向CUSTOMER和PHONE表的外键,其中指向PHONE的外键是唯一的。
@Entity
public class Customer implements java.io.Serializable{
    private Collection<Phone> phoneNumbers=new ArrayList<Phone>();
    @OneToMany(cascade={CascadeType.ALL})
    @JoinTable(name=”CUSTOMER_PHONE”,
        joinColumns={@JoinColumn(name=”CUSTOMER_ID”)},引用支配端的主键
        inverseJoinColumns={@JoinColumn(name=”PHONE_ID”)})引用到非支配端的主键
    public Collection<Phone> getPhoneNumbers(){….}
}
等价XML
<one-to-many name=”phones” target-entity=”com.titan.domain.Phone” >
<join-table name=” CUSTOMER_PHONE”>
            <join-column name=”CUSTOMER_ID”/>
            <inverse-join-column name=”PHONE_ID”/>
        </join-table>
</one-to- many >

单向many-to-one关联
    航程Cruise与船Ship之间就是多对一关系。
    关系数据库设计:CRUISE表维护着一个指向SHIP表的外键。
    @Entity
    public class Cruise implements java.io.Serializable{
        private Ship ship;
        @ManyToOne
        @JoinColumn(name=”SHIP_ID”)
        public Ship getShip(){……..}
    }
    等价XML
    <many -to-one name=”ship” target-entity=”com.titan.domain.Ship” fetch=”EAGER”>
            <join-column name=”SHIP_ID”>
    </many -to- one >

双向one-to-many关联
    双向one-to-many和双向many-to-one是一样的。
    Cruise航行和预订Reservation舱位是一对多关系
    数据库设计:RESERVATION表包含指向CRUISE表的外键CRUISE_ID
    @Entity
    public class Reservation implements java.io.Serializable{
        private Cruise cruise;
        @ManyToOne
        @JoinColumn(name=”CRUISE_ID”)
        public Cruise getCruise(){……..}
    }
    @Entity
    public class Cruise implements java.io.Serializable{
        private Collection<Reservation> reservation=new ArrayList< Reservation >;
        @OneToMany(mappedBy=”cruise”)
        public Collection< Reservation > getReservations(){……..}
    }
    等价XML
    Cruise内的映射
    <one-to-many name=”Reservations” target-entity=”com.titan.domain. Reservations”
        fetch=”LAZY” mapped-by=”cruise”>
    </one-to- many >
    Reservation内的映射
    <many -to-one name=”cruise” target-entity=”com.titan.domain.Cruise” fetch=”EAGER”>
            <join-column name=”CRUISE_ID”>
    </ many -to- one >

双向many-to-many关联
    Reservations(预订舱位)和Customer是多对多关系。Reservations记录预订某舱位的所有乘客;而        Customer可以预订多个舱位
    数据库设计:使用关联表RESERVATION_CUSTOMER,它维护两个外键字段,一个指向预订表,另一个指向    顾客表
    程序设计:Customer中有Reservation的集合,而Reservation中也保存Customer的集合
    @Entiy
    public class Reservation implements java.io.Serializable{
        private Set<Customer> customers=new HashSet<Customer>();
        Set表示一个预订舱位中的每个顾客都是不同的
        @ManyToMany
        @JoinTable(name=” RESERVATION_CUSTOMER”,
            joinColumns={@JoinColumn(name=”RESERVATION_ID”)},//Reservation是支配端
                inversJoinColumns={@JoinColumn(name=”CUSTOMER_ID”)})
        public Set<Customer> getCustomer(){…..}
    }
    @Entity
    public class Customer implements java.io.Serializable{
        private Collection< Reservation > reservations=new ArrayList< Reservation >();
        @ManyToMany(mappedBy=”customers”)        //处于关联关系的反转端
        public Collection< Reservation > getReservations(){…….}
    }
   
    等价的XML
    在Reservation映射中
    <many -to-many name=”customers” target-entity=”com.titan.domain.Customer” >
    <join-table name=” RESERVATION_CUSTOMER”>
            <join-column name=”RESERVATION_ID”/>
            <inverse-join-column name=” CUSTOMER_ID”/>
        </join-table>
    </many -to- many >
   
    在Customer映射中
    <many -to-many name=”reservation” target-entity=”com.titan.domain.Reservaton”
        fetch=”LAZY” mapped-by=”customers”>
    </many -to- many >

单向many-to-many关联
reservation可以预订多个舱位,每个舱位可以重复预订,因为乘客不只一人。但是舱位不需要知道有那些预订与其相关.单向many-to- many的数据库和双向many-to-many一样,但在程序中Cabin没有引用Reservations的引用,XML中也没有相关映射,其他和双向many-to-many一样

双向的java代码
    始终要对双向关联的两端同时进行设置,才会所改变反应到数据库中。如删除预订某舱位的一个顾客
    reservation.getCustomers.remove(customer);
    customer.getReservations.remove(reservation);

映射集合型关系
    还可以使用java.util.List或java.util.Map来表示关联关系
    基于有序列表的关联关系
        使用java.util.List可以包含重复的对象,还可以对集合进行排序,默认是按主键做升序。
        用法,在定义注解时,添加@OrderBy,如
        @ManyToMany
        @OrderBy(“lastName ASC”)。如果按多列@OrderBy(“lastName ASC,firstName ASC”)
        等价XML
        <many-to-many…….>
            <order-by>lastName ASC</order-by>……..
        </ many-to-many >
    基于Map的关联关系
        使用java.util.Map可以将关联关系的某个成员属性作为key,实体本身作为value
        用法,在定义注解时,添加@MapKey注解,如
        @OneToMany
        @JoinColumn
        @MapKey(name=”number”)//以电话号码为key,默认为主键,这个key一定是唯一的。
        public Map<String,Phone> getPhoneNumbers(){….}
        等价XML
        <one-to-many……..>
            <map-key name=”number”/>
            <join-column name=”CUSTOMER_ID”>
        </one-to-many >

游离实体和FetchType
每个描述关联的注解都包含一个fetch属性,它指定关系型成员属性在接受查询时是否被读入。如果定义为FetchType.LAZY,则只有在代码访问到该关联时,它才被初始化。这种延迟加载仅在bean受persistence context托管时才生效,如果是游离对象,试图访问涉及的关联关系会引起异常。

级联
当通过entity manager操作entity bean实例时,同样的操作会自动地作用于该实体所拥有的任何关系型成员属性上,这就叫级联。级联可以通过注解 @javax.persistence.CascadeType声明,它有ALL、PERSIST、MERGE、REMOVE和REFRESH选项,分别代表所有操作、persist()、merge()、remove()和refresh()。如果使用cascade= {CascadeType.PERSIST}或<cascade-persist/>表示执行persist()操作时,会发生级联
    PERSIST:用于在数据库中创建实体
    MERGE:用于处理实体的同步,亦即数据库插入和更新操作。合并是一种将游离对象的状态同步到持久    化存储设备的过程
    REMOVE:删除实体
    REFRESH:根据数据库来刷新对象实例的状态
何时使用级联:级联仅仅是一种减少EntityManager API调用次数的便捷工具而已,如果出于性能考虑,减少数据库访问量,而同时又满足业务要求,可不用级联

 

EJB3.0笔记-8实体继承

Java Persistence规范支持实体继承(entity inheritance),多态关系/关联,以及多态查询。这些特性在EJB CMP2.1规范中完全没有的。
假设存在一个层次结构:PersonàCustomeràEmpoyee(雇员预订有折扣,也可以是顾客)
映射继承层次到关系数据库,有三种方式(这三种方式的类文件除了声明映射策略的注解(如:Inheritance)不一样外,其他都是一样的):

每个类层次结构一张表
Person,Customer和Employee实体的属性都体现在同一张表中,表通过一个额外字段(如DISCRIMINATOR)的值来区别记录属于哪个类。注解方式:
@Table(name=”PERSON_HIERARCHY”)表名
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)单表继承策略
@DiscrimiinatorColumn(name=”DISCRIMINATOR”,区分记录类别的字段名
discriminatorType=DiscriminatorType.STRING)该字段的类型,有STRING、CHAR和INTEGER
    @DiscriminatorValue(“PERSON”)表中DISCRIMINATOR字段的值为”PERSON”的记录表示Person
    public class Person{…….}
    
    @Entity
    @DiscriminatorValue(“CUST”)    //discriminator字段值为”CUST”的记表示Customer类
    public class Customer extends Person{……..}

    @Entity    //discriminator字段使用默认值”Employee”
    public class Employee extends Customer{…......}
   
    等价XML
    <entity class=”com.titan.domain.Person”>
        <inheritance strategy=”SINGLE_TABLE”/>
        <discriminator-column name=”DISCRIMINATOR”  discriminator-type=”STRING”/>
        <discriminator-value>PERSON</ discriminator-value >
        ….
    </entity>
    <entity class=”com.titan.domain.Customer”>
        <discriminator-value>CUST</ discriminator-value >
    </entity>
    <entity class=”com.titan.domain.Employee”/>
   
优点:SINGLE_TABLE映射策略最容易实现,由于是单表操作,所以性能在三种继承方式中是最佳的。
缺点:所有代表子类的成员属性都必须允许为null。比如保存Customer信息,因为Customer没有Employee特有字段,如果该字段不允许为空,保存Customer就不可能通过数据库的约束检查。同时每一行记录都有与其不直接相关的字段,该策略不遵循数据库范式。

每个具体类一张表
    有三张表,每张表都拥有代表该具体类的所有成员属性(包含本身定义的和从父类继承而来的)的字段
    注解
    @Entity
    @Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
    public class Person{…..}
   
    @Entity
    public class Customer extends Person{………}

    @Entity
    public class Employee extends Customer{……..}
    等价XML
    <entity class=”com.titan.domain.Person”>
        <inheritance strategy=” TABLE_PER_CLASS”/>
        ….
    </entity>
    <entity class=”com.titan.domain.Customer”/>
    <entity class=”com.titan.domain.Employee”/>
优点:可以在子类的成员属性上定义它特有的约束。对于映射遗留的schema,些策略更为容易
缺点:每张表都有冗余字段,代表基类的成员属性,所以不遵循数据库范式。为了实现这种策略,容器可以在加载实体或多态关系时使用多次查询,这会影响性能;或者使用SQL UNION,但这种方式不是所有数据库都支持。所以不推荐使用这种策略

每个子类一张表
    有三张表,表中仅包含在该类定义的成员属性(不包含父类的属性)
    以EMPLOYEE、CUSTOMER和PERSON共享同一个主键值为例
    注解
    @Entity
    @Inheritance(strategy=InheritanceType.JOINED)
    public class Person{…….}

    @Entity
    public class Customer extends Person{….. }

    @Entity
    @PrimaryKeyJoinColumn(name=”EMP_PK”)
    //name表示EMPLOYEE用于join的字段,默认为父类CUSTOMER的主键,如果子类与基类主键字段名相同,
    //可以不用该注解。referencedColumnName父类用于join的字段,默认为父类对应表的主键
    public class Employee extends Customer{…. }
    等价XML
    <entity class=”com.titan.domain.Person”>
        <inheritance strategy=” JOINED”/>
        ….
    </entity>
    <entity class=”com.titan.domain.Customer”/>
    <entity class=”com.titan.domain.Employee”>
    <primary-key-join-column name=”EMP_PK”/>
    </entity>
    优点:遵循数据库范式,可以在任何表字段定义特定约束
    缺点:性能逊于SINGLE_TABLE策略

非实体基类
    Person基类存在于领模型中,但是不打算将其作为实体保存到数据库中。
    数据库设计:没有PERSON表,CUSTOMER表中含有person类的属性,EMPLOYEE只含有它特有的属性
    注解:
    @MappedSuperclass
    public class Person{……..}

    @Entity
    @Table(name=”CUSTOMER”)
    @Inheritance(strategy=InheriatanceType.JOINED)
    @AttributeOverride(name=”lastName” ,column=@Column(name=”SURNAME”))
    //覆盖父类声明的默认字段映射
    public class Customer extends Person{…..}

    @Entity
    @Table(name=” EMPLOYEE”)
    @PrimaryKeyJoinColumn(name=”EMP_PK”)
    public class Employee extends Customer{….}
   
    等价XML
    <mapped-superclass class=”com.titan.domain.Person”>
        ……..
    < /mapped-superclass >
    <entity class=”com.titan.domain. Customer”>
        <inheritance strategy=” JOINED”/>
        <attribute-override name=”lastName”>
            <column name=”SURNAME”/>
        </attribute-override>
    </entity>
    <entity class=”com.titan.domain.Employee”>
    <primary-key-join-column name=”EMP_PK”/>
    </entity>

 

 

 

EJB3.0笔记-9实体查询与EJB QL

在java persistence中,可以同时使用EJB QL查询语言和原生的SQL来完成查询操作。由于EJB QL是一种描述Java对象的查询语言,而entity manager会为你处理由EJB QL向原生SQL转换的工作,因此它具有跨数据库厂商实现的移植能力。EJB QL比SQL更加紧凑和更容易阅读,但是EJB QL不可能都支持数据库所有私有特性,所以功能上没有厂商原生SQL那么功能全面。

Query API
    通过javax.persistence.Query接口来实现查询功能,而Query接口可以通过javax.persistence. EntityManager得到。
   
 参数
    EJB QL支持具名参数(named parameters)和指示参数(positional parameters),如:
    Query query=entityManager.createQuery(“from Customer c where c.firstName=:first and
         c.lastName=:last”);
    query.setParameter(“first”,first);
    query.setParameter(“last”,last);
    return query.getResultList();
    或者
    Query query=entityManager.createQuery(“from Customer c where c.firstName=?1 and
         c.lastName=?2”);
    query.setParameter(1,first);
    query.setParameter(2,last);
    return query.getResultList();

日期型参数
    如果要将java.util.Date或java.util.Calendar参数传入查询中,就得使用特殊的setParameter方法,    如下:
    setParameter(String name,java.util.Date value,TemporalType temporalType);
    setParameter(String name,Calendar value,TemporalType temporalType);
    其中temporalType表示将Date或Calendar参数转换成原生SQL类型时,使用的是何种数据库类型

对结果分页
    Query query=entityManager.createQuery(“from Customer c”);
    return query.setMaxResults(max).setFirstResult(index).getResultList();

Hints
    有些java.persistence厂商会为你提供一些能在执行查询时使用的附加功能,比如Jboss的EJB3.0允许    为查询定义超时。
    Query query=entityManager.createQuery(“from Customer c”);
    query.setHint(“org.hibernate.timeout”,1000);

FlushMode
    默认的flush模式,会在em查询之前,执行flush动作(把更新同步到数据库)。如果希望在查询前不执行    flush,可以通过
    Query query=entityManager.createQuery(“from Customer c”);
    query.setFlushMode(FlushModeType.COMMIT);

EJB QL
EJB QL是按照实体的抽象持久结构来表达的,包括:抽象结构名称、基本成员属性和关系型成员属性。抽象结构名,默认是非限定类名,如果使用了
@Entity(name=”Cust”)
public class Customer{….}
那么查询Customer 的EJB QL为 select c from Cust AS c;

简单查询
    select object(c) from Customer as c 等价于select c from Customer as c

选择实体和关系型成员属性
    对于
    @Entity
    public class Customer{
        private int id;
        private String first;
        private Address address;
        @Id
        public int getId(){…..}
        public getFirstName(){return first;}
        …………
    }
    选择成员属性可以使用:
    select c.firstName from Customer as c;或者
    select c.first from Customer as c;
    选择关联的实体可以使用:
    select c.address from Customer as c;
    选择关联实体的属性可以使用:
    select c.address.city  from Customer as c;
    但是如果Address不是持久型成员,只是普通的class,上面的语句是非法的。可以使用
    @Embedded private Address address;让语句变回合法。

构造函数表达式
    可以在select子句是指定一个构造函数,用于创建普通的JAVA对象(而非实体),如:
    select new com.tian.domain.Name(c.firstName,c.lastName) from Customer c;

IN操作符和INNER JOIN
    返回所有乘客的所有预订信息
    select r from Customer as c ,in(c.reservations) r;
    等效于
    select r from Customer as c inner join c.reservations r;//inner可以省略

LEFT JOIN
    查询所有顾客及他们的电话号码,如果顾客没有电话号码以null代替
    select c.firstName ,p.number from Customer c left join c.phoneNumbers p;
    //left join可以写成left outer join

Fetch Joins
如果关系型成员属性FetchType在定义XML时(或注解)设为LAZY,那么在程序访问到这些成员时才从数据库加载数据,有时会增加数据库访问量,可以通过JOIN FETCH强制加载这些关系成员,如:
select c from customer c left join fetch c.phones;这时会提前加载Phone关联

使用DISTINCT
关键字distinct确保查询不会返回重复项。如查询所有预订舱位的顾客,由于顾客可以预订多次,所有distinct可以派上用场。
select distinct cust from Reservation as res, in (res.customers) cust;

where 子句与字面常量
where 子句用于缩小选择范围。如果使用到字符串常量,可以使用单引号括起来,如果字符串中又有一个单引号,请用两个单引号表示;如果是数值型的常量,就直接书写;如果是布尔型,常量取值用true和false。如
where name=’capital one’; where name=’wendy’’s’;where hasGoodCredit=true;
如果不想涉及这些细节,可以直接使用查询参数,让查询API处理这些问题

where 子句与运算符的优先级
点号(.)à数学运算符(+-*/)à比较运算符(=,>,like,between,in,is null,is empty,member of)à逻辑运算符(not,and or)

where子句与数学运算符
允许查询操作在做比较时执行算术运算。运算过程中,数值也许被放宽或提升,如int 与double相乘,先把int 变成double,结果也是double的

where子句和逻辑运算符

and 和or运算符的行为与java语言中的&&和||有所不同。&&只有左操作数为true时才会对右操作数求值,and要由转换出的原生语言决定

where 子句和in

    IN用来检验是否与一组字面常量中的元素相匹配,如:
    where c.address.state IN(‘FL’,’TX’,’WI’);
    也可以使用参数
    where c.address.state IN(?1,?2,?3,5.7);
where 子句与is null
    比较运算符is null允许检验路径表达式是否为null。它也可以用来检验输入参数,如
    select c from Customer as c
    where :city is not null and :state is not null
    and c.address.state=:state

where 子句与is empty

is empty检验集合类型的关系是否为空(没有元素),集体类型的关系是从不会为null的。对from子句已经被赋予标识符的集合型关系使用is empty是非法的,如:
select r from Reservation as r inner join r.customers as c //已经赋予标识符c,表明c一定不为空
where r.customers is not empty;

where 子句与member of

member of用于判断某个实体是否是集合型关系的一个成员,如
where cust not member of  res.customers

where子句与like

可以使用两个特殊的字符“%”(表示任意字符序列)和“_”(表示单个字符),如果字符串本来就有%或_,可以使用“/”字符来避免冲突

函数表达式
    字符串处理函数
        LOWER(String):转换成小写
        UPPER(String):转换成大家
        TRIM([[leading|trailing|both][trim_char]from)]String):去除指定字符trim_char,默认为空格
        CONCAT(String1,String2):连接字符串
        LENGTH(String):求字符串长度
        LOCATE(String1,String2[,start]):String1在String2的什么位置
        SUBSTRING(String1,sart,length):截取字符串
    数字函数
        ABS(number):绝对值
        SORT(double):平方根
        MOD(int,int):求余数
    返回日期和时间的函数
        CURRENT_DATE:返回当前日期
        CURRENT_TIME:返回当前时间
        CURRENT_TIMESTAMP:返回当前时间戳
    聚合函数
        COUNT():返回查询结果集中的条目数
        MAX():找出最大值
        MIN():找出最小值
        AVG(numeric):平均值
        SUM(numerc):求和

ORDER BY子句
对返回集合进行排序。在EJB QL中有一个使用限制:order by子句中出现的属性,必须出现在select子句中,或者是select子句选中实体的一个属性。如:
select c from Customers as c order by c.address desc;select addr.zip from Address as addr order by add.zip;
以下是非法的
select c from Customers as c order by c.address.city desc;// city不是c的直接属性
GROUP BY 与HAVING
group by 用于分组,having对分组进一步筛选。group by子句中指定的字段必须出现于查询的返回结果中,这和SQL的要求是一致的。

子查询
    子查询是内嵌于主查询的另一个查询。EJB QL支持在where和having子句中使用子查询
    查询费用超过$100,000的所有航程
    from Cruise cr where 100000<(select sum(res.amountPaid) from cr.reservations res)
    
    ALL,ANY,SOME
    如果子查询返回多项结果,可以使用ALL、ANY和SOME对结果做进一步限定
    如果子查询中的所有内容都与条件表达式相匹配,那么操作符ALL就返回true
    查询所有预付了舱位预订定金的航程
    from Cruise cr where 0< all(select res.amountPaid from cr.reservations res)
    如果子查询中的有任意一项与条件表达式相匹配,那么操作符ALL或SOME就返回true
   
    EXISTS
    如果子查询包含一项或多项结果,那么操作符EXISTS返回true

批量的UPDATE与DELETE
    给所有名叫Bill Burke的乘客增加$10
    update Reservation res set res.amountPaid=(res.amountPaid+10)
    where exists(
        select c from res.customers c
        where c.firstName=’Bill’ and c.lastName=’Burke’
    );
    是否可以写成?
    update Reservation res set res.amountPaid=(res.amountPaid+10)
    where res.customers.firstName=’Bill’ and res.customers.lastName=’Burke’

    删除所有Bill Burke的舱位预订记录
    delect from Reservation res
    where exist(
        select c from res.customers c
        where c.firstName=’Bill’ and c.lastName=’Burke’
    )

原生查询
EntityManager接口有三个创建原生查询的方法:一个返回标量值,一个返回实体类型,还有一个返回多个实体与标量值组合

标量原生查询
createNativeQuery(String sql);

简单实体的原生查询

createNativeQuery(String sql,Class entityClass);根据某个实体定义好的映射元数据,将返回值映射到该实体

复杂原生查询

createNativeQuery(String sql,String mappingName)
    返回多个实体
        @Entity
        @SqlResultSetMapping(name=”customerAndCreditCardMapping”,//映射名
                entities={@EntityResult(entityClass=Customer.class),//entities指定映射的实体
                    @EntityResult(entityClass=CreditCard.class,
           fields={@FieldResult(name=”id”,column=”CC_ID”),//指定实体内成员与返回字段的映射
                    @FieldResult(name=”number”,column=”number”)})
                        })
        public class Customer{………}
        等价XML
        <entity-mappings>
            <sel-result-set-mapping name=” customerAndCreditCardMapping”>
                <entity-result entity-class=”com.titan.domain.Customer”/>
                <entity-result entity-class=”com.titan.domain.CreditCard”>   
                    <field-result name=”id” column=”CC_ID”/>
                    <field-result name=”number” column=”number”/>
                </entity-result>
            </sel-result-set-mapping>
        </entity-mappings>
        使用方法
        String sql=”select c.id,c.firstName,cc.id As CC_IC,cc.number from CUST_TABEL
           c,CREDIT_CARD_TABLE cc….”
        manager.createNativeQuery(sql,”customerAndCreditCardMapping”);
       
   返回结果既有标量也有实体
        @SqlResultSetMapping(name=”reservationCount”,//映射名
                entities={@EntityResult(entityClass=Cruise.class,              
                        fields={@FieldResult(name=”id”,column=”id”) })},
                columns={@ColumnsResult(name=”resCount”)}//标量
                )
        @Entity
        public class Cruise{……}
        等价XML
        <entity-mappings>
            <sel-result-set-mapping name=” reservationCount”>
                <entity-result entity-class=”com.titan.domain.Cruise”>   
                    <field-result name=”id” column=”id”/>
                </entity-result>
                <column-result name=”resCount”>
            </sel-result-set-mapping>
        </entity-mappings>
        使用方法
        String sql=”select c.id,count(Resrvation.id) as resCount from Cruise c left join
             Reservation on c.id=………..”;
        manager.createNativeQuery(sql,” reservationCount”);

具名EJB QL查询
    预先定义好EJB QL或原生的SQL查询
    使用@javax.persistence.NamedQuery用来预定EJB QL的,NamedQuerys用于定义多条查询
    @NamedQuerys({
        NamedQuery(name=”getAverageReservateion”,//名字
            query=”select AVG(r.amountPaid) from Cruise As c Join c.reservatons r where
                 c=:cruise”),//EJB QL
        NamedQuery(……)
    })
    @Entity
    public class Cruise{…….}
    等价XML
    <entity-mapping>
        <named-query name=” getAverageReservateion”>
            <query>
                EJB QL
            </query>
        </ named-query >
    </entity-mapping >
    使用方式
    Query query=em.createNamedQuery(“getAverageReservateion”);
    query.setParameter(“cruise”.cruise);

具名原生查询
    使用@javax.persistence.NamedNativeQuery注解来预定义具名原生SQL查询
    @NamedNativeQuery(
        name=”findCustAndCCNum”,
        query=”select ……..”,//原生SQL
        resultClass=”…”,//只返回一个实体时用
        resultSetMapping=”….”
        //一个声明的@SqlResultSetMapping,用于返回多个实体和或实体与标量混合的查询
    )
等价XML
    <entity-mappings>
        <named-native-query name=” getAverageReservateion” result-set-mapping=”….”>
            <query>
                原生SQL
            </query>
        </ named-native-query >
    </entity-mappings >
    使用方式
    Query query=em.createNamedQuery(“getAverageReservateion”);
    query.setParameter(“cruise”.cruise);

 

 

 

EJB3.0笔记-11Session beans

Session bean包含业务逻辑而entity class则模塑持久数据,Session bean还可以表示任务流(taskflow),所谓任务流是指,为完成一项特定任务所需的所有步骤。Session bean与entity class的关系就像剧本和演员的关系,剧本指导演员工作。

Session bean可以分为两种基本类型:stateless和stateful。stateless不维护任何介于两次方法调用间的状态。stateful session bean是客户端应用程序在服务器端的延续。调用stateful session bean的方法,可以从会话状态中读取数据,也可以向其中写入数据,这些数据在所有方法调用之间都是共享的。stateful session bean也许有一个超时期,如果客户端在超时前没有对其进行任何调用,该实例就会被容器自动销毁,除了超时,客户端也可以通过bean的remove方法明确将其移除。stateless session bean也可能存在一个超时值,同样也可以被客户端强制移除,但是效果和stateful session bean不一样,它只是简单地让该客户端指向EJB Object的引用失效,而bean实例并不被销毁,它可以继续为其他客户端请求服务。

Stateless Session Bean
stateless session bean具有很好的性能,它不保存任何会话状态,因此也就不需要钝化或激活,这进一步降低了切换的开销。可以使用实例变量(没有static修饰)来维护实例的内部状态,但是不能保证该实例一直为同一个客户端服务,也就无法为客户维护状态。是否可以使用类变量(有static修饰)来维护bean的内部状态?如果stateless 实现了实例池,stateless ejb的一个变量被一个用户改变了,其它用户使用到这个stateless ejb时,会使用到已经变化的变量(ejb其他书籍)

以一个实例来说明session bean
ProcessPayment EJB
是一个stateless bean,TravelAgent EJB通过它向乘客收取航程的费用。

业务接口:ProcessPayment
public interface ProcessPayment{
    public boolean byCheck(Customer customer,CheckDO check,double amount)throws PaymentException;//通过支票支付
    public boolean byCredit(Customer customer,CreditCardDO card,double amount)throws PaymentException;//通过信用卡支付
    ……
}
一个stateless session bean可以拥有一个或多个业务接口(也就是方法),业务接口可以是远程接口,也可以是本地接口,但是不能二者兼备。如果远程接口和本地接口拥有相同的方法,EJB规范允许为它们定义公共的基类。可以使用@javax.ejb.Remote和@javax.ejb.Local声明远程接口和本地接口。

本地和远程接口
@Local
public interface ProcessPaymentLocal  extends ProcessPayment{…}
@Remote
public interface ProcessPaymentRemote  extends ProcessPayment{…}

将entity bean作为参数
在ProcessPayment EJB的业务接口中,每一个方法接受一个Customer entity bean(顾客)作为参数,它实现了java.io.Serializable或Externalizable,以便序列化并通过网络进行传输。

领域对象:CreditCardDO和CheckDO
是简单的可序列化Java对象。CreditCardDO将信用卡相关的数据搜集在一起,以便使它更易于在网格传输。

应用程序异常PaymentException
由其他java子系统抛出的异常与EJB所要刻画的业务流程毫无关系,所以要把它们包装成系统特定业务异常。EJB容器将任何不从 RuntimeException继承的异常都当作应用程序异常,由于应用程序异常会被传播到作为调用方的客户端,因而此类异常中的任何实例变量都必须能够被序列化。

Bean class:ProcessPaymentBean
ProcessPaymentEJB模塑了一个专门的业务流程。
@Stateless
public class ProcessPaymentBean implements ProcessPaymentRemote,ProcessPaymentLocal{….}
还可以使用@Local和@Remote注解对bean直接进行标注:
@Stateless
@Local(ProcessPaymentLocal.class)
@Remote(ProcessPaymentRemote.class)
public class ProcessPaymentBean {….}
但这种方式类的结构不清晰,不推荐使用。

访问环境型成员属性(注入)
每个EJB容器都有一个自己的内部注册表(registry),用于保存配置数据和指向外部资源或服务的引用。该注册表被称为Enterprise Naming Context(ENC).在部署EJB容器时,系统会将内嵌于注解(如@Resource)中的元数据,以及保存于EJB XML部署描述文件中的信息填入ENC中。ProcessPaymentBean中有两个要注入的变量:
@Resource(mappedName=”titanDB”)DataSource dataSource;//数据源
@Resource(name=”min”) int minCheckNumber;//常量

XML部署描述文件
EJB可以选择在JAR文件的META-INF/ejb-jar.xml中定义XML部署描述信息
<?xml version="1.0"?>
<ejb-jar………..>
   <enterprise-beans>
      <session>    是一个session bean
         <ejb-name>ProcessPaymentBean</ejb-name>    ejb名
         <remote>com.titan.processpayment.ProcessPaymentRemote</remote>    实现的业务接口
         <local>com.titan.processpayment.ProcessPaymentLocal</local>
         <ejb-class>com.titan.processpayment.ProcessPaymentBean</ejb-class>    类名
         <session-type>Stateless</session-type>    什么类型的session bean
          <env-entry>    注入常量值
             <env-entry-name>min</env-entry-name>
             <env-entry-type>java.lang.Integer</env-entry-type>
             <env-entry-value>10</env-entry-value>
             <injection-target>
                <injection-target-class>
                   com.titan.processpayment.ProcessPaymentBean
                </injection-target-class>
                <injection-target-name>minCheckNumber</injection-target-name>
             </injection-target>
          </env-entry>
         <resource-ref>    注入数据源
             <res-ref-name>theDatasource</res-ref-name>
             <res-type>javax.sql.DataSource</res-type>
             <res-auth>Container</res-auth>
             <mapped-name>java:/DefaultDS</mapped-name>
             <injection-target>
                <injection-target-class>
                   com.titan.processpayment.ProcessPaymentBean
                </injection-target-class>
                <injection-target-name>dataSource</injection-target-name>
             </injection-target>
          </resource-ref>
       </session>
   </enterprise-beans>
</ejb-jar>

SessionContext
SessionContext对象被作为bean实例与EJB容器交互的接口。Session bean可以通过@Resource SessionContext ctx;注解获得该对象的引用。SessionContext.getBusinessObject(Class class)方法返回一个指向当前EJB的引用。

EJBContext
SessionContext继承自javax.ejb.EJBContext类。EJBContext.lookup()可以在EJB ENC中查找数据。EJBContext.getCallerPrincipal()用于获取java.security.Principal对象,该对象代表了当前访问bean的客户端。EJBContext.isCallerInRole(String role)可以辨别当前访问bean的客户端是否具有指定的角色。也是通过@Resource SessionContext ctx;注解获得这个对象。

Stateless session bean的生命周期
Stateless session bean的生命周期相当简单,只有两个状态:Does Not Exist和Method-Ready Pool。Method-Ready Pool是一个实例池,由未被使用的stateless session bean对象组成。stateless bean在其生命周期中定义了实例池,而stateful bean则没有。
    
    Does Not Exist状态
    表示stateless bean还没有被实例化
   
    Method-Ready Pool状态
    当EJB服务器首次启动时,它可能会创建一些stateless bean的实例(一些厂商可能没有对stateless实    例做池化处理),并使它们进入Method-Ready Pool状态。如果stateless实例的数量不足以为客户端请    求提供服务时,服务器就会创建更多的实例,并将它们添加到实例池中。

    进入Method-Ready Pool状态
    当实例从Does Not Exist状态迁移到Method-Ready Pool时,会执行三步操作。1,容器调用stateless的    class.newInstance()方法构造一个实例。2.容器根据注解或XML部署描述文件,把所需资源注入进来。    3,容器产生一个post-consruction事件,调用stateless中标注了@PostConstruct的回调函数。回调函    数必须返回void,不带参数,且不能抛出checked exception,如:
    @PostConstruct
    public void init(){…}
    等价XML
    <session>
        <ejb-name>myBean</ejb-name>
        <post-contruct>
            <lifecycle-callback-method>init</lifecycle-callback-method>
        </post-contruct>
    </session>

    处于Method-Ready Pool状态
    一旦bean实例处于Method-Ready Pool状态,它已经为响应客户端请求做好了准备;一旦bean实例完成    对客户端请求服务,它就会解除与EJB Object的关联,并重新回到Method-Ready Pool状态

    离开Method-Ready Pool状态
    当EJB服务器不再需要bean实例时,它会移除内存对象来减少处于Method-Ready Pool状态的实例总数,    stateless就变回Does Not Exist状态,这时会产生bean的PreDestroy事件。bean 可以为某个方法标注    @javax.annotation.PreDestroy注解,以注册响应该事件的回调方法,如:
    @PreDestroy
    public void cleanup(){……}
    等价XML
    <session>
        <ejb-name>myBean</ejb-name>
        <pre-destroy>
            <lifecycle-callback-method>cleanup</lifecycle-callback-method>
        </ pre-destroy>
    </session>

Stateful Session Bean
每个stateful session bean在生命周期内只服务于一个客户端,它维护着会话状态,它可以在不同的方法调用间维护特定于某个客户端的数据。

建立TravelAgentEJB
TravelAgentEJB是一个stateful session bean,它封装了航程预订的业务逻辑。为了完成这项任务,bean需要知道是哪位乘客希望预订哪次航程的船舱。

远程接口:TravelAgent
在编写远程接口时,我们只关注bean的业务定义。这个接口在客户端和服务器端都使用,所以要定义得简单点
@Remote
public interface TravelAgentRemove{
    public Customer findOrCreateCustomer(String first,String last);//查找或创建用户
    public void updateAddress(Address addr);//更新用户地址
    public void setCrruiseID(int cruise);//航程,注意这里使用ID,是为了方便客户端使用。
    public void setCabinID(int cabin);//舱位,保持舱位信息
    public TicketDO bookPassage(CreditCardDO card,double price);
    //预订航程,客户、船舱和航程被stateful session bean保持,所以只需要信息卡和票价就可以交易
}

领域对象:TicketDO
它是Reservation entity bean的简化版,用于预订成功后把信息返回给客户端,由于返回Reservation要序列化大量对象,效率很低,所以使用TicketDO
public class TicketDDO implements java.io.Serializable{
    public int customerID;
    public int cruiseID;
    public int cabinID;
    public double price;
    public String description;//打印时使用
    ….
}

Bean的客户端代码
即客户端怎么调用服务器端session bean.假设有一个带GUI输入域的java应用程序在使用TravelAgentEJB
Context jndi=getInitialContext();
Object ref=jndi.lookup(“TravelAgentBean/remote”);
TravelAgentRemote agent=(TravelAgentRemote)PortableRemoteObject.narrow(ref, TravelAgentRemote.class);
Customer cust=agent.findOrCreateCustomer(textField_firstName.getText(),textField_lastName.getText());//得到或创建乘客
Address updatedAddress=new Address(一系列用户输入的信息);
agent.updateAddress(updatedAddress);//更新乘客地址为最新的地址
agent.setCruiseID(用户输入的信息);
agent.setCabinID(用户输入的信息);
CreditCardDO card=new CreditCardDO(一系列用户输入的信息);
double price=double.valueOf(用户输入的信息);
TicketDO ticket=agent.bookPassage(card,price);// 行程预订
PrintingService.print(ticket);//打印出一张预订票

TravelAgentBean服务器端代码
这是一个服务器端EJB。在设计的角度上看,将任务流封装到了此stateful session bean中,同时为客户端提供一个简单的接口TravelAgentRemove,为实现策略变更时提供更好的灵活性
@Stateful    //是一个stateful session bean
public class TravelAgentBean implements TravelAgentRemote{
    @PersistenceContext(unitName=”titan”);
    private EntityManager entityManager;//注入持久化管理者,用于数据库操作
    @EJB private ProcessPaymentLocal processPayment;
    //注入EJB,这个EJB用于以信用卡方式向乘客收费。
    private Customer customer;//用于保存乘客的信息,以便在多个方法调用间重复使用
    private Cruise cruise;//保存航程信息,以便在多个方法调用间重复使用
    private Cabin cabin;

    //查询用户,如果没有找到,就创建一个新的并保存到数据库
    public Customer findOrCreateCustomer(String first,String last){….}
    //让用户地址为最新的.
    public void updateAddress(Address addr){
        this.customer.setAddress(addr);
        this.customer=entityManager.merge(customer);
    }
    public void setCruiseID(int cruise){
        this.cabin=entityManger.find(Cabin.class,cabinID);//查找航程
    }   
    public void setCabinID(int cabin){….}//查找船舱,实现方式同上
   
    @Remove //执行这个方法后,容器可以把这个session bean移除
    public TicketDO bookPassage(CreditCardDO card,double price){
        try{
            //预订
            Reservation reservaton=new Reservation(customer,cruise,cabin,price,new Date());
            entityManager.persist(reservation);//保存到数据库
            //扣钱。customer能在各种方法间使用,是因为这个是stateful session
            processPayment.byCredit(customer,card,price);
            //返回购票信息到客户端,它只保存一些ID,序列化效率效高
            TicketDO ticket=new TicketDO(customer,cruise,cabin,price);
            return ticket;
        }catch(Exception e){
            throw new EJBException(e);
        }
    }
}

XML部署文件
<?xml version="1.0"?>
<ejb-jar………..>
   <enterprise-beans>
      <session>    //是一个session bean
         <ejb-name>TravelAgentBean</ejb-name>    //ejb名
         <remote>com.titan.travelagent.TravelAgentRemote</remote>//实现的业务接口
         <ejb-class>com.titan.travelagent.TravelAgentBean</ejb-class>//类名
         <session-type>Stateful</session-type>//什么类型的session bean
          <ejb-local-ref>    //注入本地EJB
             <ejb-ref-name>ejb/PaymentProcessor</ejb-ref-name>
             <ejb-ref-type>Session</ejb-ref-type> //类型
             <local>com.titan.processpayment.ProcessPaymentLocal</local> //本地接口          
               <injection-target>
                <injection-target-class>
                   com.titan.travelagent.TravelAgentBean
                </injection-target-class>
                <injection-target-name>processPayment</injection-target-name>
             </injection-target>
          </env-entry>
           <persistence-context-ref>
             <persistence-context-ref-name>persistence/titan</persistence-context-ref-name>
             <persistence-unit-name>titan</persistence-unit-name>
             <injection-target>
                 <injection-target-class>
                    com.titan.travelagent.TravelAgentBean
                 </injection-target-class>
                 <injection-target-name>entityManager</injection-target-name>
             </injection-target>
          </persistence-context-ref>
       </session>
   </enterprise-beans>
</ejb-jar>

Stateful Session Bean的生命周期
Stateful Session Bean和其他类型的bean最大的不同是它不使用实例池。它在整个生命周期中只服务于一个客户端。它的生命周期包含三种状态:Does Not Exist,Method-Ready和Passivated
    Dost Not Exist状态
    表示stateful bean实例还未被实例化。
   
    Method-Ready状态
    表示可以为来自客户端的请求提供服务。客户端第一次调用stateful session bean的方法时,bean的    生命周期就开始了,首先容器调用bean class的newInstance()方法新建一个实例,接着容器将所有依    赖都注入到bean实例中,如果bean class定义了@PostConstruct回调方法,容器将调用此方法。bean实    例离开Method-Ready状态之后,不是进入Passivated状态就是进入Does Not Exist状态。客户端应用程    序可以调用业务接口中标注了@Remove的方法,将bean移除。如果bean在Method-Ready状态下超时,容    器可能会调用@PreDestory回调方法。
   
    Passivated状态
    为了节省服务器资源,容器可能将bean实例钝化:将bean的会话状态保存起来,并将其从内存中移除。    如果客户端请求调用被钝化的bean,容器会将其重新激活,如果该bean定义了@PostActivate回调方        法,则该方法将被调用。当bean激活时,对象被反序列化,非持久化数据成员会被设置为对应类型的默    认值,所以应用使用@PostActivate回调方法来重置这些非持久型数据成员的值。

系统异常
只要bean方法抛出系统异常,容器就将相关的EJB object设置为无效,并销毁这个实例,此时bean直接变为Does Not Exist状态,容器并不会调用bean的@preDestory回调方法

Stateful session bean和Extend Persistence Context
通过@PresistenceContext注解注入TravelAgentBean中的EntityManager默认为一个transaction-scoped persistence context,也就是说TravelAgentBean中的每一个方法,在其作业域内都有一个事务开始和结束,这意味着任何要保存或获取的entity bean实例都将在方法调用结束时变成游离对象。可以使用@PresistenceContext(unitName=”titan”,type=EXTENDED)让注入的EntityManager是一个Extend Persistence Context,让查询所得的entity一直保持托管状态。Stateful session bean是唯一允许注入Extend Persistence Context的EJB组件。

嵌套的Stateful session bean
使用@EJB注入另一个Stateful session bean。我们不用管理内嵌的stateful session bean的生命周期,它随着容纳它的bean的创建而创建,当容纳它的Bean被移除时,它也被移除。

 

 

 

EJB3.0笔记-12 Message-Driven beans

所有EJB3.0开发商都必须提供一个JMS provider的实现,JMS provider对于message-driven bean而言绝对是必须的。JMS是一套用于访问企业消息系统的开发商中立的API。JMS在其中扮演的角色与JDBC很相似:JDBC提供一套用于访问各种不同关系数据库的公共API,JMS也提供了独立于特定厂商的企业消息系统访问方式。JMS使用消息服务(messaging service)来帮助enterprise bean发送信息,消息服务有时也称为消息代理。JMS是专门为不同java应用系统之间传递各类信息而设计的。

使用JMS重新实现TravelAgentEJB
修改11章开发的TravelAgentEJB,使其能在预订完成时利用JMS来通知其他java应用程序。
以下相关类(如Connection)属于JSM API
@Resource(mappedName=”connectionFactoryNameGoesHere”)
private ConnectionFactory connectionFactory;//注入
@Resource(mappedName=”TicketTopic”)//注入
private Topic topic;
@Remove
public TicketDO bookPassage(CreditCardDO card,double price){
    ………..
    Connection connect= connectionFactory.createConnection();//创建JMS provider连接
    Session session=connect.createSession(true,0);//创建Session对象
    // createSession(boolean transacted,int acknowledgeMode);
    //这两个参数会被容器忽略,EJB规范建议分别设置为true和0
    MessageProducer producer=session.createProducer(topic);//创建消息生产者
    TextMessage textMsg=session.createTextMessage();//创建文本消息
    textMsg.setText(ticketDescription);
    producer.send(textMsg);
    connect.close();
    ………..
}

ConnectionFactory和Topic

为了发送JMS,我们需要一个指向JMS provider的连接和一个消息目标地址。使用JMS连接工厂(JMS connection factory)可以获得JMS provider连接;而消息目标地址则可以由一个Topic(主题)对象来表示,也可以由queue(队列)来表示。

消息的类型
在JMS中,消息是一个java对象,它包含两个部分:消息头和消息正文。JMS API定义了多种不同的消息类型(TextMessage、MapMessage、ObjectMessage、StreamMessage和BytesMessage),并提供了不发送和接收这些消息的方法。
使用MapMessage的例子:
MapMessage mapMsg=session.createMapMessage();
mapMsg.setInt(“CustomerID”,ticket.customerID.intValue());
mapMsg.setInt(“Price”,ticket.price);
producer.send(mapMsg);
使用ObjectMessage的例子:
ObjectMessage objectMsg=session.createObjectMessage();
objctMsg.setObject(ticket);
producer.send(objectMsg);

JMS应用客户端
用于接收和处理乘客的预订信息,它在收到消息之后将每和票据的信息输出到屏幕
public class JmsClient_TicketConsumer implements javax.jms.MessageListener{//监听消息
 
   public static void main(String [] args) throws Exception{
      new JmsClient_TicketConsumer();
      while(true) { Thread.sleep (10000); }     
   }
  
   public JmsClient_TicketConsumer () throws Exception{
      Context jndiContext = getInitialContext ();
     
      ConnectionFactory factory = (ConnectionFactory)
      jndiContext.lookup ("ConnectionFactory");
     
      Queue ticketQueue = (Queue)
      jndiContext.lookup ("queue/titan-TicketQueue");
     
      Connection connect = factory.createConnection();     
      Session session = connect.createSession(false,Session.AUTO_ACKNOWLEDGE);     
      MessageConsumer receiver = session.createConsumer(ticketQueue); //消息消费者
      receiver.setMessageListener(this);

      connect.start ();//开始监听是否有信息到达
   }
  
   public void onMessage (Message message){//每当有新消息送达,该方法被调用
      try{
         ObjectMessage objMsg = (ObjectMessage)message;//强制转换成发送信息时的信息类型
         TicketDO ticket = (TicketDO)objMsg.getObject ();
         System.out.println ("********************************");
         System.out.println (ticket);
         System.out.println ("********************************");
      } catch (JMSException displayed){
         displayed.printStackTrace ();
      }
   }
  
   public static Context getInitialContext ()throws javax.naming.NamingException{
    Properties env=new Properties();
    env.put(…….);//使用开发商专有属性来配置InitialContext,也可以在jndi.properties文件中设置
          return new InitialContext (env);
   }
}

JMS的异步性
JMS消息处理的一个主要优势它的异步性。当JMS客户端发送消息时,它并不等待回应,只是将消息发送到消息路由,由消息路由来负责将消息传递给其他客户端。而使用java RMI和JAX-RPC时,客户端以同步方式调用bean方法,当前线程会被阻塞直到方法执行完毕为止。当需要与其他应用程序时行通信时,RMI的种种不足使JMS成为一种极富吸引力的替代方案。同时发送方的事务与安全上下文是不会传播到接收方的,不过JMS客户端和JMS provider可以共同拥有一个分布式事务。

JMS的消息传递模型
JMS provider提供了两种消息传递模型:发布-订阅模型(publicsh-and-subscribe,pub/sub)和点对点模型(point-to-point,p2p),JMS规范将其称为消息传递域(messaging domain)。通过情况下,发布-订阅主要用于一对多的消息广播,而点对点则用于一对一的消息传递。

发布-订阅
在发布-订阅消息传递模型中,生产者可以通过一个被称为主题的虚拟通道,将消息发送给多个消费者。任何发送到某一主题的消息都会被转送给订阅了该主题的所有消费者。此模型基本上是一个推模型(push-based model),消息会自动广播,消费者无须通过主动请求或轮询主题方式获得新的消息。

点对点
点对点的消息传递模型允许JMS客户端通过一个被称为队列的虚拟通道,以同步或异步的方式收发消息。它是一个拉模型(pull-based model)或轮询模型,在此类模型中,消息浊自动推送给客户端的,而是由客户端从队列中获得。一个队列可以有多个接收者,但是每条消息只能由一个接收者接收。

应该选用哪一种消息传递模型
两者实际上都可以使用“推”或“拉”的方式。JMS提供了一套同时适用于这两种模型的API。采用pub/sub模型所能完成的工作几乎都可以采用点对点来完成,反之亦然。某些情况下,这取决于个人偏好,或熟悉的程度。利用pub/sub模式,JMS通过使用专门的主题,可以先对消息进行过滤,而后再分发给消费者;而p2p保证每条消息只由一个消费者处理,当消息需要被分别处理,同时又要保证消息的先后顺序时,这一能力尤为重要。

Session Bean不应该接收消息
Session Bean响应来自EJB客户端的调用,但是它们不能像message-driven bean那样响应JMS消息(消息驱动,也就是接收到消息时,一些方法自动执行),我们可以编写一个业务方法中消费JMS消息的session bean,但是这个方法必须由EJB客户端来调用。如
Connection connect = factory.createConnection();     
Session session = connect.createSession(true,0);     
MessageConsumer receiver = session.createConsumer(queue);      //消息消费者
TextMessage textMsg=( TextMessage) receiver.receive();

connect.close();
可见使用session bean编写代码来接收信息是不明智的。

了解更多有关JMS的内容
JMS代表了分布式计算环境下一种功能强大的范式,以上对其的简介的目的是为讨论message-driven bean作铺垫的。

基于JMS的Message-Driven Bean
MDB是无状态的,事务感知的服务器组件,用于处理通过JMS进行递送的异步消息。MDB负责处理消息,而容器则负责管理包括事务、安全、资源、并发和消息确认在内的组件外环境。MDB是用来响应异步消息的,它不有远程或本地接口(方法自动调用,而非由客户端调用)

ReservationProcessor EJB
ReservationProcessor EJB是一个message-driven bean,其作用是接收代表新到预订通知的JMS消息,当它收到消息时,会创建一个新的Reservatin EJB,然后使用ProcessPayment EJB处理支付,并送出一张票据,它是TravelAgent EJB的一个自动化版本。源码如下:
@MessageDriven(activationConfig={//含义见下一节
       @ActivationConfigProperty(propertyName="destinationType",
                             propertyValue="javax.jms.Queue"),
       @ActivationConfigProperty(propertyName="acknowledgeMode",
                             propertyValue="Auto-acknowledge")})
public class ReservationProcessorBean implements javax.jms.MessageListener {
//MessageListener 监听器接口只定义了onMessage()方法
   @PersistenceContext(unitName= "titanDB")//注入
   private EntityManager em;

   @EJB
   private ProcessPaymentLocal process;

   @Resource(mappedName="ConnectionFactory")
   private ConnectionFactory connectionFactory;

   public void onMessage(Message message) {
   //类似于TravelAgentEJB的bookPassage()方法,此方法会自动执行,所以称为自动化
      System.out.println("Received Message");
      try {
        //从收到的消息中取出数据,类似于得到请求参数
        MapMessage reservationMsg = (MapMessage)message;     
         int customerPk = reservationMsg.getInt("CustomerID");
         int cruisePk = reservationMsg.getInt("CruiseID");
         int cabinPk = reservationMsg.getInt("CabinID");
    
         double price = reservationMsg.getDouble("Price");
    
         // 获取信用卡
         Date expirationDate =  new Date(reservationMsg.getLong("CreditCardExpDate"));
         String cardNumber = reservationMsg.getString("CreditCardNum");
         String cardType = reservationMsg.getString("CreditCardType");
         CreditCardDO card = new CreditCardDO(cardNumber, expirationDate, cardType);
        
         Customer customer = em.find(Customer.class, customerPk);
         Cruise cruise = em.find(Cruise.class, cruisePk);
         Cabin cabin = em.find(Cabin.class, cabinPk);
         //创建代表舱位预订记录的Reservation
         Reservation reservation = new Reservation(customer, cruise, cabin, price,
                 new Date());
         em.persist(reservation);
            //处理信用卡支付
         process.byCredit(customer, card, price);
    
         TicketDO ticket = new TicketDO(customer,cruise,cabin,price); 
         deliverTicket(reservationMsg, ticket);//发送信息给客户端,见下文
      }
      catch(Exception e) {
         throw new EJBException(e);
      }
   }
    //MDB也可以使用JMS来发送信息
   public void deliverTicket (MapMessage reservationMsg, TicketDO ticket) throws JMSException, NamingException{
      Queue queue = (Queue)reservationMsg.getJMSReplyTo();//从接收的信息中得到回复消息目标地址
      Connection connect = connectionFactory.createConnection();
      Session session = connect.createSession(true,0);
      MessageProducer sender = session.createProducer(queue);
      ObjectMessage message = session.createObjectMessage();
      message.setObject(ticket);//把ticket发送到客户端
      sender.send(message);
      connect.close();
   }
}

@MessageDriven
MDB使用@javax.ejb.MessageDriven注解。

@ActivationConfigProperty
@Message-Driven.activationConfig()属性接受一组@ ActivationConfigProperty注解作为参数,用一组简单的“名/值”对来描述MDB配置.EJB3.0为基于JMS的message-driven bean定义了一组固定属性,它们是:acknowledgeMode、messageSelector、destinationType和subscriptionDurability

消息选择器(message Selector)
消息选择器让MDB选择性地接收来自特定主题或队列的消息,它以SQL-92条件表达式语法(SQL中的where子句)的一个子集为基础的,如:
 @ActivationConfigProperty(propertyName="messageSelector", propertyValue="MessageFormat=’Versin 3.4’")
JMS消息生产者通过以下代码设置Message的MessageFormat属性
Message message=session.createMapMessage();
message.setStringProperty(“MessageFormat”,”Versin 3.4”);

确认模式
JMS确认是指JMS客户端通知JMS provider确认消息已经到达的一种机制。在EJB中,收到信息后发送确认是MDB容器的职责。
@ActivationConfigProperty(propertyName="acknowledgeMode", propertyValue="Auto-acknowledge")
确认模式有两个取值:Auto-acknowledge和Dups-ok-acknowledge。前者告诉容器,在消息交给MDB实例处理后,容器应该立即向JMS provider发送确认。后者容器任何时间发送确认都是可接受的,由于MDB容器可能延迟很长时间才发送确认,JMS provider可能认为容器没有收到消息而重发信息,所以使用这种模式时,MDB必须能够正确处理重复消息。在实际上大多数确认模式都会被忽略,如果事务由容器来管理,确认行为是在事务上下文中执行:如果事务成功,消息就会被确认;如果事务失败,消息就不会被确认

订阅的持续性
当基于JMS的MDB使用javax.jms.Topic时,我们必须在部署描述文件中声明:这一订阅是Durable或NonDurable。如果 EJB容器由于某些原因失去与JMS provider的连接,Durable表示,在此期间Bean本应收到的消息是不会丢失的,当容器重新获得连接后,这些消息会由容器发送给MDB,这种行为称为保存-转发消息传递机制。声明为NonDurable的MDB,会丢失连接期间本应收到的消息,所以它可以改善JMS provier的性能,但降低MDB的可靠性。声明方式:
@ActivationConfigProperty(propertyName="subscriptionDurability", propertyValue="Durable")

ReservationProcessor EJB的部署描述文件
<?xml version="1.0"?>
<ejb-jar …...>
   <enterprise-beans>
      <message-driven>
             <ejb-name>ReservationProcessorBean</ejb-name>
             <ejb-class>com.titan.reservationprocessor. ReservationProcessorBean </ejb-class>
             <message -type>javax.jms.MessageListener</ message -type>
    <transacton-type>Container</transacton-type >
    <message-destination-type>javax.jms.Queue</message-destination-type>
    <activation-config>
        <activation-property>
            <activation-config-property-name>destinationType</activation-config-property-name>
            <activation-config-property-value>javax.jms.Queue</activation-config-property-value>
        <activation-property>
        …………
<activation-config>
             <ejb-local-ref>
                 <ejb-ref-name>ejb/PaymentProcessor</ejb-ref-name>
                 <ejb-ref-type>Session</ejb-ref-type>
                 <local>com.titan.processpayment.ProcessPaymentLocal</local>
                 <injection-target>
                        <injection-target-class>
                               com.titan.reservationprocessor. ReservationProcessorBean
                        </injection-target-class>
                    <injection-target-name>process</injection-target-name>
                 </injection-target>
          </ejb-local-ref>
          <persistence-context-ref>
                 <persistence-context-ref-name>persistence/titan</persistence-context-ref-name>
                <persistence-unit-name>titan</persistence-unit-name>
                 <injection-target>
                         <injection-target-class>
                                com.titan.reservationprocessor. ReservationProcessorBean
                         </injection-target-class>
                         <injection-target-name>em</injection-target-name>
                 </injection-target>
              </persistence-context-ref>
    <resource-ref>
                 <res-ref-name>jms/ConnectionFactory</res-ref-name>
                 <res-type>javax.jms.ConnectonFactory </res-type>
                 <res-auth>Container</res-auth>
                 <mapped-name>ConnectionFactory</mapped-name>
                 <injection-target>
                        <injection-target-class>
                               com.titan.reservationprocessor. ReservationProcessorBean
                        </injection-target-class>
                        <injection-target-name>connectionFactory</injection-target-name>
                 </injection-target>
          </resource-ref>
       </message-driven>
   </enterprise-beans>
</ejb-jar>

ReservationProcessor客户端
发送预订消息的客户端
代码实现见前面的章节“使用JMS重新实现TravelAgentEJB”

接收票据信息的客户端
代码实现见前面的章节“JMS应用客户端”
             
Message-driven bean的生命週期
MDB实例的生命周期有两个状态:Does Not Exist和Method-Ready Pool.
Does Not Exist状态
MDB没有被实例化。

Method-Ready Pool状态
和stateless session bean对应状态相似,也使用了实例池。EJB服务器首次启动时,它可能创建一些MDB实例,当MDB实例不足以处理发来的消息时,容器会调用bean class的Class.newInstance()方法创建一个新的bean实例,然后根据bean的元数据或XML声明注入相关资源,如果bean定义了PostConstruction回调函数,容器最后会调用该方法。当有消息送递MDB时,容器会将这条消息委派给任何一个处于MRP状态的bean 实例。当服务器移除一部分内存对象以减少处于MRP状态的bean总数时,MDB将到达Does Not Exist状态,这时可能触发@PreDestroy回调事件。

基于连接器的Message-Driven Bean
基于JMS的MDB非常有用,但它也存在局限:EJB开发商往往只支持少量的JMS provider(消息收发双方都使用相同的JMS API)。它只支持JMS编程模型而不支持其他类型的消息系统。EJB3.0还支持一个可扩展的message-driven bean定义,使之可以对任何开发商的任何消息系统提供服务,即基于JCA的MDB,JCA定义了EJB容器和异步连接器之间的规则,从而使用MDB可以自动处理来自EIS的消息。JCA连接器可以从开发商X处购得,使用JCA连接器的MDB,可以自动处理开发商X开发的EIS系统发送过来的消息。例子,一个处理email的MDB
package com.vendorx.email;//来自开发商X的JCA
public inteface EmilListener{
    public void onMessage(javax.mail.Message message);
//这个方法可以有不同形式的方法名称和签名,并允许拥有返回值,由JCA开发商决定
}
@MessageDriven(activationConfig={//属性依赖于连接器的类型及具体要求
       @ActivationConfigProperty(propertyName="mailServer",
                             propertyValue="mail.ispx.com"),
       @ActivationConfigProperty(propertyName="serverType",
                             propertyValue="POP3")})
public class EmailBean implements com.vendorx.email.EmilListener{//实现JCA开发商的接口
    public void onMessage(javax.mail.Message message){
        javax.mial.internet.MimeMessage msg=( javax.mial.internet.MimeMessage)message;
        Address[] addressed=msg.getFrom();//来自那里的email.
        ……..
}
}

消息连接
允许由任一enterprise bean发出的消息,能够最终路由到位于 同一部署环境中的指定message-driven bean处,通过使用消息连接,可以在同一应用程序内的不同组件之间控制消息流转。可以定义一个TicketDistributor EJB分发由TravelAgent EJB发送到同一个JMS主题的消息,专门定义一个MDB来处理分发工作可以获得列好的灵活性和系统性能。EJB规范没有为消息连接提供注解,必须使用XML部署描述文件来使用这个功能。
<session>
    …..
    <message-destination-link>Distributor</message-destination-link>
…..
</session>
<message-driven>
    <ejb-name>TicketDistributorEJB</ejb-name>
    <message-destination-link>Distributor</message-destination-link>
</ message-driven >
<assembly-descriptor>
    <message-destination>
        <message-destination-name>Distributor</message-destination-name>
    </message-destination>
<assembly-descriptor>
这时由session bean 发送到目标地址的消息会由MDB TicketDistributorEJB收到转发。session bean可以发送消息,但不要接收消息。

 

 

 

EJB3.0笔记-13 JNDI ENC与依赖注入

请搞清楚:通过注解还是XML注册资源,通过注入还查找得到资源,注解与XML等价方式。
以下说的是EJB引用资源

每个部署于应用服务器中的EJB容器都拥有一个属于它自己的内部注册表(internal registry),该内部注册表被称为Enterprise Naming Context(ENC),ENC是由JNDI实现的。开发人员可以通过注解或XML为某个EJB 的JNDI ENC(这是一个局部JNDI)定义资源、EJB或外部环境的别名,而后在编写EJB业务逻辑时,通过别名在JNDI中查找这些引用,在EJB3.0中,还可以把这些引用直接注入到bean数据成员中。

JNDI ENC中可以注册哪些内容
指向EJB接口的引用、JMS队列或主题的目标地址、JMS连接工厂、数据源、任何JCA资源、基本的数据类型常量,还有一些java EE服务(javax.ejb.TimerService、javax.transaction.UserTransaction、org.omg.CORBA.ORB)

如何配置JNDI ENC
ENC的JNDI名字空间可以通过XML或通过注解来定义。

如何从ENC中获取引用
通过InitialContext查找
@Stateful
@EJB(name=”ejb/ProcessPayment”,   //类级的注解
    beanInterface=ProcessPaymentLocal.class,
    beanName=”ProcessPaymentBean”)
public class TravelAgentBean implements TravelAgentRemote{
        public TicketDO bookPassage(CreditCarDO card,double amount){
            ProcessPaymentLocal payment=null;
            try{
                //EJB厂商不同,初始化方式也许不同
                javax.naming.InitialContext ctx=new InitialContext();
                //如果是本地接口,对于其他资源(如EntityManagerFactory等)也是类似的查找方法
                payment=(ProcessPaymentLocal)ctx.lookup(
                        “java:comp/env/ejb/ ProcessPayment”);//使用java:comp/env上下文
                //如果是远程接口,要用narrow方法
                Object ref= ctx.lookup(“java:comp/env/ejb/ ProcessPayment”);
                (ProcessPaymentRemote)javax.rmi.PortableRemoteObject.narrow(ref,
                     ProcessPaymentRemote.class);
            }catch(javax.naming.NamingException ne){…..}……..
        }
}

使用EJBContext查找

javax.ejb.SessionContext和javax.ejb.MessageDrivenContext接口都继承自javax.ejb.EJBContext,并且都可以用来查找ENC注册项。
@Stateful
@EJB(name=”ejb/ProcessPayment”,
    beanInterface=ProcessPaymentLocal.class,
    beanName=”ProcessPaymentBean”)
public class TravelAgentBean implements TravelAgentRemote{
        @Resource private javax.ejb.SessionContext ejbContext;
        public TicketDO bookPassage(CreditCarDO card,double amount){
            ProcessPaymentLocal payment=(ProcessPaymentLocal)ejbContext.lookup(
            “ejb/ ProcessPayment”);
            ……….
        }
}

注解注入
import com.titan.travelagent;//包名
@Stateful
public class TravelAgentBean implements TravelAgentRemote{
        @EJB private ProcessPaymentLocal payment;//通过成员变量注解直接注入
        //默认ENC名称为:com.titan.travelagent.TravelAgentBean/payment;
        //或者通过setter方法注解直接注入
        @EJB public void setProcessPayment(ProcessPaymentLocal payment){
        //默认ENC名称为:com.titan.travelagent.TravelAgentBean/processPayment;
            this.payment=payment;
        }
}

XML注入
<enterprise-beans>
    <session>
        <ejb-name>TravelAgentBean</ejb-name>
        <ejb-local-ref>
            <ejb-ref-name> com.titan.travelagent.TravelAgentBean/payment </ejb-ref-name>注册名
            <ejb-ref-type>Session</ejb-ref-type>EJB类型有Session、Entity
            <local>com.tian.Processpayment.ProcessPaymentLocal</local>
            实现接口的EJB,由于ENC名与注解的一致,这个配置会覆盖注解
            <ejb-link>ProcessPaymentBean</ejb-link>
            <injection-target>     要直接注入时才用这个配置
                <injection-target-class>
                    com.titan.travelagent.TravelAgentBean
                </injection-target-class>
                下面的payment也可以使用processPayment,以通过setter注入
                <injection-target-name>payment</injection-target-name>
            </injection-target>
        </ejb-local-ref>
    </session>
</enterprise-beans>

XML覆盖
XML可以覆盖注解,它比注解具有一更高的优先级。覆盖方式见上一节,通过XML可以覆盖注解中要引入的实现类。

注入与继承
如果父类的数据成员或成员方法标有注入用注解,则注入父类的引用也会在子类中被引入,子类也可以覆盖父类的注入注解,但是如果父类的方法是private的,那么子类是无法覆盖它的注入注解的,这时父类和子类可能存在两个不同的引用。

不同类型的引用与注入
EJB类型的引用
@java.ejb.EJB用于引入一个EJB,@java.ejb.EJBs用于引入多个EJB
@EJB(name=”ejb/ProcessPayment”, //ENC注册名
    beanInterface=ProcessPaymentLocal.class,//接口
    beanName=”processPaymentEJB”,//实现这个接口的类,如果有多个类实现这个接口,必须指定这个
    mappedName=”….”)//根据EJB容器厂商的不同而有所不同,可能是一个键值,指向厂商的全局注册表
以上注解标注在bean class类之上,表示注册一个引用,要通过lookup查找,如果用于bean class的setter方法或数据成员(类型是EJB的成员),却表示直接注入

通过XML指定远程EJB引用
<enterprise-beans>
    <session>
        <ejb-name>TravelAgentBean</ejb-name>//EJB名
        <ejb-ref>
            <ejb-ref-name> com.titan.travelagent.TravelAgentBean/payment </ejb-ref-name>注册名
            <ejb-ref-type>Session</ejb-ref-type>    EJB类型有Session、Entity
            <remote>com.tian.Processpayment.ProcessPaymentRemote</ remote>    远程接口
            <ejb-link>ProcessPaymentBean</ejb-link>实现接口的EJB,如果接口有多个实现类必须配置
            <injection-target>        要直接注入时才用这个配置
                <injection-target-class>
                    com.titan.travelagent.TravelAgentBean
                </injection-target-class>
                <injection-target-name>payment</injection-target-name>也可以使用processPayment
            </injection-target>
        </ejb-local-ref>
    </session>
</enterprise-beans>

通过XML指定本地EJB引用
<enterprise-beans>
    <session>
        <ejb-name>TravelAgentBean</ejb-name>
        <ejb-local-ref>
            <ejb-ref-name> com.titan.travelagent.TravelAgentBean/payment</ejb-ref-name>注册名
            <ejb-ref-type>Session</ejb-ref-type>     EJB类型有Session、Entity
            <local>com.tian.Processpayment.ProcessPaymentLocal</local>    本地接口
            <ejb-link>ProcessPaymentBean</ejb-link>     与注解beanName等价,表示引用的EJB名。
            <injection-target>        要直接注入时才用这个配置
                <injection-target-class>
                    com.titan.travelagent.TravelAgentBean
                </injection-target-class>
                <injection-target-name>payment</injection-target-name>也可以使用processPayment
            </injection-target>
        </ejb-local-ref>
    </session>
</enterprise-beans>

EJB名称的二义性和重载
对于某个特定的EJB JAR部署包而言,部署描述文件中的<ejb-name>元素及任何@Stateless或@Stateful注解的name属性必须是全局唯一的。但是对于同一个.ear文件的不同EJB-JAR部署包内,EJB名称是可以重复的,这了区分这些EJB,可以使用扩展语法:@EJB(beanName=”inventory-ejb.jar#InventoryEJB”) InventoryLocal inventory;

EntityManagerFactory类型的引用
在应用服务器对ENC进行配置或将EntityManagerFactory注入到EJB中之后,应用服务器会为你管理EntityManagerFactory实例的生命周期。定义引用的方式:
@javax.persistence.PersistenceUnit(name=”persistence/TitanDB”,//JNDI ENC名
    unitName=”TitanDB”)//persistence.xml文件中声明的persistence unit名称
该注解标注bean class时,表示不直接注入,如果标注在bean class的setter方法或数据成员上,表示直接注入,如:
@ PersistenceUnit(unitName=”crmDB”)private EntityManagerFactory crm;
如果要引用多个persistenceunit,要使用@PersistenceUnits注解。

通过XML指定EntityManagerFactory类型的引用
<enterprise-beans>
    <session>
        <ejb-name>TravelAgentBean</ejb-name>
        <persistence-unit-ref>
            < persistence-unit-ref-name>persistence/TitanDB</persistence-unit-ref-name>注册名
            persistence.xml文件声明的persistence unit名
            < persistence-unit-ame>TitanDB</ persistence-unit-ame>
            <injection-target>        要直接注入时才用这个配置
                <injection-target-class>
                    com.titan.travelagent.TravelAgentBean
                </injection-target-class>
                <injection-target-name>ships<injection-target-name>
            </injection-target>
        </ persistence-unit-ref >
    </session>
</enterprise-beans>

Persistence Unit名称的作用域及重载问题

可以在不同的地方声明persistence unit,它可以定义在EJB-JAR、EAR/lib目录下的JAR或WAR文件中。如果定义的persistence unit有重名,可以通过这个扩展语法访问:@ PersistenceUnit(unitName=”inventory.jar#InventoryDB”)

EntityManager类型的引用
当将EntityManager注册到JNDI ENC或注入到EJB时,EJB容器会对EntityManager所依赖的persistence contect具有完全的控制权(控制它的生命周期),注册方式与EntityManagerFactory很相似,如:
@PersistenceContext(name”persistence/TitanDB”//JNDI ENC名
    unitName=”TitanDB”//与peristence.xml文件中声明的peristence unit名称相同
    type=PersistenceContextType.EXTENDED)//表示extended persistence context(方法间事务,只用于        //stateful session),还有一个取值TRANSACTION。
可以用于标注bean class,当标注在bean class的setter方法或数据成员上时,表示直接注入,如:
@ PersistenceContext(unitName=”crmDB”)private EntityManager crm;

通过XML指定EntityManager类型的引用
<enterprise-beans>
    <session>
        <ejb-name>TravelAgentBean</ejb-name>
        <persistence-context-ref>
            <persistence-context-ref-name>persistence/TitanDB</persistence-context-ref-name>名
            persistence.xml文件声明的persistence unit名
            <persistence-context-ame>TitanDB </persistence-context-ame>
            <persistence-context-type>EXTENDED</persistence-context-type>
            <injection-target>        要直接注入时才用这个配置
                <injection-target-class>
                    com.titan.travelagent.TravelAgentBean
                </injection-target-class>
                <injection-target-name>ships<injection-target-name>
            </injection-target>
        </ persistence- context -ref >
    </session>
</enterprise-beans>

资源类型的引用
外部资源同样会被映射到JNDI ENC名字空间中的名称上,并且可以有选择地注入到bean实例的数据成员或setter方法中,被引用的外部资源类型可以是:javax.sql.DataSource、javax.jms.ConnectionFactory、 javax.jms.QueueConnectionFactory、javax.jms.TopicConnectonFactory、 javax.mail.Session、java.net.URL、javax.resource.cci.ConnectionFactory以及由 JCA资源适配器定义的其他类型。下面以javax.sql.DataSource为例。

@javax.annotation.Resource(annotation:注解)
@ Resource注解除了用来引用外部资源,还可以用来引用JMS消息的目标地址、JNDI的环境注册项、EJBContext,以及其他Java EE的核心服务。

@ Resource(name=”jdbc/OracleDB”,//JNDI ENC名
    type=javax.sql.DataSource,//引入的类别
    authenticationType=AuthenticationType.APPLICATION,
    //认证方式,有容器认证(取值CONTAINER,容器根据部署的用户名和密码进行认证)和程序自行认证
    //(getConnection时要提供用户名和密码)
    shareable=true,//多个EJB在同一事务中是否可以使用相同的资源,使用相同的资源连接,以提高性能
    mappedName=”java:/DefaultDS”)//外部资源的厂商专用标识符,大多数情况,它的取值等价于一个                                    //全局JNDI名。
如果要引用多个资源,可以使用@javax.annotation.Resources注解

通过XML指定资源类型的引用
<enterprise-beans>
    <session>
        <ejb-name>TravelAgentBean</ejb-name>
        <resource-ref>
            <res-ref-name>jdbc/OracleDB</res-ref-name>ENC注册名
            <res-type>javax.sql.DataSource</res-type>类名
            <res-auth>Container</res-auth>
            <mapped-name>java:/DefaultDS</mapped-name>
            java:/DefaultDS是一个全局JNDI中定义的一个数据源,数据源的配置请查询相关书籍
           
            <injection-target>        要直接注入时才用这个配置
                <injection-target-class>
                    com.titan.travelagent.TravelAgentBean
                </injection-target-class>
                <injection-target-name>oracle<injection-target-name>
            </injection-target>
        </resource-ref >
    </session>
</enterprise-beans>

资源环境和受管对象
资源环境注册项可以引用包含受管对象的资源,受管对象是一种在部署期间被配置到容器中,并在运行期间由容器负责管理的资源,它的配置书中没有给出例子。资源环境注册项也可可以用于诸如:javax.transaction.Usertransaction和 javax.transaction.TransactionSynchronizationRegistry服务,这时定义authenticationType和shareable 是非法的。
<enterprise-beans>
    <session>
        <ejb-name>TravelAgentBean</ejb-name>
        <resource-env-ref>
            <resource-env-ref-name>jdbc/OracleDB</resource-env-ref-name>//ENC注册名
            <resource-env-ref-type>javax.transaction.Usertransaction</resource-env-ref-type>类
            <injection-target>        要直接注入时才用这个配置
                <injection-target-class>
                    com.titan.travelagent.TravelAgentBean
                </injection-target-class>
                <injection-target-name>utx<injection-target-name>
            </injection-target>
        </resource-env-ref >
    </session>
</enterprise-beans>

环境注册项还可以用来配置String、Integer、Long、Double、Float、Byte、Boolean和Short这些包装类型的常量。
<enterprise-beans>
    <session>
        <ejb-name>ProcessPaymentBean</ejb-name>
        <env-entry>
            <env-entry-name>minCheckNumber</env-entry-name>ENC注册名
            <env-entry-type>java.lang.Integer</env-entry-type>类名
            <env-entry-value>2000</env-entry-value>常量值,这种引用几乎都是用XML来配置的
            <injection-target>        要直接注入时才用这个配置
                <injection-target-class>
                    com.titan.processpayment.ProcessPaymentBean
                </injection-target-class>
                <injection-target-name>minCheckNumber<injection-target-name>
            </injection-target>
        </env-entry>
    </session>
</enterprise-beans>

消息目标地址类型的引用
在JNDI ENC中注册的消息目标地址引用指向JMS的主题或队列。
@Resource(name=”jms/TicketTopic”,//ENC名
    type=javax.jms.Topic,//类型,还可以是队列(javax.jms.Queue)
    mappedName=”topic/TicketTopic”)//全局的厂商专用标识符
注意,使用注解无法设置目标地址的连接(message-destincton-link)
等价XML
<enterprise-beans>
    <session>
        <ejb-name>TravelAgentBean</ejb-name>
        <message-destination-ref>
            <message-destination-ref-name>jms/TicketTopic</message-destination-ref-name>注册名
            <message-destination-type>javax.jms.Topic</message-destination-type>消息类型
            从目标地址收消息还是向其发消息
            <message-destination-usage>Produces</message-destination-usage>
            <message-destination-link>Distributor </message-destination-link>消息分发地址
            <mapped-name> topic/TicketTopic </mapped-name>
            topic/TicketTopic表示主题,它的配置请找相关书籍,原书练习中只讲到配置队列
            <injection-target>        要直接注入时才用这个配置
                <injection-target-class>
                    com.titan.travelagent.TravelAgentBean
                </injection-target-class>
                <injection-target-name>ticketTopic <injection-target-name>
            </injection-target>
        </ message-destination-ref >
    </session>
</enterprise-beans>

Web Service类型的引用

在JNDI ENC中注册的web service引用指向服务的接口或服务的终端接口。19章有详细介绍
<enterprise-beans>
    <session>
        <ejb-name>TravelAgentBean</ejb-name>
        <service-ref>
            <service-ref-name>service/ChargeItProcessorService</service-ref-name>ENC注册名
          <service-interface>com.charge_it.ProcessorService</service-interface>JAX-RPC服务接口
            <wsdl-file>META-INF/wsdl/ChargeItProcesor.wsdl</wsdl-file> WSDL文件位置
            <jaxrpc-mapping-file>META-INF/mapping.xml </jaxrpc-mapping-file> JAX-RPC映射文件
            <service-qname>chargeIt:ProcessorService</service-qname>与WSDL文件定义的服务名一致
            <mapped-name>webservice/ChargeItProcessorService</mapped-name>
            webservice/ChargeItProcessorService映射应用服务器全局注册项
            <injection-target>        要直接注入时才用这个配置
                <injection-target-class>
                    com.titan.travelagent.TravelAgentBean
                </injection-target-class>
                <injection-target-name>chargeService <injection-target-name>
            </injection-target>
        </ message-destination-ref >
    </session>
</enterprise-beans>

 

 

 

EJB3.0笔记-18 web service标准化

Web service的出现为真正意义上的跨越硬件、操作系统、编程语言和应用系统的互操作能力提供了可能。它可以基于XML、SOAP和WSDL标准。Sun 和JCP引入了不少web service API,包括:java API for XML Web Services(JAX-WS)、Java API for XML-based RPC(JAX-RPC)、SOAP with Attachments API for Java(SAAJ)和java API for XML Registries(JAXR)。

本章主要介绍一些底层技术,包括:XML Schema和XML Namespace ,SOAP,以及WSDL

XML Schema和XML Namespace(为了方便输入,XML注解部分使用"//"表示)
XML Schema
XML Schema的目的类似于DTD,用于验证XML的文件结构的。XML文档的正确性有两个判断依据:文档格式是否正确;文档内容是否合法。格式正确包括使用正确元素,使用正确的字符来表示元素的开始和结束。合法性包括元素顺序、结构(出现多少次)、属性(元素中的元素)和类型(string等)都正确。 Schema定义如:
<?xml version=’1.0’ encodeing=’UTF-8’ ?>
<schema xmlns=”http://www.w3.org/2001/XMLSchema”
xmlns:titan=”http://www.titan.com/Reservation”
targetNamespace=”http://www.titan.com/Reservation”>
    <element name=”address” type=”titan:AddressType”/>
//元素address使用复杂类型,XML Schema支持20多种内建类型,比如string,int,long,double,datetime等
    <ComplexType name=”Addresstype”>
        <sequence>
        // sequence表示元素必须按声明的顺序出现。可以使用ALL表示允许元素以任意顺序出现
            <element name=”street” type=”string” maxOccurs=”2” />
            //maxOccurs表示最大出现次数,默认为1,可以设置为unbounded,表示可以出现无限多次
            <element name=”city” type=”string” minOccurs=”1”/>
            //minOccurs表示最小出现次数,默认为1,可以设置为0,表示可以被忽略
        </sequence>
    </ComplexType>
</schema>
运行期间,XML解析器会将文档与相应的schema进行对比,以确保文档符合schema定义的规则

XML Namespace
一个XML文档可以同时引用多个XML标记语言(XML shcema),这样来自不同标记语言的元素就可以使用不同的XML schema分别验证。同时XML schcema也可以引用其他XML schcema文档,来生成新的XML schcema。
<? xml version=’1.0’ encoding=’UTF-8’ ?>
<res:reservation xmlns:res=”http://www.titan.com/Reservation”>
    <res:customer>
        <res:last-name>Jones</res:last-name>
        <res:first-name>Sara</res:first-name>
        <addr:address xmlns:addr=”http://www.titan.com/Address”>
            //引用另一个xml schcema
            <addr:street>3245 west lst ave.</addr:street>
        </addr:address>
    </res:customer>
</res:reservation>

其中xmlns:res是一个XML Namespace,名字空间的使用格式是:xmlns:prefix=”URI”。prefix可以使用任何内容,只要不包括空白或特殊字符,它也可以省略,如:<reservation xmlns=”http://www.titan.com/Reservation”>;URI是一种标识符,它是URL的一个超集,一般使用URL来表示,它必须与XML schema中声明的targetNamespace属性值一致,它并不指向一个合法地址,如果想让解析器下载某个XML Schema以供验证,可以使用schemaLocation,如:
<? xml version=’1.0’ encoding=’UTF-8’ ?>
//使用的名字空间,与XML Schema的targetNamespace一致
< reservation xmlns=”http://www.titan.com/Reservation”  
    xmlns:xsi=”http:www.w3.org/2001/XMLSchema-Instance”// 提供schemaLocation属性的名字空间
    xsi:schemaLocation=”http://www.titan.com/Reservation//下载的XML schema 的Namespace URI
     http://www.titan.com/schemas/reservation.xsd”>//下载的XML schema的URL
    <customer>
        <last-name>Jones</res:last-name>
        <first-name>Sara</res:first-name>
        <addr:address xmlns:addr=”http://www.titan.com/Address”>
            <addr:street>3245 west lst ave.</addr:street>
        </addr:address>
    </customer>
</reservation>

合并多个XML Schema
<? xml version=’1.0’ encoding=’UTF-8’ ?>
//默认的名字空间,如果type没有前缀,就使用这个名字空间
<schema xmlns=”http:www.w3.org/2001/XMLSchema”
    //使用xmlns引入要使用到的XML Schema,并指定前缀以区别
    xmlns:xsi=”http:www.w3.org/2001/XMLSchema-Instance”// 提供schemaLocation属性的名字空间
    xmlns:addr=”http://www.titan.com/Address”//将引用到的XML Schema的名字空间
   //给本身的名字空间指定前缀,以方便查阅哪此元素属于哪个XML Schema定义的
   xmlns:res=”http://www.titan.com/Reservation”
   //本身的名字空间,以便被其他XML Schema或XML文档使用
    targetNamespace=” http://www.titan.com/Reservation”
    <element name=”reservation” type=”res:ReservationType”/>
    <ComplexType name=”reservation”>
        <sequence>
            <element name=”customer” type=”res:CustomerType”/>
            <element name=”price-paid” type=double”/>
        </sequence>
    </ComplexType>
    <ComplexType name=”CustomerType”>
        <sequence>
           <element name=”last-name” type=”string”/>  //string是默认名字空间的元素
          //addr表示元素是http://www.titan.com/Address名字空间的 
          <element name=”address” type=”addr:AddressType”>
        </sequence>
    </ComplexType>
</schema>

SOAP 1.1
SOAP与其他分布式对象协议最为主要的区别在于它是基于XML的。SOAP有自己的XML Schema定义,并大量依赖于用户自定义XML Schema。每一条经网络被送出的SOAP消息,都包含了标准SOAP元素与应用数据的XML文档,如:
<? xml version=’1.0’ encoding=’UTF-8’ ?>
<env:Envelope xmlns:env=”http://schemas.xmlsoap.org/soap/envelope/”>//SOAP本身的XML Schema(Envelope:信封)
    <env:Header/>//携带基础数据,如安全令牌、事务ID、路由信息等等
    <env:Body>    //携带用于交换的应用数据
        //用户自定义XML Schema代表应用数据
        <reservation xmlns=”http://www.titan.com/Reservation”>
            <customer>LEE</customer>
            <price-paid>6449.3</price-paid>
        </reservation>
    </env:Body>
</env:Envelope>

Web Service样式
以上的SOAP消息是Document/Literal(逐字的)样式的,它指明消息正文是一份XML Schema的实例文档。EJB3.0支持另一种样式-RPC/Literal。这种样式指明SOAP是一个带参数和返回值的RPC调用(发送远程调用,而前者只是传送信息?)。
假如java接口定义了一个方法:
public interface TravelAgent{
    public void makeReservation(int cruiseID,int cabinID,int customerId,double price);
}
使用RPC/Literal样式的SOAP文件
<? xml version=’1.0’ encoding=’UTF-8’ ?>
<env:Envelope xmlns:env=”http://schemas.xmlsoap.org/soap/envelope/”
xmlns:titan=”http://www.titan.com/TravelAgent”>
    <env:Body>    
        <titan:makeReservation>//被调用的web service操作    
            <cruiseID >23..</ cruiseID>
            <cabinID >144</cabinID>
            <customerId >9393</customerId >
            <price>5677.88</price>
        </titan: makeReservation >
    </env:Body>
</env:Envelope>
EJB3.0还支持SOAP消息的RPC/Encoded模式,但其互操作不理想,书中也没有进一步介绍

使用HTTP交换SOAP消息
SOAP消息具有网络协议无关的特点,它主要使用HTTP协议进行消息交换,使用HTTP的优势在于它可以穿透防火墙。有EJB3.0中,用于交换SOAP消息的API被称为Java API for XML-based Web Services(JAX-WS),它隐藏了SOAP消息的细节。

WSDL .11
WSDL(Web Service Description Language)是一种用来描述web service的XML文档,它与编程语言、平台和协议无关。WSDL的协议无关性意味着,它可以用于描述任何类型的web service,而不仅仅是基于HTTP的SOAP类型。
假设你想开发一个实现如下 接口的web service组件:
pubic interface TravelAgent{
    public String makeReservation(int cruiseID,int cabinID,int customerId,double price);
}
为了让不同语言、不同平台的任何应用都能使用SOAP协议来调用以上方式,使用WSDL描述SOAP消息。现在的web service平台,比如Jboss都为你提供了生成WSDL的工具。
<?xml version="1.0" encoding="UTF-8" ?>
<definitions name="TravelAgent"
      targetNamespace="http://www.titan.com/TravelAgent"    //此schema的名字空间
      xmlns:titan="http://www.titan.com/TravelAgent"        //给本XML Schema一个前缀
      xmlns:xsd="http://www.w3.org/2001/XMLSchema"    //使用到的XML Schema名字空间
      xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
      xmlns="http://schemas.xmlsoap.org/wsdl/">
<!—描述自定义类型的types元素-->
<types>
      <xsd:schema targetNamespace="http://www.titan.com/TravelAgent"
            <xsd:complexType name=”ReservationType”>
                <xsd:element name=”cruiseId” type=”xsd:int”/>
                <xsd:element name=”price-paid” type=”xsd:double”/>
            </xsd:complexType>
    </xds:schema>
</types>

<!—描述参数和返回值的message元素,使用自定义类型-->
<message name="RequestMessage">
    <part name="reservation" type="titan:ReservationType"/>
</message>

<!—不用自宝义类型的message
<message name="RequestMessage">
     <part name="cruiseId " type="xsd:int"/>
     <part name="price-paid" type="xsd:double"/>
</message>
-->
<message name="ResponseMessage">
       <part name="reservationId" type="xsd:string"/>
</message>
<!—
描述web service抽象接口的portType元素。包含了接口名,方法名,输入/输出参数
-->
<portType name="TravelAgent">
    <operation name="makeReservation" >
<!—
EJB3.0支持两种风格的web service消息:request-responset 和one-way。前者包含一个<input>元素,后跟一个<output>元素,然后是一个或多个<fault>元素。而one-way有且只有一个<input>元素
 -->
       <input  message="titan:RequestMessage"/>
       <output  message="titan:ResponseMessage"/>
   </operation>
</portType>

<!—
描述所用协议和编码风格的binding元素。绑定是特定于某个<portType>元素的:其<operation>、<input>及<output>元素描述了有关<portType>的实现细节。这里使用了基于RPC/Literal消息风格的HTTP协议。
-->
<binding name=" TravelAgentBinding" type="titan:TravelAgent">
    <soap:binding style="rpc"<!—style还可以是document-->
          transport="http://schemas.xmlsoap.org/soap/http"/>
     <operation name=”makeReservation">
            <soap:operation soapAction=" "/>
            <input>
                <soap:body use="literal" namespace="http://www.titan.com/TravelAgent"/>
             </input>
             <output>
                <soap:body use="literal" namespace="http://www.titan.com/TravelAgent"/>
            </output>
     </operation>
</binding>
<!—
描述web service的Internet地址的service元素。address指定收发SOAP消息的URL地址
-->
<service name="TravelAgentService">
      <port name="TravelAgentPort" binding="titan:TravelAgentBinding">
           <soap:address  location="http://www.titan.com/webservices/TravelAgent"/>
       </port>
</service>
</definitions>

UDDI 2.0
UDDI(Universal Description,Discovery and Integration)是一种规范,它定义了在互联网上发布和寻找web service的标准。与XML、SOAP、WSDL不同,它并非web service的基础。但是在Java EE中,它被认为是web service的一个基本要素

 

 

 

EJB3.0笔记-19 EJB3.0与Web Service

前一章说到:Sun 和JCP引入了不少web service API,包括:java API for XML Web Services(JAX-WS)、Java API for XML-based RPC(JAX-RPC)、SOAP with Attachments API for Java(SAAJ)和java API for XML Registries(JAXR)。其中JAX-RPC是JAX-WS的前身,它与RMI非常相似。SAAJ是一套用于对SOAP消息的结构进行操纵的API,而JAXR则允许对web service的注册项进行访问,通过是UDDI。

通过JAX-RPC 访问Web Service
利用JAX-RPC,EJB可以通过网络访问位于任何Java或非Java平台上的Web Service。有三种API可以用来访问Web Service:生成存根(stub),动态代理,以及动态调用接口(Dynamic Invocation Interace ,DII)。使用动态代理最为常见,因为存根在Java EE平台之间是不可移植的。动态代理与典型的Java RMI编程模型非常相似,客户端通过一个实现了远程接口的网络存根对远程服务进行访问,调用过程为:
1.客户端调用JAX-RPC代理的方法,该代理实现了服务的端点接口
2.方法调用被转换成SOAP消息并发往服务器
3.服务器进行处理
4.服务器将处理结果作为SOAP应答消息发回客户端
5.SOAP应答消息被转换成返回值或异常返回给客户端
其中服务器端和客户端代码对SOAP XML信息是不知情的,由API自动处理这些信息

通过WSDL生成JAX-RPC制品
提供Web Service的组织会负责提供相关WSDL文件(这些文件也可以根据类生成),客户端使用这个文件生成端点接口和服务接口
假如Titan客户与Charge-it公司签订了一个处理客户信用卡支付的转包合同,Charge-It维护着一个基于.NET的系统,并通过web service将它的信用卡处理应用暴露给客户端使用.
它提供的WSDL文件内容如下:(为了方便书写,XML文档的注解使用"//"表示)
<?xml version="1.0" encoding="UTF-8" ?>
<definitions
    targetNamespace="http://charge-it.com/Processor"    //此schema的名字空间
    xmlns:tns="ttp://charge-it.com/Processor"        //给本XML Schema一个前缀
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"    //使用到的XML Schema名字空间
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
     xmlns="http://schemas.xmlsoap.org/wsdl/">
<!—描述参数和返回值的message元素-->
<message name="chargeRequest">
    <part name="name" type="xsd:string"/>
    <part name="number" type="xsd:string"/>
</message>
    
<message name="chargeResponse">
     <part name="return" type="xsd:int"/>
</message>
<!—
描述web service抽象接口的portType元素。包含了接口名,方法名,输入/输出参数
-->
<portType name="Processor">
     <operation name="charge" >
<!—
EJB3.0支持两种风格的web service消息:request-responset 和one-way。前者包含一个<input>元素,后跟一个<output>元素,然后是一个或多个<fault>元素。而one-way有且只有一个<input>元素
 -->
        <input  message="tns:chargeRequest”/>
        <output  message="tns:chargeResponse"/>
     </operation>
</portType>

<!—
描述所用协议和编码风格的binding元素。绑定是特定于某个<portType>元素的:其<operation>、<input>及<output>元素描述了有关<portType>的实现细节。这里使用了基于RPC/Literal消息风格的HTTP协议。它是方法调用转化相应的SOAP信息的根据
-->
<binding name="ProcessorSoapBinding" type="tns:Processor">
     <soap:binding style="rpc"<!—style还可以是document-->
        transport="http://schemas.xmlsoap.org/soap/http"/>
            <operation name=”charge">
               <soap:operation soapAction=" " style=”rpc”/>
               <input>
                   <soap:body use="literal" namespace="http://charge-it.com/Processor"/>
               </input>
               <output>
                  <soap:body use="literal" namespace="http://charge-it.com/Processor“/>
               </output>
           </operation>
</binding>
<!—
描述web service的Internet地址的service元素。address指定收发SOAP消息的URL地址。
-->
<service name="ProcessorService">
       <port name="ProcessorPort" binding="tns:ProcessorSoapBinding">
           <soap:address  location=" http://charge-it.com/ProcessorService "/>
        </port>
</service>
</definitions>

根据WSDL文件,JAX-RPC编译器会生成如下两个接口服务的端点接口和服务接口
package com.charge_it;
public interface Processor extends java.rmi.Remote{    //端点接口根据<portType>生成
    public int charge(String name,String number)throws java.rmi.RemoteException;
}

package com.charge_it;
public interface ProcessorService extends javax.xml.rpc.Service{
    //服务接口源自WSDL<service>元素
    public com.charge_it.Processor getProcessorPort()thorws javax.xml.rpc.ServiceaException;
}

在EJB中调用服务
客户端EJB调用暴露出来的方法方式:
com.charge_it.Processor processor=processorService.getprocessorPort();
processor.charge(customerName,card.number);
注意,如果web service方法调用成功之后,系统发生错误,事务不会回滚扣款操作,只会回滚预订本身。

<service-ref>部署元素
上面的processorService变量,通过<service-ref>注入
<enterprise-beans>
    <session>
        <ejb-name>TravelAgentBean</ejb-name>
        <service-ref>
            <service-ref-name>service/ChargeItProcessorService</service-ref-name>//ENC注册名
          <service-interface>com.charge_it.ProcessorService</service-interface>JAX-RPC服务接口
            <wsdl-file>META-INF/wsdl/ChargeItProcesor.wsdl</wsdl-file> //WSDL文件位置
            <jaxrpc-mapping-file>META-INF/mapping.xml </jaxrpc-mapping-file> //JAX-RPC映射文件
         <service-qname>chargeIt:ProcessorService </service-qname>//与WSDL文件定义的服务名一致
            <mapped-name>webservice/ProcessorService</mapped-name>//映射应用服务器全局注册项
            <injection-target>        //要直接注入时才用这个配置
                <injection-target-class>
                    com.titan.travelagent.TravelAgentBean
                </injection-target-class>
                <injection-target-name> processorService <injection-target-name>
            </injection-target>
        </ message-destination-ref >
    </session>
</enterprise-beans>

JAX-RPC映射文件
Java EE环境下的JAX-RPC服务或客户端必须包含一个JAX-RPC映射文件,以便在不同的Java EE实现之间移植,开发者不用手工编写这种映射文件,Java EE提供有生成这些文件的工具,它和WSDL 一样,通常部署于META-INF目录下。

利用JAX-RPC定义Web Service
java ee为定义JAX-RPC的web service提供两种不同的编程模型:web 容器模型(或servlet模型)和EJB容器模型。本书只介绍EJB容器模型。这一节学习怎么把EJB暴露成web service。
首先创建WSDL文件,WSDL文件格式参考”通过WSDL生成JAX-RPC制品”这一节
从WSDL 文件生成服务的端点接口(也可以从Java入手,先写端点接口再生成WSDL和JAX-RPC文件)
public interface TravelAgentEndpoint extends java.rmi.Remote{……...}//这个是端点接口
public class TravelAgentBean implements TravelAgentEndpoint{…….}//这个是stateless EJB
准备部署文件:一个WSDL文件,一个JAX-RPC映射文件,以及一个webservice.xml文件。WSDL第一步已经创建,JAX-RPC可以通过它生成,webservice.xml文件将各个部署文件串接在一起,如:
<?xml version="1.0" encoding="UTF-8"?>
<webservices
xmlns="http://java.sun.com/xml/ns/j2ee"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:titan=”http://www.titan.com/TravelAgent”
            xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
                http://www.ibm.com/webservices/xsd/j2ee_web_services_1_1.xsd"
        version="1.1">
        <webservice-description>
                //描述EJB端点,可以是任意内容
                <webservice-description-name>TravelAgentService</webservice-description-name>
                <wsdl-file>META-INF/wsdl/travelagent.wsdl</wsdl-file>//WSDL文件位置
                //JAX-RPC文件位置
                <jaxrpc-mapping-file>META-INF/travelagent_mapping.xml</jaxrpc-mapping-file>
                <port-component>
                        //EJB端点逻辑名,可以是任意内容
                        <port-component-name>TravelAgentEndpoint</port-component-name>
                        <wsdl-port>titan:TravelAgentport </wsdl-port>//对应WSDL的<port>元素
                        <service-endpoint-interface>//端点接口
                                com.titan.webservice.TravelAgentEndpoint
                        </service-endpoint-interface>
                        <service-impl-bean>//实现端点接口的EJB
                                <servlet-link>TravelAgentBean</servlet-link>
                        </service-impl-bean>
                </port-component>
        </webservice-description>
</webservices>

使用JAX-WS
为了将一个stateless EJB暴露为web service,使用JAX-RPC不厌其烦地定义WSDL、JAX-RPC映射,以及webservices.xml文件。如果使用JAX-WS就只用添加一些注解就可以了。

把stateless EJB暴露为web service
@Stateless
@WebService//是在javax.jws包内的

public class TravelAgentBean{
    @WebMethod
   
    public String makeReservation(int cruiseId,double price){….}
}
@SOAPBinding注解
指定web service样式
@SOAPBinding(
    style=Style.DOCUMENT,//还有Style.RPC
    use=Use.LITERAL,//还有ENCODED,SOAP encoding在WS-I Basic Profile 1.1规范中已经被禁止了
    parameterStyle=ParameterStyle.WRAPPED//还有ParameterStyl.BARE
)

@WebParam注解
输入/输出参数定义。拥有属性:name-参数名,对应wsdl中的<part>元素;mode-参数用于输入、输出或是两者皆有。如果参数被用于输入,那么它必须被包裹成特殊的持有类型(holder type)
public int checkStatus(@WebParam(name=”ReservationID”)String reservationId,
@WebParam(name=”CustomerID”,mode=WebParam.Mode.OUT)javax.xml.ws.Holer<Integer> customerId
){……}
相应生成的WSDL如下:
<message name="CheckStatus">
        <part name=" ReservationID" type="xsd:string"/>
</message>
<message name="CheckStatusResponse">
        <part name="return" type="xsd:int"/>
        <part name=" CustomerID " type="xsd:int"/>
</message>

@javax.jws.WebResult
与@WebParma作为返回值的注解功能一样
@WebMethod
@WebResult(name=”ReservationID”)
public String makeReservation(int cruiseId,double price){….}
生WSDL
<ComplexType name=”ReserveResponse”>
    <sequence>
        //nillable表示是否允许为null
         <element name=” ReservationID” type=”xds:string” nillable=”true”>
    </sequence>
</ComplexType>
<message name="CheckStatusResponse">
     <part name="parameters" element=”tns:ReserveResponse"/>
</message>

@OneWay注解
生成的WSDL没有<output>元素,服务器和客户端可以通过异步的方式调用,是否真的异步执行要由Java EE实现来决定

分离Web service契约
EJB实现类无需实现任何接口,它本身就可以直接被暴露为web service。如果没有@WebMethod注解,那么类中的所有方法都被暴露为web service操作。也可以让EJB实现接口,只暴露实现接口的方法,如:
@WebService
public interface TravelAgentEndpoint{…..}
@WebService(endpointInterface=”com.titan.webservice.TravelAgentEndpoint”)
public class TravelAentBean implements TravelAgentEndpoint{……}

客户端EJB调用web service
服务的端点接口
由于一个web service可以运行于任何平台,使用任何语言,所以在这种情况下,我们无法重用服务端的接口,而是根据WSDL产生一个符合本客户端的接口。
package com.charge_it;
@WebService           
@SOAPBinding(style=SOAPBinding.Style.RPC)
public interface Processor {    //端点接口根据<portType>生成
    public int charge(String name,String number);
}

服务类
服务类用于得到服务的端点接口
package com.charge_it;
@WebServiceClient(name=”ProcessorService” targetNamespace=”http://charge-it.com/Processor”
wsdlLocation=”http://charge-it.com/Processor?wsdl”)
public class ProcessorService extends javax.xml.ws.Service {//服务接口源自WSDL<service>元素
    public com.charge_it.Processor getProcessorPort(){……};//返回服务的端点接口Processor
    ……..
}

在EJB中调用服务

客户端EJB调用暴露出来的方法方式:
@WebServiceRef(ProcessorService.class)
Processor processor;

………
processor.charge(customerName,card.number);