美文网首页JavaJava服务器端编程Java学习笔记
Java EE开发系列教程 - 使用EJB组件与JPA层交互

Java EE开发系列教程 - 使用EJB组件与JPA层交互

作者: 阳光下的松木 | 来源:发表于2017-03-04 10:03 被阅读481次

    英文同步版链接

    Github项目源码

    EJB 全称为Enterprise Java Beans,封装了应用程序的业务逻辑并提供与容器服务交互的能力,如事务和安全。本教程在上次创建的JPA模块的基础上,继续添加EJB模块,并且实现真正与数据库交互。本次项目实现了添加用户到数据库,查询所有用户并显示在JSF页面上。

    Project Structure

    ScreenShot1.png

    实现DAO(数据访问)层

    首先在 notebookDomain 模块中添加一些代码,定义数据库操作的接口。在 com.zxuqian.notebook.dao 包中创建名为 IUserDao 的接口。代码如下:

    
    public interface IUserDao {
    
        User getUserById(Long id);
    
        List<User> getAllUsers();
    
        Long addUser(User user);
    
        void deleteUser(User user);
    }
    
    
    

    接着,在 com.zxuqian.notebook.dao.impl 包下,定义此接口的实现类 UserDao。它包含一个只用一个参数的构造方法,接收 EntityManager 的实例对象。因为JPA模块并不属于容器管理,而EJB运行在容器的EJB组件中,所以我们需要从EJB中把容器注入的 EntityManager 对象传递给 UserDao

    
    public class UserDao implements IUserDao, Serializable {
    
    
        private EntityManager entityManager;
    
        public UserDao(EntityManager entityManager) {
            this.entityManager = entityManager;
        }
    
    
        @Override
        public User getUserById(Long id) {
            return this.entityManager.find(User.class, id);
        }
    
        @Override
        public List<User> getAllUsers() {
            List<User> userList = this.entityManager.createNamedQuery(
                    "getAllUsersQuery", User.class).getResultList();
            return userList;
        }
    
        @Override
        public Long addUser(User user) {
            this.entityManager.persist(user);
            return user.getId();
        }
    
        @Override
        public void deleteUser(User user) {
            this.entityManager.remove(user);
    
        }
    }
    
    
    

    这个类中的方法分别进行了对 EntityManager API的调用,现作简单说明:

    • find() 用来根据指定 id 查询数据库,并返回相应的Java对象。
    • createNamedQuery() 执行预先定义好的命名查询语句,在本项目中,此名称定义在 User 类中,稍后进行介绍。
    • persist() 插入数据到数据库中。
    • remove() 根据传递过来的 User 对象,从数据库中删除指定记录。

    命名查询使用 @NamedQuery 注解来定义相关查询, User 类如下所示:

    
    @NamedQuery(name = "getAllUsersQuery", query = "from User u")
    public class User implements Serializable {
    
    

    这里的查询语言是JPA定义的,叫做 JPQL ,与SQL语法类似。 这里的语句意为查询所有用户。如果查询所有列,可省略 Select 子句和列名。

    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="notebookDomain" transaction-type="JTA">
            <jta-data-source>java:/MySqlDS</jta-data-source>
            <properties>
                <!-- Have to define dialect and use hibernate ddl generation strategy -->
                <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
                <property name="hibernate.dialect" value="MySQL5" />
            </properties>
        </persistence-unit>
    </persistence>
    
    

    因为之前JPA原生 <property> 属性在Wildfly实现中并不总是生效,所以这里换成了Wildfly默认的JPA实现,即Hibernate。把属性改成了Hibernate专有的。hibernate.hbm2ddl.auto 定义是否自动生成表,create-and-drop 意为如果表存在,则删除后再创建。hibernate.dialect 用来指定数据库厂商,以根据不同的数据库生成厂商相关的SQL语句。

    创建EJB模块

    EJB Service client 模块

    EJB可以用接口来定义此Bean是本地还是远程的。本地bean只能在部署应用的同一容器中访问,而远程bean可以被集群中的服务器所访问。

    现在,创建一个maven模块,名为 notebookServiceClient

    pom.xml 文件内容为:

    <?xml version="1.0" encoding="UTF-8"?>
    <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/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>notebookRoot</artifactId>
            <groupId>com.zxuqian</groupId>
            <version>0.0.2</version>
            <relativePath>../notebookRoot/pom.xml</relativePath>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>notebookServiceClient</artifactId>
    
        <build>
            <resources>
                <resource>
                    <directory>src</directory>
                    <excludes>
                        <exclude>**/*.java</exclude>
                    </excludes>
                </resource>
            </resources>
            <plugins>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
        <dependencies>
            <dependency>
                <groupId>com.zxuqian</groupId>
                <artifactId>notebookDomain</artifactId>
            </dependency>
            <dependency>
                <groupId>org.jboss.spec.javax.ejb</groupId>
                <artifactId>jboss-ejb-api_3.2_spec</artifactId>
                <scope>provided</scope>
            </dependency>
        </dependencies>
    
    </project>
    
    

    此模块也继承自 notebookRoot 模块, 并且依赖于 notebookDomain 模块和jboss ejb模块.

    创建名为 IUserService 的接口,并写入如下代码:

    
    public interface IUserService {
    
        User getUserById(Long id);
    
        List<User> getAllUsers();
    
        Long addUser(User user);
    
        void deleteUser(User user);
    }
    
    
    

    这些方法定义了数据库CRUD操作,根据用户ID查询用户,查询所有用户,添加一个用户,删除一个用户。

    再创建另一个名为 IUserServiceLocal 的接口,继承自 IUserService, 并添加 @Local 注解,表明它为本地bean接口。

    
    @Local
    public interface IUserServiceLocal extends IUserService {
    }
    
    

    创建一个远程bean接口, IUserServiceRemote

    
    @Remote
    public interface IUserServiceRemote extends IUserService {
    }
    
    
    

    这两个接口都使用父接口的方法,所以类体留空即可。

    EJB Service 模块

    创建Maven模块 notebookService。 此模块是 notebookServiceClient 的具体实现。 pom.xml文件内容为:

    
    <?xml version="1.0" encoding="UTF-8"?>
    <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/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>notebookRoot</artifactId>
            <groupId>com.zxuqian</groupId>
            <version>0.0.2</version>
            <relativePath>../notebookRoot/pom.xml</relativePath>
        </parent>
    
        <modelVersion>4.0.0</modelVersion>
        <packaging>ejb</packaging>
    
        <artifactId>notebookService</artifactId>
        <build>
            <resources>
                <resource>
                    <directory>src</directory>
                    <excludes>
                        <exclude>**/*.java</exclude>
                    </excludes>
                </resource>
            </resources>
            <plugins>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                </plugin>
                <plugin>
                    <artifactId>maven-ejb-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    
        <dependencies>
            <dependency>
                <groupId>org.jboss.spec.javax.ejb</groupId>
                <artifactId>jboss-ejb-api_3.2_spec</artifactId>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.hibernate.javax.persistence</groupId>
                <artifactId>hibernate-jpa-2.1-api</artifactId>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>com.zxuqian</groupId>
                <artifactId>notebookDomain</artifactId>
            </dependency>
            <dependency>
                <groupId>com.zxuqian</groupId>
                <artifactId>notebookServiceClient</artifactId>
            </dependency>
        </dependencies>
    
    </project>
    
    

    这里的不同之处是 <packaging> 的值为 ejb, 意为此模块将被打包成 EJB 格式。 如果EJB版本小于3.0,则需要在模块的 src/META-INF 文件夹下添加 ejb-jar.xml 文件,如果EJB版本大于3.0,则可以省略。这里提供 ejb-jar.xml 文件内容,其实只是一个空的定义文件:

    
    <?xml version="1.0" encoding="UTF-8"?>
    <ejb-jar xmlns="http://java.sun.com/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
              http://java.sun.com/xml/ns/javaee/ejb-jar_3_1.xsd"
             version="3.1">
    </ejb-jar>
    
    

    在此模块下创建名为 UserServiceBean 的Java类,实现 IUserServiceLocal 接口并添加 @Stateful 注解。@Stataful 意思为此EJB在客户端与其交互中,保存所有状态。另一种EJB类型为@Stateless,即在每次客户端请求时,从bean池里取出一个新的bean,状态不会保存。此类使用 @PersistenceContext 注解注入了一个EntityManager 对象,注入即是让容器管理此对象的创建与销毁。@PostConstruct 是生命周期注解,意思是在对象创建之后调用此方法,即 init() 方法,此方法初始化 UserDao 类,并传递 EntityManager 对象。

    
    @Stateful
    public class UserServiceBean implements IUserServiceLocal {
    
        @PersistenceContext
        private EntityManager entityManager;
    
        private IUserDao userDao;
    
        @PostConstruct
        private void init() {
            this.userDao = new UserDao(entityManager);
        }
    
        public User getUserById(Long id) {
            return this.userDao.getUserById(id);
        }
    
        public List<User> getAllUsers() {
            return this.userDao.getAllUsers();
        }
    
        public Long addUser(User user) {
            return this.userDao.addUser(user);
        }
    
        public void deleteUser(User user) {
            this.userDao.deleteUser(user);
        }
    }
    
    

    JSF调用EJB服务

    现在可以使用JSF来调用EJB服务了。在之前的 notebook 模块中,创建一个名为 UserBackBean 的Java类:

    
    public class UserBackBean implements Serializable {
    
        private Logger logger = Logger.getLogger(UserBackBean.class.getCanonicalName());
    
        @EJB
        private IUserServiceLocal userService;
    
        private List<User> users;
    
        private User user;
    
        public UserBackBean() {
            this.user = new User();
        }
    
        public User getUser() {
            return user;
        }
    
        public void setUser(User user) {
            this.user = user;
        }
    
        public List<User> getUsers() {
            return users;
        }
    
        public void setUsers(User user) {
            this.users.add(user);
        }
    
        public String register() {
            this.userService.addUser(this.user);
            return this.getAllUsers();
        }
    
        public String getAllUsers() {
            this.users = this.userService.getAllUsers();
    
            return "user_list";
        }
    }
    
    

    此类作为JSF页面的后端bean,提供属性和方法供页面使用,利用EL表达式。

    • IUserServiceLocal 使用 @EJB 注解,不但声明了它是EJB组件,而且它的生命周期由容器管理,所以不用手动初始化它。
    • users 成员变量保存从数据库查询出来的所有 User 对象。
    • usernotebookDomain 模块中的 User 实体,用来接收用户从页面中输入的数据。
    • register() 方法保存用户数据到数据库中,并调用 getAllUsers() 方法跳转到 user_list 页面来显示所有用户的用户名。
    • getAllUsers() 用来查询所有用户,并跳转到 user_list 页面来显示所有用户的用户名。

    再创建一些JSF页面。首先创建 register.xhtml 页面,位于 WebContent 目录下:

    
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
            "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:h="http://xmlns.jcp.org/jsf/html"
          xmlns:jsf="http://xmlns.jcp.org/jsf"
          xmlns:f="http://xmlns.jcp.org/jsf/core">
    
        <h:form>
            <label for="username">Username: </label>
            <h:inputText id="usernmae" value="#{userBackBean.user.username}" /><br />
            <label for="password">Password: </label>
            <h:inputSecret id="password" value="#{userBackBean.user.password}" /><br />
            <label for="date_of_birth">Date of birth: </label>
            <input type="date" jsf:id="date_of_birth" value="#{userBackBean.user.dateOfBirth}">
                <f:convertDateTime pattern="yyyy-MM-dd"/>
            </input>
            <br />
            <label for="email">Email: </label>
            <input type="email" jsf:id="email" value="#{userBackBean.user.email}" /><br />
            <label for="phone">Phone number: </label>
            <h:inputText  value="#{userBackBean.user.phone}" /><br />
    
            <h:commandButton value="Submit" action="#{userBackBean.register}" />
        </h:form>
    </html>
    
    

    这里使用EL表达式引用后端bean的属性和方法。<h:commandButton> 标签中的action 属性调用了 UserBackBean 中的 register() 方法。

    再创建页面 user_list.xhtml

    
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
            "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:h="http://xmlns.jcp.org/jsf/html"
          xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
    
        <h:head>
            <title>Qiantu - A simple Notebook</title>
        </h:head>
        <h:body>
            <ul>
                <c:forEach items="#{userBackBean.users}" var="user">
                    <li>#{user.username}</li>
                </c:forEach>
            </ul>
        </h:body>
    </html>
    
    

    这里使用了 JSTL 标签库,提供了一系列的标签方法对数据进行迭代,访问和保存。 <c:forEach> 用来循环访问一个集合或数组中的元素。 items 属性指定要循环的集合或数组的变量名,var 属性可自定义每个元素的变量名。在 <c:forEach> 标签体里,定义 <li> 标签显示每个用户的用户名。

    index.xhtml 页面, 在 </h:body> 标签之前添加如下标签:

    
        <p><h:outputLink value="register.xhtml">Register</h:outputLink></p>
        <h:form>
            <p><h:commandLink action="#{userBackBean.getAllUsers}">List All Users</h:commandLink></p>
        </h:form>
    
    

    <h:outputLink> 会生成对应的html <a> 标签,并跳转到 value 属性定义的页面。

    <h:commandLink> 必须定义在 <h:form> 标签里。 <h:outputLink><h:commandLink> 的不同之处在于 <h:commandLink> 可以在页面跳转前,在后端bean中做一些操作,这里调用了 UserBackBean 类中的 getAllUsers() 来从数据库中查询所有用户并初始化 users 变量, 以供 user_list.xhtml 页面使用。

    配置 Maven

    因为新增了两个模块,所以需要修改 notebookRoot 模块的 pom.xml 文件。

    这里添加了新的 <pluginManagement> 标签,可以提供统一管理插件的版本和通用配置的功能,这样可以在子模块中省略插件的版本号,并且继承一些插件的配置。

    
    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.1</version>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                </plugin>
                <plugin>
                    <artifactId>maven-war-plugin</artifactId>
                    <version>2.3</version>
                </plugin>
                <plugin>
                    <artifactId>maven-ejb-plugin</artifactId>
                    <version>2.3</version>
                    <configuration>
                        <ejbVersion>3.2</ejbVersion>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-ear-plugin</artifactId>
                    <version>2.10</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
    
    

    另外,新创建的模块添加到了 <dependencyManagement> 标签中:

    
    <dependency>
        <groupId>com.zxuqian</groupId>
        <artifactId>notebookService</artifactId>
        <version>${project.version}</version>
        <type>ejb</type>
    </dependency>
    <dependency>
        <groupId>com.zxuqian</groupId>
        <artifactId>notebookServiceClient</artifactId>
        <version>${project.version}</version>
    </dependency>
    
    

    这里的 notebookService 定义了 <type>标签,值为 ejb, 即需要和 notebookService 模块中定义的 <packaging> 的值相同,否则会找不到此依赖。

    另外 notebookDomain 模块中的 pom.xml 也需要一些注意:

    
    <build>
        <resources>
          <resource>
            <directory>src</directory>
            <excludes>
              <exclude>**/*.java</exclude>
            </excludes>
          </resource>
        </resources>
    
        <plugins>
          <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.5.1</version>
            <configuration>
              <source>1.8</source>
              <target>1.8</target>
            </configuration>
          </plugin>
        </plugins>
      </build>
    
    

    <resources> 标签必须定义哪些目录包含资源文件,即除了源代码之外的相关配置文件。 这里定义 src 文件夹下除了以 .java 结尾的都是资源文件。这样才能把 persistence.xml 打包进最终生成的Jar文件中。

    其他模块对<dependency>也做了简单改动,即引用新创建的EJB模块,具体代码可查看github上的项目源代码,这里不再赘述。

    测试

    notebookRoot 模块上运行 install 之后, 使用 wildfly:run 部署 notebookEAR 模块。 打开浏览器输入如下URL:

    http://localhost:8080/notebook

    点击 Register 链接,

    ScreenShot2.png

    添加一些数据并点击 Submit 按钮,然后就可以看到新创建的用户列表:

    ScreenShot3.png

    相关文章

      网友评论

      本文标题:Java EE开发系列教程 - 使用EJB组件与JPA层交互

      本文链接:https://www.haomeiwen.com/subject/rcpdgttx.html