使用 Maven 通过 IDEA 开发 JPA + EJB + JSF 项目

栏目: Java · 发布时间: 8年前

内容简介:使用 Maven 通过 IDEA 开发 JPA + EJB + JSF 项目

之前写过使用IDEA创建EJB工程的文章,不过现在有个课题需要结合 JPA + EJB + JSF, 虽然可以按照前文所述方法进行,但是我想使用 Maven 进行管理,因此直接在新建工程时选择 EJB 项目就不行了,应该选择 Maven 项目。

你可以先看看之前的文章: 使用 IDEA 创建 EJB 工程

使用 IDEA 创建 EJB 工程

分析

首先,我们分析一下项目所需结构。

JPA 主要是用于 ORM 了,因此都是实体类,相当于 Model 层。

EJB 则分为接口和实现类,接口应该复制给客户端(JSF Web 层)调用,而实现类则是 EJB 的 Bean, 相当于 DAO 层。

最后是 JSF Web模块,页面展示 View 层。

因此得到如下依赖关系:

EJB -> EJB-API -> JPA

Web -> EJB-API -> JPA

这么分模块是为了使用 Maven 管理时更好地处理模块之间的依赖关系。

新建各模块

下一步我们就可以新建一个 Maven 父项目。

给他配置全局的依赖,如 slf4j 和 logback, Java EE API 等。

配置内容:

<dependencies>
    <!-- 日志 -->
    <dependency>
        <groupid>org.slf4j</groupid>
        <artifactid>slf4j-api</artifactid>
        <version>1.7.21</version>
    </dependency>
    <dependency>
        <groupid>ch.qos.logback</groupid>
        <artifactid>logback-core</artifactid>
        <version>1.1.7</version>
    </dependency>
    <dependency>
        <groupid>ch.qos.logback</groupid>
        <artifactid>logback-classic</artifactid>
        <version>1.1.7</version>
    </dependency>
    <!--JavaEEAPI,注意是 provided,因为 JBoss 作为 JavaEE 服务器实现了其 API-->
    <dependency>
        <groupid>javax</groupid>
        <artifactid>javaee-api</artifactid>
        <version>7.0</version>
        <scope>provided</scope>
    </dependency>
</dependencies>
 
<build>
    <plugins>
        <plugin>
            <groupid>org.apache.maven.plugins</groupid>
            <artifactid>maven-compiler-plugin</artifactid>
            <version>3.3</version>
            <!-- 指定源码级别,防止每次编辑 pom 文件后 IDEA 都会认为源码级别为 1.5 -->
            <configuration>
                <source />1.8
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
</build>

JPA

之后按照依赖顺序新建 JPA 模块。注意需要配置打包类型为 jar(默认是 pom )以供 EJB 模块和 web 模块使用。

<!-- 打包成 jar 给 web-->
<packaging>jar</packaging>

EJB-API

然后添加 EJB-api 模块,因为 Web 层只需要 EJB 的接口,不需要其实现类,因此在这里我们把接口和实现分在两个模块中。

这个模块也需要打包成 jar. 该模块需要依赖于 JPA 模块。

<dependencies>
    <dependency>
        <groupid>com.youthlin.demo</groupid>
        <artifactid>Demo-jpa</artifactid>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

EJB

添加 EJB 模块。依赖中设置其依赖于 api 模块。

<dependencies>
    <dependency>
        <groupid>com.youthlin.demo</groupid>
        <artifactid>Demo-ejb-api</artifactid>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

Web

最后添加 web 模块。在新建 Module 时可以选择 Create from archetype, 然后选择 maven-archtype-webapp.

使用 Maven 通过 IDEA 开发 JPA + EJB + JSF 项目
新建web模块

注意:可以添加 archetypeCatalog=internal 属性以使得创建模块时更快,不至于卡死。

使用 Maven 通过 IDEA 开发 JPA + EJB + JSF 项目
新建web模块:属性设置

该模块的依赖是:

<dependencies>
    <dependency>
        <groupid>junit</groupid>
        <artifactid>junit</artifactid>
        <version>3.8.1</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupid>com.youthlin.demo</groupid>
        <artifactid>Demo-ejb-api</artifactid>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-entitymanager -->
    <dependency>
        <groupid>org.hibernate</groupid>
        <artifactid>hibernate-entitymanager</artifactid>​​
        <version>5.0.10.Final</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

其中 junit 是创建 web 模块时选择的模板自动添加的,EJB-api 是我们需要手动添加的,api 层已经依赖 jpa 模块了,不需重复指定。

Hibernate-entitymanager 作为 JPA 的规范的实现提供商,当然你也可以选择其他的,比如 Eclipse-Link 之类的。

项目结构

新建后项目结构如下:

使用 Maven 通过 IDEA 开发 JPA + EJB + JSF 项目
项目结构 – 依赖关系

演示

然后我们写个最简单的 Hello World 演示一下。

JPA

首先是实体类 User.

package com.youthlin.demo.model;
 
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.io.Serializable;
 
/**
* Created by lin on 2016-09-14-014.
* User 要通过 EJB 传输的实体类需要实现 Serializable 接口才能序列化
*/
@Entity
public class User implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;
    String name;
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

API

然后是 DAO 接口。

package com.youthlin.demo.dao;
 
import com.youthlin.demo.model.User;
 
import javax.ejb.Remote;
import java.util.List;
 
/**
* Created by lin on 2016-09-14-014.
* user dao
*/
@Remote
public interface UserDao {
    Usersave(Useruser);
 
    List<User> findAll();
}

当然,使用 Remote 还是 Local 可以你自己选了,这里随便选了个:远程接口。

EJB

再次是 DAO 实现类。

首先我们在这个模块中配置 persisten.xml 和 ejb-jar.xml 两个文件。

<?xmlversion="1.0" encoding="UTF-8" ?>
<persistence>
    <persistence-unitname="demo" transaction-type="JTA">
        <!-- 直接用数据源。不用数据源而在 properties 里指定连接的话可以连接但 persist 的对象不会同步到数据库 -->
        <jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
        <!-- 不需要指定 provider-->
        <!--<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>-->
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"/>
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.hbm2ddl.auto" value="update"/>
            <!--<property name="hibernate.format_sql" value="true"/>-->
        </properties>
    </persistence-unit>
</persistence>

上述代码为了演示方便,使用的是 JBoss 默认自带的内存数据库的数据源 ExampleDS (服务器关闭释放内存,则数据库中数据也将消失), 当用于实际项目中时,你需要自行配置 JBoss 数据源,相关操作可自行搜索。

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

该文件用于指明本项目是一个 EJB 项目,需要生成打包文件部署到服务器。

package com.youthlin.demo.dao.impl;
 
import com.youthlin.demo.dao.UserDao;
import com.youthlin.demo.model.User;
 
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import java.util.List;
 
/**
* Created by lin on 2016-09-14-014.
* user dao Impl
*/
@Stateless
public class UserDaoImpl implements UserDao {
    @PersistenceContext(name = "demo")
    private EntityManagerem;
 
    @Override
    public Usersave(Useruser) {
        em.persist(user);
        return user;
    }
 
    @Override
    public List<User> findAll() {
        TypedQuery<User> query = em.createQuery("select u from User as u", User.class);
        return query.getResultList();
    }
}

这里的 EntityManager 对象 em 使用 @PersistenceContext(name = "demo") 注解表明,该对象由容器自动注入。name的值就是 persistence.xml 中配置的 unitName.

Web

最后是 Web 层。根据上面的接口,你也可以看出我打算只写一个功能:添加用户和列出用户。

<!DOCTYPEweb-appPUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
    <display-name>ArchetypeCreatedWebApplication</display-name>
 
    <servlet>
        <servlet-name>FacesServlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>FacesServlet</servlet-name>
        <url-pattern>*.xhtml</url-pattern>
    </servlet-mapping>
    
    <welcome-file-list>
        <welcome-file>index.xhtml</welcome-file>
    </welcome-file-list>
</web-app>

第一部分定义 JSF 拦截器,所有 .xhtml 请求都由 JSF 处理。第二部分说明欢迎文件 index.xhtml.

package com.youthlin.demo.util;
 
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
 
importjavax.naming.Context;
importjavax.naming.InitialContext;
importjavax.naming.NamingException;
importjava.util.Hashtable;
importjava.util.concurrent.ConcurrentHashMap;
 
/**
* Created by lin on 2016-09-02-002.
* 获取远程对象 工具 类
*/
public class EJBUtil {
    private static final Loggerlog = LoggerFactory.getLogger(EJBUtil.class);
    private static ConcurrentHashMap<Class, Object> map = new ConcurrentHashMap<>();
 
    /**
     * 获取远程对象, 第一次获取后会缓存起来,之后获取的将是缓存的对象
     * 注意:远程接口实现类 <strong> 必须 </strong > 以 < code>Impl</code > 结尾
     *
     * @param clazz 远程对象的类型
     * @return 获取的远程对象
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(Class<T> clazz) {
        return getBean(clazz, false);
    }
 
    /**
     * 获取远程对象
     * 注意:远程接口实现类 <strong> 必须 </strong > 以 < code>Impl</code > 结尾
     *
     * @param clazz 远程对象的类型
     * @param force true 表示强制每次都从远程获取而不使用缓存
     * @return 获取的远程对象
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(Class<T> clazz, boolean force) {
        if (!force && map.containsKey(clazz)) {
            log.trace(" 直接返回已缓存的对象:{}", clazz);
            return (T) map.get(clazz);
        }
        boolean hasException = false;
        Object result = null;
        Hashtable<String, String> jndiProperties = new Hashtable<>();
        jndiProperties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
        try {
            Contextcontext = new InitialContext(jndiProperties);
            //这里的moduleName在Artifact里设置,不确定是什么名字的话可以在EJB部署时看到发布的JNDI名称
            final String moduleName = "Demo_ejb_EJB";
            final String fullName = "ejb:/" + moduleName + "/"
                    + clazz.getSimpleName() + "Impl" + "!" + clazz.getName();
            log.debug("EJB 全名 =" + fullName);
            result = context.lookup(fullName);
            log.debug("result={},class={}", result, result.getClass());
            return (T) result;
        } catch (NamingException e) {
            e.printStackTrace();
            hasException = true;
        } catch (Exception e) {
            hasException = true;
        } finally {
            log.trace(" 远程对象获取完毕 ");
            if (!hasException && result != null) {
                map.put(clazz, result);
            }
        }
        log.warn(" 获取远程对象失败 ");
        return null;
    }
}

目前演示 EJB 中只有一个类,若有多个类,那么每次获取远程 EJB 都要写个 Context.lookup 再强制转换是很烦人的,因此我把它提取出来作为一个工具类。不过这里约定了实现类和接口的命名规则:实现类必须以 接口名加Impl 命名。当然仅仅是为了获取方便(虽然实现类和客户端不应有关联,客户端只和 JNDI 名称有关联,但这里默认 JNDI 名称就是包含类名嘛),你也可以自己自定义啦。

package com.youthlin.demo.action;
 
importcom.youthlin.demo.dao.UserDao;
importcom.youthlin.demo.model.User;
importcom.youthlin.demo.util.EJBUtil;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
 
importjavax.faces.bean.ManagedBean;
importjavax.faces.bean.SessionScoped;
importjava.util.List;
 
/**
* Created by lin on 2016-09-14-014.
* UserBean
*/
@ManagedBean
@SessionScoped
public class UserBean {
    private final Loggerlog = LoggerFactory.getLogger(UserBean.class);
    private UserDaouserDao = EJBUtil.getBean(UserDao.class);
    private String name;
 
    public void save() {
        Useruser = new User();
        user.setName(name);
        userDao.save(user);
        log.debug("保存成功");
    }
 
    public List<User> getUsers() {
        List<User> users = userDao.findAll();
        log.debug("users = {}", users);
        return users;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
}

JSF 中的Managed Bean 相当于 Action 层,你可以根据业务需要自行设置 Scope 为会话或请求范围。这里演示得很简单,为了减少获取 EJB 的次数,设置成了会话范围。(不过在 EJBUtil 里我们已经做了缓存了。)

<!DOCTYPE html>
<htmllang="zh-CN"
      xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:c="http://java.sun.com/jsp/jstl/core">
<h:head>
    <metahttp-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <metaname="viewport" content="width=device-width,initial-scale=1.0"/>
    <metahttp-equiv="X-UA-Compatible" content="ie=edge"/>
    <title>Demo</title>
</h:head>
<body>
<h:form>
    <h:inputTextvalue="#{userBean.name}"/>
    <h:commandButtonaction="#{userBean.save}" value="添加"/>
</h:form>
<ulid="list">
    <c:forEachitems="#{userBean.users}" var="user">
        <li>${user.name}</li>
    </c:forEach>
</ul>
</body>
</html>

页面很简单,甚至连 CSS 都没写,因为我是演示的目的。只涉及到了表单、循环,以及 XHTML 和 Bean 之间的关联。

#{bean.property} 就是 JSF 中的表达式写法了,在%lt;c:foreach>里还是用的美元符号 ${xxx}.

配置

写好后,打开 Project Structure 定位到 Modules – Demo-ejb – EJB 发现右边有提示

‘EJB’ Facet resources are not includeed in an artifact.

因此点击 Create Artifact,注意打包类型 只能 选择 Archived, 且后缀需要以 jarrar 结尾。并且需要 加上 ejb 和 jpa 模块的编译内容 到 Output 中,否则运行时会找不到类。

使用 Maven 通过 IDEA 开发 JPA + EJB + JSF 项目
EJB 打包注意

同样, web 打包也需要确保 api 和 jpa 模块的代码包含进来了。

使用 Maven 通过 IDEA 开发 JPA + EJB + JSF 项目
web 模块打包

最后,如果需要日志打印,这里配置的是 slf4j + logback, 但是 JBoss 的日志会和这个由冲突,还需要另外的配置文件

<?xmlversion="1.0" encoding="UTF-8"?>
<jboss-deployment-structure>
    <ear-subdeployments-isolated>false</ear-subdeployments-isolated>
    <deployment>
        <!--Exclusionsallowyouto preventtheserverfromautomaticallyaddingsomedependencies-->
        <exclusions>
            <modulename="org.slf4j"/>
            <modulename="org.slf4j.ext"/>
            <modulename="org.slf4j.impl"/>
            <modulename="org.slf4j.jcl-over-slf4j"/>
        </exclusions>
    </deployment>
</jboss-deployment-structure>

这样才能正常打印日志。

效果

启动服务器,就可以看到如下界面了:

使用 Maven 通过 IDEA 开发 JPA + EJB + JSF 项目
index 页面

添加用户后:

使用 Maven 通过 IDEA 开发 JPA + EJB + JSF 项目
添加用户后

总结

使用 Maven 分层次管理还是比较方便的,需要注意的就是,生成的 Artifact 包需要保护该模块依赖的模块的编译内容,否则将导致 ClassNotFound 异常。另外就是 JBoss 的日志冲突的坑需要略微注意。

你可以在 GitHub 上找到本文的完整代码,直接使用 IEDA 即可打开运行。

感谢阅读。

最后,祝大家中秋节快乐!


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Four

The Four

Scott Galloway / Portfolio / 2017-10-3 / USD 28.00

NEW YORK TIMES BESTSELLER USA TODAY BESTSELLER Amazon, Apple, Facebook, and Google are the four most influential companies on the planet. Just about everyone thinks they know how they got there.......一起来看看 《The Four》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具