跳转至

EJB 简介

原文:http://zetcode.com/java/ejb/

在本教程中,我们学习如何使用 Enterprise JavaBeans。 我们使用 GlassFish,NetBeans,Derby 和 Maven。

Enterprise JavaBean (EJB) 是服务器端组件,封装了应用的业务逻辑。 EJB 在 EJB 容器中运行,该容器负责各种系统级服务,包括事务管理,安全性和并发控制。 EJB 是 Java EE 规范的一部分。

GlassFish 是 Java EE 的参考实现,它包括 Enterprise JavaBeans 容器。 我们将在 GlassFish 中运行示例。 Apache Derby 是完全用 Java 实现的开源关系数据库。 Oracle 以 Java DB 的名义分发相同的二进制文件。

第一个 EJB

我们在 NetBeans 中创建一个新的 Web 应用。 该项目将称为MyFirstEJB。 从“服务器和设置”页面,选择 GlassFish 服务器,并将上下文更改为myfirstejb

Server and Settings

图:服务器和设置

在此对话框中,我们选择应用服务器,Java EE 版本和上下文路径。

index.html

<!DOCTYPE html>
<html>
    <head>
        <title>Test page</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <p>Test page</p>
    </body>
</html>

这是我们的index.html页面。 如果我们访问应用的根页面,它将返回。

我们右键单击应用图标,然后选择一个 Session Bean 类型的新 EJB。 我们将 bean 称为MyFirstBean,键入com.zetcode.ejb包,然后选择无状态会话类型。

Creating a new session bean in NetBeans

图:在 NetBeans 中创建一个新的会话 Bean

无状态会话 Bean 不维护与客户端的对话状态。 当客户端调用无状态 Bean 的方法时,该 Bean 的实例变量可能包含特定于该客户端的状态,但仅在调用期间包含。 该方法完成后,客户端特定的状态将丢失。

FirstBean.java

package com.zetcode.ejb;

import javax.ejb.Stateless;

@Stateless
public class FirstBean {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String sayHello() {

        StringBuilder sb = new StringBuilder("Hello ");
        sb.append(this.getName()).append("!");
        return sb.toString();
    }
}

MyFirstBean是无状态会话 Bean。 使用@Stateless装饰创建一个无状态会话 bean。 它具有no-interface view,其中不使用本地业务接口,并且 Bean 类的所有公共方法都自动向调用者公开。

public String sayHello() {

    StringBuilder sb = new StringBuilder("Hello ");
    sb.append(this.getName()).append("!");
    return sb.toString();
}

MyFirstBean's的工作是构造对调用者的问候。

接下来,我们通过右键单击项目图标并从 Web 类别中选择 Servlet 文件类型来创建一个新的 Servlet。 我们将 servlet 称为Greet,然后键入com.zetcode.web包。 我们将 URL 模式更改为/greet

New servlet

图:创建新的 servlet

在新的 Servlet 对话框中,我们提供 Servlet 名称及其包。

Greet.java

package com.zetcode.web;

import com.zetcode.ejb.FirstBean;
import java.io.IOException;
import java.io.PrintWriter;
import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(name = "Greet", urlPatterns = {"/greet"})
public class Greet extends HttpServlet {

    @EJB
    private FirstBean firstBean;

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        response.setContentType("text/plain;charset=UTF-8");

        firstBean.setName(request.getParameter("name"));
        String msg = firstBean.createMessage();

        try (PrintWriter out = response.getWriter()) {

            out.println(msg);

        }
    }
}

Greet Servlet 从客户端发送的 URL 中读取名称参数,调用 EJB 的createMessage()业务方法,并以纯文本形式返回响应。

@EJB
private FirstBean firstBean;

@EJB注解将 EJB 注入 Servlet。

response.setContentType("text/plain;charset=UTF-8");

servelt 响应以 UTF-8 字符集的纯文本格式显示。

firstBean.setName(request.getParameter("name"));

我们从请求中检索name参数,并将其设置为 EJB。

String msg = firstBean.createMessage();

我们将其称为createMessage()业务方法。

MyFirstEJB project structure

图:MyFirstEJB 项目结构

我们构建应用并将其部署到 GlassFish 服务器。 要构建应用,请右键单击项目图标,然后选择“构建”。 要部署应用,请右键单击项目图标,然后选择“部署”。

$ curl localhost:8080/myfirstejb/greet?name=Jan
Hello Jan!

使用curl工具,我们连接到myfirstejb Web 应用的Greet servlet,并向其传递参数。 Web 应用以问候语响应。

$ curl localhost:8080/myfirstejb/
<!DOCTYPE html>
<html>
    <head>
        <title>Test page</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <p>Test page</p>
    </body>
</html>

访问根页面,应用将返回 HTML 测试页面。

使用实体 bean 持久化数据

在第二个示例中,我们创建一个将读取和保存汽车的 Web 应用。 汽车对象将保存在 Derby 数据库中。

我们使用car-app名称创建一个新的 Java Web 应用。 然后,我们创建一个新的Car实体。 实体类文件类型位于持久性类别中。 包将为com.zetcode.persistence。 主键类型为Long,并选中了创建持久性单元选项。

在下一页中,我们将持久性单元名称更改为carpu,然后选择默认的EclipseLink持久性供应器。 我们选择jdbc/sample数据源,并选择了Create表生成策略。 jdbc/sample数据源是指默认情况下位于用户主目录的.netbeans-derby子目录中的sample数据库。

Persistence provider

图:持久性供应器

在此视图中,我们提供了持久性单元名称,持久性供应器,数据源和表生成策略。

实体 Bean

实体 Bean 是一种 Enterprise JavaBean,它表示持久存储中存在的业务实体对象。 实体 bean 由主键标识。 与在客户端会话的生存期内生存的会话 Bean 不同,实体 Bean 即使在 EJB 容器崩溃时也可以幸免。

Car.java

package com.zetcode.persistence;

import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="Cars")
public class Car implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="Id")
    private Long id;    

    @Column(name="Name")
    private String name;

    @Column(name="Price")
    private int price;      

    public Car() { }

    public Car(String name, int price) {

        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }    

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

Car是 EJB 实体 Bean。 它是要存储在 Derby 数据库中的业务对象。

@Entity
@Table(name="Cars")
public class Car implements Serializable {

实体 bean 用@Entity注解修饰。 该实体映射到Cars表。

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name="Id")
private Long id;   

每个实体都有一个唯一的对象标识符。 此唯一标识符或主键使客户端能够定位特定的实体实例。 @Id声明此实体 bean 的标识符属性,@GeneratedValue注解用于指定主键的生成方式,@Column将标识符映射到数据库表的Id列。

public Car() { }

持久性框架需要无参数的构造器。

EJB

我们创建一个本地无状态ManageCarBean企业 bean。 这次 EJB 具有本地接口视图。

ManageCarBeanLocal.java

package com.zetcode.ejb;

import javax.ejb.Local;
import com.zetcode.persistence.Car;

@Local
public interface ManageCarBeanLocal {

    void saveCar(Car car);
    void setPrice(int price);
    void setName(String name);

    Car getCar(Long id);
}

该接口定义了 EJB 客户端要使用的方法。 客户端只能通过 bean 的业务接口中定义的方法来访问会话 bean。

@Local
public interface ManageCarBeanLocal {

@Local装饰指定该接口是本地业务接口。

ManageCarBean.java

package com.zetcode.ejb;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import com.zetcode.persistence.Car;

@Stateless
public class ManageCarBean implements ManageCarBeanLocal {

    private String name;
    private int price;

    @PersistenceContext(unitName = "carpu")
    private EntityManager em;

    public String getName() {
        return name;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    @Override
    public void setPrice(int price) {
        this.price = price;
    }

    @Override
    public void saveCar(Car car) {

        em.persist(car);
    }

    @Override
    public Car getCar(Long id) {

        Car car = em.find(Car.class, id);
        return car;
    }
}

ManageCarBean读取并保存汽车对象。

@PersistenceContext(unitName = "carpu")
private EntityManager em;

容器使用persistence.xml中的信息创建EntityManager@PersistenceContext注解将实体管理器注入到 Bean 中。 管理器已映射到carpu持久性单元。 实体管理器用于通过持久性上下文与数据进行交互。

@Override
public void saveCar(Car car) {

    em.persist(car);
}

saveCar()方法将汽车对象保存到数据库中; 在我们的例子中是 Derby。

@Override
public Car getCar(Long id) {

    Car car = em.find(Car.class, id);
    return car;
}

getCar()方法通过其 ID 查找汽车。

persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">

  <persistence-unit name="carpu" transaction-type="JTA">
    <jta-data-source>jdbc/sample</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties>
      <property name="eclipselink.ddl-generation" value="create-tables" />
    </properties>
  </persistence-unit>

</persistence>

persistence.xml文件是一个配置文件,其中包含有关我们在应用中使用的数据库的信息。 jdbc/sample是 GlassFish 服务器随附的内置数据源。 在我们的应用中,我们将数据保存在 Derby 的预先创建的sample数据库中。 如果eclipselink.ddl-generation属性不存在,则会自动导致创建Cars表。

NetBeans Derby tool

图:NetBeans Derby 工具

我们可以使用 NetBeans Derby 工具来连接和管理数据库中的数据。 该工具位于“服务”窗口中。

servlet

我们创建两个 servlet:SaveCarReadCar。 我们将它们放入com.zetcode.web包中。 Servlet 是从 NetBeans Web 类别创建的。 两个 servlet 均以纯文本形式响应。

Apache Common Lang JARs

图:Apache Common Lang JAR

我们还包括用于帮助程序方法的 Apache Common Lang JAR。 可以从项目网站下载该库。

ValidateParameter.java

package com.zetcode.util;

import org.apache.commons.lang3.math.NumberUtils;

public class ValidateParameter {

    private static final int MAX_PRICE_CAR = 10_000_000;

    public static boolean validateName(String param) {

        return !(null == param || "".equals(param) || 
                NumberUtils.isNumber(param));
    }

    public static boolean validateId(String param) {

        return !(null == param || "".equals(param) || 
                !NumberUtils.isNumber(param));
    }   

    public static boolean validatePrice(String param) {

       if (null == param || "".equals(param) || !NumberUtils.isNumber(param)) {
           return false;
       }

       int price = Integer.valueOf(param);

       return !(price < 0 || price > MAX_PRICE_CAR);

    }     
}

ValidateParameter类用于验证请求参数。 参数不能为null或为空,并且 ID 和价格值必须为数字。 另外,价格必须在合理范围内。 我们使用 Apache Common Lang 的NumberUtils.isNumber()检查数字值。

SaveCar.java

package com.zetcode.web;

import java.io.IOException;
import java.io.PrintWriter;
import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.zetcode.ejb.ManageCarBeanLocal;
import com.zetcode.persistence.Car;
import com.zetcode.util.ValidateParameter;

@WebServlet(name = "SaveCar", urlPatterns = {"/save"})
public class SaveCar extends HttpServlet {

    @EJB
    private ManageCarBeanLocal manageCarBean;

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        String sname = request.getParameter("name");
        String sprice = request.getParameter("price");

        String message;

        if (ValidateParameter.validateName(sname)
                && ValidateParameter.validatePrice(sprice)) {

            message = String.format("%s car is saved", sname);

            int price = Integer.valueOf(sprice);
            Car car = new Car(sname, price);
            manageCarBean.saveCar(car);

        } else {

            message = "Wrong parameters";
        }

        response.setContentType("text/plain;charset=UTF-8");

        try (PrintWriter out = response.getWriter()) {

            out.println(message);

        }
    }
}

SaveCar Servlet 的目的是调用适当的 EJB 来保存汽车。 汽车对象的数据是从客户端发送的 URL 中读取的。

@WebServlet(name = "SaveCar", urlPatterns = {"/save"})

@WebServlet注解将SaveCar Servlet 映射到/save路径。

@EJB
private ManageCarBeanLocal manageCarBean;

ManageCarBeanLocal企业 bean 被注入到 servlet 中。

String sname = request.getParameter("name");
String sprice = request.getParameter("price");

从请求中读取参数。

if (ValidateParameter.validateName(sname)
        && ValidateParameter.validatePrice(sprice)) {

参数正在验证中。

Car car = new Car(sname, price);
manageCarBean.saveCar(car);

创建一个新的汽车对象并将其保存到数据库。 为了保存汽车对象,我们利用了ManageCarBeansaveCar()方法。

ReadCar.java

package com.zetcode.web;

import com.zetcode.ejb.ManageCarBeanLocal;
import java.io.IOException;
import java.io.PrintWriter;
import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.zetcode.persistence.Car;
import com.zetcode.util.ValidateParameter;

@WebServlet(name = "ReadCar", urlPatterns = {"/read"})
public class ReadCar extends HttpServlet {

    @EJB
    private ManageCarBeanLocal manageCarBean;

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        response.setContentType("text/plain;charset=UTF-8");

        String message;
        String pid = request.getParameter("id");

        if (ValidateParameter.validateId(pid)) {

            Long carId = Long.valueOf(pid);

            Car rcar = manageCarBean.getCar(carId);

            if (null != rcar) {

                message = String.format("Name: %s, Price: %d",
                        rcar.getName(), rcar.getPrice());

            } else {

                message = "Cannot find car with this ID";
            }
        } else {

            message = "Wrong parameters";
        }

        try (PrintWriter out = response.getWriter()) {

            out.println(message);
        }
    }
}

ReadCar servlet 调用ManageCarBean企业 bean 从数据库中读取数据; 它根据提供的 ID 选择汽车对象。

String sid = request.getParameter("id");

从 URL 读取 ID。

Long carId = Long.valueOf(pid);

Car rcar = manageCarBean.getCar(carId);

if (null != rcar) {

    message = String.format("Name: %s, Price: %d",
            rcar.getName(), rcar.getPrice());

} else {

    message = "Cannot find car with this ID";
}

ManageCarBeanreadCar()方法用于检索汽车对象。

web.xml

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>

    <error-page>
        <error-code>404</error-code>
        <location>/error404.txt</location>
    </error-page>

    <error-page>
        <location>/error.txt</location>
    </error-page>    

</web-app>

web.xml文件中,我们提供了两个错误页面。 error.txt是所有错误的默认错误页面,error404.txt是 404 错误的错误页面,当客户端请求不存在的资源时触发。 注意,error404.txt必须先于error.txt

Error pages

图:错误页面s

我们将两个文本文件放置在网页中。

error.txt

Error has occurred, check the GlassFish log file

这是一般错误页面。

error404.txt

404 : Page was not found

这是 404 错误的错误页面。

部署方式

是时候部署应用了。 通过右键单击 Web 项目并选择 Deploy 命令来部署应用。 请注意,当我们清理项目时,将取消部署该应用。 如果未运行所选的应用服务器,那么 deploy 命令还将启动它。

$ ./asadmin list-applications
MyFirstEJB  <ejb, web>
car-app     <ejb, web>  
Command list-applications executed successfully.

GlassFish 的asadmin工具可用于确定当前已部署的应用。

GlassFish 启动时,NetBeans 会自动启动 Derby 服务器。 可以在 GlassFish 服务器设置中将其关闭。

GlassFish server properties

图:GlassFish 服务器属性

在 NetBeans 的“服务”选项卡中,我们展开“服务器”节点,然后选择 GlassFish 属性。 在那里,我们可以看到“启动已注册 Derby 服务器”选项。

$ curl localhost:8080/car-app/doread
404 : Page was not found

如果尝试访问不存在的资源,则会收到自定义 404 错误消息。

$ curl "localhost:8080/car-app/save?name=Volvo&price=29000"
Volvo car is saved

沃尔沃汽车将保存到数据库中。 注意 URL 的双引号的用法。

$ curl "localhost:8080/car-app/save?name=Bentley&price=299000000"
Wrong parameters

验证器发现了不切实际的高汽车价格。

$ curl localhost:8080/car-app/read?id=401
Name: Volvo, Price: 29000

我们从数据库中读取了一辆汽车。

使用 Maven 构建项目

NetBeans 在开发过程中为我们节省了许多繁琐的工作。 但是使用 Maven 手动构建项目是有益的。

mvn archetype:generate -DgroupId=com.zetcode -DartifactId=car-app 
    -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false 

我们创建一个新的 Maven 项目。 我们使用maven-archetype-webapp类型。

$ tree 
.
└── car-app
    ├── pom.xml
    └── src
        └── main
            ├── resources
            └── webapp
                ├── index.jsp
                └── WEB-INF
                    └── web.xml

6 directories, 3 files

tree命令向我们显示了创建的项目结构。

$ mkdir -p src/main/java/com/zetcode/ejb
$ mkdir src/main/java/com/zetcode/persistence
$ mkdir src/main/java/com/zetcode/web
$ mkdir src/main/java/com/zetcode/util
$ mkdir src/main/resources/META-INF

我们创建目录。

复制应用源文件后,项目结构如下所示:

$ tree
.
├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── zetcode
        │           ├── ejb
        │           │   ├── ManageCarBean.java
        │           │   └── ManageCarBeanLocal.java
        │           ├── persistence
        │           │   └── Car.java
        │           ├── util
        │           │   └── ValidateParameter.java
        │           └── web
        │               ├── ReadCar.java
        │               └── SaveCar.java
        ├── resources
        │   └── META-INF
        │       └── persistence.xml
        └── webapp
            ├── index.jsp
            └── WEB-INF
                └── web.xml

不要忘记复制web.xml文件。 Maven 为我们创建了一个空的web.xml文件。

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.zetcode</groupId>
    <artifactId>car-app</artifactId>
    <packaging>war</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>car-app Maven Webapp</name>
    <url>http://maven.apache.org</url>

    <dependencies>

        <dependency>
            <groupId>org.eclipse.persistence</groupId>
            <artifactId>eclipselink</artifactId>
            <version>2.5.0</version>
        </dependency>   

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.0</version>
        </dependency>        

        <dependency>  
            <groupId>javax</groupId>    
            <artifactId>javaee-web-api</artifactId>    
            <version>7.0</version>  
        </dependency>     

    </dependencies>  

    <build>

        <plugins>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                <source>1.8</source>
                <target>1.8</target>
                </configuration>
            </plugin>            

        </plugins>    

        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
            </resource>
        </resources>
        <finalName>car-app</finalName>
    </build>         

</project>

pom.xml是一个文件,其中包含有关项目的信息以及 Maven 用于构建项目的配置详细信息。 我们提供了 Eclipse 链接持久性供应器,Apache Commons lang 库和 Java EE Web API 的依赖项。 我们指示 Maven 使用 Java8。最终的构建文件称为car-app.war,位于target子目录中。

$ mvn package

该项目是使用mvn package命令构建的。

$ ./asadmin start-domain 

从 GlassFish 的bin目录中,启动 GlassFish 服务器。

$ ./asadmin start-database --db-home ~/.netbeans-derby

Derby 服务器已启动。 NetBeans 在.netbeans-derby目录中创建 Derby 系统主目录,该目录位于用户的主目录中。 在这里,我们可以找到我们之前使用过的sample数据库。

$ ~/bin/glassfish-4.1.1/glassfish/bin/asadmin deploy target/car-app.war

我们部署应用。 我们看到警告语Table/View 'CARS' already exists in Schema 'APP'。 这是eclipselink.ddl-generation属性的结果,该属性设置为create_tables。 我们可以忽略警告。

$ curl localhost:8080/car-app/read?id=401
Name: Volvo, Price: 29000

我们检索先前创建的汽车。

在本教程中,我们为一些简单的业务逻辑创建了 Enterprise JavaBeans。 我们已使用 NetBeans,Derby 和 Maven 作为示例。 ZetCode 具有以下相关教程: Derby 教程Java 教程Stripes 教程



回到顶部