EJB
1、J2EE 是什么?
J2EE 是Sun 公司提出的多层(multi-diered),分布式(distributed),基于组件(component-base)的企
业级应用模型(enterpriese application model).在这样的一个应用系统中,可按照功能划分为不同
的组件,这些组件又可在不同计算机上,并且处于相应的层次(tier)中。所属层次包括客户层
(clietn tier)组件,web 层和组件,Business 层和组件,企业信息系统(EIS)层。
JAVA EE=Components(规范—servlet/jsp/ejb/jsf)+Contanous(容器àWEB容器/EJB容器/spring容器)
+MVC模式+service(服务)+多层次
在大型的应用中一般会有多个web服务器和app应用,这样可以解决,1,多台机器负载均衡,server集群(类似多个Tomcat)2,app调用web的方法,远程方法调用,3,透明的错误切换(从web1切换到web2 客户端不能察觉,其实是web2有web1 的备份)4 db数据共享
2,Enterprice JavaBeans 3.0是一个用于分布式业务应用的标准服务端组件模型。采用Enterprice JavaBeans 架构编写的应
用是可伸的、事务性的、多用户安全的。可以一次编写这些应用,然后部署在任何支持Enterprice JavaBeans 规范
的服务器平台,如jboss、weblogic。
Enterprise JavaBean(EJB)定义了三种企业Bean,分别是会话Bean(Session Bean),实体Bean(Entity Bean)
和消息驱动Bean(MessageDriven Bean)
- EJB的开发流程:①开发ejb ②组装应用 ③ 部署 ④ 管理 ⑤ 开发工具
4,Session Bean:Session Bean 用于实现业务逻辑,它分为有状态bean 和无状态bean。每当客户端请求时,容器就
会选择一个Session Bean 来为客户端服务,session bean是一个与其他系统打交道的边界bean,响应客户端的调用(生命较短)。Session Bean 可以直接访问数据库,但更多时候,它会通过Entity Bean
实现数据访问。
1)EJB实现远程调用的机制:代理
注意:客户端和服务端都实现一样的接口,在代理的实现类保存目标类的引用,这就叫代理.如果没有共同的接口,那就是委托。客户端调用服务端的应用,其实调用的是Stub(桩)。
2,Ejb的编写:
①商业接口和bean:注意区别本地接口和远程接口,在同一个虚拟机的是本地接口,不是在同一个虚拟机的是远程接口(远程接口消耗资源) 如 本地 local 远程 stateless[U1] (mappedName=“ejb/datecom[U2] ”)
②,本地接口@Local 远程接口@Remote
例子:
@Local[U3]
public interface LocalHello{
String sayHello(String name);
}
import javax.ejb.Remote;
@Remote[U4]
interface RemoteHello{
String sayHello(String name);
}
import javax.ejb.Stateless;
@Stateless(mappedName=" ejb/helloEjb[U5] ")
@remote(RemoteHello.class)
public class StatelessHello implements RemoteHello[U6] , LocalHello {
public String sayHello(String name) {
return "hello"+name;
}
}
调用session bean
Context ctx = new InitialContext();
HelloRemote hr = (HelloRemote)ctx.lookup("ejb/helloEjb[U7] ");
3,无状态session Bean 主要用来实现单次使用的服务,该服务能被启用许多次,但是由于无状态会话Bean 并不保留任
何有关状态的信息,其效果是每次调用提供单独的使用。在很多情况下,无状态会话Bean 提供可重用的单次使
用服务。无状态就意味着共享,多客户端,可以构建pooling池[U8]
注意:单例bean一定是无状态的。通过配置文件sun-ejb-jar.xml(netbeans环境 glassfish服务器)来配置这个池。
配置文件举例如下 :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sun-ejb-jar PUBLIC "-//Sun Microsystems,
Inc.//DTD Application Server 9.0 EJB 3.0//EN"
"http://www.sun.com/software/appserver/dtds/sun-ejb-jar_3_0-0.dtd">
<sun-ejb-jar>
<enterprise-beans>
<ejb>
<ejb-name>SllcBean</ejb-name><!-- 指定ejb的名字 -->
<!-- 对池的配置 -->
<bean-pool>
<steady-pool-size>2</steady-pool-size> <!-- 稳定状态下 池中ejb数量 -->
<resize-quantity>2</resize-quantity> <!-- 创建ejb的步长 一次新创建两个 -->
<max-pool-size>6</max-pool-size> <!-- 池中容许的ejb最大数目 -->
</bean-pool>
</ejb>
</enterprise-beans>
</sun-ejb-jar>
2)无状态session bean的生命周期
1 构造方法(只能无参)
2 属性的依赖注入
3 @PostConstruct标记的方法
4 商业方法
不存在---------------------------------->存在(在 Stateless Bean Pool 中被管理)---
(被销毁 时间容器决定 销毁前执行@PreDestroy标记的方法)
----------------------------------------------------------------------------------------------->不存在
由不存在到存在,并非在用户调用此ejb时才转换,容器对ejb的加载分为懒汉式和非懒汉式,若为
后者,则在ejb容器启动时就完成了对ejb的加载,就是说容器启动后ejb就是存在的状态了。
有状态的session bean 有状态Bean 是一个可以维持自身状态的会话Bean。每个用户都有自己的一个实例,在用户的生存期内,StatefulSession Bean 保持了用户的信息,即“有状态”;一旦用户灭亡(调用结束或实例结束),Stateful Session Bean 的生命期也告结束。即每个用户最初都会得到一个初始的Stateful Session Bean。Stateful Session Bean 的开发步骤与Stateless Session Bean 的开发步骤相同。Stateful Session Bean跟踪用户的状态,一个bean只能被一个用户所使用。此类bean不是管理在 bean pool 中,是比较消耗资源的,一般的时候要尽量避免使用。
- 。
1)容器保存[U9] 信息(java ee接口的保存)
注意:把常用的session bean 放在内存中,把不常用的放在外部,只有用到时才调用。
- 继承SessionSynchronization接口实现事务同步。
支持事务,实现同步接口,用来保存事务在失败时可以回复原来的状态。
afterBegin()容器刚启动时,容器用于保护现场的方法。
beforeCompletion()
afterCompletion()在事务提交时被容器调用,如果参数是false则表示事务失败,此时要处理状态的恢复。
- 例子和无状态的差不多,只是用@stateful表示
// 有状态会话bean 挂到JNDI服务器名为"ejb/teller"的节点上
@Stateful(mappedName = "ejb/teller")
// mappedName = "jdbc/pointbase", type = DataSource.class 指定别名的真实值 对应的数据类型
// 倘若不在这里指定的话 就得在配置文件中说明了
@Resource(name = "jdbc/tellerDB", mappedName = "jdbc/pointbase", type = DataSource.class)
// 这里实现SessionSynchronization接口,是位了防止数据库操作失败而可能造成的用户数据不准确
public class TellerBean implements TellerRemote, TellerLocal, SessionSynchronization {
private String name;
private int cash, cashBak;
// transient关键字表明此对象无需保存到外部存储中去
private transient Connection con;
// 使用依赖注入
@Resource[U10]
private SessionContext sctx;
// 放入外部存储前执行
@PrePassivate
// bean被销毁前执行
@PreDestroy
public void passivate() {
try {
// bean被放入外部存储前或销毁前 将连接归还连接池
con.close();
} catch (SQLException ex) {
Logger.getLogger(TellerBean.class.getName()).log(Level.SEVERE, null, ex);
}
}
// 从外部存储取回后执行
@PostActivate
// 构造完毕后执行
@PostConstruct
public void activate() {
try {
Context ctx = new InitialContext();
// 数据源实际JNDI节点名为"jdbc/oracle" 这里采用的是别名
// "java:comp/env"起始,说明采用的是别名
// "jdbc/tellerDB"对应值在类开头已注入
DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/tellerDB");
con = ds.getConnection();
} catch (Exception e) {
throw new EJBException(e);
}
}
public void login(String name, int cash) {
this.name = name;
this.cash = cash;
}
public void openAccount(String accountno, String name, int balance) {
cash += balance;
PreparedStatement stm = null;
try {
stm = con.prepareStatement("insert into bank_account(accountno, name, balance) values (?, ?, ?)");
stm.setString(1, accountno);
stm.setString(2, name);
stm.setInt(3, balance);
stm.executeUpdate();
} catch (SQLException se) {
// EJB容器只有在捕捉到RunTime异常后才会认为事务执行失败
// 故这里调用SessionContext的setRollbackOnly方法 告诉容器 事务失败 要回滚
sctx.setRollbackOnly();
se.printStackTrace();
} finally {
if (stm != null) {
try {
stm.close();
} catch (SQLException se) {
}
}
}
}
public void deposit(String accountno, int amt) {
cash += amt;
PreparedStatement stm = null;
try {
stm = con.prepareStatement("update bank_account set balance=balance+? where accountno=?");
stm.setInt(1, amt);
stm.setString(2, accountno);
stm.executeUpdate();
} catch (SQLException se) {
sctx.setRollbackOnly();
se.printStackTrace();
} finally {
if (stm != null) {
try {
stm.close();
} catch (SQLException se) {
}
}
}
}
public int withdraw(String accountno, int amt) {
return 0;
}
public int getBalance(String accountno) {
PreparedStatement stm = null;
ResultSet rs = null;
try {
stm = con.prepareStatement("select balance from bank_account where accountno=?");
stm.setString(1, accountno);
rs = stm.executeQuery();
if (rs.next()) {
return rs.getInt(1);
}
return -1;
} catch (SQLException se) {
sctx.setRollbackOnly();
return -1;
}
}
/********** SessionSynchronization接口提供的三个方法 *************/
// 事务开始后立刻执行
public void afterBegin() throws EJBException, RemoteException {
cashBak = cash; // 将cash中数据拷贝一份到cashBak中 以备cash数据出错时没有备份
}
// 事务提交前执行
public void beforeCompletion() throws EJBException, RemoteException {
}
// 事务提交后执行 boolean的真假代表事务执行的成败
public void afterCompletion(boolean committed) throws EJBException, RemoteException {
if(!committed) cash = cashBak; // 若cash数据出错 则恢复
}
// 执行@Remove标签标记的方法 告诉EJB容器 此bean可以被销毁了
// 执行此方法后 然后会执行@PreDestroy方法 接着销毁此bean 至此 此bean生命结束
@Remove
public int logout() {
return cash;
}
}
单元测试 测试类 : 测试方法如下(要使用的包为JUnit 4.1) 单元测试。
package net.kettas.ejb3.test.day2;
import javax.naming.Context;
import javax.naming.InitialContext;
import net.kettas.ejb3.day2.TellerRemote;
import org.junit.Assert;
import org.junit.Test;
public class TellerEJBJUnitTest {
@Test
public void tellerOpers() throws Exception {
Context ctx = new InitialContext();
TellerRemote tr = (TellerRemote)ctx.lookup("ejb/teller");
Assert.assertNotNull(tr);
tr.login("george", 100000);
tr.openAccount("312", "abc", 10000);
Assert.assertEquals(10000, tr.getBalance("312"));
tr.deposit("312", 2000);
Assert.assertEquals(12000, tr.getBalance("312"));
Assert.assertEquals(112000, tr.logout());
}
}
4)有状态的session bean 的生命周期
5, Stateless Session Bean 与Stateful Session Bean 的区别。这两种Session Bean 都可以将系统逻辑放在方法之中执行,不同的是Stateful Session Bean 可以记录呼叫者的状态,因此一个使用者会有自己的一个实例。Stateless Session Bean 虽然也是逻辑组件,但是他却不负责记录使用者状态,也就是说当使用者呼叫Stateless Session Bean 的时候,EJB 容器并不会寻找特定的Stateless Session Bean 的实体来执行这个method。换言之,很可能数个使用者在执行某个Stateless Session Bean 的methods 时,会是同一个Bean的实例在执行。从内存方面来看,Stateful Session Bean 与Stateless Session Bean 比较,Stateful Session Bean 会消耗J2EE Server 较多的内存,然而Stateful Session Bean 的优势却在于他可以维持使用者的状态。
6 Invoke 拦截器 @ArounedInvoke 方法参数
实现步骤:①拦截器是增加功能的 ejb的构造方法无参数
public class LogInterceptor {
private static Logger logger = Logger.getLogger("LogInterceptor");
@PostConstruct[U11]
public void construct(InvocationContext ic) {
logger.info("@PostConstruct: " + ic.getTarget().getClass().getSimpleName());
}
@PreDestroy
public void destroy(InvocationContext ic) {
logger.info("@PreDestroy: " + ic.getTarget().getClass().getSimpleName());
}
@AroundInvoke
public Object invoke(InvocationContext ic) throws Throwable{
logger.info("before calling " + ic.getMethod().getName());
Object ret = ic.proceed()[U12] ;
logger.info("after calling " + ic.getMethod().getName());
return ret;
}
}
②在session bean中调用拦截器
@Stateless(mappedName="ejb/sllc")
@Interceptors(LogInterceptor.class)[U13]
public class SllcBean implements SllcRemote, SllcLocal {
private static Logger logger =
Logger.getLogger("SllcBean");
private SessionContext sctx;
public SllcBean(){
logger.info("Constructor: SllcBean");
}
[U14] @Resource
public void setContext(SessionContext sctx){
logger.info("Dependency injection: setContext");
this.sctx = sctx;
}
@PostConstruct[U15]
public void construct(){
logger.info("@PostConstruct: construct");
}
不多 3 zedessionContext sctx;
@PreDestroy
public void destroy(){
logger.info("@PreDestroy: destroy");
}
public void test() {
logger.info("just test.");
}
}
7,生命周期的标签: Session Bean 的生命周期
EJB 容器创建和管理session bean 实例,有些时候,你可能需要定制session bean 的管理过程。例如,你可能想在创
建session bean 实例的时候初始化字段变量,或在bean 实例被销毁的时候关掉外部资源。上述这些,你都可能通过
在bean 类中定义生命周期的回调方法来实现。这些方法将会被容器在生命周期的不同阶段调用(如:创建或销
毁时)。通过使有下面所列的注释,EJB 3.0 允许你将任何方法指定为回调方法。这不同于EJB 2.1,EJB 2.1 中,
所有的回调方法必须实现,即使是空的。EJB 3.0 中,bean 可以有任意数量,任意名字的回调方法。
·@PostConstruct:当bean 对象完成实例化后,使用了这个注释的方法会被立即调用。这个注释同时适用于
有状态和无状态的会话bean。
·@PreDestroy:使用这个注释的方法会在容器从它的对象池中销毁一个无用的或者过期的bean 实例之前调
用。这个注释同时适用于有状态和无状态的会话bean。
·@PrePassivate:当一个有状态的session bean 实例空闲过长的时间,容器将会钝化(passivate)它,并把它的
状态保存在缓存当中。使用这个注释的方法会在容器钝化bean 实例之前调用。这个注释适用于有状态的会话bean。
当钝化后,又经过一段时间该bean 仍然没有被操作,容器将会把它从存储介质中删除。以后,任何针对该bean
方法的调用容器都会抛出例外。
·@PostActivate:当客户端再次使用已经被钝化的有状态session bean 时,新的实例被创建,状态被恢复。
使用此注释的session bean 会在bean 的激活完成时调用。这个注释只适用于有状态的会话bean。
·@Init:这个注释指定了有状态session bean 初始化的方法。它区别于@PostConstruct 注释在于:多个@Init
注释方法可以同时存在于有状态session bean 中,但每个bean 实例只会有一个@Init 注释的方法会被调用。这取
决于bean 是如何创建的(细节请看EJB 3.0 规范)。@PostConstruct 在@Init 之后被调用。
另一个有用的生命周期方法注释是@Remove,特别是对于有状态session bean。当应用通过存根对象调用使用了
@Remove 注释的方法时,容器就知道在该方法执行完毕后,要把bean 实例从对象池中移走。
8, 在EJB中依赖注入(dependency injection),类似spring的注入
EJB 3.0,对任何POJO,提供了一个简单的和优雅的方法来解藕服务对象和资源。使用@EJB 注释,你可以将EJB
存根对象注入到任何EJB 3.0 容器管理的POJO 中。如果注释用在一个属性变量上,容器将会在它被第一次访问
之前赋值给它。依赖注入只工作在本地命名服务中,因此你不能注入远程服务器的对象。
例子:
@Stateless(mappedName=" ejb/ InjectionBean ")
@Remote (Injection.class)
public class InjectionBean implements Injection {
@EJB (beanName="HelloWorldBean")
//@EJB (mappedName="HelloWorldBean/remote")
[U16] HelloWorld helloworld;
public String SayHello() {
return helloworld.SayHello("注入者");
}
@Resource[U17] (mappedName = "java:/DefaultMySqlDS")
DataSource myDb;
@EJB (beanName="HelloWorldBean")[U18]
public void setHelloworld(HelloWorld helloworld) {
this.helloworld = helloworld;
Connection conn = myDb.getConnection();。。。。。数据库
} }
8,定时服务(Timer Service)
定时服务用作在一段特定的时间后执行某段程序,估计各位在不同的场合中已经使用过。下面就直接介绍EJB3.0
定时服务的开发过程。定时服务的开发过程与会话Bean 的开发过程大致相同,但比会话Bean 多了几个操作,那
就是使用容器对象SessionContext 创建定时器,并使用@Timeout 注释声明定时器方法。
@Stateless
@Remote (TimerService.class)
public class TimerServiceBean implements TimerService {
private int count = 1;
private @Resource SessionContext ctx;
public void scheduleTimer(long milliseconds){
count = 1;
ctx.getTimerService().createTimer(new Date(new Date().getTime() +
milliseconds),milliseconds, "大家好,这是我的第一个定时器");
}
@Timeout
public void timeoutHandler(Timer timer) {
System.out.println("---------------------");
System.out.println("定时器事件发生,传进的参数为: " + timer.getInfo());
System.out.println("---------------------");
if (count>=5){
timer.cancel();//如果定时器触发5次,便终止定时器
}
count++;
}
}
下面是TimerServiceBean 的Remote 业务接口
TimerService.java
package com.foshanshop.ejb3;
public interface TimerService {
public void scheduleTimer(long milliseconds);
}
通过依赖注入@Resource SessionContext ctx,我们获得SessionContext 对象,调用ctx.getTimerService().createTimer
(Date arg0, long arg1, Serializable arg2)方法创建定时器,三个参数的含义如下:
Date arg0 定时器启动时间,如果传入时间小于现在时间,定时器会立刻启动。
long arg1 间隔多长时间后再次触发定时事件。单位:毫秒
Serializable arg2 你需要传给定时器的参数,该参数必须实现Serializable 接口。
当定时器创建完成后,我们还需声明定时器方法。定时器方法的声明很简单,只需在方法上面加入@Timeout 注
释,另外定时器方法必须遵守如下格式:
void XXX(Timer timer)
在定时事件发生时,此方法将被执行。
9, 安全服务(SECURITY SERVICE)
使用Java 验证和授权服务(JAAS)可以很好地解决在应用程序变得越来越复杂时,安全需求也会变得很复杂的问题,你可以用它来管理应用程序的安全性。JAAS具有两个特性:验证(Authentication)和授权(authorization),认证是完成用户名和密码的匹配校验;授权是决定用户可以访问哪些资源,授权是基于角色的
1)开发的第一步是定义安全域,安全域的定义有两种方法
第一种方法:通过Jboss 发布文件(jboss.xml[U19] )进行定义
<?xml version="1.0" encoding="UTF-8"?>
<jboss>
<!-- Bug in EJB3 of JBoss 4.0.4 GA
<security-domain>java:/jaas/other</security-domain>
-->
<security-domain>other</security-domain>
<unauthenticated-principal>AnonymousUser</unauthenticated-principal>
</jboss>
第二种方法:通过@SecurityDomain 注释进行定义[U20] ,注释代码片断如下:
@Stateless
@Remote ({SecurityAccess.class})
@SecurityDomain("other")
public class SecurityAccessBean implements SecurityAccess{}
2)定义好安全域之后,因为我们使用Jboss 默认的安全域”other”,所以必须使用users.propertes 和roles.properties 存
储用户名/密码及用户角色。现在开发的第二步就是定义用户名,密码及用户的角色。用户名和密码定义在users.propertes 文件,用户所属角色定义在roles.properties 文件。以下是这两个文件的具体配置:
users.propertes(定义了本例使用的三个用户)
lihuoming=123456
zhangfeng=111111
wuxiao=123
roles.properties(定义了三个用户所具有的角色,其中用户lihuoming 具有三种角色)
lihuoming=AdminUser,DepartmentUser,CooperateUser
zhangfeng=DepartmentUser
wuxiao=CooperateUser
以上两个文件必须存放于类路径下。在进行用户验证时,Jboss 容器会自动寻找这两个文件。
3)开发的第三步就是为业务方法定义访问角色。本例定义了三个方法:AdminUserMethod(),
DepartmentUserMethod(),AnonymousUserMethod(),第一个方法只允许具有AdminUser 角色的用户访问,第二个方
法只允许具有DepartmentUser 角色的用户访问,第三个方法允许所有角色的用户访问。下面是Session Bean 代码。
@Stateless
@Remote (SecurityAccess.class)
public class SecurityAccessBean implements SecurityAccess{
@RolesAllowed[U21] ({"AdminUser"})
public String AdminUserMethod() {
return "具有管理员角色的用户才可以访问AdminUserMethod()方法";
}
@RolesAllowed({"DepartmentUser"})
public String DepartmentUserMethod() {
return "具有事业部门角色的用户才可以访问DepartmentUserMethod()方法";
}
@PermitAll[U22]
public String AnonymousUserMethod() {
return "任何角色的用户都可以访问AnonymousUserMethod()方法, 注:用户必须存在
users.properties文件哦";
}
}
自定义安全域
把用户名/密码及角色存放在users.propertes 和roles.properties 文件,不便于日后的管理。大多数情况下我们都希望把用户名/密码及角色存放在数据库中。为此,我们需要自定义安全域。他采用数据库存储用户名及角色(两个表)下面的例子定义了一个名为foshanshop的安全域,安全域在[jboss 安装目录]/server/default/conf/login-config.xml 文件中定义,本例配置片断如下:
<!-- ....................... foshanshop login configuration ....................-->
<application-policy name="foshanshop">
<authentication>
<login-module code="org.jboss.security.auth.spi.DatabaseServerLoginModule"
flag="required">
<module-option name="dsJndiName">java:/DefaultMySqlDS</module-option>
<module-option name="principalsQuery">
select password from sys_user where name=?
</module-option>
<module-option name="rolesQuery">
select rolename,'Roles' from sys_userrole where username=?
</module-option>
<module-option name = "unauthenticatedIdentity">AnonymousUser</module-option>
</login-module>
</authentication>
</application-policy>
上面使用了Jboss 数据库登录模块(org.jboss.security.auth.spi.DatabaseServerLoginModule),他的dsJndiName 属性(数据源JNDI 名)使用DefaultMySqlDS 数据源(本教程自定义的数据源,关于数据源的配置请参考前面章节:JBoss数据源的配置), principalsQuery 属性定义Jboss 通过给定的用户名如何获得密码, rolesQuery 属性定义Jboss通过给定的用户名如何获得角色列表,注意:SQL 中的'Roles'常量字段不能去掉。unauthenticatedIdentity 属性允许匿名用户(不提供用户名及密码)访问。“foshanshop”安全域使用的sys_user 和sys_userrole 表是自定义表,实际项目开发中你可以使用别的表名。sys_user 表必须含有用户名及密码两个字段,字段类型为字符型,至于字符长度视你的应用而定。sys_userrole 表必须含有用户名及角色两个字段,字段类型为字符型,字符长度也视你的应用而定。
4,实体Bean:实体是JPA[U23] 中用来做持久化操作的Java 类.从名字上我们就能猜到,实体bean 代表真实物体的数据,在JDBC+JavaBean 编程中,通常把JDBC查询的结果信息存入JavaBean[U24] ,然后供后面程序进行处理。在这里你可以把实体Bean 看作是用来存放数据的
JavaBean。但比普通JavaBean 多了一个功能,实体bean 除了担负起存放数据的角色,还要负责跟数据库表进行
对象与关系映射(O/R Mapping),通过标注或xml表明实体和表的关系,下图就说明了这一映射:
1)ORMapping的四项基本原则:类型--à表 对象---à行 关系---à外键 属性----->列
2)Java Persistence API 定义了一种方法,可以将常规的普通Java 对象(有时被称作POJO)映射到数据库。这些普
通Java 对象被称作entity bean。[U25] 除了是用Java Persistence 元数据将其映射到数据库外,entity bean 与其他Java 类
没有任何区别。事实上,创建一个Entity Bean 对象相当于新建一条记录,删除一个Entity Bean 会同时从数据库
中删除对应记录,修改一个Entity Bean 时,容器会自动将Entity Bean 的状态和数据库同步。
Java Persistence API 还定义了一种查询语言(JPQL),具有与SQL 相类似的特征,只不过做了裁减,以便处理Java
对象而非原始的关系schema。
3)一个实体Bean 应用由实体类和persistence.xml 文件组成。persistence.xml 文件在Jar 文件的META-INF 目录。
persistence.xml 文件指定实体Bean 使用的数据源及EntityManager 对象的默认行为。persistence.xml 文件的配置说
明如下:<persistence-unit name="StudentMgmtPU" transaction-type="JTA">
<!-- 持久化框架的提供者 -->
<provider>oracle.toplink.essentials.PersistenceProvider</provider>
<!-- 已经在JNDI上配置好了的数据源 -->
<jta-data-source>jdbc/mysql</jta-data-source>
<mapping-file/> xml的映射文件,而在javaee中一般用标注
<properties>
<!-- 在部署的时候创建表 在取消部署的时候删除表 -->
<property name="toplink.ddl-generation" value="drop-and-create-tables"/>
</properties>
</persistence-unit>
</persistence>
若不采用glassfish默认的ToopLink作为持久化框架,采用 Hibernate + 另外配置的数据源
则此时的配置文件格式如下 :
<persistence >
<persistence-unit name="TestPU" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>test.NewEntity</class>
<properties>[U26]
<property name="hibernate.connection.username" value="zhangweifeng "/>
<property name="hibernate.connection.driver_class" value="oracle.jdbc.driver.OracleDriver"/>
<property name="hibernate.connection.password" value="123"/>
<property name="hibernate.connection.url" value="jdbc:oracle:thin:@localhost:1521:XE"/>
<property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
</properties>
</persistence-unit>
</persistence>
- hibernate和jpa的相似之处。
Hibernate jpa
Configuration----------------------------à persistence
SessionFactory---------------------------à EntityManagerFactory
Session -------------------------à EntityManager
| save()-----------------------------à persist()
| update()--------------------------à merge()
|delete()---------------------------à remove()
|get()/load()-----------------------à find()/getRefernce()
Transaction----------------------------àEntityTransaction
配置文件:xxx.cfg.xml--------------------àpersistence.xml
5)jpa的常用标注例子
@Entity([name="Adm"])// @Entity指明这是需要持久化的实体 若加上name属性,则以后在操作实体时 用Adm取代Admin
@Table(name="ADMINS")// 指定数据库中与本实体对应的表 若去掉此标注 则默认与实体名一致
@EntityListeners(EntityLcLoggerListener.class) // 指定监听器 监听对实体的数据库操作
// 建立命名查询,以后根据name即可得到对应的Query
// 如 : Query q = entityManager.createNamedQuery("findByName");
@NamedQueries({
@NamedQuery(name="findByName", query="select a from Admin a where a.name=:name"),
@NamedQuery(name="findAll", query="select a from Admin a")
})
public class Admin implements Serializable[U27] {
private static final long serialVersionUID = 1L;
// ID的生成由JPA自动管理
// 若持久化框架是hibernate 数据库为oracle 则自动生成名为"hibernate_sequence"的序列
// 若持久化框架是toplink 数据库为oracle 则自动创建一个ID号生成表
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
@Basic(optional=false) // 指定在内存中即对数据进行检查 是否满足条件 无需在数据库中才进行检查
@Column[U28] (length=16,nullable=false,unique=true) // 指定字段属性以及约束
private String name;
@Basic(optional=false)
@Column(length=8,nullable=false)
private String password;
@Column(length=64)
private String address;
@Temporal(javax.persistence.TemporalType.DATE) // 由于这里是util包下面的Date类型 故要变换一下
@Column(name="EXPIRE_DATE")// 指定对应的字段名
private Date expireDate;
// 乐观锁时的version字段
@Version
private int version;
public Admin() {
}
public Admin(String name, String password, String address) {
this.name = name;
this.password = password;
this.address = address;
}
// GET and SET 方法
...
}
注意:@Id 注释指定id 属性为表的主键,它可以有多种生成方式:
·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")
private Integer id;
·SEQUENCE:使用数据库的SEQUENCE 列来保证唯一(Oralce 数据库通过序列来生成唯一ID),例子代码如下:
@SequenceGenerator(name="Person_SEQUENCE", //为该生成方式取个名称
sequenceName="Person_SEQ") //sequence的名称(如果不存在,会自动生成)
public class Person implements Serializable{
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="Person_SEQ")
private Integer id;
·IDENTITY:使用数据库的INDENTIT 列来保证唯一(像mysql,sqlserver 数据库通过自增长来生成唯一ID)
·AUTO:由容器挑选一个合适的方式来保证唯一(由容器决定采用何种方式生成唯一主键,hibernate 会根据
数据库类型选择适合的生成方式,相反toplink 就不是很近人情)
·NONE:容器不负责主键的生成,由调用程序来完成。
@GeneratedValue 注释定义了标识字段的生成方式,本例personid 的值由MySQL 数据库自动生成。
注意:其他属性标签
1如果不想让一些成员属性映射成数据库字段,我们可以使用@Transient 注释进行标注,属性将不会被持久化成数据库字段
2,@Lob 注释用作映射这些大数据类型,当属性的类
型为byte[], Byte[]或java.io.Serializable 时,@Lob 注释将映射为数据库的Blob 类型,当属性的类型为char[],
Character[]或java.lang.String 时,@Lob 注释将映射为数据库的Clob 类型
3,属性进行延时加载,这时我们需要用到@Basic 注释
Dao层的编码。操作Admin的EJB :
@Stateless(mappedName="ejb/adminDao")
public class AdminDaoBean implements AdminDaoRemote, AdminDaoLocal {
// 注入EntityManager对象(相当于hibernate中的Session对象)
// 注意这里不是用@Resource注入
/**
* 在测试的时候,可以这么获得EntityManager对象 :
* public static void main(String[] args) {
* // "TestPU"即为配置文件中<persistence-unit/>标签name属性对应的值
* EntityManagerFactory emf = Persistence.createEntityManagerFactory("TestPU");[U29]
* EntityManager em = emf.createEntityManager();
* EntityTransaction et = em.getTransaction();
* et.begin()[U30] ;
* NewEntity s = new NewEntity("123", "11", 18, "111");
* em.persist(s);
* System.out.println("Id: " + s.getId());
* et.commit();
* ...
* }
*/
@PersistenceContext[U31]
private EntityManager em;
public Integer save(Admin admin) {
em.persist(admin);
return admin.getId();
}
public Admin update(Admin admin) {
return em.merge(admin);
}
public Admin get(Integer id) {
return em.find(Admin.class, id);
}
public void delete(Integer id) {
Admin admin = get(id);
em.remove(admin);
}
public Admin getByName(String name) {
Query q = em.createNamedQuery("findByName"[U32] );
// a.name=:name ---> a.name=?
// Query q = em.createQuery("select a from Admin a where a.name=:name");
q.setParameter("name", name);
return (Admin)q.getSingleResult();
}
public java.util.Set<Admin> getAll() {
Query q = em.createNamedQuery("findAll");
// Query q = em.createQuery("select a from Admin a");
return new HashSet<Admin>(q.getResultList());
}
}
注意:持久化实体管理器EntityManager
1, find()或getReference(),Person person = em.find(Person.class,id); Person person = em.getReference (Person.class, 1);
当在数据库中没有找到记录时,getReference()和find()是有区别的,find()方法会返回null,而getReference()方法
会抛出javax.persistence.EntityNotFoundException 例外,另外getReference()方法不保证实体Bean 已被初始化。
如果传递进getReference()或find()方法的参数不是实体Bean,都会引发IllegalArgumentException 例外
2, 添加persist(),em.persist(person);
3, EntityManager.flush()更新实体, 当实体正在被容器管理时,你可以调用实体的set 方法对数据进行修改,在容器决定flush 时,更新的数据才会同步到数据库。如果你希望修改后的数据实时同步到数据库,你可以flush()。将实体的改变立刻刷新到数据库中
4, 合并Merge(),merge ()方法是在实体Bean 已经脱离了EntityManager 的管理时使用,当容器决定flush 时,数据将会同步到数据库中,
5, 删除Remove()
6, 执行JPQL 操作createQuery(),通过EntityManager 的createQuery()或createNamedQuery()方法创建一个Query 对象
(Query query = em.createQuery("select p from Person p where p. name=’黎明’"); List result = query.getResultList();)
7, 执行SQL 操作createNativeQuery(),注意这里操作的是SQL 语句,并非JPQL,千万别搞晕了。
Query query = em.createNativeQuery("select * from person", Person.class); List result = query.getResultList();
8, 刷新实体refresh(),通过refresh()方法刷新实体,容器会把数据库中的新值重写进实体。这种情况一般发生在你获取了实体之后,有人更新了数据库中的记录,这时你需要得到最新的数据
7)事务管理服务
1>.EJB声明式事务(也叫容器管理事务)
由EJB容器来负责管理事务,EJB的Bean类不需要直接编写事务的代码,通过标注或配置文
件来声明事务特性获得容器提供的事务。有两种方式:①一般性的事务管理(只管理一个事务,事务结束后就关闭,一个事物范围内)
②可扩展的事务管理(可以管理多个事务,但是只要一个事物出错,就可能导致与数据库的数据不同步,只能是有状态的bean使用,一般很少用)
@PersistenceContext
protected EntityManager em;
@TransactionAttribute(TransactionAttributeType.REQUIRED[U33] )
public void ModifyProductName(String newname, boolean error) throws Exception {
Query query = em.createQuery("select p from Product p");
List result = query.getResultList();
if (result!=null){
for(int i=0;i<result.size();i++){
Product product = (Product)result.get(i);
product.setName(newname+ i);
em.merge(product);
}
if (error && result.size()>0) throw new TransException ("抛出应用例外");
}
}
}
事务的传播行为
EJB的Bean对待客户端事务的行为
共分为六种:
1.REQUIRED:方法在一个事务中执行,如果调用的方法已经在一个事务中,则使用该事务,否则将创建一
个新的事务。
2.MANDATORY:如果运行于事务中的客户调用了该方法,方法在客户的事务中执行。如果客户没有关联到
事务中,容器就会抛出TransactionRequiredException。如果企业bean 方法必须用客户事务则采用Mandatory 属性。
3.REQUIRESNEW:方法将在一个新的事务中执行,如果调用的方法已经在一个事务中,则暂停旧的事务。在
调用结束后恢复旧的事务。
4.SUPPORTS:如果方法在一个事务中被调用,则使用该事务,否则不使用事务。
5.NOT_SUPPORTED:如果方法在一个事务中被调用,容器会在调用之前中止该事务。在调用结束后,容器
会恢复客户事务。如果客户没有关联到一个事务中,容器不会在运行入该方法前启动一个新的事务。用
NotSupported 属性标识不需要事务的方法。因为事务会带来更高的性能支出,所以这个属性可以提高性能。
6.Never:如果在一个事务中调用该方法,容器会抛出RemoteException。如果客户没有关联到一个事务中,
容器不会在运行入该方法前启动一个新的事务。
标注
@TransactionManagement
用在类之前,用来告诉容器现在使用BMT/CMT
*TransactionManagementType.BEAN
*TransactionManagementType.CONTAINER(默认)
@TransactionAttribute
用在方法之前,用来声明事务传播行为
8)Entity 的生命周期和状态
在EJB3 中定义了四种Entity 的状态:
1. 新实体(new)。Entity 由应用产生,和EJB3 Persistence 运行环境没有联系,也没有唯一的标示符(Identity)。
2. 持久化实体(managed)。新实体和EJB3 Persistence 运行环境产生关联(通过persist(), merge()等方法),在EJB3
Persistence 运行环境中存在和被管理,标志是在EJB3 Persistence 运行环境中有一个唯一的标示(Identity)。
3. 分离的实体(detached)。Entity 有唯一标示符,但它的标示符不被EJB3 Persistence 运行环境管理, 同样的该
Entity 也不被EJB3 Persistence 运行环境管理。
4. 删除的实体(removed)。Entity 被remove()方法删除,对应的纪录将会在当前事务提交的时候从数据库中删除
持久化规范允许你在实体类中实现回调方法,当这些事件发生时将会通知你的实体对象。当然你也可以使用一个外部类去拦截这些事件,这个外部类称作实体监听者。通过@EntityListeners[U34] 注释绑定到实体Bean。
生命周期回调事件
如果需要在生命周期事件期间执行自定义逻辑,请使用以下生命周期事件注释关联生命周期事件与回调方法,EJB
3.0 允许你将任何方法指定为回调方法。这些方法将会被容器在实体生命周期的不同阶段调用。
下图演示了实体生命周期事件之间的关系:
@PostLoad 事件在下列情况触发
1.执行EntityManager.find( )或getreference( )方法载入一个实体后
2.执行JPQL 查询过后
3.EntityManager.refresh( )方法被调用后
@PrePersist 和@PostPersist 事件在实体对象插入到数据库的过程中发生,@PrePersist 事件在调用
EntityManager.persist( )方法后立刻发生,级联保存也会发生此事件,此时的数据还没有真实插入进数据库。
@PostPersist 事件在数据已经插入进数据库后发生。
@PreUpdate[U35] 和@PostUpdate 事件的触发由更新实体引起,@PreUpdate 事件在实体的状态同步到数据库之前触
发,此时的数据还没有真实更新到数据库。@PostUpdate 事件在实体的状态同步到数据库后触发,同步在事务提
交时发生。
@PreRemove 和@PostRemove 事件的触发由删除实体引起,@PreRemove 事件在实体从数据库删除之前触发,
即调用了EntityManager.remove()方法或者级联删除时发生,此时的数据还没有真实从数据库中删除。
@PostRemove 事件在实体已经从数据库中删除后触发。
6.12.2 在外部类中实现回调---------最好覆盖toString()方法
Entity listeners(实体监听者)用作拦截实体回调事件,通过@javax.persistence.EntityListeners 注释可以把他们绑
定到一个实体类。在Entity listeners 类里,你可以指定一个方法拦截实体的某个事件,所指定的方法必须带有一
个Object 参数及返回值为void,格式如下:void <MethodName>(Object)
监听器 : 一般可以用作日志管理,对Entity生命周期回调。
public class EntityLcLoggerListener {
private static Logger logger = Logger.getLogger("EnityLcLoggerListener");
@PrePersist
public void persist(Object e) {
logger.info("before saving " + e);
}
@PostPersist
public void persisted(Object e) {
logger.info("after saving " + e);
}
@PreUpdate
public void update(Object e) {
logger.info("before updating " + e);
}
@PostUpdate
public void updateed(Object e) {
logger.info("after updating " + e);
}
@PreRemove
public void remove(Object e) {
logger.info("before removing " + e);
}
@PostRemove
public void removed(Object e) {
logger.info("after removing " + e);
}
@PostLoad
public void loaded(Object e) {
logger.info("after loading " + e);
}
6.12.3 在Entity 类中实现回调
除了外部类可以实现生命周期事件的回调,你也可以把回调方法写在Entity 类中。要注意:直接写在Entity 类
中的回调方法不需带任何参数,格式如下:void <MethodName>()
EntityLifecycle.java 程序片断
@Entity
@Table(name = "EntityLifecycle")
public class EntityLifecycle implements Serializable{
@PostLoad
public void postLoad() {
System.out.println("载入了实体Bean{" + this.getClass().getName( ) + "}");}
9)JPA的高级部分
9.1多态,继承
因为关系数据库的表之间不存在继承关系,Entity 提供三种基本的继承映射策略[U36] :
每个类分层结构一张表(一张大表)(table per class hierarchy)
每个子类一张表(table per subclass)
每个具体类一张表(table per concrete class)
9.1.1 每个类分层结构一张表(一张大表)(table per class hierarchy) 需要一个字段来区分子类
@Entity
1 单表对应整个类层次结构 也就是一张超级大表的形式 有继承关系 查询效率比较高
但是属性多的话 表会变得很宽 同时子类型字段不容许为空 */
@Table(name="PAYMENTS")// 指定对应的表名
@Inheritance(strategy=InheritanceType.SINGLE_TABLE[U37] ) //指定形式为一张大表
由于是一张大表 name属性指定实体类型字段名 discriminatorType属性指定此字段值类型
@DiscriminatorColumn[U38] (name="PAYMENT_TYPE",discriminatorType=DiscriminatorType.INTEGER)
2,在子类中的实体bean[U39] 中标注@DiscriminatorValue(“car”)
该策略的优点:
SINGLE_TABLE 映射策略在所有继承策略中是最简单的,同时也是执行效率最高的。他仅需对一个表进行
管理及操作,持久化引掣在载入entiry 或多态连接时不需要进行任何的关联,联合或子查询,因为所有数据都存
储在一个表。
该策略的缺点:
这种策略最大的一个缺点是需要对关系数据模型进行非常规设计,在数据库表中加入额外的区分各个子类的
字段,此外,不能为所有子类的属性对应的字段定义not null 约束,此策略的关系数据模型完全不支持对象的继
承关系。
选择原则:查询性能要求高,子类属性不是非常多时,优先选择该策略。
9.1.2每个子类一张表(table per subclass)
这种映射方式为每个类创建一个表。在每个类对应的表中只需包含和这个类本身的属性对应的字段,子类对应的
表参照父类对应的表。
1,父类的标注 :@Entity
@Inheritance(strategy=InheritanceType.JOINED)
@Table(name="Vehicle")
2,子类中: @Entity
@Table(name="Car")
@PrimaryKeyJoinColumn(name="CarID")//把主键对应的列名更改为CarID
该策略的优点:
这种映射方式支持多态关联和多态查询,而且符合关系数据模型的常规设计规则。在这种策略中你可以对子
类的属性对应的字段定义not null 约束。
该策略的缺点:
它的查询性能不如上面介绍的映射策略。在这种映射策略下,必须通过表的内连接或左外连接来实现多态查
询和多态关联。
选择原则:子类属性非常多,需要对子类某些属性对应的字段进行not null 约束,且对性能要求不是很严格时,
优先选择该策略。
9.1.3 每个具体类一张表(table per concrete class)
这种映射方式为每个类创建一个表。在每个类对应的表中包含和这个类所有属性(包括从超类继承的属性)对应
的字段。要使用每个具体类一张表(table per concrete class)策略,需要把@javax.persistence.Inheritance 注释的strategy 属性
设置为InheritanceType.TABLE_PER_CLASS。
@Entity
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
@Table(name="Vehicle")
注意:一旦使用这种策略就意味着你不能使用AUTO generator 和IDENTITY generator,即主键值不能采用数据库
自动生成.
该策略的优点:
在这种策略中你可以对子类的属性对应的字段定义not null 约束。
该策略的缺点:
不符合关系数据模型的常规设计规则,每个表中都存在属于基类的多余的字段。同时,为了支持策略的映射,持
久化管理者需要决定使用什么方法,一种方法是在entity 载入或多态关联时,容器使用多次查询去实现,这种方
法需要对数据库做几次来往查询,非常影响执行效率。另一种方法是容器通过使用SQLUNIOU 查询来实现这种
策略。
选择原则:除非你的现实情况必须使用这种策略,一般情况下不要选择。
9.2 JTA对实体对应关系的处理 :
one --- one :
Order类 :
@Entity(name="TOrder")
@Table(name="ORDERS")
public class Order implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
// 指定关系属性的对应关系 以及级联策略
@OneToOne(cascade={CascadeType.ALL})
@JoinColumn(name="SID")
private Shipment shipment;
[U40]
public Order() {
}
public Order(String name) {
this.name = name;
}
// GET and SET
...
}
Shipment类 :
@Entity
@Table(name="SHIPMENTS")
public class Shipment implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String city;
private String zipcode;
@OneToOne(mappedBy="shipment[U41] ")
private Order order;
public Shipment() {
}
public Shipment(String city, String zipcode) {
this.city = city;
this.zipcode = zipcode;
}
// GET and SET
...
}
League (1) --- (n) Team --- (n) Player
League :
@Entity
@Table(name="LEAGUES")
public class League implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(length=7)
private Long id;
@Basic(optional=false)
@Column(length=64,unique=true,nullable=false)
private String name;
@Basic(optional=false)
@Column(length=32,nullable=false)
private String sport;
// 若加上下面代码 则为双向关联
// mappedBy="league"表明这一端非主体 无外键ID 不维护关系 主体端对应关系属性名为"league"
// fetch=FetchType.EAGER指定采用热切查询 即将关系对象一并查询出来
// 在默认的情况下 若关系对象为Set集合的话 采用的是懒加载 若非集合 则为非懒加载
// @OneToMany(mappedBy="league", fetch=FetchType.EAGER, cascade={...})
// private Set<Team> teams = new HashSet<Team>();
public League() { }
public League(String name, String sport) { this.name = name; this.sport = sport;
}// GET and SET
...
}
Team :
@Entity
@Table(name="TEAMS")
public class Team implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(length=7)
private Long id;
@Basic(optional=false)
@Column(length=64,unique=true,nullable=false)
private String name;
@Column(length=64)
private String city;
// 主体方 拥有外键ID 维护关系 省略的标记为 : @JoinColumn(name="league_id")
@ManyToOne(cascade={CascadeType.MERGE,CascadeType.PERSIST, CascadeType.REFRESH})
private League league;
// 若加上下面代码 则为双向关联
// mappedBy="teams"表明这一端非主体 无外键ID 不维护关系 主体端对应关系属性名为"teams"
// fetch=FetchType.EAGER指定采用热切查询 即将关系对象一并查询出来
// @ManyToMany(mappedBy="teams", fetch=FetchType.EAGER, cascade={...})
// private Set<Player> players = new HashSet<Player>();
public Team() {
}
public Team(String name, String city) {
this.name = name;
this.city = city;
}// GET and SET
...
}
Player :
@Entity
@Table(name="PLAYERS")
public class Player implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(length=7)
private Long id;
@Basic(optional=false)
@Column(length=32, nullable=false)
private String name;
@Column(length=16)
private String position;
@Column(precision=12,scale=2) // 指定精度
private BigDecimal salary;
// 主体方 由于拥有的关系属性是一个集合 故不可能把外键ID建在本表中
// 此时 就必须建中间表了
@ManyToMany(fetch=FetchType.EAGER,cascade={CascadeType.MERGE,CascadeType.PERSIST, CascadeType.REFRESH})
// 配置中间表
@JoinTable(
name="P_T_MAP",
joinColumns=@JoinColumn(name="PID"),
inverseJoinColumns=@JoinColumn(name="TID")
)
private Set<Team> teams = new HashSet<Team>();
public Player() {}
public Player(String name, String position, BigDecimal salary) {
this.name = name;
this.position = position;
this.salary = salary;
}// GET and SET
...
public void joinTeam(Team team){ teams.add(team); }
public void leaveTeam(Team team){ teams.remove(team); }
}
8 整合了struts1的一个企业级应用 :
(1) EJB应用部分 : 这里的MVC模式中 负责Dao操作的EJB应用部分显然是M一层
[1] 三个实体 League Team Player 以及他们之间的关系,在JPA中已说明
[2] 负责处理各个实体的Dao
由于一些方法是每个Dao都得具备的 所以 做成模版接口 :
public interface GenericDao<E, ID extends Serializable> {
E save(E e);
E get(ID id);
E update(E e);
void delete(ID id);
Set<E> getAll();
}
模版接口的实现 :
public abstract class GenericDaoJPAImpl<E, ID extends Serializable> implements GenericDao<E, ID>{
abstract protected EntityManager [U42] getEntityManager();
private Class<E> entityClass;
private String entityName;
public GenericDaoJPAImpl(){
entityClass= (Class<E>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
Entity e = entityClass.getAnnotation(Entity.class);
if(e != null){ entityName = e.name();}
if(entityName == null){ entityName = entityClass.getSimpleName();}
}
public void setEntityName(String entityName){ this.entityName = entityName; }
public E save(E e) { getEntityManager().persist(e); return e;
}
public E get(ID id) { E e = getEntityManager().find(entityClass, id); return e; }
public E update(E e) { return getEntityManager().merge(e); }
public void delete(ID id) { E e = get(id); getEntityManager().remove(e); }
public Set<E> getAll(){
Query q = getEntityManager().createQuery("select e from " + entityName + " e");
return new HashSet<E>(q.getResultList());
}
}
由于EJBDao不会直接暴露给C层调用 而是通过一个"门面"把这些EJBDao聚合起来 C层通过这个门面
来调用Dao中的方法 也是因为这样 EJBDao只需要提供本地接口(供"门面"本地调用) 无需提供远程接口
LeagueDao 本地接口 :
@Local
public interface LeagueDao extends GenericDao<League, Long>{
}
LeagueDao 实现 :
package net.kettas.roster.dao;
@Stateless
public class LeagueDaoBean extends GenericDaoJPAImpl<League, Long> implements LeagueDao{
@PersistenceContext // 注入EntityManager对象
private EntityManager em;
@Override
protected EntityManager getEntityManager() { return em; }
}
TeamDao 本地接口 :
@Local
public interface TeamDao extends GenericDao<Team, Long>{
Set<Team> getByLeague(Integer leagueId);
}
TeamDao 实现 :
@Stateless
public class TeamDaoBean extends GenericDaoJPAImpl<Team, Long> implements TeamDao{
@PersistenceContext
private EntityManager em;
@Override
protected EntityManager getEntityManager() { return em;
}
public Set<Team> getByLeague(Integer leagueId) {
Query q = em.createQuery("select t from Team t where t.league.id=:leagueId");
return new HashSet<Team>(q.getResultList());
}
}
PlayDao 本地接口 :
@Local
public interface PlayerDao extends GenericDao<Player, Long> {
Set<Player> getPlayersOfTeam(Integer teamId);
Set<Player> getPlayersByPosition(String position);
Set<Player> getPlayersHigherSalary(String name);
Set<Player> getPlayersBySalaryRange(BigDecimal high, BigDecimal low);
Set<Player> getPlayersBySport(String sport);
Set<Player> getPlayersByCity(String city);
Set<Player> getPlayersNotOnTeam();
Set<String> getSportsOfPlayer(Integer playerId);
}
PlayDao 实现 :
@Stateless
public class PlayerDaoBean extends GenericDaoJPAImpl<Player, Long> implements PlayerDao {
@PersistenceContext
private EntityManager em;
@Override
protected EntityManager getEntityManager() {
return em;
}
public Set<Player> getPlayersOfTeam(Integer teamId) {
Query q = em.createQuery("select p from Player p join p.teams t where t.id=:teamId");
q.setParameter("teamId", teamId);
return new HashSet<Player>(q.getResultList());
}
public Set<Player> getPlayersByPosition(String position) {
Query q = em.createQuery("select p from Player p where p.position=:position");
q.setParameter("position", position);
return new HashSet<Player>(q.getResultList());
}
public Set<Player> getPlayersHigherSalary(String name) {
Query q = em.createQuery("select p from Player p, Player p1 where p.salary > p1.salary and p1.name=:name");
q.setParameter("name", name);
return new HashSet<Player>(q.getResultList());
}
public Set<Player> getPlayersBySalaryRange(BigDecimal high, BigDecimal low) {
Query q = em.createQuery("select p from Player p where p.salary between :high and :low"[U43] );
q.setParameter("high", high).setParameter("low", low);
return new HashSet<Player>(q.getResultList());
}
public Set<Player> getPlayersBySport(String sport) {
Query q = em.createQuery("select p from Player p join p.teams t where t.league.sport=:sport");
q.setParameter("sport", sport);
return new HashSet<Player>(q.getResultList());
}
public Set<Player> getPlayersByCity(String city) {
Query q = em.createQuery("select p from Player p join p.teams t where t.city=:city");
q.setParameter("city", city);
return new HashSet<Player>(q.getResultList());
}
public Set<Player> getPlayersNotOnTeam() {
Query q = em.createQuery("select p from Player p where p.teams is empty");
return new HashSet<Player>(q.getResultList());
}
public Set<String> getSportsOfPlayer(Integer playerId) {
Query q = em.createQuery("select l.sport from Player p join p.teams t join t.league l where p.id=:playerId");
q.setParameter("playerId", playerId);
return new HashSet<String>(q.getResultList());
}
}
[3] 负责把这些EJBDao聚合到一起供C层(Struts)调用的"门面",显然门面应该同时实现
本地和远程接口 :
本地接口 :
package net.kettas.roster;
@Local
public interface RosterLocal {
League createLeague(League league);
Team createTeam(Long leagueId, Team team);
Player createPlayer(Player player);
void joinTeam(Long playerId, Long teamId);
void leaveTeam(Long playerId, Long teamId);
Set<Player> getPlayersBySport(String sport);
}
远程接口略
"门面"的实现 :
@Stateless(mappedName = "ejb/roster")
public class RosterBean implements RosterRemote, RosterLocal {
// 注入所有EJBDao
@EJB(beanName = "LeagueDaoBean")
private LeagueDao leagueDao;
@EJB(beanName = "TeamDaoBean")
private TeamDao teamDao;
@EJB(beanName = "PlayerDaoBean")
private PlayerDao playerDao;
public League createLeague(League league) {
League l = leagueDao.save(league);
return l;
}
public Team createTeam(Long leagueId, Team team) {
Team t = teamDao.save(team);
League l = leagueDao.get(leagueId);
t.setLeague(l);
return t;
}
public Player createPlayer(Player player) {
Player p = playerDao.save(player);
return p;
}
public void joinTeam(Long playerId, Long teamId) {
Player p = playerDao.get(playerId);
Team t = teamDao.get(teamId);
p.joinTeam(t);
}
public void leaveTeam(Long playerId, Long teamId) {
Player p = playerDao.get(playerId);
Team t = teamDao.get(teamId);
p.leaveTeam(t);
}
public Set<Player> getPlayersBySport(String sport) {
Set<Player> players = playerDao.getPlayersBySport(sport);
Set<Player> ps = new HashSet<Player>();
for(Player p : players){
Player ptmp = new Player(p.getName(),p.getPosition(),p.getSalary());
ptmp.setId(p.getId());
for(Team t : p.getTeams()){
Team ttmp = new Team(t.getName(),t.getCity());
ttmp.setId(t.getId());
League l = new League(t.getLeague().getName(), t.getLeague().getSport());
l.setId(t.getLeague().getId());
ttmp.setLeague(l);
ptmp.joinTeam(ttmp);
}
ps.add(ptmp);
}
return ps;
}
// 其他方法
...
}
(2) Web应用部分(将EJB应用部分导入其中 就像导JAR包一般)
[1] 由于Action中不能通过@EJB(beanName="")的方式注入EJB应用中的"门面",故这里必须提供
一些工具类来获取"门面",实现间接访问EJBDao
此类定义一个Map,在查找JNDI上的对象时,先看看Map中是否已经含有此对象,若有则直接返回
无需重新去JNDI上查找,节省时间和资源。若无,则先将查找出来的对象放入Map中, 然后返回
// 了解此工具类的作用
public class CachingServiceLocator {
private InitialContext ic;
private Map cache;
private static CachingServiceLocator me;
static {
try {
me = new CachingServiceLocator();
} catch(NamingException se) {
throw new RuntimeException(se);
}
}
private CachingServiceLocator() throws NamingException {
ic = new InitialContext();
cache = Collections.synchronizedMap(new HashMap());
}
public static CachingServiceLocator getInstance() { return me; }
private Object lookup(String jndiName) throws NamingException {
Object cachedObj = cache.get(jndiName);
if (cachedObj == null) {
cachedObj = ic.lookup(jndiName);
cache.put(jndiName, cachedObj);
}
return cachedObj;
}
/**
* will get the ejb Local home factory. If this ejb home factory has already been
* clients need to cast to the type of EJBHome they desire
*
* @return the EJB Home corresponding to the homeName
*/
public EJBLocalHome getLocalHome(String jndiHomeName) throws NamingException {
return (EJBLocalHome) lookup(jndiHomeName);
}
/**
* will get the ejb Remote home factory. If this ejb home factory has already been
* clients need to cast to the type of EJBHome they desire
* @return the EJB Home corresponding to the homeName
*/
public EJBHome getRemoteHome(String jndiHomeName, Class className) throws NamingException {
Object objref = lookup(jndiHomeName);
return (EJBHome) PortableRemoteObject.narrow(objref, className);
}
/**
* This method helps in obtaining the topic factory
* @return the factory for the factory to get topic connections from
*/
public ConnectionFactory getConnectionFactory(String connFactoryName) throws NamingException {
return (ConnectionFactory) lookup(connFactoryName);
}
/**
* This method obtains the topc itself for a caller
* @return the Topic Destination to send messages to
*/
public Destination getDestination(String destName) throws NamingException {
return (Destination) lookup(destName);
}
/**
* This method obtains the datasource
* @return the DataSource corresponding to the name parameter
*/
public DataSource getDataSource(String dataSourceName) throws NamingException {
return (DataSource) lookup(dataSourceName);
}
/**
* This method obtains the mail session
* @return the Session corresponding to the name parameter
*/
public Session getSession(String sessionName) throws NamingException {
return (Session) lookup(sessionName);
}
/**
* @return the URL value corresponding
* to the env entry name.
*/
public URL getUrl(String envName) throws NamingException {
return (URL) lookup(envName);
}
/**
* @return the boolean value corresponding
* to the env entry such as SEND_CONFIRMATION_MAIL property.
*/
public boolean getBoolean(String envName) throws NamingException {
Boolean bool = (Boolean) lookup(envName);
return bool.booleanValue();
}
/**
* @return the String value corresponding
* to the env entry name.
*/
public String getString(String envName) throws NamingException {
return (String) lookup(envName);
}
public Object getEjb3BI(String jndiName) throws NamingException{
return lookup(jndiName);
}
}
异常类 :
package net.kettas.roster;
import javax.naming.NamingException;
class RosterException extends RuntimeException{
public RosterException(NamingException ex) {
super(ex);
}
}
通过上面的工具类,查找指定jndi名的EJB :
public class RosterUtils {
// 这里的JNDI引用名是是在web.xml中配置的
public static final String ROSTER_LOCAL = "java:comp/env/ejb/rosterLocal";
public static final String ROSTER_REMOTE = "java:comp/env/ejb/rosterRemote";
public static RosterLocal getLocal(){
try {
return (RosterLocal) CachingServiceLocator.getInstance().getEjb3BI(ROSTER_LOCAL);
} catch (NamingException ex) {
Logger.getLogger(RosterUtils.class.getName()).log(Level.SEVERE, null, ex);
throw new RosterException(ex);
}
}
public static RosterRemote getRemote(){
try {
return (RosterRemote) CachingServiceLocator.getInstance().getEjb3BI(ROSTER_REMOTE);
} catch (NamingException ex) {
Logger.getLogger(RosterUtils.class.getName()).log(Level.SEVERE, null, ex);
throw new RosterException(ex);
}
}
}
[2] 配置文件 :
web.xml :
<web-app >
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>debug</param-name>
<param-value>2</param-value>
</init-param>
<init-param>
<param-name>detail</param-name>
<param-value>2</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<session-config>
<!-- 在配置文件中单位是分钟 若在程序里面 那就是秒了 -->
<session-timeout> 30</session-timeout>
</session-config>
<!-- EJB的JNDI引用名在这里配置 -->
<ejb-ref>
<ejb-ref-name>ejb/rosterRemote</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<home/>
<remote>net.kettas.roster.RosterRemote</remote>
<ejb-link>RosterBean</ejb-link>
</ejb-ref>
<ejb-local-ref>
<ejb-ref-name>ejb/rosterLocal</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<local-home/>
<local>net.kettas.roster.RosterLocal</local>
<ejb-link>RosterBean</ejb-link>
</ejb-local-ref>
</web-app>
struts-config.xml :
<struts-config>
<form-beans>
<form-bean name="LeagueForm" type="net.kettas.roster.form.LeagueForm"/>
</form-beans>
<action-mappings>
<action input="/leagueInfo.jsp" name="LeagueForm" parameter="create" path="/create" scope="request" type="net.kettas.roster.action.LeagueAction" validate="false">
<forward name="success" path="/displayLeague.jsp"/>
</action>
</action-mappings>
<!-- 资源文件 -->
<message-resources parameter="net/kettas/roster/ApplicationResource"/>
</struts-config>
[3] Action :
public class LeagueAction extends MappingDispatchAction {
/* forward name="success" path="" */
private final static String SUCCESS = "success";
public ActionForward create(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
League l = ((LeagueForm)form).getLeague();
l = RosterUtils.getLocal().createLeague(l); // 通过工具类 取得"门面" 调用其中方法
((LeagueForm)form).setLeague(l);
return mapping.findForward(SUCCESS);
}
}
ActionForm :
public class LeagueForm extends org.apache.struts.action.ActionForm {
private League league = new League();
public Long getId(){ return league.getId();}
public void setId(Long id){ league.setId(id); }
public String getName(){ return league.getName();}
public void setName(String name){ league.setName(name); }
public String getSport(){ return league.getSport();}
public void setSport(String sport){ league.setSport(sport); }
public League getLeague() { return league; }
public void setLeague(League league) { this.league = league; }
}
[4] 前端页面 :
创建League leagueInfo.jsp :
<%@page contentType="text/html" pageEncoding="GBK"%>
<html>
<body>
<h2>Please enter a league to add</h2>
<form action="create.do">
Name:<input type="text" name="name"><br/>
Sport:<input type="text" name="sport"><br/>
<input type="submit" value="Create a League">
</form>
</body>
</html>
显示League信息 displayLeague.jsp :
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<body>
<h2>League Information is</h2>
<!-- requestScope是jstl的隐含对象,代表request范围内可用的变量,
和jsp中使用request基本一样 -->
ID: ${requestScope.LeagueForm.league.id}<br/>
Name: ${requestScope.LeagueForm.league.name}<br/>
Sport: ${requestScope.LeagueForm.league.sport}<br/>
<a href="leagueInfo.jsp">Add a league</a>
</body>
</html>
持久化的高级应用:1大量的更新和删除
- 级联操作
- 乐观锁的版本不会更新,verson不会自动更新
- 持久化不会同步
2,Join连接操作
- 隐式连接 person.name 点号连接
- 显示连接:A 内连接(以左为主,但是右不能为空)B,左连接 (以左为主,右可以为空)
C,JOIN FETCH实例化查询 (参考hibernate)
注意,最好不用 from T1,T2 这种方式查询 会产生笛卡尔连接降低查询性能(比如10乘以10的查询次数)
最好用 from T1 join T2 连接 子查询可以用 join代替
3,查询时可以构建新对象,select new person() from xxxx ------写一个实体有相应的构造方法
Java 消息服务(Java Message Service,简称JMS)是企业级消息传递系统,紧密集成于Jboss Server 平台之中。
企业消息传递系统使得应用程序能够通过消息的交换与其他系统之间进行通信。
消息组成
消息传递系统的中心就是消息。一条Message 分为三个组成部分:
· 头(header)是个标准字段集,客户机和供应商都用它来标识和路由消息。
JMSMessageID: 标识提供者发送的每一条消息, 发送过程中由提供者设置
JMSDestination: 消息发送的Destination, 由提供者设置
JMSDeliveryMode: 包括DeliveryMode.PERSISTENT(被且只被传输一次)和
DeliveryMode.NON_PERSISTENT(最多被传输一次)
JMSTimestamp: 提供者发送消息的时间, 由提供者设置
JMSExpiration: 消息失效的时间, 是发送方法的生存时间和当前时间值的和, 0 表明消息不会过期
JMSPriority: 由提供者设置, 0 最低, 9 最高
JMSCorrelationID: 用来链接响应消息和请求消息, 由发送消息的JMS 程序设置
JMSReplyTo: 请求程序用它来指出回复消息应发送的地方
JMSType: JMS 程序用来指出消息的类型
JMSRedelivered: 消息被过早的发送给了JMS 程序, 程序不知道消息的接受者是谁
· 属性(property)支持把可选头字段添加到消息。如果您的应用程序需要不使用标准头字段对消息编目和分类,
您就可以添加一个属性到消息以实现这个编目和分类。提供set<Type>Property(...) 和get<Type>Property(...) 方法
以设置和获取各种Java 类型的属性,包括Object。JMS 定义了一个供应商选择提供的标准属性集。
JMSXUserID: 发送消息的用户的身份
JMSXAppID: 发送消息的应用程序的身份
JMSXDeliveryCount: 尝试发送消息的次数
JMSXGroupID: 该消息所属的消息组的身份
JMSXGroupSeq: 该消息在消息组中的序号
JMSXProducerTxID: 生成该消息的事物的身份
JMSXConsumerTxID: 使用该消息的事物的身份
JMSXRcvTimestamp: JMS 将消息发送给客户的时间
· 消息的主体(body)包含要发送给接收应用程序的内容。每个消息接口特定于它所支持的内容类型。
JMS 为不同类型的内容提供了它们各自的消息类型,但是所有消息都派生自Message 接口。
· StreamMessage:包含Java 基本数值流,用标准流操作来顺序的填充和读取。
· MapMessage:包含一组名/值对;名称为string 类型,而值为Java 的基本类型。
· TextMessage:包含一个String。
· ObjectMessage:包含一个Serializable Java 对象;能使用JDK 的集合类。
· BytesMessage:包含未解释字节流: 编码主体以匹配现存的消息格式。
消息的传递模型:
JMS 支持两种消息传递模型:点对点(point-to-point,简称PTP)和发布/订阅(publish/subscribe,简称pub/sub)。
这两种消息传递模型非常相似,只有以下区别:
PTP 消息传递模型规定了一条消息只能传递给一个接收方。
Pub/sub 消息传递模型允许一条消息传递给多个接收方。
一个队列可以关联多个队列发送方和接收方,但一条消息仅传递给一个队列接收方。如果多个队列接收方正在监
听队列上的消息,jboss JMS 将根据“先来者优先”的原则确定由哪个队列接收方接收下一条消息。如果没有队
列接收方在监听队列,消息将保留在队列中,直至队列接收方连接队列为止。
2.发布/订阅消息传递
通过发布/订阅(pub/sub) 消息传递模型,应用程序能够将一条消息发送到多个应用程序。Pub/sub 消息传递应用
程序可通过订阅主题来发送和接收消息。主题发布者(生成器)可向特定主题发送消息。主题订阅者(使用者)
从特定主题获取消息。
与PTP 消息传递模型不同,pub/sub 消息传递模型允许多个主题订阅者接收同一条消息。JMS 一直保留消息,
直至所有主题订阅者都收到消息为止。
上面两种消息传递模型里,我们都需要定义消息发送者和接收者,消息发送者把消息发送到Jboss JMS 某个
Destination,而消息接收者从Jboss JMS 的某个Destination 里获取消息。消息接收者可以同步或异步接收消息,
一般而言,异步消息接收者的执行和伸缩性都优于同步消息接收者
4,消息驱动Bean(MDB):是设计用来专门处理基于消息请求的组件。它能够收发异步JMS 消息,并能够轻易地与
其他EJB 交互。它特别适合用于当一个业务执行的时间很长,而执行结果无需实时向用户反馈的这样一个场合。参考spring的消息
消息发送者 :
public class Main {
@Resource(mappedName="jms/qcf")
private static QueueConnectionFactory qcf; // 得到会话连接工厂
@Resource(mappedName="jms/q")// 消息发送的目的地
private static Queue q;
/**
* @param args the command line arguments
*/
public static void main(String[] args){
QueueConnection con = null;
QueueSession ses = null;
QueueSender sender = null;
try{
con = qcf.createQueueConnection(); // 得到会话连接
ses = con.createQueueSession(true, QueueSession.AUTO_ACKNOWLEDGE); // 根据会话连接创建一个会话
sender = ses.createSender(q); // 根据会话以及消息发送目的地创建消息发送者
Message msg = ses.createTextMessage("My first JMS program."); // 根据会话创建消息
sender.send(msg); // 向目的地发送消息
ses.commit(); // 提交会话
}catch(JMSException e){
try {
ses.rollback();
} catch (JMSException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
e.printStackTrace();
}finally{
try{ if(sender != null) sender.close();
}catch(JMSException e){e.printStackTrace();}
try{ if(ses != null) ses.close();
}catch(JMSException e){e.printStackTrace();}
try{ if(con != null) con.close();
}catch(JMSException e){e.printStackTrace();}
}
}
}
消息接收者 :
public class Main {
@Resource(mappedName="jms/qcf")
private static QueueConnectionFactory qcf;
@Resource(mappedName="jms/q")
private static Queue q;
public static void main(String[] args) {
QueueConnection con = null;
QueueSession ses = null;
QueueReceiver receiver = null;
try{
con = qcf.createQueueConnection();
ses = con.createQueueSession(true, QueueSession.AUTO_ACKNOWLEDGE);
receiver = ses.createReceiver(q);
con.start();
TextMessage msg = (TextMessage)receiver.receive();
System.out.println(msg.getText());
ses.commit();
}catch(JMSException e){
e.printStackTrace();
try {
ses.rollback();
} catch (JMSException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}finally{
try{
if(receiver != null) receiver.close();
}catch(JMSException e){e.printStackTrace();}
try{
if(ses != null) ses.close();
}catch(JMSException e){e.printStackTrace();}
try{
if(con != null) con.close();
}catch(JMSException e){e.printStackTrace();}
}
}
}
9,web 服务
Web Service:1 程序间以一种标准的方式通讯(和程序的开发语言,运行的操作系统,硬件,网络无关)
2,用SOAP ,WSDL等通讯,以xml文档进行数据的交换的网络应用程序
技术 接口 线上协议 名字服务
Web Servive WSDL SOAP UDDI
EJB java RMI IIOP JNDI
Web Service特点 : 跨平台、跨语言、跨Internet,最底层是由 XML + HTTP 实现的。
三个角色:
实现一个完整的Web 服务包括以下步骤:
◆ Web 服务提供者设计实现Web 服务,并将调试正确后的Web 服务通过Web 服务中介者发布,并在UDDI 注册中心注册 ; (发布)
◆ Web 服务请求者向Web 服务中介者请求特定的服务,中介者根据请求查询 UDDI 注册中心,为请求者寻找满足请求的服务(其实现是通过服务注册器(uddi)来获得wsdl文件,再使用wsdl文档将请求绑定到SOAP调用服务提供者的服务); (发现)
◆ Web 服务中介者向Web 服务请求者返回满足条件的Web 服务描述信息,该描述信息用WSDL 写成,各种支持Web 服务的机器都能阅读;(发现)
◆ 利用从Web 服务中介者返回的描述信息生成相应的SOAP 消息,发送给Web 服务提供者,以实现Web 服务的调用;(绑定)
◆ Web 服务提供者按SOAP 消息执行相应的Web 服务,并将服务结果返回给Web 服务请求者。(绑定)
WSDL (本质是xml文档,web服务的描述语言)
包括三个部分 : ①抽象(定义类型、消息...) 指定服务内容(方法参数,返回值,数据类型)
| <types><!--定义数据类型与元素-->
| - <message[U44] name="HelloSoapIn"></message>
| <portType name="HelloWorldSoap"><!--定义web服务的抽象接口,由binding和service元素来实现-->
<operation name="Hello"><!--声明每一个operation元素要用一个或多个消息定义来定义它的输入,输出以及错误
<input message="s0:HelloSoapIn" /> <!--传递到web服务的有效负载-->
<output message="s0:HelloSoapOut" /> <!--传递给客户的有效负载-->
</operation>
</portType>
②绑定(生成SOAP)
<binding name="HelloWorldSoap" type="s0:HelloWorldSoap"><!--将一个抽象portType映射到一组具体协议(如soap和http),消息传递样式(rpc或文档)以及编码样式-->
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document" /> <!--指定了传输soap消息的internet协议以及其操作的错误消息传递样式(rpc或文档)-->
- <operation name="Hello">
<soap:operation soapAction="http://workshop.bea.com/HelloWorld/Hello" style="document" /> <!--为具体的操作指定了消息传递样式(rpc或文档)-->
- <input>
<soap:body [U45] use="literal" /> <!--use元素取单值-->
</input>
- <output>
<soap:body use="literal" />
</output>
</operation>
</binding>
③实现(指定访问名)
<service name="HelloWorld"><!--包含一个或多个port元素,其中每个port元素表示一个不同的web服务-->
<documentation>A very simple web service.</documentation>
- <port name="HelloWorldSoap" binding="s0:HelloWorldSoap"><!--将URL赋给了一个特定bing--->
<soap:address[U46] location="http://localhost:7001/WebServices/HelloWorld.jws" />
</port>
</service>
SOAP,简单对象访问协议(Simple Object Access Protocol,SOAP)实际上是一种 Web 服务技术,但 Web 服务中客户机和服务器之间的数据交换格式是通过灵活的 XML 模式实现的。SOAP的结构 : 相当于一封信,header是信封,body是信体
实例 :
Web 服务端(新建Web应用) :
package testWS;
@WebService(
// 在客户端生成一个名为"UserOper"的接口,此接口正是对应这个类
name = "UserOper",
// 在客户端生成一个名为"UserOperService"的工具类
// 此工具类中含有一个 "get" + portName 的方法,如这里为 getUserOperService()
// 此方法返回对当前类的远程引用,给人的感觉就是返回当前类
serviceName = "UserOperService",
// 最好以大写字母开头
portName = "UserOperPort",
// ???
targetNamespace = ""
)
@Resource(name = "yinkui", mappedName = "jdbc/mysql", type = DataSource.class)
public class UserMgmt {
// @Resource(mappedName="jdbc/mysql")
// private DataSource ds;
// 指定方法为Web服务方法
@WebMethod
public void removeUser(int id){
Connection conn = null;
PreparedStatement ps = null;
try{
Context con = new InitialContext();
DataSource ds = (DataSource)con.lookup("java:comp/env/yinkui");
conn = ds.getConnection();
ps = conn.prepareStatement("delete from user_info where user_id = " + id);
ps.executeUpdate();
}catch(Exception ex){
ex.printStackTrace();
}finally{
try{ conn.close(); }catch(Exception ex){ }
}
}
}
根据服务端生成Web客户端(测试时新建Java应用) :
在生成Web客户端时,工具自动为我们生成了大量的代码,使用起来很方便
package webclient;
// 这些包和接口以及类什么的都是自动生成的 直接引用
public class Main {
public static void main(String[] args) {
// 注意这里的写法
UserOper u = new UserOperService().getUserOperPort();
u.removeUser(100006);
}
}
EJB补充说明:1单元测试---jnuit Assert工具类
2 命令模式, 测试先行,先写测试,根据测试的要求再写具体的实现----先要结果在给过程
3应用服务器有那些?
BEA WebLogic Server,IBM WebSphere Application Server,Oracle9i Application Server,jBoss,Tomcat
116、应用服务器与WEB SERVER 的区别?
应用服务器:Weblogic、Tomcat、Jboss。。。。。。
WEB SERVER:IIS、 Apache
企业及应用 打包(.ear) 一个完整的企业应用包含EJB 模块和WEB 模块,在发布企业应用时,我们需要把它打成*.ear 文件,在打包前我们必须配置application.xml 文件,该文件存放于打包后的META-INF 目录。我们在application.xml 文件中需要指
定EJB 模块和WEB 模块的信息,
| à WEB APP (.war)
.ear |à EJB .jar
|àResouce 资源适配器(.rar)
一般在NetBean 和elicsape都有自动的打包工具
Java ee 的设计方案 两个集中连个发散
PL/SQL
PL/SQL是 Procedure Language & Structured Query Language 的缩写。ORACLE的SQL是支持ANSI(American national Standards Institute)和ISO92 (International Standards Organization)标准的产品。PL/SQL是对SQL语言存储过程语言的扩展。从ORACLE6以后,ORACLE的RDBMS附带了PL/SQL。它现在已经成为一种过程处理语言,简称PL/SQL。目前的PL/SQL包括两部分,一部分是数据库引擎部分;另一部分是可嵌入到许多产品(如C语言,JAVA语言等)工具中的独立引擎。可以将这两部分称为:数据库PL/SQL和工具PL/SQL。两者的编程非常相似。都具有编程结构、语法和逻辑机制。工具PL/SQL另外还增加了用于支持工具(如ORACLE Forms)的句法,如:在窗体上设置按钮等。本章主要介绍数据库PL/SQL内容。
§1.2.1 PL/SQL的好处
§1.2.1.1 有利于客户/服务器环境应用的运行
对于客户/服务器环境来说,真正的瓶颈是网络上。无论网络多快,只要客户端与服务器进行大量的数据交换。应用运行的效率自然就回受到影响。如果使用PL/SQL进行编程,将这种具有大量数据处理的应用放在服务器端来执行。自然就省去了数据在网上的传输时间。
PL/SQL程序由三个块组成,即声明部分、执行部分、异常处理部分。
PL/SQL块的结构如下:
DECLARE
/* 声明部分: 在此声明PL/SQL用到的变量,类型及游标,以及局部的存储过程和函数 */
BEGIN
/* 执行部分: 过程及SQL 语句 , 即程序的主要部分 */
EXCEPTION
/* 执行异常部分: 错误处理 */
END;
其中 执行部分是必须的。
学习的流程:数据类型变量---》流程控制---》存储过程----》函数----》包-----》job
PL/SQL day1
--设置数据库输出,默认为关闭,每次新打开窗口都要从新设置
set serveroutput on
--调用 包 函数 参数
execute dbms_output.put_line('hello world');
--或者用call调用,相当于java的调试程序打桩
call dbms_output.put_line('hello world');
--pl语句块是pl/sql里最小的编程块,其中可以再嵌套begin end
begin
dbms_output.put_line('Hello World');
dbms_output.put_line('2*3='||(2*3));
dbms_output.put_line('what''s');
end;
--如何声明变量,所有变量必须再declare中声明,程序中不允许声明
--没有初始化的变量默认值为null,屏幕上null是看不见的,命名习惯:一般变量以v_开头
--注意number也能存小数,最长38位,所以以后建议整数都用binary_integer存
--long是字符类型;boolean类型不能打印
--标准变量类型:数字,字符,时间,布尔
declare
v_number1 number ;
v_number2 number(3,2) ;
v_number3 binary_integer :=1;
v_name varchar2(20) :='kettas';
v_date date :=sysdate;
v_long long :='ni hao';
v_b boolean := true;
begin
if (v_number1 is null) then
dbms_output.put_line( 'hello');
end if;
dbms_output.put_line(v_number1);
dbms_output.put_line(v_number2);
dbms_output.put_line(v_number3);
dbms_output.put_line(v_name);
dbms_output.put_line(v_date);
dbms_output.put_line(v_long);
--dbms_output.put_line(v_b);
end;
--组合类型:record ,table
--record类型最常用,声明的时候可以加not null,但必须给初始值
--如果record类型一致可以互相赋值,如果类型不同,里面的字段恰好相同,不能互相赋值
declare
type t_first is record(
id number,
name varchar2(20)
);
v_first t_first;
begin
v_first.id:=1;
v_first.name:='sx';
dbms_output.put_line(v_first.id);
dbms_output.put_line(v_first.name);
end;
--考虑一下,orcale中赋值是拷贝方式还是引用方式?
declare
v_number1 number:=1;
v_number2 number;
type t_first is record(
id number,
name varchar2(20)
);
v_first t_first;
v_second t_first;
begin
v_number2 := v_number1;
v_number1:=2;
dbms_output.put_line('number1'||v_number1);
dbms_output.put_line(v_number2);
v_first.id:=1;
v_first.name:='sx';
v_second := v_first;
v_first.id:=2;
v_first.name:='kettas';
dbms_output.put_line(v_first.id);
dbms_output.put_line(v_first.name);
dbms_output.put_line(v_second.id);
dbms_output.put_line(v_second.name);
end;
--table类型,相当于java中的map,就是一个可变长的数组
--key必须是整数(可以是负数),value可以是标量,也可以是record
--可以不按顺序赋值,但必须先赋值后使用
declare
type t_tb is table of varchar2(20) index by binary_integer;
v_tb t_tb;
begin
v_tb(100):= 'hello';
v_tb(98):='world';
dbms_output.put_line(v_tb(100));
dbms_output.put_line(v_tb(98));
end;
declare
type t_rd is record(id number,name varchar2(20));
type t_tb is table of t_rd index by binary_integer;
v_tb2 t_tb;
begin
v_tb2(100).id:=1;
v_tb2(100).name:='hello';
dbms_output.put_line(v_tb2(100).id);
dbms_output.put_line(v_tb2(100).name);
end;
-- %type 和 %rowtype以及如何从数据库把数据取回来
create table student(
id number,
name varchar2(20),
age number(3,0)
)
insert into student(id,name,age) values(1,'sx',25);
--查找一个字段的变量
declare
v_name varchar2(20);
v_name2 student.name%type;
begin
select name into v_name2 from student where rownum=1;
dbms_output.put_line(v_name2);
end;
--查找一个类型的变量,推荐用*
declare
v_student student%rowtype;
begin
select * into v_student from student where rownum=1;
dbms_output.put_line(v_student.id||' '||v_student.name||' '||v_student.age);
end;
--也可以按字段查找,但是字段顺序必须一样,不推荐这样做
declare
v_student student%rowtype;
begin
select id,name,age into v_student from student where rownum=1;
dbms_output.put_line(v_student.id||' '||v_student.name||' '||v_student.age);
end;
--注意:insert,update,delete,select都可以,create table,drop table不行
--dql,dml,和流程控制可以在pl/sql里用,ddl不行
declare
v_name student.name%type :='shixiang';
begin
insert into student (id,name,age) values( 2,v_name,26);
end;
declare
v_name student.name%type :='shixiang';
begin
update student set name = v_name where id=1;
end;
--变量的可见空间
declare
v_i1 binary_integer :=1;
begin
declare
v_i2 binary_integer:=2;
begin
dbms_output.put_line(v_i1);
dbms_output.put_line(v_i2);
end;
--dbms_output.put_line(v_i1);
--dbms_output.put_line(v_i2);
end;
-----下午课程:流程控制
--if 判断
declare
v_b boolean :=true;
begin
if v_b then
dbms_output.put_line('ok');
end if;
end;
--if else
declare
v_b boolean :=false;
begin
if v_b then
dbms_output.put_line('ok');
else
dbms_output.put_line('false');
end if;
end;
--if elsif else
declare
v_name varchar2(20):='sx';
begin
if v_name='0701' then
dbms_output.put_line('0701');
elsif v_name ='sx' then
dbms_output.put_line('sx');
else
dbms_output.put_line('false');
end if;
end;
--loop循环,注意退出exit是退出循环,不是退出整个代码块
declare
v_i binary_integer := 0;
begin
loop
if v_i >10 then
exit;
end if;
v_i := v_i+1;
dbms_output.put_line('hehe');
end loop;
dbms_output.put_line('over');
end;
--更简单的写法
declare
v_i binary_integer := 0;
begin
loop
exit when v_i>30;
v_i := v_i+1;
dbms_output.put_line('hehe');
end loop;
dbms_output.put_line('over');
end;
--while循环
declare
v_i binary_integer:=0;
begin
while v_i<30 loop
dbms_output.put_line('hello' || v_i);
v_i := v_i+1;
end loop;
dbms_output.put_line('over');
end;
--for循环,注意不需要声明变量
begin
for v_i in 0..30 loop
dbms_output.put_line('hello'||v_i);
end loop;
dbms_output.put_line('over');
end;
--练习1用循环往student里插入30条记录
--要求:id为0,1,2...
-- name为kettas0,kettas1,kettas2...
-- age为11,12,13...
--练习2,假设不知道数据库里的数据规则和数量,
--把所有的student数据打印到终端
begin
delete from student;
for v_i in 0..30 loop
insert into student(id,name,age) values(v_i,'kettas'||v_i, 10+v_i);
end loop;
end;
--rownum是伪列,在表里没有
--数据库是先执行from student遍历student表,如果没有where条件过滤,则先做成一个结果集,然后再看select
--后面的条件挑出合适的字段形成最后的结果集,如果有where条件,则不符合条件的就会从第一个结果集中删除,
--后面的数据继续加进来判断。所以如果直接写rownum=2,或者rownum>10这样的语句就查不出数据。
--可以用一个子查询解决
select rownum,id from student where rownum=2;
declare
v_number binary_integer;
v_student student%rowtype;
begin
select count(*) into v_number from student;
for i in 1..v_number loop
select id,name,age into v_student
from
(select rownum rn,id,name,age from student)
where rn=i;
dbms_output.put_line('id: '||v_student.id||' name:'||v_student.name);
end loop;
end;
--异常的定义使用
begin
dbms_output.put_line(1/0);
exception
when others then
dbms_output.put_line('error');
end;
declare
e_myException exception;
begin
dbms_output.put_line('hello');
raise e_myException;--raise抛出异常
dbms_output.put_line('world');
dbms_output.put_line(1/0);
exception
when e_myException then
dbms_output.put_line(sqlcode);--当前会话执行状态,错误编码
dbms_output.put_line(sqlerrm);--当前错误信息
dbms_output.put_line('my error');
when others then
dbms_output.put_line('error');
end;
--error表,每次有异常可以加到表里,相当于log日志
--id,code,errm,information,create_date
--目前先这么写,明天学存储过程后就再改成一个过程,用的时候只需要调用即可
drop table error;
create table error(
id number,
code number,
errm varchar2(4000),
information varchar2(4000),
create_date date
)
drop sequence my_key;
create sequence my_key;
insert into error(id,code,errm,information,create_date)
values(my_key.nextVal,1,'xxx','xxxxx',sysdate);
declare
e_myException exception;
begin
dbms_output.put_line('hello');
raise e_myException;
dbms_output.put_line('world');
dbms_output.put_line(1/0);
exception
when e_myException then
declare
v_code binary_integer;
v_errm varchar2(4000);
begin
v_code:=sqlcode;
v_errm:=sqlerrm;
insert into error(id,code,errm,information,create_date)
values(my_key.nextVal,v_code,v_errm,'e_myException',sysdate);
end;
when others then
dbms_output.put_line('error');
end;
--cursor 游标(结果集)用于提取多行数据
--定义后不会有数据,使用后才有
--一旦游标被打开,就无法再次打开(可以先关闭,再打开)
declare
cursor c_student is
select * from student;
begin
open c_student;
close c_student;
end;
--第2种游标的定义方式,用变量控制结果集的数量
declare
v_id binary_integer ;
cursor c_student is
select * from student where id>v_id;
begin
v_id:=10;
open c_student;
close c_student;
end;
--第3种游标的定义方式,带参数的游标,用的最多
declare
cursor c_student(v_id binary_integer) is
select * from student where id>v_id;
begin
open c_student(10);
close c_student;
end;
--游标的使用,一定别忘了关游标
declare
v_student student%rowtype;
cursor c_student(v_id binary_integer) is
select * from student where id>v_id;
begin
open c_student(10);
fetch c_student into v_student;
close c_student;
dbms_output.put_line(v_student.name);
end;
--如何遍历游标fetch
--游标的属性 %found,%notfound,%isopen,%rowcount
--%found:若前面的fetch语句返回一行数据,则%found返回true,如果对未打开的游标使用则报ORA-1001异常
--%notfound:与%found行为相反码 ····················
--%isopen:判断游标是否打开
--%rowcount:当前游标的指针位移量,到目前位置游标所检索的数据行的个数,若未打开就引用,返回ORA-1001
--loop方式遍历游标
declare
v_student student%rowtype;
cursor c_student(v_id binary_integer) is
select * from student where id>v_id;
begin
open c_student(10);
loop
fetch c_student into v_student;
exit when c_student%notfound;
dbms_output.put_line('name: ' || v_student.name);
end loop;
close c_student;
end;
--while循环遍历游标,注意,第一次游标刚打开就fetch,%found为null,进不去循环
--如何解决:while nvl(c_student%found,true) loop
declare
v_student student%rowtype;
cursor c_student(v_id binary_integer) is
select * from student where id>v_id;
begin
open c_student(10);
while c_student%found is null or c_student%found loop
fetch c_student into v_student;
dbms_output.put_line('name: ' ||v_student.name);
end loop;
close c_student;
end;
--for循环遍历,最简单,用的最多,不需要声明v_student、打开关闭游标、fetch。
declare
cursor c_student(v_id binary_integer) is
select * from student where id>v_id;
begin
for v_student in c_student(10) loop
dbms_output.put_line('name: '||v_student.name);
end loop;
end;
--goto例子,不推荐使用goto,会使程序结构变乱
declare
i binary_integer :=0;
begin
if i=0 then goto hello;end if;
<<hello>>
begin
dbms_output.put_line('hello');
goto over;
end;
<<world>>
begin
dbms_output.put_line('world');
goto over;
end;
<<over>>
dbms_output.put_line('over');
end;
PL/SQL day2
--存储过程:把匿名块存储下来
--匿名块运行后不会在数据库留下
declare
v_content varchar2(4000):='hello';
begin
dbms_output.put_line(v_content);
end;
--创建或修改一个过程,sx_println(形式参数,声明类型即可),as可以替换成is
--变量可以声明再as和begin之间
create or replace procedure sx_println(v_content varchar2)
as
begin
dbms_output.put_line(v_content);
end;
--调用有参过程
execute sx_println('sx');
call sx_println('sx');
begin
sx_println('sx');
end;
--删除一个过程
drop procedure sx_println;
--查看数据库里的过程
select * from user_procedures;
desc user_procedures;
select object_name,procedure_name from user_procedures;
--procedure里可以调用其他的procedure
create or replace procedure say_hello
is
begin
sx_println('hello');
end;
--调用无参过程的方式: execute say_hello; call say_hello(); begin say_hello; end;
--输入参数in,输入参数不能进行赋值,默认不写就是in,v_name in varchar2中的varchar2不能设置长度,以对方传过来的为主。
--out 只负责赋值,外界对其赋值不起作用,存储返回的结果。
--in out 既可以当输入页可以当输出。
--存储过程没有重载,这个有参的say_hello会替代上面的无参say_hello
create or replace procedure say_hello(v_name in varchar2)
as
begin
--v_name :='a';
sx_println('hello '||v_name);
end;
--调用有参的存储过程的两种方式
begin
--say_hello('shixiang');
say_hello(v_name=>'sx');
end;
--多个参数的存储过程
create or replace procedure say_hello
(v_first_name in varchar2,v_last_name in varchar2)
as
begin
sx_println('hello '||v_first_name||'.'||v_last_name);
end;
--调用多个参数的两种方式
begin
say_hello('cksd0701','shixiang');
end;
--用指定形参名的方式调用可以不按顺序赋值
begin
say_hello(v_last_name=>'sx',v_first_name=>'0701');
end;
--out输出参数,用于利用存储过程给一个或多个变量赋值,类似于返回值,
create or replace procedure say_hello
(v_name in varchar2,v_content out varchar2)
begin
v_content:='hello ' || v_name;
end;
--调用
declare
v_con varchar2(200);
v_in varchar2(20):='cksd0702';
begin
say_hello(v_in,v_con);
sx_println(v_con);
end;
--in out参数,既赋值,又取值
create or replace procedure say_hello(v_name in out varchar2)
as
begin
v_name:=v_name||' hi';
end;
--调用
declare
v_inout varchar2(20):='sx';
begin
say_hello(v_inout);
sx_println(v_inout);
end;
--缺省参数 default为默认值
create or replace procedure say_hello(v_name varchar2 default 'a',v_content varchar2 default 'hello')
as
begin
sx_println(v_name||' '||v_content);
end;
--调用,用指名型参名的方式调用更好 ,指明参数赋值,避免多个参数的顺序的问题。
begin
say_hello();
end;
begin
say_hello(v_name=>'sx');
end;
begin
say_hello(v_content=>'hi');
end;
--function函数
--过程和函数都以编译后的形式存放在数据库中,函数可以没有参数也可以有多个参数并有一个返回值。
--过程有零个或多个参数,没有返回值。函数和过程都可以通过参数列表接收或返回零个或多个值,函数和过程的主要区别不在于返回值
--而在于他们的调用方式。过程是作为一个独立执行语句调用的,函数以合法的表达式的方式调用.
--传入和返回的参数的
create or replace function func(v_name in varchar2)
return varchar2
is
begin
return(v_name||' hello');
end;
--调用
declare
v_name varchar2(20);
begin
v_name:=func('shixiang');
sx_println(v_name);
end;
--out参数的函数
create or replace function func(v_name in varchar2,v_content out varchar2)
return varchar2
is
begin
v_content:=v_name||' hello';
return v_content;
end;
--调用 相当调用引用
declare
v_name varchar2(20):='ccccccccccc';
v_name1 varchar2(20):='zzzzzzzzz';
begin
v_name1:=func('shixiang',v_name);
v_name:='ccccccccccc';
sx_println(v_name1);
end;
--in out 参数
create or replace function func(v_name in out varchar2)
return varchar2
is
begin
v_name:=v_name||' hello';
return 's';
end;
--调用
declare
v_inout varchar2(20):='sx';
v_ret varchar2(20);
begin
v_ret:=func(v_inout);
sx_println(v_inout);
sx_println(v_ret);
end;
--存储过程调函数,函数调用存储过程
create or replace procedure print(v_name varchar2,v_res out varchar2)
as
v_test varchar2(30);
v_s varchar2(30);
begin
--dbms_output.put_line('==========');
v_test:=callPrint(v_name,v_res);
if(v_test=v_s) then
dbms_output.put_line('equlas');
end if;
v_res:=v_test||'zzzzzzzz';
end;
create or replace function callPrint(v_name varchar2,v_res out varchar2)
return varchar2
as
begin
--print();
--return 'sss';
v_res:=v_name||'hello';
return v_res;
end;
declare
v_test varchar2(30);
begin
v_test:=callPrint();
dbms_output.put_line(v_test);
end;
declare
v_print varchar2(30);
begin
print('zhangsan',v_print);
dbms_output.put_line(v_print);
end;
--子程序:没有CREATE OR REPLACE关键字,子程序的定义放在程序语句块的声明部分
--子程序只能被该程序语句块使用
declare
procedure println(v_name varchar2)
as
begin
dbms_output.put_line(v_name);
end;
begin
println('sx');
end;
declare
v_name1 varchar2(20);
function f_func(v_name in varchar2)
return varchar2
is
begin
return(v_name||' hello');
end;
begin
v_name1:=f_func('sx');
sx_println(v_name1);
end;
--删除函数
drop function func;
--下午:包,触发器
--和java的包类似,可以把函数,过程,变量等相关对象定义在一个包下,便于管理,例如:跟产品相关的包,用户相关的包,订单相关的包
--包里可以有过程,函数,变量,类型,异常,游标
--包只能存储再数据库中,不能是本地的
--包分两部分:包头(各种声明),包体(各种实现),可以分开编译
--package 先申明后在body中实现具体的实现
create or replace package sx
as
procedure testP(v_name varchar2);
--包里面可以发生重载,但要注意参数大类型必须不同
procedure testP(v_name binary_integer);
function testF(v_name varchar2) return binary_integer;
type t_student_tb is table of student%rowtype
index by binary_integer;
type t_student_re is record(
id binary_integer,
name varchar2(20)
);
v_id binary_integer:=1;
cursor c_student is select * from student;
end sx;
--使用包
declare
v_length binary_integer;
v_student_tb sx.t_student_tb;
begin
v_student_tb(1).id:=1;
v_student_tb(1).name:='sx';
v_student_tb(1).age:=25;
sx.v_id :=1;
for v_student in sx.c_student loop
dbms_output.put_line(v_student.id);
end loop;
sx.testP('hello');
--sx.testP1('hello');
v_length := sx.testF('hehe');
dbms_output.put_line(v_length);
end;
--看看v_id的值,实际上数据库为每一个会话(线程)保存一个变量的缓存
declare
v_student_tb sx.t_student_tb;
begin
dbms_output.put_line(sx.v_id);
end;
--包体里也可以写变量类型函数过程等,如果包头里没有声明,包体里有,相当与私有,只能在包体内部使用
create or replace package body sx
as
procedure testP(v_name varchar2)
is
begin
dbms_output.put_line(v_name);
end;
procedure testP1(v_name varchar2)
is
begin
dbms_output.put_line(v_name);
end;
procedure testP(v_name binary_integer)
is
begin
dbms_output.put_line(v_name);
end;
function testF(v_name varchar2) return binary_integer
is
begin
return length(v_name);
end;
end sx;
--触发器(有点像监听器,在操作数据的前后的动作)
主要针对 insert ,update,delete操作 两种类型:before,after(表级,行级)
drop table test_sx;
create table test_sx(
id number,
name varchar2(20),
age number
);
--创建表级触发器:只会在动作前后操作,而不是在插入每条数据就触发。
--before insert
create or replace trigger trg_test1
before insert on test_sx
declare
v_name varchar2(20);
begin
sx_println('before insert test table');
end;
--after insert
create or replace trigger trg_test2
after insert on test_sx
declare
v_name varchar2(20);
begin
sx_println('after insert test table');
end;
--before update
create or replace trigger trg_test3
before update on test_sx
declare
v_name varchar2(20);
begin
sx_println('before update test table');
end;
--after update
create or replace trigger trg_test4
after update on test_sx
declare
v_name varchar2(20);
begin
sx_println('after update test table');
end;
--before delete
create or replace trigger trg_test5
before delete on test_sx
declare
v_name varchar2(20);
begin
sx_println('before delete test table');
end;
--after delete
create or replace trigger trg_test6
after delete on test_sx
declare
v_name varchar2(20);
begin
sx_println('after delete test table');
end;
--表级(语句级)触发器,只会在动作前后触发,而不是每插入一条数据就触发
insert into test_sx select * from test_sx;
--行级触发器,只是在表级触发器加for each row
--before insert
create or replace trigger trg_test7
before insert on test_sx for each row
begin
sx_println('before insert test row');
end;
--after insert
create or replace trigger trg_test8
after insert on test_sx for each row
begin
sx_println('after insert test row');
end;
--:new 行触发器的一个特殊record变量,指当前操作行对象
create or replace trigger trg_test7
before insert on test_sx for each row
begin
sx_println('before insert test row new id: '||:new.id);
end;
create or replace trigger trg_test8
after insert on test_sx for each row
begin
sx_println('after insert test row new id:'|| :new.id);
end;
--可以写触发条件
create or replace trigger trg_test7
before insert on test_sx for each row when(new.id>10)
begin
sx_println('before insert test row new id: '||:new.id);
sx_println('before insert test row old id: '||:old.id);
end;
--:old 指原对象,对于update来说要更新谁,谁就是old
--对于insert来说,old为空
--对于delete来说,new为空
create or replace trigger trg_test9
before delete on test_sx for each row
begin
insert into test_sx values(32,'sss',24);
end;
create or replace trigger trg_test10
afters delete on test_sx for each row
declare
v_name varchar2(20);
begin
insert into test_sx values(32,'sss',24);
end;
--触发器功能强大,但是不易于维护,所以不适合做非常复杂的逻辑
--一般用于维护数据的完整性
--共有12种触发器类型(不全面)
--三个语句(insert/update/delete)
--两种类型(before/after)
--两种级别(表级/行级)
--一个表最多可以定义12种触发器
--触发器的限制:触发器的主体是pl/sql语句块,所有能出现在pl/sql中的语句再触发器主体中都是合法的限制.不应该使用事务控制语句commit,rollback,savepoint,
--由触发器调用的审核过程与函数都不能使用事务控制语句,不能声明任何long或者long raw 变量,可以访问的表有限
--行级触发器无法读 变化表:被dml语句正在修改的表,或者对自定义触发器的表进行select操作
--限制表:跟变化表有外键关联的表.
--dbms_job包:允许调度pl/sql语句块,让它再指定时间自动运行,类似于unix的cron
drop table testJob;
create table testJob(
create_date date
);
create or replace procedure doJob
as
begin
insert into testJob values(sysdate);
end;
--创建JOB,submit的4个参数:job号,过程名,当前运行时间,下一次运行时间
declare
v_job binary_integer;
begin
dbms_job.submit(v_job,'doJob;',sysdate,'sysdate+1/1440');
sx_println(v_job);
end;
--查看job
desc user_jobs;
select job,what from user_jobs;
--手动运行job
begin
dbms_job.run(21);
end;
--删除JOB
begin
dbms_job.remove(21);
end;
--相关的几个job操作
--修改要执行的操作:dbms_job.what(jobno,what);
--修改下次执行时间:dbms_job.next_date(job,next_date);
--修改间隔时间: dbms_job.interval(job,interval);
--停止job: dbms_job.broken(job,broken,nextdate);
--动态sql
declare
v_day varchar2(20);
begin
select to_char(sysdate,'dd') into v_day from dual;
if v_day='01' then
insert into test1 values(1,'xx');
elsif v_day='02' then
insert into test2 values(1,'xx');
elsif v_day='03' then
insert into test3 values(1,'xx');
...
end;
declare
v_day varchar2(20);
begin
execute immediate 'insert into test'|| v_day||'values(....)';
end;
--oracle8i以前只能用dbms_sql实现动态sql,比较麻烦
--动态sql虽然方便但是效率很低,慎用.
最后用java调用
public class TestMain3 {
public static void main(String[] args) throws Exception{
Class.forName("oracle.jdbc.driver.OracleDriver");
String url="jdbc:oracle:thin:@localhost:1521:XE";
Connection con=DriverManager.getConnection(url,"sunying","sunying");
CallableStatement c=con.prepareCall("{call pro(?,?)}");
c.setInt(1,10);
c.registerOutParameter(2,oracle.jdbc.OracleTypes.CURSOR);
c.execute();
ResultSet rs=(ResultSet)c.getObject(2);
while(rs.next()){
System.out.println(rs.getInt(1)+"====="+rs.getString(2)+"===="+rs.getInt(3));
}
c.close();
con.close();
}
}
create or replace package sun
as type sun_cur is REF CURSOR;
end sun;
create or replace procedure pro(v_cur out sun.sun_cur)
as
begin
open v_cur for select * from student;
end;
stateless是无状态的,stateful有状态
Jndi的名字。因为客户端需要通过JNDI 查找EJB,那么JNDI 是什么?
JNDI(The Java Naming and Directory Interface,Java 命名和目录接口) 是一组在Java 应用中访问命名和目录服务的API。为开发人员提供了查找和访问各种命名和目录服务的通用、统一的方式。借助于JNDI 提供的接口,能够通
过名字定位用户、机器、网络、对象服务等。
命名服务:就像DNS 一样,通过命名服务器提供服务,大部分的J2EE 服务器都含有命名服务器。
目录服务:一种简化的RDBMS 系统,通过目录具有的属性保存一些简单的信息。目录服务通过目录服务器实现,比如微软ACTIVE
DIRECTORY 等。
在运行一个含有main方法的java程序时,会对应的启动一个虚拟机。一个web容器(tomcat...)或是一个 EJB容器(jboss...)同样都对应着一个java虚拟机。那么,在这些虚拟机之间的方法调用就是远程方法调用,而无须考虑这些虚拟机是否在同一台机器上。
远程接口
// mappedName="ejb/sllc" 表明此ejb被创建后 挂在JNDI服务器的名为"ejb/sllc"的节点上 context.lookup("ejb/sllc")方法即可取得此ejb的引用(不是ejb的本身)
继承远程接口,也可以用标签@remote(xxx.class)
采用jndi来调用资源
pooling池
的配置
有状态的stateful session bean 必须实现Serialiazble接口,只有这样容器才能在他们不再使用的时候,序列化存储他们的状态
/* 为了测试几个标签,这里采用Connection作为实例变量* 这里本不应该采用Connection作为实例变量,应当如下这么注入DataSource 从JNDI服务器上找到名为"jdbc/oracle"的节点,此节点挂着的正是已配置好的DataSource
注意 @Resource注入时 mappedName不能用别名 只能是节点真实值
@Resource(mappedName="jdbc/oracle")
private DataSource ds;
// 构造后执行 InvocationContext对象记录着被拦截的ejb的信息,这里此对象是被注入的 注意不能作为实例变量采用@Resource注入 因为具有"层面"的特征倘若在被拦截的ejb中也含有@PostConstruct标记的方法 则ejb的方法会被覆盖而不会被执行
在商业方法前后添加功能。
直接用标签
@Interceptors(LogInterceptor.class)就可以调拦截器
注意:对于一个无状态的session bean必须提供一个无参的构造方法。
Session bean的生命周期的标签,见下面的session bean 的生命周期
@EJB 注释的beanName 属性指定EJB 的名称(如果没有设置过@Stateless 或@Stateful 的name 属性,默认为不带
包名的类名),他的另一个属性mappedName 指定EJB 的全局JNDI 名。
@EJB 注释只能注入EJB 存根对象,除@EJB 注释之外,EJB 3.0 也支持@Resource 注释来注入来自JNDI 的任何资源,比如数据库的资源,先配置连接池
DefaultMySqlDS连接池的名字
@EJB 注释如果被用在JavaBean 风格的setter 方法上时,容器会在属性第一次使用之前,自动地用正确的参数调用bean 的setter 方法
jboss.xml(本例使用Jboss 默认的安全域”other”)。 jboss.xml 必须打进Jar 文件的META-INF 目录。
由于使用的是Jboss 安全注释,程序采用了硬编码,不利于日后迁移到其他J2EE 服务器(如:WebLogic),所以
作者不建议使用这种方法定义安全域
@RolesAllowed 注释定义允许访问方法的角色列表,如角色为多个,可以用逗号分隔
。@PermitAll 注释定义所有
的角色都可以访问此方法。
JPA(java persistence Api)是对Hibernate、TopLink( GlassFish默认的持久化框架 )等的一次再封装,屏蔽了这些持久化框架之间的差异。在创建实体的时候,会提示选择持久化框架(hibernate、toplink...)以及数据源,这里选择在控制台
中配置好了的JNDI管理着的数据源。
Javabean的特点:是普通的java,实现Seializable接口 2提供没有参数的构造方法 3 属性为私有,且提供set和get方法 4最好覆盖equal和hashset方法 5利用的观察者模式。
实体Bean 可分为Bean 管理的持续性(BMP)和容器管理的持续性(CMP)两种
相当于hibernate的配置文件
注:实体bean 需要在网络上传送时必须实现Serializable 接口,否则将引发java.io.InvalidClassException
例外。
@javax.persistence.Column 注释定义了将成员属性映射到关系表中的哪一列和该列的一些结构信息(如列名是否唯一,是否允许为空,是否允许更新等),他的属性介绍如下:
·name: 映射的列名。如:映射Person 表的PersonName 列,可以在name 属性的getName 方法上面加入
@Column(name = "PersonName"),如果不指定映射列名,容器将属性名称作为默认的映射列名。
·unique: 是否唯一
·nullable: 是否允许为空
·length: 对于字符型列,length 属性指定列的最大字符长度
·insertable: 是否允许插入
·updatable: 是否允许更新
·columnDefinition: 定义建表时创建此列的DDL
·secondaryTable: 从表名。如果此列不建在主表上(默认建在主表),该属性定义该列所在从表的名字。
通过配置文件的名字TestPU获得EntityManagerFactory
开始事务
EntityManager 是由EJB 容器自动地管理和配置的,不需要用户自己创建,他用作操作实体Bean。它可以持久化环境,相当于hibernate的一级case。
如果persistence.xml 文件中配置了多个不同的持久化内容。在注入EntityManager 对象时必须指定持久化名称,可
以通过@PersistenceContext 注释的unitName 属性进行指定,例:
@PersistenceContext(unitName=" TestPU ")
EntityManager em;
在实体中已经标注,直接调用名字就可以,可以从用
无状态的Bean,一般事务管理。
如果没有指定参数,@TransactionAttribute 注释使用REQUIRED 作为默认参数。
见 5)jpa的常用标注例子
作者对@PostPersist 和@PostUpdate 事件触发的时机有点怀疑,文档上说@PostPersist 事件在数据真实插入进数据库后发生,但作者测试的结果是:在数据还没有真实插入进数据库时,此事件就触发了。
@PostUpdate 事件的情况一样
类似hibernate的映射
需要把@javax.persistence.Inheritance 注释的strategy属性设置为
InheritanceType.SINGLE_TABLE。除非你要改变子类的映射策略,否则@Inheritance 注释只能放在继承层次的基类。通过鉴别字段的值,持久化引掣可以区分出各个类,并且知道每个类对应那些字段。鉴别字段通过@javax.persistence.DiscriminatorColumn 注释进行定义,name 属性定义鉴别字段的列名,discriminatorType 属性
定义鉴别字段的类型(可选值有:String, Char, Integer),如果鉴别字段的类型为String 或Char,可以用length 属
性定义其长度。@DiscriminatorValue 注释为继承关系中的每个类定义鉴别值,如果不指定鉴别值,默认采用类名。
自己定义的区分列,不写的话默认是Dtype STRING类型
子类没有对应的表,故不要id,子类的区别字段就是“car”
关系所有者拥有外键ID 由它维护关系
倘若去掉这一标记 则默认为 : @JoinColumn(name="shipment_id")
一对一关联可能是双向的, 在双向关联中, 有且仅有一端是作为主体(owner)端存在的.
主体端负责维护联接列(即更新). 对于不需要维护这种关系的从表则通过mappedBy属性进行声明mappedBy的值指向主体的关联属性.
由于这里不是EJB 故不能采用如此注入的方式获得EntityManager对象 @PersistenceContext
private EntityManager em;
于是 这里采用了抽象方法的方式 由子类(EJB)实现抽象方法 注入获取EntityManager对象
设置参数
<!--描述web服务使用的消息的有效负载,即直接发送到web服务或直接从web服务接受的消息<part name="parameters" element="s0:Hello" /> <!--可以使用rpc样式,也可以使用文档样式,本part使用的是文档岩石-->
指名①中的参数,方法是放在soap的head中还是放在body中
指定访问的地址