Java技术融合:JNI、EJB 3与JPA的深度解析
1. Java与C++协作:JNI的应用
Java代码和C++代码可以协同工作,创建一个小型的电子邮件客户端。其中,Java代码负责用户界面以及消息和文件夹信息的存储,而C++代码则通过COM来访问MS Outlook中的文件夹和电子邮件。Java Native Interface(JNI)技术能够让Java代码与C++代码轻松协作,为开发者带来便利。
1.1 JNI的作用
JNI是一种强大的机制,可用于用Java编写高级系统。将Java与本地代码链接起来,开发者能够利用操作系统提供的功能,例如在Windows中使用COM,或者使用本地用户界面库(相比Swing可提高速度)。
1.2 示例应用
通过一个小型电子邮件客户端的示例,可以看到JNI在实际问题中的应用。Java代码负责前端界面和数据存储,C++代码负责与MS Outlook的交互,二者通过JNI实现无缝协作。
2. EJB 3与Java Persistence API(JPA)概述
2.1 EJB的发展历程
在过去,Enterprise JavaBeans(EJBs)曾被过度炒作,但在实际应用中,其架构的复杂性导致了诸多问题。例如,XML部署描述符的要求、多余的回调方法生成以及复杂的EJB查询语言结构,都给开发者带来了不愉快的开发体验。此外,实体bean在大量事务处理时会引发延迟问题,影响系统性能。
2.2 EJB 3的改进
幸运的是,最新的EJB 3规范吸取了以往的经验教训,进行了大量必要的改进。现在,会话bean是由EJB容器管理的普通Java对象(POJOs)。会话bean部署到EJB容器后,会创建一个存根对象,并在服务器的Java Naming Directory Interface(JNDI)注册表中注册。客户端代码可以通过JNDI中的接口类名获取bean的存根。这种控制反转的概念使开发者能够独立于创建方式来构建应用程序组件。
2.3 EJB 3和JPA的新特性
EJB 3和Java Persistence API(JPA)规范为J2EE框架带来了一些重要的修改,具体如下:
- 用于Java持久化的查询语言,是EJB QL的扩展。
- 会话bean和消息驱动bean的拦截器功能。
- Java语言元数据注解。
- 简化企业bean类型。
- 消除会话bean的EJB组件接口/主页接口要求。
- 通过Java Persistence API简化实体持久化,支持轻量级领域建模,包括继承和持久化。
新的EJB 3框架试图通过注入服务对象或在运行时拦截执行上下文,将应用服务与POJOs融合。通过避免框架类的交互,应用的继承结构可以更加灵活。声明式服务通过Java代码中的注解标记来实现。依赖注入(DI)模式的实现使应用之间保持松散耦合,服务对象可以根据运行时配置由对象工厂类生成,并在运行时注入到POJOs中。
3. Java Persistence API(JPA)详解
3.1 JPA的关键特性
- 命名查询 :命名查询是用元数据表示的静态查询,既可以作为本地查询,也可以作为JPA查询使用,具有很高的灵活性。
- POJO实体 :实体现在是独立的模块,不再受过去版本中严格组件结构的束缚。
- 标准化的O/R映射 :映射结构遵循Java Persistence API规范的非专有格式。
- EntityManager API :EntityManager负责对实体执行标准的数据库创建、读取和更新操作。
3.2 实体
实体是一种轻量级的持久化领域对象,由辅助类管理以维护对象的数据状态。在EJB 3中,实体必须使用@Entity注解进行标记,或者在部署描述符中进行相应指定。实例变量代表实体的持久化状态,与JavaBean属性相关联。实例变量的可访问性由private、protected和public访问修饰符决定。实体是存在且与其他对象可区分的对象,通常与其他实体具有关系或关联。
3.3 查询语言
Java Persistence查询语言用于独立于通常用于查询和发布后端数据库数据内容的SQL结构来定义实体查询。该查询语言使用类似SQL的语法,根据抽象模式类型及其之间的关系来选择项目。Java Persistence查询语言语句可以是三种不同类型的操作:选择、更新或删除。
3.3.1 SELECT语句的组成
| 子句 | 作用 |
|---|---|
| SELECT | 确定要收集的值的类型 |
| FROM | 声明查询其他子句使用的域 |
| WHERE | 限制查询结果的集合 |
| GROUP BY(可选) | 聚合结果分组的标准 |
| HAVING(可选) | 对分组聚合进行过滤 |
| ORDER BY(可选) | 对结果集合进行排序操作 |
3.3.2 查询示例
SELECT d from DraftPicks d where d.salary < 20000.0 ORDER BY d.lastname, d.position
DELETE FROM Personnel p WHERE p.name = ‘Player A’;
UPDATE Players p SET p.moveup = ‘YES’ WHERE p.battingaverage > 300;
3.4 EntityManager
EntityManager从实体管理器工厂获取,以便使用持久化上下文来检索实体数据。这个获取过程通常通过JNDI或应用容器中的依赖注入来完成,应用容器会协调应用与工厂对象之间的交互。实体管理器有使用限制,不能在并发线程之间共享,只能单线程操作。以下是一个persistence.xml代码示例,展示了League应用中实体管理器与MySQL后端数据库进行事务处理时的连接属性:
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<persistence-unit name="test">
<provider>oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider</provider>
<class>entity.Team</class>
<class>entity.Players</class>
<properties>
<property name="toplink.jdbc.driver" value="org.gjt.mm.mysql.Driver"/>
<property name="toplink.jdbc.url" value="jdbc:mysql://localhost/ejb"/>
<property name="toplink.jdbc.user" value="root"/>
<property name="toplink.jdbc.password" value=""/>
</properties>
</persistence-unit>
</persistence>
3.5 示例应用:League
League应用通过解析命令行输入,使用testAdd、testDelete和testFind方法对EJB数据库执行三种操作(添加、删除、查找)。以下是League应用的代码:
package client;
import javax.persistence.EntityManager;
import javax.persistence.Persistence;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Query;
import java.util.Collection;
import java.util.List;
import entity.Players;
import entity.Team;
public class League {
private static EntityManagerFactory entityMgrFactory;
private static EntityManager entityMgr;
public static void main(String[] args) {
if (args.length != 1) {
System.out.println("USAGE: java League [add | remove | find]");
System.exit(1);
}
entityMgrFactory = Persistence.createEntityManagerFactory("test");
entityMgr = entityMgrFactory.createEntityManager();
if (args[0].equalsIgnoreCase("remove")) {
entityMgr.getTransaction().begin();
Team t = findTeam("PW Cannons");
System.out.println("Removing all... " + testDelete(t));
entityMgr.getTransaction().commit();
} else if (args[0].equalsIgnoreCase("add")) {
entityMgr.getTransaction().begin();
System.out.println("Inserting Team and Players... " + testAdd());
entityMgr.getTransaction().commit();
} else if (args[0].equalsIgnoreCase("find")) {
Team t = testFind("PW Cannons");
}
entityMgr.close();
}
private static String testAdd() {
Team team0 = new Team();
team0.setId(10000);
team0.setName("PW Cannons");
entityMgr.persist(team0);
Players players1 = new Players();
players1.setId(10011);
players1.setName("Player #1");
Players players2 = new Players();
players2.setId(20011);
players2.setName("Player #2");
Players players3 = new Players();
players3.setId(30011);
players3.setName("Player #3");
team0.getPlayers().add(players1);
players1.setTeam(team0);
team0.getPlayers().add(players2);
players2.setTeam(team0);
team0.getPlayers().add(players3);
players3.setTeam(team0);
return "OK";
}
private static String testDelete(Team t) {
Team t0 = entityMgr.merge(t);
entityMgr.remove(t0);
return "OK";
}
private static Team testFind(String name) {
Query q = entityMgr.createQuery("select t from Team t where t.name = :name");
q.setParameter("name", name);
return (Team)q.getSingleResult();
}
}
3.6 实体类示例
3.6.1 Team类
package entity;
import java.io.Serializable;
import javax.persistence.*;
import static javax.persistence.CascadeType.*;
import java.util.Collection;
import java.util.ArrayList;
@Entity
public class Team implements Serializable {
private int id;
private String name;
private Collection<Players> players = new ArrayList<Players>();
@Id
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@OneToMany(cascade=ALL, mappedBy="team")
public Collection<Players> getPlayers() {
return players;
}
public void setPlayers(Collection<Players> newValue) {
this.players = newValue;
}
}
3.6.2 Players类
package entity;
import javax.persistence.*;
@Entity
@Table(name="PLAYERS")
public class Players {
private int id;
private String name;
private Team team;
@Id
@Column(name="PLAYER_ID")
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Column(name="NAME")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@ManyToOne()
@JoinColumn(name="TEAM_ID")
public Team getTeam() {
return team;
}
public void setTeam(Team team) {
this.team = team;
}
}
4. 会话bean与实体bean的变迁
4.1 会话bean的类型
会话bean通常代表应用服务器上的单个客户端。会话bean通过交互式界面与后端系统进行内容的收集和发布。当客户端终止对系统的控制时,其会话bean也会终止与客户端应用的连接。会话bean有两种类型:有状态和无状态。
- 有状态bean由服务器单独管理,在客户端 - bean会话期间保留状态,且不会在客户端之间共享。
- 无状态bean是非持久化的,当客户端调用终止时,它们就会消失。也就是说,无状态bean在客户端 - bean会话期间保留状态,当客户端与该bean的关系结束时,该状态也会消失。
4.2 实体bean的问题与JPA的解决方案
过去,实体bean在大量事务处理时会引发延迟问题。早期版本中,实体bean是由会话和线程进程共享的单个资源,并且总是在事务中被访问。这意味着一个bean会一直被分配给该事务,直到该进程回滚或提交,导致其他想要访问该实体的进程排队等待。随着系统中被访问的实体bean数量增加,性能问题也会加剧。
为了解决这些问题,引入了新的Java Persistence API(JPA)。使用JPA,开发者可以在J2EE部署中更轻松地管理事务和对象 - 关系映射,采用POJO持久化模型。与以前的持久化模型相比,JPA使对象和数据在会话之间更容易保留值,减轻了开发者的实现负担。
5. 拦截器(Interceptors)
5.1 拦截器的作用
当EJB业务流程方法被调用事件触发时,EJB容器会在将控制权委托给该方法之前提供事务管理服务,以检查是否存在新的或现有的事务。此时,可以运行拦截器对传入业务流程方法的数据进行操作。
5.2 拦截器方法的定义
拦截器方法通常在bean类或单独的类中定义,但每个类只能有一个拦截器方法。当对实现了这些方法的bean进行业务方法调用时,这些拦截器方法会被调用。
5.3 注解的使用
-
@AroundInvoke注解用于标记拦截器方法,其格式为Object <method>(javax.ejb.InvocationContext),这样业务方法调用就知道在操作期间要处理的代码。 -
也可以使用
@Interceptors注解来定义单个或整个bean方法的拦截器。
5.4 InvocationContext接口
InvocationContext引用允许应用程序在拦截器链中传播状态,从而可以从bean中获取方法和对象引用,以及访问器方法(get/set)的参数项。如果为应用程序定义了多个拦截器,这些组件的传递顺序由
@Interceptors
注解中定义的顺序决定。此外,也可以使用部署描述符(web.xml)来指定拦截器组件的调用顺序,并覆盖元数据注解的规范。InvocationContext接口提供的方法如下:
| 方法 | 作用 |
| ---- | ---- |
| Object getBean() | 返回对bean的引用 |
| Method getMethod() | 返回对被调用方法的引用 |
| Object[] getParameters() | 返回传递给方法的参数 |
| void setParameters(Object[] parameters) | 设置参数 |
| Map getContextData() | 获取可以在链中共享的上下文数据 |
| Object proceed() | 进入链中的下一个拦截器,如果是最后一个拦截器,则进入业务方法 |
5.5 部署描述符中拦截器的配置
EJB 3规范中定义了四种不同的方式来描述部署描述符(web.xml)中的拦截器:
- 通配符方式:
<interceptor-binding>
<ejb-name>*</ejb-name>
<interceptor-class>INTERCEPTOR</interceptor-class>
</interceptor-binding>
这种方式将拦截器组件应用于ejb - jar文件中包含的所有企业bean。
- 类级拦截器:
<interceptor-binding>
<ejb-name>EJBNAME</ejb-name>
<interceptor-class>INTERCEPTOR</interceptor-class>
</interceptor-binding>
为特定的企业bean类定义类级拦截器。
- 方法级拦截器:
<interceptor-binding>
<ejb-name>EJBNAME</ejb-name>
<interceptor-class>INTERCEPTOR</interceptor-class>
<method-name>METHOD</method-name>
</interceptor-binding>
将特定方法与方法级拦截器关联。
- 单个方法拦截器:
<interceptor-binding>
<ejb-name>*</ejb-name>
<interceptor-class>INTERCEPTOR</interceptor-class>
<method-name>METHOD</method-name>
<method-params>
<method-param>PARAM-1</method-param>
</method-params>
</interceptor-binding>
将拦截器应用于一组方法中的单个方法。
5.6 拦截器的实现示例
以下是一个示例,展示了如何在应用程序中实现拦截器来测试不同的参数值:
package com.interceptor.interceptor_stateless_appclient;
import javax.ejb.EJB;
import com.interceptor.interceptor_stateless_ejb.*;
public class StatelessSessionAppClient {
@EJB
private static StatelessSession stateless;
public static void main(String args[]) {
testBattingAverage(246.0);
testBattingAverage(274.2);
testBattingAverage(505.0);
testBattingAverage(-373.2);
}
private static void testBattingAverage(double battingAverage) {
try {
String scoutingReport = stateless.check(battingAverage);
System.out.println("scoutingReport = " + scoutingReport);
} catch (ImproperArgumentException improperEx) {
System.out.println("Improper value found: " + battingAverage);
} catch (Exception ex) {
System.out.println("Exception: " + battingAverage);
}
}
}
package com.interceptor.interceptor_stateless_ejb;
import javax.ejb.Remote;
@Remote
public interface StatelessSession {
public String check(double val) throws ImproperArgumentException;
}
package com.interceptor.interceptor_stateless_ejb;
import javax.ejb.Stateless;
import javax.interceptor.Interceptors;
@Stateless
@Interceptors( { StatisticsTooLow.class, StatisticsTooHigh.class })
public class StatelessSessionBean implements StatelessSession {
public String check(double val) {
String report = "he’s awesome";
if (val < 300.0) report = "he’s so-so";
return report;
}
}
package com.interceptor.interceptor_stateless_ejb;
import java.lang.reflect.Method;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;
public class StatisticsTooLow {
@AroundInvoke
public Object checkIfTooLow(InvocationContext ctx) throws Exception {
Method method = ctx.getMethod();
if (method.getName().equals("check")) {
double param = (Double)(ctx.getParameters()[0]);
if (param < 0.0) {
throw new ImproperArgumentException("Illegal argument: < 0.0");
}
}
return ctx.proceed();
}
}
package com.interceptor.interceptor_stateless_ejb;
import java.lang.reflect.Method;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;
public class StatisticsTooHigh {
@AroundInvoke
public Object checkIfTooHigh(InvocationContext ctx) throws Exception {
Method method = ctx.getMethod();
if (method.getName().equals("check")) {
double param = (Double)(ctx.getParameters()[0]);
if (param > 500.0) {
throw new ImproperArgumentException("Illegal argument: > 500.0");
}
}
return ctx.proceed();
}
}
通过以上内容,我们可以看到Java技术在不同方面的应用和发展,从Java与C++的协作到EJB 3和JPA的改进,以及拦截器的使用,这些技术的融合为开发者提供了更强大、更灵活的开发工具和方法。在实际开发中,开发者可以根据具体需求选择合适的技术,构建高效、稳定的应用程序。
6. 拦截器工作流程分析
为了更好地理解拦截器的工作机制,下面通过一个mermaid格式的流程图来展示其在EJB业务方法调用时的执行流程:
graph TD
A[调用EJB业务方法] --> B[EJB容器提供事务管理服务]
B --> C{是否有拦截器}
C -- 是 --> D[执行拦截器方法]
D --> E{拦截器处理结果是否正常}
E -- 是 --> F[执行EJB业务方法]
E -- 否 --> G[抛出异常]
C -- 否 --> F
F --> H[返回结果]
从这个流程图可以清晰地看到,当调用EJB业务方法时,EJB容器首先提供事务管理服务,然后检查是否有拦截器。如果有拦截器,则执行拦截器方法,根据拦截器的处理结果决定是继续执行EJB业务方法还是抛出异常。如果没有拦截器,则直接执行EJB业务方法并返回结果。
7. 不同类型拦截器的应用场景
7.1 数据验证拦截器
像前面示例中的
StatisticsTooLow
和
StatisticsTooHigh
拦截器,就属于数据验证拦截器。它们用于验证传入业务方法的数据是否合法。在实际应用中,比如用户注册时,验证用户输入的年龄、邮箱格式等是否符合要求,就可以使用数据验证拦截器。
7.2 日志记录拦截器
日志记录拦截器可以在业务方法执行前后记录相关信息,例如方法的调用时间、传入的参数、执行结果等。这对于系统的调试和监控非常有帮助。以下是一个简单的日志记录拦截器示例:
package com.interceptor.log;
import java.lang.reflect.Method;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;
public class LoggingInterceptor {
@AroundInvoke
public Object logMethodInvocation(InvocationContext ctx) throws Exception {
Method method = ctx.getMethod();
Object[] params = ctx.getParameters();
System.out.println("调用方法: " + method.getName() + ",参数: " + java.util.Arrays.toString(params));
Object result = ctx.proceed();
System.out.println("方法 " + method.getName() + " 执行完毕,结果: " + result);
return result;
}
}
在需要使用该拦截器的bean类上添加
@Interceptors(LoggingInterceptor.class)
注解即可。
7.3 权限验证拦截器
权限验证拦截器用于检查调用业务方法的用户是否具有相应的权限。例如,在一个企业级应用中,某些敏感操作只有管理员才能执行,就可以使用权限验证拦截器来进行权限检查。以下是一个简单的权限验证拦截器示例:
package com.interceptor.permission;
import java.lang.reflect.Method;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;
public class PermissionInterceptor {
@AroundInvoke
public Object checkPermission(InvocationContext ctx) throws Exception {
Method method = ctx.getMethod();
// 假设这里有一个方法用于获取当前用户的角色
String userRole = getUserRole();
if (!isAllowed(userRole, method.getName())) {
throw new SecurityException("没有权限调用该方法");
}
return ctx.proceed();
}
private String getUserRole() {
// 实现获取用户角色的逻辑
return "user";
}
private boolean isAllowed(String userRole, String methodName) {
// 实现权限检查的逻辑
if ("admin".equals(userRole)) {
return true;
}
// 假设某些方法普通用户可以调用
return "publicMethod".equals(methodName);
}
}
8. JPA与数据库交互的优化建议
8.1 合理使用命名查询
命名查询是JPA的一个重要特性,它可以提高查询的可读性和可维护性。在实际应用中,应该尽量将常用的查询定义为命名查询。例如,在前面的
League
应用中,如果经常需要查询某个球队的信息,可以定义一个命名查询:
package entity;
import javax.persistence.Entity;
import javax.persistence.NamedQuery;
import java.io.Serializable;
import java.util.Collection;
import java.util.ArrayList;
@Entity
@NamedQuery(name = "Team.findByName", query = "select t from Team t where t.name = :name")
public class Team implements Serializable {
// 类的其他部分保持不变
}
在代码中使用命名查询:
Query q = entityMgr.createNamedQuery("Team.findByName");
q.setParameter("name", "PW Cannons");
Team team = (Team) q.getSingleResult();
8.2 批量操作优化
当需要对大量数据进行插入、更新或删除操作时,使用批量操作可以显著提高性能。例如,在插入多个球员信息时,可以使用批量插入:
private static String testAddBatch() {
entityMgr.getTransaction().begin();
for (int i = 0; i < 100; i++) {
Players player = new Players();
player.setId(10000 + i);
player.setName("Player #" + i);
entityMgr.persist(player);
if (i % 20 == 0) {
entityMgr.flush();
entityMgr.clear();
}
}
entityMgr.getTransaction().commit();
return "OK";
}
8.3 缓存机制的使用
JPA支持一级缓存和二级缓存。一级缓存是针对单个
EntityManager
的缓存,二级缓存是全局缓存。合理使用缓存可以减少对数据库的访问次数,提高系统性能。例如,在配置二级缓存时,可以在
persistence.xml
中进行如下配置:
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<persistence-unit name="test">
<provider>oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider</provider>
<class>entity.Team</class>
<class>entity.Players</class>
<properties>
<property name="toplink.jdbc.driver" value="org.gjt.mm.mysql.Driver"/>
<property name="toplink.jdbc.url" value="jdbc:mysql://localhost/ejb"/>
<property name="toplink.jdbc.user" value="root"/>
<property name="toplink.jdbc.password" value=""/>
<property name="toplink.cache.type.default" value="FULL"/>
</properties>
</persistence-unit>
</persistence>
9. 总结
通过对Java与C++协作(JNI)、EJB 3和JPA的深入解析,以及拦截器的详细介绍,我们可以看到Java技术在不断发展和完善。JNI使得Java能够与C++等本地代码协同工作,为开发提供了更多的可能性。EJB 3和JPA的改进解决了以往EJB架构中存在的诸多问题,如实体bean的性能问题,并且引入了许多新的特性,如拦截器、依赖注入等,提高了开发的灵活性和效率。
拦截器作为EJB 3中的一个重要特性,在数据验证、日志记录、权限验证等方面有着广泛的应用。合理使用拦截器可以增强系统的安全性、可维护性和可扩展性。
在JPA与数据库交互方面,通过合理使用命名查询、批量操作和缓存机制等优化建议,可以提高系统的性能和响应速度。
在实际开发中,开发者应该根据具体的业务需求和场景,灵活运用这些技术,充分发挥它们的优势,构建出高效、稳定、安全的Java应用程序。同时,随着Java技术的不断发展,我们也应该持续关注新的特性和趋势,不断提升自己的开发能力。
超级会员免费看
80

被折叠的 条评论
为什么被折叠?



