在混合持久性环境中,用于实现微服务的编程语言必须处理不同的持久性技术。您的编程语言必须能支持每种持久保存数据的不同方式。作为编程语言,Java 拥有许多 API 和框架,可帮助开发人员处理不同的持久性技术。
"Java Persistence API (JPA) 是一种在 Java 对象 / 类与关系数据库之间访问、持久化和管理数据的 Java 规范。EJB 3.0 规范中将 JPA 定义为取代 EJB 2 CMP Entity Beans 规范的一种规范。现在,在 Java 行业中,JPA 被视为对象关系映射 (ORM) 的标准行业方法。
JPA 本身只是一个规范,不是产品;它本身无法无法执行持久化或任何其他操作。JPA 只是一组接口,需要一种实现。有开源和商用的 JPA 实现可供选择,而且所有 Java EE 5 应用服务器都应支持使用它。JPA 还需要一个数据库来实现持久化。"
Java Enterprise Edition 7 (Java EE 7) 包含 Java Persistence 2.1 (JSR 338)。
发明 JPA 主要是为了得到一种对象关系映射器,以便在关系数据库中持久保存 Java 对象。API 背后的实现(持久化提供程序)可由不同的开源项目或供应商实现。使用 JPA 的另一个好处是,您的持久化逻辑更容易移植。
JPA 定义了自己的查询语言(Java 持久化查询语言 (JPQL)),可为不同数据库供应商生成查询。Java 类被映射到数据库中的表,也可以使用类之间的关系来建立对应的表之间的关系。可使用这些关系建立级联操作,所以一个类上的操作可能导致其他类的数据上的操作。JPA 2.0 引入了 Criteria API,可帮助在运行时和编译时获得正确的查询结果。在应用程序中实现的所有查询都有一个名称,可以通过这个名称找到它们。这种配置使得在完成编程几个星期后更容易知道查询的功能。
JPA 持久化提供程序实现数据库访问。以下是最常用的提供程序:
Liberty for Java EE 7 的默认 JPA 提供程序是 EclipseLink。
下面的简要介绍和代码段展示了 JPA 的特性。开始使用 JPA 的最佳方式是创建实体类来持有数据(示例 1)。
- @Entity
- public class Order {
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- private Long id;
- private String description;
- @Temporal(TemporalType.DATE)
- private Date orderDate;
- public String getDescription() {
- return description;
- }
- public void setDescription(String description) {
- this.description = description;
- }
- public Date getOrderDate() {
- return orderDate;
- }
- public void setOrderDate(Date orderDate) {
- this.orderDate = orderDate;
- }
- ……
- }
每个实体类都需要一个 @Entity 注释,然后才能通过持久化提供程序管理。实体类根据名称映射到数据库中的对应表(约定优于配置)。也可应用不同的映射。类的属性根据名称映射到基础表的列。也可覆盖属性的自动映射 (@Column)。每个实体类必须有一个实体(参见第 2 部分 "领域驱动设计" 中的 "将领域元素映射到服务")。持久化提供程序必须使用一个或多个身份列(使用 @Id 标注)来将对象的值映射到表中的数据行。数据库或持久化提供程序可通过不同方式生成身份列的值。实体类的一些属性必须以特殊方式转换,才能存储在数据库中。例如,数据库列 DATE 必须使用注释 @Temporal 映射到实体类中。
用于查询数据库的主要 JPA 接口是 EntityManager。它包含从数据库创建、读取、更新和删除数据的方法。可通过不同方式获取 EntityManager 的引用,具体取决于应用程序运行的环境。在非托管环境中(没有 servlet、EJB 或 CDI 容器),必须使用类的工厂方法 EntityManagerFactory,如示例 2 所示。
- EntityManagerFactory entityManagerFactory =
- Persistence.createEntityManagerFactory("OrderDB");
- EntityManager entityManager =
- entityManagerFactory.createEntityManager();
字符串 OrderDB 是为其创建 EntityManager 的持久化单元的名称。持久化单元用于对实体类和相关属性进行逻辑分组,以配置持久化提供程序(persistence.xml 文件中的配置)。
在托管环境中,情况更简单。可从容器注入 EntityManager,如示例 3 所示。
- @PersistenceContext
- EntityManager em;
如果注入持久化上下文时未使用 unitName,也就是配置文件 (persistence.xml) 中配置的持久化单元的名称,则会使用默认值。如果只有一个持久化单元,那么该值就是 JPA 使用的默认值。
以下各节将介绍如何使用来自 EntityManager 的方法实现一些简单的创建、检索、更新和删除方法,如示例 4 所示。
- @PersistenceContext
- EntityManager em;
- ...
- public Order createOrder(Order order) {
- em.persist(order);
- return order;
- }
EntityManager 的 persist 方法将执行两个操作。首先,EntityManager 管理该对象,这意味着它在其持久化上下文中保存该对象。可将持久化上下文视为一种缓存,在其中保存与数据库中的行相关的对象。这种关系是使用数据库事务来建立的。其次,该对象被持久存储在数据库中。如果 Order 实体类有一个 ID 属性,该属性的值由数据库生成,那么该属性的值将由 EntityManager 在将对象插入数据库中后设置。正因如此,createOrder 方法的返回值为对象本身(示例 5)。
- @PersistenceContext
- EntityManager em;
- ...
- public Order readOrder(Long orderID) {
- Order order = em.find(Order.class, orderID);
- return order;
- }
EntityManager 方法 find 在表中搜索以参数形式 (orderID) 提供主键的行。结果被转换为一个 Order 类型的 Java 类(示例 6)。
- @PersistenceContext
- EntityManager em;
- ...
- public Order readOrder(Long orderID) {
- TypedQuery<Order> query =
- em.createQuery( "Select o from Order o " +
- "where o.id = :id", Order.class );
- query.setParameter("id", orderID);
- Order order = query.getSingleResult();
- return order;
- }
示例 6 通过使用 JPQL 和一个参数显示了 find 方法的功能。从 JPQL 字符串 Select o from Order o where o.id = :id 开始,生成一个 TypedQuery。有了 TypedQuery,您就可以在生成结果对象后省略 Java 转换(参见参数 Order.class)。JPQL 中的参数可按名称 (id) 进行查找,该名称使得开发人员很容易理解它。方法 getSingleResult 确保仅在数据库中找到一行。如果有多个行与 SQL 查询对应,则抛出一个 RuntimeException。
merge 方法在数据库中执行更新(示例 7)。参数 order 是一个游离对象,这意味着它不在持久化上下文中。在数据库中更新后,EntityManger 返回一个附加的(现在包含在持久化上下文中)order 对象。
- public Order updateOrder(Order order, String newDesc) {
- order.setDescription(newDesc);
- return em.merge(order);
- }
要删除数据库中的一行数据,需要一个附加对象(示例 8)。要将该对象附加到持久化上下文中,可以运行 find 方法。如果该对象已附加,则不需要运行 find 方法。通过使用 remove 方法和附加对象的参数,可以在数据库中删除该对象。
- public void removeOrder(Long orderId) {
- Order order = em.find(Order.class, orderId);
- em.remove(order);
- }
前面已经提到,必须使用某种配置来告诉持久化提供程序,在何处查找数据库和如何处理数据库。这在名为 persistence.xml 的配置文件中完成。该配置文件需要位于您的服务的类路径中。
依赖于您的环境(是否是 Java 容器),必须通过两种方式之一完成配置(示例 9)。
- <persistence>
- <persistence-unit name="OrderDB"
- transaction-type="RESOURCE_LOCAL">
- <class>com.service.Order</class>
- <properties>
- <!-- Properties to configure the persistence provider -->
- <property name="javax.persistence.jdbc.url"
- value="<jdbc-url-of-database" />
- <property name="javax.persistence.jdbc.user"
- value="user1" />
- <property name="javax.persistence.jdbc.password"
- value="password1" />
- <property name="javax.persistence.jdbc.driver"
- value="<package>.<DriverClass>" />
- </properties>
- </persistence-unit>
- </persistence>
要配置持久化提供程序,必须做的第一件事就是定义持久化单元 (OrderDB)。持久化单元的一个属性是 transaction-type。可设置两个值:RESOURCE_LOCAL 和 JTA。第一个选项让开发人员负责在其代码中执行事务处理。如果您的环境中没有事务管理器,那么可以使用该选项。第二个选项是 JTA,它表示 Java Transaction API,包含在 Java Community Process (JCP) 中。此选项告诉持久化提供程序,将事务处理委托给运行时环境中存在的事务管理器。
在 XML 标记 <class></class> 之间,可以列出要在这个持久化单元中使用的实体类。
在该文件的 properties 部分,可以设置值来配置持久化提供程序将处理数据库的方式。以 javax.persistence.jdbc 开头的属性名称由 JPA 标准定义。示例 9 展示了如何设置数据库 URL(用于建立数据库连接)、用户名和密码。javax.persistence.jdbc.driver 属性告诉持久化提供程序应该使用哪个 JDBC 驱动程序类。
Java EE 环境的配置文件中的主要区别如示例 10 所示。
- <persistence>
- <persistence-unit name="OrderDB">
- <jta-data-source>
- jdbc/OrderDB
- </jta-data-source>
- <class>
- com.widgets.Order
- </class>
- ...
- </persistence-unit>
- </persistence>
JPA 中的默认事务处理方法是使用 JTA,所以您不需要在配置文件中设置它。jta-data-source 属性指向 Java EE 环境的应用服务器中配置的数据源的 JNDI-Name。
要在非 Java EE 环境中执行事务管理,可使用 EntityManager 的一些方法,如示例 5-11 所示。
- EntityManagerFactory emf =
- Persistence.createEntityManagerFactory("OrderDB");
- EntityManager em = emf.createEntityManager();
- EntityTransaction tx = em.getTransaction();
- tx.begin();
- try {
- em.persist(yourEntity);
- em.merge(anotherEntity);
- tx.commit();
- } finally {
- if (tx.isActive()) {
- tx.rollback();
- }
- }
示例 11 给出了非 Java EE 环境中的事务处理过程。请避免自行在 Java EE 环境中执行事务管理。还有更好的管理方式,如第小节 "Enterprise JavaBeans" 所述。
要分离微服务的数据存储,可以使用示例 12 中的配置文件来设置关系数据库的默认模式。
- <?xml version="1.0" encoding="UTF-8" ?>
- <entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm orm_2_0.xsd"
- version="2.0">
- <persistence-unit-metadata>
- <persistence-unit-defaults>
- <schema>
- ORDER
- </schema>
- </persistence-unit-defaults>
- </persistence-unit-metadata>
- </entity-mappings>
必须在 JPA 配置文件 persistence.xml 中引用此文件(示例 13)。
- <persistence-unit name="OrderDB">
- <mapping-file>
- custom-orm.xml
- </mapping-file>
此配置将模式名称被设置为所有 JPA 类的 my-orm.xml 映射文件中提供的名称,在本例中为 OrderDB。它可确保您仅使用此模式中的表。
EclipseLink 是开始支持 NoSQL 数据库的 JPA 提供程序之一(从 2.4 版开始)。从此版本开始,它们开始支持 MongoDB 和 Oracle NoSQL。预计在未来的版本中还会支持其他 NoSQL 数据库。
MongoDB 是一个面向文档的 NoSQL 数据库。它的数据结构有利于具有动态模式的 JSON 式文档。MongoDB 拥有一个专门的 JSON 格式版本,名为 BSON。
EclipseLink 2.5 版的 EclipseLink 解决方案指南给出了一个访问 MongoDB 的示例。
在决定使用 JPA(像 EclipseLink 一样)作为 MongoDB 的提供程序之前,请考虑以下几点:
如果您熟悉 JPA,而且只需要使用一些简单功能将数据存储在 NoSQL 数据库中,那么可以开始使用 JPA 提供程序实现此目的。如果数据访问变得更加复杂,那么最好使用来自 NoSQL 数据库的 Java 驱动程序。JPA 并不真的适合 NoSQL 数据库,但它是您的实现的不错起点。有关如何使用 MongoDB 的原生 Java 驱动程序的示例,请访问:
http://docs.mongodb.com/getting-started/java/
要更充分地利用 JPA 提供程序,使用 Spring Data JPA 可能很有帮助。除了 JPA 提供程序之外,Spring Data JPA 还在 JPA 提供程序之上添加了一个额外层:
http://projects.spring.io/spring-data-jpa/
下面列出了为什么 JPA 对微服务中的数据处理很有用的一些理由:
Enterprise JavaBeans 3.2 (EJB)(在 JSR 345 中指定)包含在 Java EE 规范中。EJB 并不是普通的 Java 类,原因如下:
EJB 是服务器端软件组件。从 EJB 3.0 开始,它不再使用部署描述符。EJB 的所有声明都可在 EJB 类自身中使用注释完成。与 CDI 管理 bean 的实现一样,在 EJB 容器内处理 EJB 的实现也是轻量级的。EJB 的线程处理由 EJB 容器完成(类似于 servlet 容器中的线程处理)。EJB 的一个附加特性是,它们可与 Java EE Security 结合使用。
EJB 可分为以下类型:
无状态 EJB 无法拥有任何状态,但有状态 EJB 可以。由于微服务的特征,微服务中不应使用有状态 EJB。一个 Singleton Bean 仅在一个 Java EE 服务器中存在一次。异步消息处理中会结合使用 MDB 和 JMS 提供程序。
EJB 可实现多个业务视图,必须相应地注释这些视图:
此接口中的方法只能由同一个 Java 虚拟机 (JVM) 中的客户端调用。
此接口中列出的方法可由 JVM 外部的客户端调用。
与本地接口大体相同,EJB 类的所有公共方法都向客户端公开。
在轻量型架构中(微服务应拥有这种架构),将 EJB 实现为无接口 EJB 会很有用。
EJB 提供的主要好处之一是自动事务处理。每次调用一个业务方法时,都会调用 EJB 容器的事务管理器(例外:显式关闭了事务支持的 EJB)。所以,很容易将 EJB 与事务数据存储结合使用。将 EJB 与 JPA 相集成也很容易。
示例 14 中的代码段给出了一个将 EJB 与 JPA 框架结合的示例。
- @Stateless
- @LocalBean
- public class OrderEJB {
- @PersistenceContext
- private EntityManager entityManager;
- public void addOrder(Order order) {
- entityManager.persist(order);
- }
- public void deleteOrder(Order order) {
- entityManager.remove(order);
- }
- ...
- }
根据小节 "Java Persistence API" 中的介绍,注入 EntityManager。
EJB 可拥有以下事务属性之一来处理事务数据存储(必须由 EJB 容器实现):
REQUIRED(默认)
MANDATORY
NEVER
NOT_SUPPORTED
REQUIRES_NEW
SUPPORTS
有关这些属性的更多信息,请访问网站。
这些所谓的容器管理事务 (CMT) 可用在 EJB 的任何业务方法上。应避免 Bean 管理事务 (BMT),它们也可用在 EJB 中。可在类级别上设置注释 TransactionAttribute,使该类的每个业务方法都有自己的事务属性(示例 15)。如果不执行任何设置,那么所有方法都将拥有默认事务级别 (REQUIRED)。方法级别的事务属性会覆盖类属性。
- @TransactionAttribute(REQUIRED)
- @Stateless
- @LocalBean
- public class OrderEJB {
- ...
- @TransactionAttribute(REQUIRES_NEW)
- public void methodA() {...}
- @TransactionAttribute(REQUIRED)
- public void methodB() {...}
- }
在事务管理其决定提交时,会执行一些数据库更改或验证。在某些情况下,验证数据库中的检查约束是提交事务之前的最后一步。如果验证失败,JPA 提供程序将抛出一个 RuntimeException,因为 JPA 使用运行时异常来报告错误。如果使用 EJB 执行事故管理,则捕获 RuntimeException 的位置位于 EJB 的存根代码中,EJB 容器将在这里执行事故管理。存根代码由 EJB 容器生成。因此,您无法处理此 RuntimeException,该异常被进一步抛出到它会被捕获到的地方。
如果将 REST 端点实现为 EJB,就像一些人喜欢的那样,则必须在 REST 提供程序中捕获该异常。REST 提供程序拥有异常映射器,可将异常转换为 HTTP 错误代码。但是,当在数据库提交期间抛出 RuntimeException 时,这些异常映射器不会进行干预。因此,REST 客户端会收到 RuntimeException,应该避免这种情况。
处理这些问题的最佳方式是,将 REST 端点实现为 CDI 管理的请求范围的 bean。在这个 CDI bean 中,可以使用与 EJB 内相同的注入方式。所以很容易将 EJB 注入 CDI 管理的 bean 中(示例 16)。
- @RequestScoped
- @Path("/Order")
- public class OrderREST {
- @EJB
- private OrderEJB orderEJB;
- ...
- }
也可以将 EJB 与 Spring 集成(Enterprise JavaBeans (EJB) 集成 - Spring),而且如果您愿意的话,可以使用 Transaction Management Spring 执行事务管理。但是,在 Java EE 领域,将事务管理委托给服务器会更好一些。
有关 Spring 的更多信息,请访问这个网站。
BeanValidation 也包含在 Java EE 7 规范中:Bean Validation 1.1 JSR 349。Bean Validation 的用途是在 bean 数据上轻松地定义和执行验证约束。在 Java EE 环境中,Bean 验证由不同的 Java EE 容器自动完成。开发人员只需要在属性、方法或类上以注释形式设置约束条件。验证会在调用这些元素时自动完成(如果已配置)。验证也可以在源代码中显式完成。有关 BeanValidation 的更多信息,请访问网站。
javax.validation.constraints 包中的内置约束示例如示例 17 所示。
- private String username; // username must not be null
- @Pattern(regexp="\\(\\d{3}\\)\\d{3}-\\d{4}")
- private String phoneNumber; // phoneNumber must match the regular expression
- @Size(min=2, max=40)
- String briefMessage; // briefMessage netween 2 and 40 characters
也可以组合使用多个约束条件,如示例 18 所示。
- @NotNull
- @Size(min=1, max=16)
- private String firstname;
还可以扩展约束条件(自定义约束条件)。示例 19 展示了如何自行执行验证。
- Order order = new Order( null, "This is a description", null );
- ValidatorFactory factory =
- Validation.buildDefaultValidatorFactory();
- Validator validator = factory.getValidator();
- Set<ConstraintViolation<Order>> constraintViolations = validator.validate(order);
- assertEquals( 2, constraintViolations.size() );
- assertEquals( "Id may not be null",
- constraintViolations.iterator().next().getMessage() );
- assertEquals( "Order date may not be null",
- constraintViolations.iterator().next().getMessage() );
可以通过配置 JPA 来自动执行 Bean 验证。JPA 规范要求,持久化提供程序必须验证所谓的托管类(参见示例 20)。从 JPA 的意义上讲,托管类是实体类。用于 JPA 编程的所有其他类也必须验证(例如嵌入式类、超类)。此过程必须在这些托管类参与的生命周期事件中完成。
- <persistence>
- <persistence-unit name="OrderDB">
- <provider>
- org.eclipse.persistence.jpa.PersistenceProvider
- </provider>
- <class>
- ...
- </class>
- <properties>
- <property name="javax.persistence.validation.mode" value="AUTO" />
- </properties>
- </persistence-unit>
- </persistence>
使用的来自 Java EE 产品栈的所有其他框架都可用于自动验证 bean(例如 JAX-RS、CDI),也可以使用 EJB,如示例 21 所示。
- @Stateless
- @LocalBean
- public class OrderEJB {
- public String setDescription(@Max(80) String newDescription){
- ...
- }
- }
据微服务中实现的层,需要解决一些方面的问题。
在仅包含少量层的服务中,使用 JPA 实体类作为数据传输对象 (DTO) 也更容易。将 JPA 对象与它的持久化上下文分离后,可以将它用作简单 Java 对象 (POJO)。还可以将这个 POJO 用作 DTO,以便将数据传输到 REST 端点。以这种方式传输数据有一些缺点。BeanValidation 注释与 JPA 注释混合在一起,这可能导致 Java 类包含大量注释,而且您的 REST 端点与数据库的关系更紧密。
如果微服务稍大一点或需要处理更复杂的数据模型,那么最好使用一个单独层来访问数据库。这个额外层基于 JPA 类来实现所有数据访问方法。此层中的类是数据访问对象 (DAO)。可以使用 DAO 类为 REST 端点生成 DTO 类,一方面关注数据模型 (DAO),另一方面关注客户端 (DTO)。这种模式的缺点是,必须将 DAO 层中处理的 JPA 类转换为 DTO,并转换回来。为了避免创建大量样板代码来执行此任务,可以使用一些框架来帮助转换。可以使用以下框架来转换 Java 对象:
要增加 BeanValidation 带来的可能性,使用 Spring 获得额外的特性可能很有用。有关的更多信息,请参见网页上的 "Validation, Data Binding, and Type Conversion"。
如果您的微服务不打算将数据存储在事务数据存储中,可以考虑使用 CDI 管理 bean 代理 EJB。CDI 1.1 是在 JSR 346 中指定的,包含在 Java EE 7 规范中。CDI 管理 bean 可能是这些情况下的不错替代方案。
有关 CDI 管理 bean 的更多信息,请访问下面这个网站:
http://docs.oracle.com/javaee/6/tutorial/doc/giwhl.html
与 EJB 相比,CDI 本身没有 Java EE 安全机制,没有注入持久化上下文。此过程必须由开发人员自己完成,或使用其他框架完成。举例而言,Apache DeltaSpike 有许多模块可用于扩展 CDI 的功能。有关 Apache DeltaSpike 的更多信息,请访问:
http://deltaspike.apache.org/index.html
可使用其他框架来扩展 CDI 管理 bean 的功能。EJB 有一个可在应用服务器中管理的线程池。CDI 目前没有与此功能相对应的功能。能够配置线程池,这在高负载的环境中很有帮助。
为了实现不在 Java EE 应用服务器中运行的微服务,CDI 和其他模块提供了许多对这些环境有用的功能。
为了在 Java 领域实现事件驱动架构,JMS API 提供了相关支持,Java Message Service 2.0 JSR 343 中也指定了该 API。JMS 用于与必须通过面向消息的中间件 (MOM) 实现的消息提供程序通信。
当 JMS 从 1.1 版更新到 2.0 版(包含在 Java EE 7 规范中)时,进行了大量返工来让 API 更容易使用。JMS 2.0 兼容更低的版本,所以您可以对新微服务使用现有代码或使用新的简化 API。下一个版本不会弃用旧 API。
依据智能端点和哑管道方法,基于 Java 的微服务必须将 JSON 消息发送到 JMS 提供程序托管的端点。消息的发送方被称为生成者,消息的接收方被称为使用者。这些端点可具有以下类型:
一个队列中的消息仅由一个使用者使用。队列中的消息序列可按不同的顺序使用。队列采用端到端的语义使用。
这些消息可供多个使用者使用。这是发布 / 订阅语义的实现。
在基于 REST 的微服务中(其中基于 JSON 的请求由客户端发出),对消息系统也采用 JSON 格式是一个不错的主意。其他消息应用程序使用了 XML。如果您的微服务系统中仅有一种格式,则更容易实现 JSON。
- @Stateless
- @LocalBean
- public class OrderEJB {
- @Inject
- @JMSConnectionFactory("jms/myConnectionFactory")
- JMSContext jmsContext;
- @Resource(mappedName = "jms/PaymentQueue")
- Queue queue;
- public void sendMessage(String message) {
- jmsContext.createProducer().send(queue, message);
- }
需要一个 JMSContext 和一个 Queue 才能发送消息(示例 22)。如果消息发送方在 Java EE 应用服务器中运行,则会注入这些对象。示例 22 使用一个 EJB,所以注入了这些资源。必须在应用服务器中对注入的对象进行配置。如果发生异常,则会抛出一个运行时异常 JMSRuntimeException。
注入的 JMSContext 在 JTA 事务中的范围为 transaction。所以如果您的消息生成者是 EJB,您的消息将传送到事务的上下文中,这样做可以避免松散的消息。
要使用来自队列的消息,使用消息驱动 EJB (MDB) 就能轻松实现,如示例 23 所示。使用 MDB 的另一个优势是,消息的使用在事务中完成,所以不会丢失消息。
- @MessageDriven(
- name="PaymentMDB",
- activationConfig = {
- @ActivationConfigProperty(
- propertyName="messagingType",
- propertyValue="javax.jms.MessageListener"),
- @ActivationConfigProperty(
- propertyName = "destinationType",
- propertyValue = "javax.jms.Queue"),
- @ActivationConfigProperty(
- propertyName = "destination",
- propertyValue = "PaymentQueue"),
- @ActivationConfigProperty(
- propertyName = "useJNDI",
- propertyValue = "true"),
- }
- )
- public class PaymentMDB implements MessageListener {
- @TransactionAttribute(
- value = TransactionAttributeType.REQUIRED)
- public void onMessage(Message message) {
- if (message instanceof TextMessage) {
- TextMessage textMessage = (TextMessage) message;
- String text = message.getText();
- ...
- }
- }
- }
必须使用 @MessageDriven 注释将 EJB 类型声明为消息驱动 EJB。在此注释内,可以设置 MDB 的名称和一些激活配置。激活配置的属性将 MDB 与处理队列或主题的 JMS 消息系统相关联。在托管您的 MDB 的应用服务器环境中,很容易配置这些元素。MDB 本身会实现一个 MessageListener 接口,该接口只有一个方法:onMessage。每当 JMS 提供程序有消息要处理时,它就会调用此方法。使用一个事务属性注释该方法,以表明它是在一个事务内调用的。MDB 的默认事务属性是 TransactionAttributeType.REQUIRED。在该方法内,必须转换消息对象,而且消息可以提取为字符串。也可使用其他消息类型。
仅使用 MDB 处理消息是一种不错的做法。将 MDB 保持为一个技术类。您的剩余业务代码应在 MDB 调用的 Java POJO 中实现。此配置使业务代码更容易在 JUnits 中测试。
前面已经提到过,每个 MDB 在一个事务内运行,所以不会丢失消息。如果在处理消息期间发生错误,而且 EJB(处理 MDB)收到此运行时异常,那么该消息会重新传送到 MDB(错误循环)。可在 JMS 提供程序中配置重试次数,它指定了发生此错误的频率。在达到重试上线次数后,消息通常被放入一个错误队列中。错误队列中的消息必须单独处理。
如果一个业务事务涵盖多个微服务,可使用事件驱动架构(参见小节 "跨微服务的数据共享")。这意味着发送事件的微服务必须执行以下任务:
接收微服务必须执行以下任务:
为了保持一致,如果数据存储是事务性的,这两个操作必须在一个事务中完成。对于消息系统的生成者和使用者,也要满足此要求。在这些情况下,必须使用分布式事务。事务伙伴是数据存储和 JMS 提供程序(不是两个微服务的两个数据存储)。
要跟踪生成和使用的消息,使用关联 ID 很有用。消息生成者指定的关联 ID 与使用者使用的消息相关联。这个关联 ID 也可用在微服务的日志记录中,以便获得微服务调用的完整通信路径。
Java 提供了一个类来生成唯一 Id:UUID。这个类可用于生成关联 ID。示例 24 展示了如何设置关联 ID。
- // JMSContext injected as before
- JMSProducer producer = jmsContext.createProducer();
- producer.setJMSCorrelationID(UUID.randomUUID().toString());
- producer.send(queue, message);
示例 25 展示了如何获取关联 ID。
- // message received as before
- String correlationId = message.getJMSCorrelationID();
有关 UUID 的更多信息,请访问下面这个网站:
http://docs.oracle.com/javase/7/docs/api/java/util/UUID.html
如果使用非 JMS 提供程序作为面向消息的中间件,JMS 可能不是向此系统发送消息的正确方法。可结合使用 RabbitMQ(一个 AMQP 代理)和 JMS,因为 Pivotal 为 RabbitMQ 实现了一个 JMS 适配器。有关 RabbitMQ 的更多信息,请访问下面这个网站:
http://www.rabbitmq.com/
Apache Qpid 也为 AMQP 协议实现了一个 JMS 客户端。这些只是一些示例,表明使用 JMS 与非 JMS 提供程序通信也很有用。但是,根据您的需求,使用消息系统的原生 Java 驱动程序有可能会更好一些。有关 Apache Qpid 的更多信息,请访问下面这个网站:
http://qpid.apache.org/index.html
Spring 对 JMS 消息提供程序的支持是处理 JMS 消息的另一种方案。有关 Spring 的更多信息,请访问下面这个网站:
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/jms.html
根据您需要的面向消息的中间件中的特性,您可能需要一个非 JMS 消息提供程序系统。MQTT 是一种最适合物联网 (IoT) 和 AMQP 的需求的消息协议。它是为实现跨供应商移植而开发的。
JMS 不能用于与这些系统通信。通过使用它们提供的客户端,可以使用它们提供的所有特殊功能。有关 MQTT 的更多信息,请访问下面这个网站:
http://mqtt.org/
Apache Kafka 是一个非 JMS 提供程序,但它提供了一个 Java 驱动程序。人们提出了一种增强请求,希望实现一个适配器,让客户端能与 Apache Kafka 传输 JSM 消息,但此问题仍待解决。所以最好使用 Kafka 提供的 Java 驱动程序。有关 Kafka 的更多信息,请访问下面这个网站:
http://kafka.apache.org/
RabbitMQ 是一个消息代理系统,它实现了 AMQP 协议,而且还提供了一个 Java 客户端。有关 RabbitMQ 的更多信息,请访问下面这个网站:
https://www.rabbitmq.com/
Spring 有一个与基于 AMQP 的消息系统通信的库。Spring 还提供了对 MQTT 协议的支持。有关 Spring 的更多信息,请访问下面这个网站:
http://projects.spring.io/spring-amqp/
可以看到,支持结合使用 Java 和非 JMS 消息系统。
本文重点介绍了如何使用基于 Java 的微服务实现微服务在数据处理方面保持可管理。下一部分我们将回到第一部分中讲到的演化策略,将介绍可在实践中考虑和应用的可能战略。好了,学习愉快,下次再见!
来源: http://www.ibm.com/developerworks/cn/analytics/library/se-sonarg-big-data-security-guardium-trs/index.html