企业级 Java 开发:EJB 与 JPA 入门指南
1. 有状态会话 Bean 的释放
在购物流程中,当客户完成购物准备结账时,需要调用带有
@Remove
注解的
MyShoppingCart
方法来完成购物流程并释放有状态会话 Bean,以便其他客户使用。例如:
myCart.addItem(mySecondItem);
// 客户准备结账
...
myCart.placeOrder();
在上述示例中,
placeOrder()
方法应标记为
@Remove
注解。同时,还应在 Bean 上提供另一个
@Remove
方法,允许客户取消订单并释放 Bean。
另外,还可以使用
@StatefulTimeout
注解来释放有状态会话 Bean。该注解可以指定 Bean 在没有任何活动的情况下可以分配给客户的时长。当这个时间到期时,会话超时,Bean 被释放。
2. 单例 Bean
单例 Bean 通常用于存储可被所有 Bean 访问和共享的数据,或者用于控制对某些外部资源的访问。例如,如果对某个外部 Web 服务的连接数量有限,可以创建一个单例来对需要这些连接的 EJB 进行限流。单例 Bean 还可以用作应用程序的全局存储(或缓存),其状态在客户端之间共享。
每个 JVM 每个应用程序中,具有给定名称的单例 Bean 只能存在一个。如果应用程序中需要多个单例,应给它们不同的名称。EJB 容器不会创建单例 Bean 的池。要创建单例 EJB,只需使用
@Singleton
注解标记它:
@Singleton
public class MyGlobalStorage {
...
}
单例 Bean 的创建时间由 EJB 容器决定,除非你特别要求在应用程序启动时创建该 Bean,这称为急切初始化,可以使用
@Startup
注解:
@Startup
@Singleton
public class MyGlobalStorage {
private Map<String, Object> = new HashMap<String, Object>();
...
addToStorage(String key, Object objToStore){...}
removeFromStorage(String key){...}
}
如果要创建一个在特定时间向队列发送消息的程序,可以编写一个单例 Bean,并请求 EJB 容器在应用程序启动时实例化它。使用
@PostConstruct
注解可以让容器在 Bean 的构造函数完成后立即调用一个方法:
@Startup
@Singleton
public class MyStockQuoteServer {
...
@PostConstruct
void sendPriceQuotes(){
// 连接到股票价格源并将消息发送到队列的代码放在这里
}
}
要访问单例的业务方法,客户端 Java 类需要调用特定单例上的公共静态方法
getInstance()
。如果手动实现单例设计模式,需要在类中声明一个私有构造函数和一个公共静态的
getInstance()
方法,否则 EJB 容器会处理这些。例如:
MyGlobalStorage.getInstance().addToStorage(“emplOfTheMonth”, bestEmployee);
EJB 容器允许对单例 Bean 进行并发访问,默认情况下,它应用容器管理的并发策略,让开发人员不必担心竞争条件等问题。只需使用
@Lock
注解指定在读取或写入操作期间是否要锁定资源。如果更喜欢自己编写线程同步代码,可以切换到 Bean 管理的并发,使用
@ConcurrencyManagement
注解设置并发类型:
@Singleton
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
public class MyGlobalStorage {
...
}
3. EJB 部署
在部署到任何应用服务器之前,EJB 通常会打包成一个存档文件,可以是 jar、企业存档(ear,即扩展名为 .ear 的 jar 文件)或 war。即使是像当前示例这样简单的双类 Web 应用程序,也应压缩并作为一个文件部署。如果客户端是 Web 应用程序,将 EJB 放在 war 文件中会很方便。
部署步骤如下:
1. 在 Eclipse 项目
Lesson32
中,右键单击“Deployment Descriptor”部分,选择“Export WAR file”。
2. 片刻后,将得到包含 servlet 和 EJB 的
Lesson32.war
文件。
这个 war 文件可以部署到任何符合 Java EE 6 标准的应用服务器中。Java EE 6 使 EJB 组件非常轻量级,这个 war 文件可能不到 4 KB。如果有多个 EJB 类,可以将它们放在
WEB-INF/lib
目录下的一个或多个 jar 文件中。
如果客户端不是小型 Web 应用程序,或者想单独部署 EJBs,可以将它们打包到单独的 jar 或 ear 文件中。创建 ear 文件的根目录必须包含所有编译后的 Java 类,以及(如果适用)
META-INF
目录中的可选
ejb-jar.xml
文件。实际上,还可以创建一个 ear 文件,其中不仅包含 EJBs,还包含 war 文件。
4. 消息驱动 Bean
消息驱动 Bean(MDB)的唯一功能是通过 JMS API 从队列和主题中检索消息。客户端无需查找或调用它们的方法,只需将消息放入队列或发布到主题,监听这些目的地消息的 MDB 会自动被调用。
MDB 必须实现
MessageListener
接口,所有配置参数可以在
@MessageDriven
注解的参数中指定,示例如下:
@MessageDriven(mappedName=”jms/testQueue”, activationConfig = {
@ActivationConfigProperty(propertyName = “acknowledgeMode”,
propertyValue = “Auto-acknowledge”),
@ActivationConfigProperty(propertyName = “destinationType”,
propertyValue = “javax.jms.Queue”)
})
public class MyMessageBean implements MessageListener {
MessageDrivenContext ctx;
// 需要一个无参数的构造函数
public MyListener() {}
public void onMessage(Message message){
// 业务逻辑在此实现
}
}
当名为
testQueue
的队列中出现消息时,EJB 容器会从池中选择一个 MDB,并调用其
onMessage()
方法,将队列中的消息作为参数传递。与第 30 课中描述的独立消息接收器不同,MDB 为你提供了出色的免费功能,如分布式事务处理、自动池化、接收器与其他 Bean 的共置,以及在部署描述符中简单地将队列或主题分配给接收器。此外,还可以通过在部署描述符中指定池大小来轻松配置接收器的数量。
可以使用任何客户端向队列发送消息,如独立客户端、servlet、EJB 等。
5. 定时器服务
许多企业应用程序需要使用调度程序在特定时间执行某些重复任务。Unix 应用程序广泛使用 Cron 调度程序,Windows 也有任务调度程序(控制面板 → 计划任务),开源的 Quartz 调度程序在 Java 开发人员中也很受欢迎。
EJB 3.1 包含
@Schedule
注解,它接受基于日历的表达式,以便在 Bean 中安排所需功能的执行。例如,可以创建表达式,使某些业务方法每秒、每分钟、每小时、每周一、工作日午夜等时间被调用。
以下是一个创建定时器的示例,该定时器将在工作日的上午 9:30 到下午 4:00 之间每秒调用
getPriceQuotes()
方法:
@Stateless
public class MyStockQuoteFeed {
@Schedule(second=”*”, minute=”*”, hour=”9:30-16:00”, dayOfWeek=”Mon-Fri”)
public List getPriceQuotes(){
// 连接到价格报价源的代码放在这里
...
}
}
除了使用
@Schedule
注解,还可以使用
TimeService
类及其方法
createTimer()
、
createSingleActionTimer()
、
createIntervalTimer()
和
createCalendarTimer()
以编程方式创建定时器。此外,还可以在部署描述符
ejb-jar.xml
中配置定时器。
6. 实践项目
实践项目要求将
StockServer
类实现为 EJB,并使用定时器每秒自动生成并打印股票价格报价。新的报价应通过 JMS API 发送到
testQueue
,并由消息驱动 Bean 消费。
实践步骤如下:
1. 在 Eclipse 项目
Lesson32
中创建一个新的无状态会话 Bean
StockServerBean
,包含
getQuote()
方法:
@Stateless
public class StockServerBean {
private String price=null;
private ArrayList<String> nasdaqSymbols = new ArrayList<String>();
public StockServerBean(){
// 定义一些硬编码的纳斯达克符号
nasdaqSymbols.add(“AAPL”);
nasdaqSymbols.add(“MSFT”);
nasdaqSymbols.add(“YHOO”);
nasdaqSymbols.add(“AMZN”);
nasdaqSymbols.add(“MOT”);
}
public void getQuote(String symbol){
if(nasdaqSymbols.indexOf(symbol.toUpperCase()) != -1) {
// 为有效符号生成随机价格
price = (new Double(Math.random()*100)).toString();
}
System.out.println(“The price of ”+ symbol + “ is ” + price);
}
}
-
使用
@Schedule注解让getQuote()方法每秒被调用。 -
将
getQuote()方法中的println()语句替换为将生成的价格报价作为文本消息发送到在第 31 课中配置的队列MyJMSTestQueue的代码。 -
创建一个名为
MyPriceConsumer的 MDB,用于从队列MyJMSTestQueue中检索并打印消息。 - 将此应用程序部署到 GlassFish 并进行测试。
7. Java 持久化 API 简介
在之前的学习中,了解了各种类型的企业 Java Bean,可以在其中编写应用程序的业务逻辑。现在是时候讨论数据持久化了。如果在线商店允许用户使用会话 Bean 下订单,那么也应该有保存数据的机制。通常,数据会持久化到数据库管理系统(DBMS)中。
Java 持久化 API(JPA)定义了一种将 Java 类映射到其关系数据库对等物的标准方法,这个过程也称为对象关系映射(ORM)。虽然第 36 课将介绍流行的第三方 ORM 框架 Hibernate,但本课是对由任何符合 Java EE 6 标准的服务器实现的标准 JPA 2.0 的简要介绍。
8. 整体概述
过去,J2EE 规范建议使用实体 EJB 来提供与数据库的所有交互。目前,实体 Bean 正在经历精简过程,应该使用 JPA 来处理应用程序的数据查询和持久化。实际上,JPA 2.0 也可以在 Java SE 应用程序中使用。
JPA 使你能够指定和运行查询以及更新数据,而无需像在第 22 课学习 JDBC 时那样编写 SQL 语句。Java 持久化 API 允许使用元数据将 Java 类映射到数据库表,并使用 Java 持久化查询语言(JPQL)、持久化标准 API 和 SQL 语言的原生数据库查询来执行创建、检索、更新和删除(CRUD)操作。其理念是创建一个特定于应用程序的领域模型,作为一组相互关联的 Java 类,并将其映射到相应的数据存储(DBMS)。
9. 实体概念
如果一个带有
@Entity
注解的 Java 类有无参构造函数,那么可以称它为实体。每个实体实例对应于数据库表中的一行,例如:
@Entity
public class Employee{
...
}
如果从一个空数据库开始,JPA 工具可以根据 Java 实体创建数据库表。也可以将 Java 实体映射到现有的数据库表。与数据库表一样,Java 实体可以具有一对一关系(如一个
Employee
实体对应一个
OfficeAddress
实体)、一对多关系(如一个
Customer
有多个
Orders
)、多对一关系(一对多关系的相反情况)和多对多关系(例如,一个
UniversityClass
有许多注册的
Students
,而每个
Student
可以注册多个班级)。
每个实体类必须定义一个包含唯一值的字段,这相当于数据库表中的主键。
EntityManager
类将处理对象。在持久化数据之前,可以使用 Bean 验证 API 验证值。
10. 对象映射到数据库表
可以通过注解、XML 配置文件或两者结合的方式将 Java 类映射到数据库表。例如,常见字段可以使用注解映射,而特定于 DBMS 的映射可以在 XML 文件中完成。映射不一定是一对一的,一个 Java 实体可以映射到多个数据库表的一组列。
除了将字段映射到表列之外,Java 实体还可以包含可嵌入类,如
Employee
实体中的
Address
。
通常,数据库表有一个主键,即一个或多个唯一标识每一行的列。Java 实体必须有一个或多个字段使每个实例唯一。这样的实体 ID 可以用
@Id
注解标记,并可以通过添加
@GeneratedValue
注解请求 JPA 提供程序自动生成 ID。以下是一个
Employee
实体的示例:
@Entity
public class Employee{
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@NotNull
@Size(max=10)
public String firstName;
@NotNull
@Size(min=2, max=20)
public String lastName;
@Column(name=”boss_name”)
public String managerName;
@OneToMany (mappedBy = “employee”)
public List<Address> addresses = new ArrayList<Address>();
}
如果没有在注解参数中指定表名,JPA 会假设存在一个与实体类同名的对应数据库表,在本例中为
Employee
。指定的策略
GenerationType.IDENTITY
表示 DBMS 有一个自动递增的自动生成主键。许多数据库管理系统支持具有类似功能的标识列或序列对象。
必须有值的字段标记为
@NotNull
。如果上述
Employee
实体的实例在
firstName
或
lastName
字段中没有值,Bean 验证可以捕获并生成错误。不需要持久化的实体字段应标记为
@Transient
注解。
如果表列名与实体字段名不同,可以使用
@Column
注解指定列名。根据上述示例,数据库列名
boss_name
对应于实体
Employee
的字段
managerName
。
并非每个对应于数据库中某些数据的 Java 类都必须是实体。可以有可嵌入类,它们定义属于实体的一组属性。例如,公司给每个员工一部由电话号码和型号标识的智能手机,可以创建一个 Java 类来表示这样的设备,并使用
@Embeddable
注解标记它:
@Embeddable
public class SmartPhone implements Serializable{
@Size(max=10)
public String phoneNumber;
public String model;
}
现在,
Employee
实体可以嵌入
SmartPhone
类型的属性以及其他字段:
@Entity
public class Employee{
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@NotNull
public String firstName;
...
@Embedded
public SmartPhone companyPhone;
}
上述代码展示了
Employee
和
Address
实体之间的一对多关系。一个员工可以有多个地址,因此
Employee
引用了
Address
实体的集合。
11. JPQL
JPQL 是一种类似 SQL 的查询语言。主要区别在于,SQL 操作的是 DBMS 模式、表和存储过程,而 JPQL 操作的是领域模型中的 Java 对象及其属性。执行 JPQL 查询时,无需了解底层数据存储对象的详细信息。
如果事先知道查询,可以进行预编译;否则,可以在运行时动态构建它们。类似地,在 JDBC 中可以使用
PreparedStatement
或
Statement
。JPQL 包含易于记忆(且不区分大小写)的关键字,如
SELECT
、
FROM
、
WHERE
、
ORDER BY
、
GROUP BY
等。
以下是一些 JPQL 查询示例:
- 查找所有有姓 Smith 的下属的经理:
SELECT e.managerName,
FROM Employee AS e
WHERE e.lastName=’Smith’
- 查找公司发放了 iPhone 的所有员工:
SELECT e.firstName, e.lastName
FROM Employee AS e
WHERE e.companyPhone.model=’iPhone’
-
选择某些实体的所有字段(相当于 SQL 中的
Select *):
SELECT e FROM Employee AS e
-
查找居住在纽约的所有员工(
Employee和Address实体之间的一对多关系):
SELECT DISTINCT e
FROM Employee AS e JOIN e.addresses as a
WHERE a.cityl=’New York’
通过以上内容,我们对企业级 Java 开发中的 EJB 和 JPA 有了初步的了解,掌握了单例 Bean、EJB 部署、消息驱动 Bean、定时器服务等概念,以及如何使用 JPA 进行数据持久化和对象关系映射。在实际开发中,可以根据具体需求灵活运用这些技术,构建出高效、可扩展的企业应用程序。
企业级 Java 开发:EJB 与 JPA 入门指南
12. 实体关系总结
Java 实体之间的关系丰富多样,以下为你总结常见的几种关系:
| 关系类型 | 示例 | 描述 |
| ---- | ---- | ---- |
| 一对一 |
Employee
与
OfficeAddress
| 一个
Employee
实体对应一个
OfficeAddress
实体 |
| 一对多 |
Customer
与
Orders
| 一个
Customer
可以有多个
Orders
|
| 多对一 | 与一对多相反 | 多个实体对应一个实体 |
| 多对多 |
UniversityClass
与
Students
| 一个
UniversityClass
有许多注册的
Students
,每个
Student
可注册多个
UniversityClass
|
这些关系的存在,使得 Java 实体能够准确地模拟现实世界中的各种业务场景,为数据的组织和管理提供了强大的支持。
13. JPA 映射方式对比
JPA 提供了多种将 Java 类映射到数据库表的方式,下面为你详细对比不同映射方式的特点:
| 映射方式 | 优点 | 缺点 | 适用场景 |
| ---- | ---- | ---- | ---- |
| 注解 | 代码简洁,直观,易于理解和维护 | 不适合复杂的、特定于 DBMS 的映射 | 简单的、通用的映射场景 |
| XML 配置文件 | 可以进行复杂的、特定于 DBMS 的映射 | 配置文件可能会变得复杂,不易维护 | 复杂的、需要精细控制的映射场景 |
| 注解与 XML 结合 | 兼具注解和 XML 的优点,灵活度高 | 增加了一定的复杂度 | 既有简单映射又有复杂映射需求的场景 |
根据不同的项目需求和复杂度,选择合适的映射方式,能够提高开发效率和代码的可维护性。
14. 定时器服务的不同实现方式
定时器服务在企业应用中起着重要的作用,EJB 3.1 提供了多种实现定时器的方式,下面为你详细介绍:
-
@Schedule
注解
:
-
优点
:使用简单,通过基于日历的表达式可以方便地指定定时任务的执行时间,如每秒、每分钟、每小时等。
-
缺点
:表达式的编写需要一定的学习成本,对于复杂的定时规则可能不够灵活。
-
示例代码
:
@Stateless
public class MyStockQuoteFeed {
@Schedule(second=”*”, minute=”*”, hour=”9:30-16:00”, dayOfWeek=”Mon-Fri”)
public List getPriceQuotes(){
// 连接到价格报价源的代码放在这里
...
}
}
-
编程方式
:使用
TimeService类及其方法createTimer()、createSingleActionTimer()、createIntervalTimer()和createCalendarTimer()。- 优点 :灵活性高,可以根据具体需求动态创建定时器,适用于复杂的定时任务。
- 缺点 :代码实现相对复杂,需要对定时器的创建和管理有深入的理解。
-
部署描述符配置
:在
ejb-jar.xml中配置定时器。- 优点 :将定时器配置与代码分离,便于修改和维护。
- 缺点 :需要熟悉 XML 配置文件的编写,不够直观。
开发人员可以根据项目的具体需求,选择合适的定时器实现方式。
15. 实践项目流程图
下面是实践项目的流程图,展示了将
StockServer
类实现为 EJB 并使用定时器服务的整个流程:
graph LR
A[创建 StockServerBean] --> B[使用 @Schedule 注解]
B --> C[替换 println 语句发送消息到队列]
C --> D[创建 MyPriceConsumer MDB]
D --> E[部署应用到 GlassFish 并测试]
这个流程图清晰地展示了实践项目的步骤,帮助你更好地理解整个开发过程。
16. 数据持久化流程
使用 JPA 进行数据持久化时,通常遵循以下步骤:
1.
定义实体类
:使用
@Entity
注解标记 Java 类,定义实体的属性和关系。
2.
配置映射
:通过注解、XML 配置文件或两者结合的方式,将实体类映射到数据库表。
3.
创建
EntityManager
:
EntityManager
是 JPA 中用于管理实体的核心类,负责实体的持久化操作。
4.
执行 CRUD 操作
:使用
EntityManager
的方法,如
persist()
、
find()
、
merge()
、
remove()
等,执行创建、检索、更新和删除操作。
5.
事务管理
:在进行数据持久化操作时,需要进行事务管理,确保数据的一致性和完整性。
以下是一个简单的数据持久化示例:
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class PersistenceExample {
public static void main(String[] args) {
// 创建 EntityManagerFactory
EntityManagerFactory emf = Persistence.createEntityManagerFactory("myPersistenceUnit");
// 创建 EntityManager
EntityManager em = emf.createEntityManager();
try {
// 开始事务
em.getTransaction().begin();
// 创建实体对象
Employee employee = new Employee();
employee.setFirstName("John");
employee.setLastName("Doe");
// 持久化实体
em.persist(employee);
// 提交事务
em.getTransaction().commit();
} catch (Exception e) {
// 回滚事务
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
e.printStackTrace();
} finally {
// 关闭 EntityManager
em.close();
// 关闭 EntityManagerFactory
emf.close();
}
}
}
17. JPQL 与 SQL 的对比
虽然 JPQL 和 SQL 都是用于数据查询的语言,但它们之间存在一些重要的区别:
| 对比项 | JPQL | SQL |
| ---- | ---- | ---- |
| 操作对象 | Java 对象及其属性 | DBMS 模式、表和存储过程 |
| 编译方式 | 可以预编译或动态构建 | 通常需要预编译 |
| 关键字大小写 | 不区分大小写 | 部分数据库区分大小写 |
| 对底层数据的依赖 | 无需了解底层数据存储对象的详细信息 | 需要了解数据库表结构和字段信息 |
在使用时,需要根据具体情况选择合适的查询语言。如果更关注 Java 对象的操作,JPQL 是更好的选择;如果需要直接操作数据库表和存储过程,则 SQL 更为合适。
18. 总结与展望
通过对 EJB 和 JPA 的学习,我们掌握了企业级 Java 开发中的多个重要概念和技术。EJB 提供了强大的组件化开发能力,包括单例 Bean、消息驱动 Bean 和定时器服务等,能够帮助我们构建高效、可扩展的企业应用程序。JPA 则为数据持久化提供了标准的解决方案,通过对象关系映射,使得 Java 类与数据库表之间的映射变得简单而直观。
在未来的开发中,我们可以进一步深入学习这些技术,结合实际项目需求,灵活运用 EJB 和 JPA 的各种特性。例如,可以优化 EJB 的并发管理,提高系统的性能;可以使用 JPA 的高级特性,如 Criteria API 进行动态查询,提高查询的灵活性。同时,还可以将 EJB 和 JPA 与其他技术结合使用,如 Spring 框架,构建更加复杂和强大的企业应用。
总之,掌握 EJB 和 JPA 技术,将为我们在企业级 Java 开发领域打下坚实的基础,帮助我们开发出更加优秀的应用程序。
超级会员免费看
980

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



