EJB学习随手笔记

Posted jiangxiaoge1023

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了EJB学习随手笔记相关的知识,希望对你有一定的参考价值。

名词:
注解方式:
@persistenceContext:持续、存留;环境、上下文;
@Stateless: 无状态(无权的)
@Remote:  远程接口

一、EJB接口
remote和local的?
二、(Enterprice JavaBeans )EJB基础知识:
①EJB是一个用于分布式业务应用的标准服务端组件模型。采用EJB架构编写的应用是可伸的、事务性的、多用户安全的。一次编写这些应用,
 然后部署在任何支持EJB规范的服务器平台,如:JBOSS/WEBLOGIC。
②EJB定义了三种企业Bean,分别是会话Bean(Session Bean)、实体Bean(Entity Bean)和消息驱动Bean(MessageDriven Bean)。
 Session Bean:Session Bean用于实现业务逻辑,分为有状态Bean 和 无状态Bean。
               每当客户端请求时,容器就会选择一个Session Bean来客户端服务。
            Session Bean可以直接访问数据库,但是更多,会通过Entity Bean
           实现数据访问。
           @persistenceContext  声明进行操作的Session Bean,对象;
 实体Bean:   从名字上我们就能猜到,实体Bean代表真实物体的数据;但是这里可以把
            实体Bean看做是用来存放数据的JavaBean,比普通JavaBean多一个功能,不仅
            可以存放数据的角色,还要跟数据库进行对象和关系的映射。
            @Entity    @Table(name=“表名”)
   消息驱动Bean(MDB):是设计用来专门处理基于消息请求的组件。它能收发异步JMS消息,
                 并能轻易的与其他EJB进行交互。所以它特别适合用于当一个业务
               执行的时间特别长,而执行的结果无需实时向用户反馈的场合。
三、会话Bean(Session bean)
用于实现业务逻辑,分为有状态和无状态两种;每当客户端请求时,容器就会选择一个Session Bean来为客户端服务。
Session Bean作为业务处理对象出现在各种应用体系中;

1、JNDI==JNDI===JNDI↓

客户需要通过JNDI查找EJB(JSP---》EJB)
JNDI:(The Java Naming and Directory InterFace)
Java命名和目录的端口,是一组在Java应用中访问命名和目录服务的API。为开发人员提供了查找和访问各种命名和目录服务的通用、统一的形式。借助于
JNDI提供的接口,能够通过名字定为用户、机器、网络、对象服务等。
命名服务:就像DNS一样,通过命名服务器提供服务,大部分的J2EE服务器都含有命名服务器。
目录服务:一种简化的RDBMS系统,通过目录具有的属性保存一些简单的信息。目录服务通过目录服务器实现,比如:微软ACTIVE DIRECTORY等。
JNDI 的好处:
(1)包含大量命名和目录服务,可以使用相同API 调用访问任何命名或目录服务。
(2)可以同时连接多个命名和目录服务。
(3)允许把名称同JAVA 对象或资源关联起来,不必知道对象或资源的物理ID。
(4)使用通用接口访问不同种类的目录服务
(5)使得开发人员能够集中使用和实现一种类型的命名或目录服务客户API 上。

什么是上下文:由0或多个绑定构成。比如:java/mysql,java为上下文(context),MySql为命名
什么是子上下文(subContext):上下文下的上下文。比如:MyJNDITree/ejb/helloBean,ejb为子上下文。

JNDI编程过程
因为JNDI是一组接口,所以只需根据接口规范编程就可以。要通过JNDI进行资源访问,必须设置初始化上下文的参数,
主要是设置JNDI驱动的类名(java.naming.factory.initial)和提供命名服务的URL(java.naming.provider.url)。
因Jndi的实现产品有很多。所以java.naming.factory.initial的值因提供JNDI服务器的不同而不同。java.naming.provider.url
的值包括提供命名服务的主机地址和端口号。

下面为访问JBOSS服务器的例子代码:
Properties props = new Properties();
props.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory");
props.setProperty("java.naming.provider.url", "localhost:1099");
InitialContext = new InitialContext(props); //设置JNDI 访问的环境,如果客户端运行在jboss,不需要传入props;
HelloWorld helloworld = (HelloWorld) ctx.lookup("HelloWorldBean/remote");
下面为访问Sun应用服务器的例子代码:
Properties props = new Properties();
props.setProperty("java.naming.factory.initial","com.sun.enterprise.naming.SerialInitContextFactory");
props.setProperty("java.naming.provider.url", "localhost:3700");
InitialContext = new InitialContext(props);
HelloWorld helloworld = (HelloWorld) ctx.lookup("com.foshanshop.ejb3.HelloWorld");
下面为访问Webblogic10应用服务器的例子代码:
Properties props = new Properties();
props.setProperty("java.naming.factory.initial", "weblogic.jndi.WLInitialContextFactory");
props.setProperty("java.naming.provider.url", "t3://localhost:7001");
InitialContext = new InitialContext(props);
HelloWorld helloworld = (HelloWorld) ctx.lookup("HelloWorldBean
#com.foshanshop.ejb3.HelloWorld");

JBOSS环境下JNDI树命名约定:
① java:copm
这个上下文环境和其子上下文环境仅能被与之相关的特定组件访问和使用
② java:
子上下文环境和绑定的对象只能被Jboss服务器虚拟机内的应用访问
③ 其他上下文环境
只要实现序列化就可以被远程用户调用。

2、无状态bean开发(Stateless Session Beans)
无状态会话bean主要用来实现单次使用的服务,该服务能被启用很多次,但是由于无状态会话Bean并不保留任何有关状态的信息,其效果是每次
调用提供单独的使用,在很多情况下,无状态会话Bean 提供可重用的单次使用服务。

尽管无状态会话Bean 并不为特定的客户维持会话状态,但会有一个以其成员变量形式表示的过度状态。当一个客户调用无状态会话Bean 的方法时,
Bean 的成员变量的值只表示调用期间的一个过度状态。当该方法完成时,这个状态不再保留。

除了在方法调用期间,所有的无状态会话Bean 的实例都是相同的,允许EJB 容器给任何一个客户赋予一个实例。许多应用服务器利用这个特点,
共享无状态会话Bean 以获得更好的性能。

由于无状态会话Bean 能够支持多个客户,并且通常在EJB 容器中共享,可以为需要大量客户的应用提供更好的扩充能力。无状态会话Bean 比有状态
会话Bean 更具优势的是其性能,在条件允许的情况下开发人员应该首先考虑使用无状态会话Bean。 

@Stateless , @Remote,第一个注释定义这是一个无状态会话Bean,第二个注释指明这个无状态Bean 的remote 接口,指明实现的接口是远程接口,
在使用这两个注释时需要使用一些EJB 的类包,这些类包都可以在jboss 安装目录的client,/server/default/deploy/jboss-aop-jdk50.deployer,
/server/default/deploy/ejb3.deployer,/lib/endorsed 等文件夹下找到,或者在源代码的Lib 文件夹下获得。
  
  接口的定义如下:   HelloWorld.java
  实现类的命名规则是:接口+Bean ,如: HelloWorldBean 
  
  ***************************************************************************************************************************************
  在这里作者要重点说明一下Jboss EJB JNDI 名称默认的命名规则,命名规则如下:
1> 如果EJB 打包进后缀为*.ear 的J2EE 发布文件,默认的JNDI 路径名称是
访问本地接口:EAR-FILE-BASE-NAME/EJB-CLASS-NAME/local
访问远程接口:EAR-FILE-BASE-NAME/EJB-CLASS-NAME/remote
例:EJB HelloWorld 打包进名为HelloWorld.ear 的J2EE 应用,访问她远程接口的JNDI 名是:
HelloWorld/HelloWorldBean/remote
2> 如果EJB 应用打包成后缀为*.jar 的发布文件, 默认的JNDI 路径名称是
访问本地接口:EJB-CLASS-NAME/local
访问远程接口:EJB-CLASS-NAME/remote
例: HelloWorld 应用打包成HelloWorld.jar 文件,访问她远程接口的JNDI 名称是:HelloWorldBean/remote
另外有一点要注意:EJB-CLASS-NAME 是不带包名的,如com.foshanshop.ejb3.impl.HelloWorldBean 只需取HelloWorldBean。

目前网上很多教材获取JNDI 路径名的方式不适用在jboss 下,如:
HelloWorld helloworld = (HelloWorld) ctx.lookup(HelloWorld.class.getName());这种方式适用于Sun ApplicationServer 及glassfish
我们把上面的客户端应用打成war 文件。然后把她拷贝到“[jboss 安装目录]\server\default\deploy”目录下。如果
war文件的文件名为EJBTest.war ,我们可以通过http://localhost:8080/EJBTest/Test.jsp 访问客户端。   
****************************************************************************************************************************************
@Local 注释指明实现的接口是本地接口。当@Local 和@Remote 注释都不存在时,会话Bean 实现的接口默认为Local 接口。  
如果在本机调用EJB(确保客户端与EJB 容器运行在同一个JVM),采用Local 接口访问EJB 优于Remote 接口,因为Remote
  接口访问EJB需要经过远程方法调用(RPCs)环节,而Local接口访问EJB直接从JVM中返回EJB的引用。 
  ****************************************************************************************************************************************
  如果你试图在独立的Tomcat 服务器中执行客户端代码(如何在独立的Tomcat 环境中调用EJB 请考照第二章:在独立的Tomcat 中调用EJB),你将获得如下例外:
  java.lang.NullPointerExceptionorg.jboss.ejb3.stateless.StatelessLocalProxy.invoke(StatelessLocalProxy.java:74)产生此例外的原因是,调用Local接口
  的客户端与EJB 容器不在同一个VM(虚拟内存堆)。相对于发布到jboss deploy 目录下的客户端应用而言,他与EJB 容器运行在同一个VM。如果客户端与EJB容器
  在不同的VM,只能通过其Remote 接口进行访问。 
  
  
  调用Local 接口时,两次累加的结果都不一样,一个是2,一个是4。
  这是因为Stateless Session Bean 不负责记录使用者状态,Stateless Session Bean 一旦实例化就被加进会话池中,各个用户都可以共用。即使用户已经消亡,
  Stateless Session Bean 的生命期也不一定结束,它可能依然存在于会话池中,供其他用户调用。如果它有自己的属性(变量),那么这些变量就会受到所有
  调用它的用户的影响。
  
  在Jboss 网站看到,在EJB3.0 RC9 以上版本中Remote 及Local 接口可以指向同一个业务接口,这样客户端就不会因调用接口的不同而来回切换业务接口类。
  当然这种使用场合是在Remote 和Local 的接口方法相同的情况下。
  
3、有状态Bean开发(Stateful Session Beans)
有状态Bean是一个可以维持自身状态的会话Bean,每个用户都有自己的一个实例,在用户的生命周期内,Stateful Session Bean 保持了用户的信息,即“有状态”
一旦用户灭亡(调用结束或实例结束),Stateful Session Bean的生命周期也告结束。每个用户最初都会得到一个初始的Stateful Session Bean。

stateful session bean 必须实现Serializable 接口,这样EJB 容器才能在她们不再使用时序列化存储她们的状态信息。
@Stateful 注释定义这是一个有状态会话Bean,@Remote注释指明有状态Bean 的remote 接口。


     @SuppressWarnings("serial") 注释屏蔽缺少serialVersionUID 定义的警告。      

因为stateful session bean 的每个用户都有自己的一个实例,所以两者对stateful session bean 的操作不会影响对方。
  另外注意:如果后面需要操作某个用户的实例,你必须在客户端缓存Bean 的Stub 对象(JSP 通常的做法是用Session缓存),
  这样在后面每次调用中,容器才知道要提供相同的bean 实例。
  
4、如何改变Session Bean的JNDI名称
在Jboss 中要自定义JNDI 名称,可以使用@LocalBinding 和@RemoteBinding 注释,@LocalBinding 注释指定Session Bean 的Local 接口的JNDI 名称,
  @RemoteBinding 注释指定Session Bean 的Remote 接口的JNDI名称,例子:
  @Remote ({Operation.class})
@RemoteBinding (jndiBinding="foshanshop/RemoteOperation")
@Local ({LocalOperation.class})
@LocalBinding (jndiBinding="foshanshop/LocalOperation")
在weblogic10 中,你可以通过@Stateless.mappedName()设置全局JNDI名称
@Stateless(mappedName="OperationBeanRemote")
客户端调用EJB 的代码片断如下:
InitialContext ctx = new InitialContext(props);
Operation operation = (Operation) ctx.lookup("OperationBeanRemote#com.foshanshop.ejb3.Operation");

5、Session Bean的生命周期:


6、拦截器(Interceptor)
拦截器可以监听程序的一个或所有方法。拦截器对方法调用流提供了细粒度控制。可以在无状态会话bean、有状态会话bean 和消息驱动bean 上使用它们。
拦截器可以是同一bean 类中的方法或是一个外部类。

  @Interceptors({HelloInterceptor.class})
  public class HelloChinaBean implements HelloChina,HelloChinaRemote {
  
  @Interceptors 注释指定一个或多个在外部类中定义的拦截器。上面拦截器HelloInterceptor 对HelloChinaBean 中的所有方法进行监听。
  
  拦截器HelloInterceptor.java:
  
  public class HelloInterceptor {
@AroundInvoke
public Object log(InvocationContext ctx) throws Exception {
System.out.println("*** HelloInterceptor intercepting");
long start = System.currentTimeMillis();
try{
if (ctx.getMethod().getName().equals("SayHello")){
System.out.println("*** SayHello 已经被调用! *** " );
}
if (ctx.getMethod().getName().equals("Myname")){
System.out.println("*** Myname 已经被调用! *** " );
}
return ctx.proceed();
}catch (Exception e) {
throw e;
}finally {
long time = System.currentTimeMillis() - start;
System.out.println("用时:"+ time + "ms");
}
}
}


@AroundInvoke 注释指定了要用作拦截器的方法。用@AroundInvoke 注释指定的方法必须遵守以下格式:
public Object XXX(InvocationContext ctx) throws Exception XXX 代表方法名可以任意。

除了可以在外部定义拦截器之外,还可以将Session Bean 中的一个或多个方法定义为拦截器。下面以前面的HelloChinaBean 为例,
介绍在Session Bean 中如何定义拦截器。

@Stateless
  @Remote ({HelloChinaRemote.class})
  @Local(HelloChina.class)
public class HelloChinaBean implements HelloChina,HelloChinaRemote {
public String SayHello(String name) {
return name +"说:你好!中国.";
}
public String Myname() {
return "我是佛山人";
}
@AroundInvoke
public Object log(InvocationContext ctx) throws Exception {
try{
if (ctx.getMethod().getName().equals("SayHello")){
System.out.println("*** HelloChinaBean.SayHello() 已经被调用! *** " );
}
if (ctx.getMethod().getName().equals("Myname")){
System.out.println("*** HelloChinaBean.Myname() 已经被调用! *** " );
}
return ctx.proceed();
}catch (Exception e) {
throw e;
}
}
}
上面只需一个@AroundInvoke 注释就指定了要用作拦截器的方法。


7、依赖注入(DI)

使用@EJB 注释,你可以将EJB存根对象注入到任何EJB 3.0 容器管理的POJO 中。如果注释用在一个属性变量上,容器将会在它被第一次访问之前赋值给它。
依赖注入只工作在本地命名服务中,因此你不能注入远程服务器的对象。

@Stateless
@Remote ({Injection.class})
public class InjectionBean implements Injection {

@EJB (beanName="HelloWorldBean")
HelloWorld helloworld;
public String SayHello() {
return helloworld.SayHello("注入者");
}

@EJB 注释的beanName 属性指定EJB 的名称(如果没有设置过@Stateless 或@Stateful 的name 属性,默认为不带包名的类名),
他的另一个属性mappedName 指定EJB 的全局JNDI 名。


下面的片断演示了如何使用beanName 或mappedName 属性查找HelloWorldBean 会话bean
public class InjectionBean implements Injection {
@EJB (beanName="HelloWorldBean")

 //@EJB (mappedName="HelloWorldBean/remote")
HelloWorld helloworld;

@EJB 注释如果被用在JavaBean 风格的setter 方法上时,容器会在属性第一次使用之前,自动地用正确的参数调用bean 的setter 方法。
public class InjectionBean implements Injection {
HelloWorld helloworld;
@EJB (beanName="HelloWorldBean")
public void setHelloworld(HelloWorld helloworld) {
this.helloworld = helloworld;
}

@EJB 注释只能注入EJB 存根对象,除@EJB 注释之外,EJB 3.0 也支持@Resource 注释来注入来自JNDI 的任何资源。
下面的例子中演示了如何注入数据源。"java:/DefaultMySqlDS"是数据源DefaultMySqlDS 的全局JNDI 名。

public class InjectionBean implements Injection {
@EJB(beanName = "HelloWorldBean")
HelloWorld helloworld;
@Resource(mappedName = "java:/DefaultMySqlDS")
DataSource myDb;
public String SayHello() {
String str = "";
try {
Connection conn = myDb.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT studentName FROM student");
if (rs.next()) {
str = rs.getString(1);
}
rs.close();
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
return helloworld.SayHello(str);
}

8、定时服务(Timer Service):(60页以后)
定时服务用作在一段特定的时间后执行某段程序,估计各位在不同的场合中已经使用过。
定时服务的开发过程。定时服务的开发过程与会话Bean 的开发过程大致相同,但比会话Bean 多了几个操作,
那就是使用容器对象SessionContext 创建定时器,并使用@Timeout 注释声明定时器方法。

9、安全服务开发:


10、自定义安全域:


四、JMS(Java Message Service)( P75)

Java 消息服务(Java Message Service,简称JMS)是企业级消息传递系统,紧密集成于Jboss Server 平台之中。
企业消息传递系统使得应用程序能够通过消息的交换与其他系统之间进行通信。
 1、消息组成
消息传递系统的中心是消息。一条Message分为三个组成部分。
① 头:是个标准字段集,客户机和供应商都用它来标识和路由信息。
② 属性(property):支持把可选头字段添加到消息。如果您的应用程序需要不使用标准头字段对消息编目和分类,您就可以添加一个属性
到消息以实现这个编目和分类。提供set<Type>Property(...)和get<Type>Property(...)方法设置和获取各种Java类型的属性,包括Object。
③ 消息的主体:
 2、消息的传递模式
点对点(PTP):一条消息只能传递给一个接收方;
发布/订阅(pub/sub):一条消息传递给多个接收方;


1、消息驱动Bean(Message Driven Bean):
消息驱动Bean(MDB)是设计来专门处理基于消息请求的组件。它是一个异步的无状态Session Bean,客户端调用MDB后无需等待,立刻返回,
MDB将异步处理客户的请求。一个MDB类必须实现MessageListener接口。当容器检测到bean守候的队列一条信息时,就调用onMessage()方法,
将消息作为参数传入。MDB在OnMessage()中决定如何处理该消息。
也可用注释来配置MDB监听哪一队列。当MDB部署时,容器将会用到其中的注释消息。
当一个业务执行的时间很长,而执行结果无需实时向用户反馈时,很适合使用消息驱动Bean。如:订单成功后会给用户发一条短信等。
2、实体Bean(Entity Bean)
持久化是位于JDBC之上的一个更高层抽象。持久层将对象映射到数据库,以便在查询、装载、更新、或删除对象的时候,无需使用像
JDBC那样繁琐的API。在EJB得到早期版本中,持久化是EJB平台的一部分。从EJB 3.0开始,持久化已经自成规范,被称为Java Persistence API。


Java Persistence API 定义了一种方法,可以将常规的普通Java对象(有时被称作POJO)映射到数据库。这些普通Java对象被称作
entity bean.除了是用Java Persistence元数据将其映射到数据库外,entity bean与其他Java类没有任何区别。事实上,创建一个
Entity Bean 对象相当于新建一条记录,删除一个Entity Bean 会同时从数据库中删除对应记录,修改一个Entity Bean 时,容器会
自动将Entity Bean 的状态和数据库同步。


Java Persistence API还定义了一种查询语言(JPQL),具有与SQL相类似的特征,只不过做了裁减,以便处理Java对象而非原始的关系schema。
 注释:
@Entity 注释指明这是一个实体Bean,@Table注释指定了entity 所要映射的数据库表,其中@Table.name()用来指定映射表的表名。如果 
缺省@Table注释,系统默认采用类名作为映射表的表名。实体Bean的每个实例代表数据表中的一行数据,行中的一列对应实例中的一个属性。

@javax.persistence.Column 注释定义了将成员属性映射到关系表中的哪一列和该列的一些结构信息(如列名是否唯一,是否允许为空,是否允许更新等),
他的属性介绍如下:
·name:  映射的列名。如:映射Person 表的PersonName 列,可以在name 属性的getName 方法上面加入
@Column(name = "PersonName"),如果不指定映射列名,容器将属性名称作为默认的映射列名。
·unique: 是否唯一 
·nullable: 是否允许为空 
·length:  对于字符型列,length 属性指定列的最大字符长度 
·insertable: 是否允许插入 
·updatable: 是否允许更新 
·columnDefinition: 定义建表时创建此列的DDL 
·secondaryTable: 从表名。如果此列不建在主表上 (默认建在主表),该属性定义该列所在从表的名字。 


@Id  注释指定personid 属性为表的主键,它可以有多种生成方式: 
    ① ·TABLE:容器指定用底层的数据表确保唯一。
@TableGenerator(name="Person_GENERATOR",//为该生成方式取个名称 
                   table= "Person_IDGenerator",//生成ID的表 
                   pkColumnName= "PRIMARY_KEY_COLUMN",//主键列的名称 
                   valueColumnName= "VALUE_COLUMN",//存放生成ID值的列的名称 
                   pkColumnValue= "personid",//主键列的值(定位某条记录) 
                   allocationSize=1)//递增值 


@Id 
@GeneratedValue(strategy=GenerationType.TABLE, generator "Person_GENERATOR") 
public Integer getPersonid() { 
return personid; 


     ② ·SEQUENCE:使用数据库的SEQUENCE 列来保证唯一(Oralce 数据库通过序列来生成唯一ID)
  @SequenceGenerator(name ="Person_SEQUENCE",//为该生成方式取个名称 
                    sequenceName= "Person_SEQ")//sequence的名称(如果不存在,会自动生成) 
   public classPerson implementsSerializable{ 
   @Id 
   @GeneratedValue(strategy=GenerationType.SEQUENCE, generator ="Person_SEQ") 
   public Integer getPersonid() { 
      return personid; 
  } 
     ③ ·IDENTITY:使用数据库的INDENTIT 列来保证唯一(像mysql,sqlserver 数据库通过自增长来生成唯一ID) 
·AUTO:由容器挑选一个合适的方式来保证唯一(由容器决定采用何种方式生成唯一主键,hibernate 会根据 
数据库类型选择适合的生成方式,相反toplink 就不是很近人情) 
·NONE :容器不负责主键的生成,由调用程序来完成。


  @GeneratedValue 注释定义了标识字段的生成方式。


  注:实体bean需要在网络上传送时必须实现Serializable接口,否则将引发java.io.InvalidClassException例外。 


  @Temporal 注释用来指定java.util.Date或java.util.Calendar属性与数据库类型date,time 或timestamp中的那一种类型进行映射。
@Temporal(value=TemporalType.DATE)
public Date getBirthday() {
return birthday;
}
  Session Bean 我觉得就是dao层(接口有方法),而实现session bean 就是daoImpl(实现接口,实现方法),其中,实现Session Bean其中
加入了一个对象EntityManager em,EntityManager是由EJB容器自动地管理和配置的,不需要用户自己创建,他用作操作实体 Bean。
@PersistenceContext 
       protected EntityManager em;
查找用户用em.find()方法,更新用em.merge(),添加用em.persist(),查所有人按id排序:
public List getPersonList() { 
Query query  em.createQuery("from Person order by personid asc"); 
List list  =query.getResultList(); 
return list; 

        在类中并没有看到对 EntityManager em进行赋值,后面却可以直接使用他。这是因为容器在实例化SessionBean 后,就通过@PersistenceContext 
注释动态注入 EntityManager 对象。


如果 persistence.xml文件中配置了多个不同的持久化内容。在注入 EntityManager 对象时必须指定持久化名称,可以通过@PersistenceContext
注释的 unitName 属性进行指定,如果只有一个持久化内容配置,不需要明确指定。
@PersistenceContext(unitName="foshanshop")
EntityManager em;
persistence.xml文件的配置:↓↓↓


  2.1 持久化persistence.xml配置文件
一个实体Bean应用由实体类和persistence.xml文件组成.persisence.xml文件的META-INF目录。persistence.xml文件指定实体Bean
使用的数据源及EntityManager对象的默认行为。persistence.xml文件的配置说明如下:


<persistence> 
<persistence-unit name="foshanshop"> 
<jta-data-source>java:/DefaultMySqlDS</jta-data-source> 
<properties> 
<property name="hibernate.hbm2ddl.auto" value="create-drop"/> 
</properties> 
</persistence-unit> 
</persistence>
persistence-unit节点可以有一个或多个,每个persistence-unit节点定义了持久化内容名称、使用的数据源及持久化产品专有属性。name属性
定义持久化名称。jta-data-source节点指定实体Bean使用的数据源JNDI 名称(如何配置数据源请参考下节 “Jboss数据源的配置”),如果应用
发布在jboss下数据源名称必须带有java:/前缀,数据源名称大小写敏感。properties节点用作指定持久化产品的各项属性,各个应用服务器使用
的持久化产品都不一样如Jboss使用Hibernate,weblogic10 使用Kodo,glassfish/sun application server/Oralce 使用Toplink。因为jboss采
用Hibernate,Hibernate有一项属性hibernate.hbm2ddl.auto,该属性指定实体Bean发布时是否同步数据库结构,如果hibernate.hbm2ddl.auto的
值设为create-drop,在实体Bean 发布及卸载时将自动创建及删除相应数据库表(注意:Jboss服务器启动或关闭时也会引发实体Bean的发布及卸载)。
TopLink 产品的toplink.ddl-generation 属性也起到同样的作用。关于hibernate的可用属性及默认值你可以在 [Jboss 安装目录] 
\server\default\deploy\ejb3.deployer\META-INF/persistence.properties 文件中看见. 


小提示:如果你的表已经存在,并且想保留数据,发布实体bean 时可以把hibernate.hbm2ddl.auto 的值设为none或update, 以后为了实体bean的
改动能反应到数据表,建议使用update,这样实体Bean 添加一个属性时能同时在数据表增加相应字段。

  属性映射:
如果不想让一些成员属性映射成数据库字段,我们可以使用@Transient注释进行标注。


如果你想映射枚举对象到数据库就需要使用@Enumerated 注释进行标注。
@Enumerated(EnumType.STRING)
public CommentType getType() {
return type;
}

@Lob 注释用作映射这些大数据类型,当属性的类型为 byte[], Byte[]或 java.io.Serializable 时,@Lob 注释将映射为数据库的 Blob 类型,
当属性的类型为 char[],Character[]或 java.lang.String 时,@Lob 注释将映射为数据库的 Clob 类型。


@Lob 注释的大数据类型,为了避免每次加载实体时占用大量内存,我们有必要对该属性进行延时加载,这时我们需要用到@Basic注释。
public @interface Basic{
FetchType fetch( ) default EAGER;
boolean optional( ) default true;
}
FetchType 属性指定是否延时加载,默认为立即加载,optional属性指定在生成数据库结构时字段能否为 null
@Lob
@Basic(fetch=FetchType.LAZY)
public String getContent() {
return content;
}


  持久化管理器EntityManager:
EntityManager 常用的 API:
① Entity获取 find()或 getReference()
当在数据库中没有找到记录时,getReference()和 find()是有区别的,find()方法会返回 null,而 getReference()方法
会抛出 javax.persistence.EntityNotFoundException 例外,另外 getReference()方法不保证实体 Bean 已被初始化。
如果传递进 getReference()或 find()方法的参数不是实体 Bean,都会引发 IllegalArgumentException 例外。
② 添加persist()
如果传递进 persist()方法的参数不是实体 Bean,会引发 IllegalArgumentException 例外。
③ 更新实体
当实体正在被容器管理时,你可以调用实体的 set方法对数据进行修改,在容器决定 flush 时,更新的数据才会同
步到数据库。如果你希望修改后的数据实时同步到数据库,你可以执行 EntityManager.flush()方法。
④ 合并Merge()
merge ()方法是在实体Bean 已经脱离了EntityManager 的管理时使用,当容器决定 flush 时,数据将会同步到数据库中。
  执行 em.merge(person)方法时,容器的工作规则:
1.如果此时容器中已经存在一个受容器管理的具有相同 ID 的 person 实例,容器将会把参数 person 的内容拷贝
  进这个受管理的实例, merge()方法返回受管理的实例, 但参数 person仍然是分离的不受管理的。 容器在决定 Flush
  时把实例同步到数据库中。
2.容器中不存在具有相同 ID 的 person 实例。容器根据传进的 person 参数 Copy出一个受容器管理的 person 实
  例,同时 merge()方法会返回出这个受管理的实例,但参数 person 仍然是分离的不受管理的。容器在决定 Flush
  时把实例同步到数据库中。
如果传递进 merge ()方法的参数不是实体 Bean,会引发一个 IllegalArgumentException 例外。
⑤ 删除 Remove()
em.remove (person);
//如果级联关系cascade=CascadeType.ALL, 在删除 person 时候, 也会把级联对象删除。 把 cascade属性设为 
cascade=CascadeType.REMOVE 有同样的效果。
如果传递进 remove ()方法的参数不是实体 Bean,会引发一个 IllegalArgumentException 例外。
⑥ 执行 JPQL 操作 createQuery()
通过JPQL得到实体Bean。要执行 JPQL语句,你必须通过 EntityManager的createQuery()或 createNamedQuery()方法
创建一个 Query对象。
       Query query = em.createQuery("select p from Person p where p. name=’ 黎明’");
List result = query.getResultList();
Iterator iterator = result.iterator();
while( iterator.hasNext() ){
//处理 Person
}
Query query = em.createQuery("update Person as p set p.name =?1 where p. personid=?2");
query.setParameter(1, “ 黎明”);
query.setParameter(2, new Integer(1) );
int result = query.executeUpdate(); //影响的记录数
// 执行更新语句
Query query = em.createQuery("delete from Person");
int result = query.executeUpdate(); //影响的记录数
⑦ 执行 SQL 操作 createNativeQuery()
注意这里操作的是 SQL语句,并非 JPQL,千万别搞晕了。
Query query = em.createNativeQuery("select * from person", Person.class);
List result = query.getResultList();
if (result!=null){
Iterator iterator = result.iterator();
while( iterator.hasNext() ){
Person person= (Person)iterator.next();
… ..
}


// 直接通过 SQL执行更新语句
Query query = em.createNativeQuery("update person set age=age+2");
query.executeUpdate();
⑧ 刷新实体 refresh()
当前被管理的实体已经不是数据库中最新的数据,你可以通过 refresh()方法刷新实体,容器会把数据
库中的新值重写进实体。这种情况一般发生在你获取了实体之后,有人更新了数据库中的记录,这时你需要得到
最新的数据。当然你再次调用 find()或 getReference()方法也可以得到最新数据,但这种做法并不优雅。


如果传递进 refresh ()方法的参数不是实体 Bean,会引发一个 IllegalArgumentException 例外。
⑨ 检测实体当前是否被管理中 contains()
contains()方法使用一个实体作为参数,如果这个实体对象当前正被持久化内容管理,返回值为 true,否则为 false。
如果传递的参数不是实体 Bean,将会引发一个 IllegalArgumentException 例外。
⑩ 分离所有当前正在被管理的实体 clear()
在处理大量实体的时候,如果你不把已经处理过的实体从 EntityManager 中分离出来,将会消耗你大量的内存。
调用 EntityManager 的 clear()方法后,所有正在被管理的实体将会从持久化内容中分离出来。


在事务没有提交前(事务默认在调用堆栈的最后提交,如:方法的返回),如果调用 clear()方法,
之前对实体所作的任何改变将会掉失,所以建议你在调用 clear()方法之前先调用 flush()方法保存更改。
       em.flush();//手动将更新立刻刷新进数据库;
(11)改变实体管理器的 Flush模式 setFlushMode()
对 Flush 模式进行修改需要使用到 javax.persistence.FlushModeType
默认情况下,实体管理器的 Flush 模式为 AUTO,你可以改变他的值,如下:entityManager.setFlushMode(FlushModeType.COMMIT);
FlushModeType.AUTO: 刷新在查询语句执行前(除了find()和 getreference()查询)或事务提交时才发生,使用场合:
   在大量更新数据的过程中没有任何查询语句(除了 find()和 getreference()查询)的执行。
FlushModeType.COMMIT:刷新只有在事务提交时才发生,使用场合:在大量更新数据的过程中存在查询语句(除
   了 find()和 getreference()查询)的执行。
JDBC驱动跟数据库交互的次数。JDBC性能最大的增进是减少JDBC驱动与数据库之间的网络通讯。
FlushModeType.COMMIT 模式使更新只在一次的网络交互中完成,而
FlushModeType.AUTO 模式可能需要多次交互(触发了多少次 Flush 就产生了多少次网络交互).
(12)获取持久化实现者的引用 getDelegate( )
用过 getDelegate( )方法,你可以获取 EntityManager 持久化实现者的引用,如 Jboss EJB3 的持久化产品采用
Hibernate,可以通过 getDelegate( ) 方法获取对他的访问,如:
@PersistenceContext
protected EntityManager em;
HibernateEntityManager manager = (HibernateEntityManager)em.getDelegate();
获得对 Hibernate 的引用后,可以直接面对 Hibernate 进行编码,不过这种方法并不可取,强烈建议不要使用。
在 Weblogic 中,你也可以通过此方法获取对 Kodo 的访问。
  关系对象映射
① 映射的表名或列名与数据库保留字同名时的处理
如果应用采用的数据库是Mysql,当映射的表名或列名与数据库保留字同名时,持久化引掣转绎后的 SQL在执行
时将会出错。
采用了一种变通的方法来解决此问题。该方法针对具体数据库,不利于数据库移植。建议大家在不得已的情况下使用。
可以用``字符把 Order括起来。@Table(name = "`Order`");如果数据库是 Sqlserver 可以用 [] 把表名或列名括起来。
Sqlserver不加[]也能执行成功,建议在出错的情况下使用。
② 一对多及多对一映射
双向一对多关系,一是关系维护端(owner side),多是关系被维护端(inverse side)。在关系被维护端建立外键列
指向关系维护端的主键列。
@OneToMany(mappedBy="order",cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@OrderBy(value = "id ASC")
public Set<OrderItem> getOrderItems() {
return orderItems;
}
下面是@OneToMany注释的属性介绍:
(1) targetEntity
   Class类型的属性。
   定义关系类的类型,默认是该成员属性对应的类类型,所以通常不需要提供定义。
(2) mappedBy
   String 类型的属性。
   定义类之间的双向关系。如果类之间是单向关系,不需要提供定义,如果类和类之间形成双向关系,我们就需要
   使用这个属性进行定义,否则可能引起数据一致性的问题。
(3) cascade
   CascadeType[]类型。
   该属性定义类和类之间的级联关系。定义的级联关系将被容器视为对当前类对象及其关联类对象采取相同的操
   作,而且这种关系是递归调用的。
   cascade的值只能从 CascadeType.PERSIST (级联新建)、 CascadeType.REMOVE (级联删除)、 CascadeType.REFRESH
  (级联刷新)、CascadeType.MERGE(级联更新)中选择一个或多个。还有一个选择是使用CascadeType.ALL,表
   示选择全部四项。
       (4) fetch
   FetchType 类型的属性。
   可选择项包括: FetchType.EAGER和 FetchType.LAZY。 前者表示关系类(本例是OrderItem类)在主类(本例是 Order
   类)加载的时候同时加载,后者表示关系类在被访问时才加载。默认值是 FetchType. LAZY。
   @OrderBy(value = "id ASC")注释指明加载 OrderItem时按 id 的升序排序


@ManyToOne(cascade=CascadeType.REFRESH,optional=false)
@JoinColumn(name = "order_id")
public Order getOrder() {
return order;
}
@ManyToOne 注释有四个属性:targetEntity、cascade、fetch 和 optional,前三个属性的具体含义和@OneToMany
注释的同名属性相同,但@ManyToOne 注释的 fetch 属性默认值是 FetchType.EAGER。


optional属性是定义该关联类是否必须存在.
值为false时,关联类双方都必须存在,如果关系被维护端不存在,查询的结果为 null。
值为 true 时, 关系被维护端可以不存在,查询的结果仍然会返回关系维护端,在关系维护端中指向关系被维护端的属性为 null。
optional属性的默认值是true。optional属性实际上指定关联类与被关联类的 join 查询关系,
如:optional=false时,join查询关系为inner join; optional=true时,join查询关系为left join。

public class OrderItem implements Serializable {
@ManyToOne(cascade=CascadeType.REFRESH,optional=false)
@JoinColumn(name = "order_id")
public Order getOrder() {
return order;
}
//获取OrderItem时的SQL为:select * from OrderItem item inner join Orders o on o.order_id=item.id,
OrderItem表与orders表都必须有关联记录时,查询结果才有记录。


@ManyToOne(cascade=CascadeType.REFRESH,optional=true)
@JoinColumn(name = "order_id")
public Order getOrder() {
return order;
}
//获取OrderItem时的SQL为:select * from OrderItem item left outer join Orders o on o.order_id=item.id
如果orders表没有记录,OrderItem表有记录,查询结果仍有记录。
@JoinColumn(name = "order_id")注释指定 OrderItem 映射表的 order_id 列作为外键与 Order 映射表的主键列关联。


注意:当业务方法需要把一个实体Bean作为参数返回给客户端时,除了实体 Bean 本身需要实现Serializable 接口之外, 
     如果关联类(OrderItem)是延迟加载,还需在返回实体Bean 之前通过访问关联类的方式加载关联类。否则在客户端
     访问关联类时将会抛出加载例外。另外不管是否延迟加载,通过 join fetch 关联语句都可显式加载关联类,如业务方法 getAllOrder。
    
③ 一对一映射
一对一关系需要在关系维护端(owner side)的@OneToOne注释中定义 mappedBy属性。在关系被维护端(inverse
side)建立外键列指向关系维护端的主键列。


关系维护端:
@OneToOne(optional = true,cascade = CascadeType.ALL, mappedBy = "person")
public IDCard getIdcard() {
return idcard;
}
public void setIdcard(IDCard idcard) {
this.idcard = idcard;
}
@OneToOne注释五个属性:targetEntity、cascade、fetch、optional和 mappedBy, 前四个属性的具体含义与@ManyToOne注释的同名属性一一对应,
fetch 属性默认值是 FetchType.EAGER。 mappedBy属性的具体含义与@OneToMany注释的同名属性相同。


关系被维护端:
optional = true 设置 idcard 属性可以为 null,也就是允讦没有身份证,未成年人就是没有身份证的。
@OneToOne(optional = false, cascade = Ca

以上是关于EJB学习随手笔记的主要内容,如果未能解决你的问题,请参考以下文章

机器学习随手笔记

机器学习随手笔记

java随手笔记一

机器学习-随手笔记二(卷积神经网络TensorFlow和IV值计算)

随手笔记

随手笔记,标签切换