Java EE开发系列教程 – Web Service

by admin on 2019年10月7日

英文同步版链接

项目Github地址:

英文版同步链接:http://www.zxuqian.com/java-ee/java-ee-application-development-restful-web-service.html

这是个系列教程,看完这个,你对JavaEE注解,包括spring、hibernate,RESTFul
web service ,JAXB, 以及junit注解将会有一个全面的了解。
英文原版网址在这里
我们不光是spring网站的搬运工@农夫山泉水,实际上,翻译过程中我保证准确同时,加入了自己的理解。

一、简介

JavaBean组件是一些可移植、可重用并可组装到应用程序中的Java类,类必须是具体的和公共的。

符合下列设计规则的任何Java类均是以JavaBean:

1.对数据类型“protype”的每个可读属性,Bean下必须有下面签名的一个方法:public
proptype getProperty(){}

2.对数据类型“protype”的每个可写属性,Bean下必须有下面签名的一个方法:public
void setProperty(proptype x){}

3.定义一个不带任何参数的构造函数。

Github项目源码

由于Eclipse在Mac下的表现并不好,严重影响效率,所以在添加JPA模块之前,先教大家如何把项目迁移到IntelliJ
IDEA下。IntelliJ是一个非常强大的Java
IDE,提供流畅的操作界面和智能的项目编译和运行管理,所谓intelligent。因为项目是按Maven的结构构造,所以即使换掉IDE也不用对项目做发动,只是需要删除一些Eclipse专用的文件而已。所以如果你用Windows或者想继续使用Eclipse的话,可以跳过本节。另外由于在迁移之前,新的项目已经开发一半,所以会看到几个新项目,稍后会对其进行介绍。

Web
Service为不同应用程序间传递数据的一种通用协议。REST,全称为Representational
State
Transfer,是一种数据传输形式,它规定了数据和文件都为能够通过URI访问的资源。此教程将RESTful
web service加入到之前的工程中,然后给出生成JSON和XML数据的实例。

Hibernate JPA Annotations – Contents:

二、JavaBean与JSP的结合

JSP在动态Web应用开发中得到了越来越多的应用,JSP提供的9个内置对象极大的方便了JSP页面的控制与开发,但由于应用程序功能要求越来越强,JSP页面变得越来越臃肿且难以控制。也正因如此JSP与JavaBean相结合逐渐成为最常见的Web应用程序开发方式。

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

打开IntelliJ,在首页中选择 Import Project。

项目源代码:
https://github.com/zxuqian/Learning-Java-EE-2016/tree/09f7cf7b123e9ed3029841df98db4f5cd42b55c4

@Entity
import javax.persistence.Entity;

(一)、JSP中JavaBean相关标记

JSP页面中与JavaBean有关的标记有<jsp:useBean>、<jsp:setProperty>和<jsp:getProperty>三个。

(1)、<jsp:useBean>

该标记声明一JavaBean实例,该实例具有一定生存范围及一个唯一id,JSP页面通过这个唯一的id来识别JavaBean并通过id.method类似语句操作JavaBean。

eg:声明类Student,id为s1。

1 <jsp:useBean id="s1" class="Student" scope="application"></jsp:useBean>

scope属性值指明JavaBean的作用域,其默认值为page。

(2)、<jsp:setProperty>

该标记用于设定一个已经被创建的JavaBean组件的属性值。

eg:设定Student组件s1的classno属性

1 <jsp:setProperty name="s1" property="classno" value="56789"></jsp:setProperty>

name属性值对应JavaBean组件的id值;property属性指定想设定属性值的属性名;value属性指定属性值,该值可为字符串也可为表达式。

(3)、<jsp:getProperty>

该标记用于返回一个已经被创建的JavaBean组件的属性值。

eg:获取Student组件s1的name属性的属性值

1 <jsp:getProperty name="s1" property="name"></jsp:getProperty>
2 等价于:
3 <%=s1.name %>

图片 1ScreenShot1.png

图片 2

创建模块

打开 IntelliJ, 选择 File -> New -> Module…
菜单,在弹出的对话框中选择 Maven, 选择 parent
notebookRoot, 设置 groupIdcom.zxuqianartifactId
notebookRESTful, 点击
next,确保新创建的模块目录与其他模块在同级目录,否则此模块就会成为其他模块的父模块。点击
finish

src 同级目录创建文件夹结构
WebContent,此文件夹为web工程的根路径,用来存放JSF页面。打包工程时,maven会自动识别此文件夹。最后在此文件夹下创建
WEB-INF 目录,用来存放配置文件。

新模块的 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>notebookRESTful</artifactId>
    <packaging>war</packaging>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
            </plugin>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <configuration>
                    <warSourceDirectory>WebContent</warSourceDirectory>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.jboss.spec.javax.ws.rs</groupId>
            <artifactId>jboss-jaxrs-api_2.0_spec</artifactId>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>javax.enterprise</groupId>
            <artifactId>cdi-api</artifactId>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.jboss.spec.javax.ejb</groupId>
            <artifactId>jboss-ejb-api_3.2_spec</artifactId>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>com.zxuqian</groupId>
            <artifactId>notebookService</artifactId>
            <type>ejb</type>
        </dependency>

    </dependencies>


</project>

此处引用了新的依赖: org.jboss.spec.javax.ws.rs
这是JAX-RS规范的JBOSS实现,包含了所有的Web
service功能。另外,notebookService
模块也被引用进来,用来添加和获取用户数据供新的RESTful web
service模块调用。

@Table
import javax.persistence.Table;

(二)、JavaBean的Scope属性说明

每个JavaBean均有一个生存范围,而每个JavaBean只能在它定义的范围中才可使用,在其定义的范围外将无法访问。

<jap:useBean>利用Scope属性来声明JavaBean的生存范围。

JSP为JavaBean设定的范围可以是:page、request、session及application。

(1)、page

page是JavaBean默认生存范围。

Scope值为page的JavaBean能在包含”<jsp:useBean>”元素的JSP文件及此文件中的所有静态包含文件中使用,直至页面执行完毕向客户端发回响应或转到另一文件为止。

(2)、request

Scope值为request的JavaBean作用于任何相同请求的JSP文件中,直至页面执行完毕向客户端发回响应或在此之前已经通过某种方式(重定向、链接等)转到另一文件为止。

还可通过使用request对象访问JavaBean。

(3)、session

Scope值为session的JavaBean,从创建JavaBean开始,就能在任何使用相同session的JSP文件中使用这个Bean,该Bean存在于整个session生存期内,任何分享此session的JSP文件都能使用同一Bean。

在session的生存周期内,对该Bean的任何改动均会影响到在此session内的任意page、request对该Bean的调用。当然前提是必须在创建此Bean的文件里事先用page指令指定了session=true。

(4)、application

Scope值为application的JavaBean,作用于整个application的生存周期内,从创建Bean开始就能在任何使用相同application的JSP文件中使用Bean。在该Bean存在于整个application生存周期内,任何分享此application的JSP文件均能使用同一Bean。在application生存周期内,对此Bean属性的任何改动均会影响到此application内另一page、另一request及另一session里对该JavaBean的调用。

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

选择 Import project from external model 然后选择Maven 并点击 next.

编码

notebookRESTful 模块中,创建一个Java类 UserResource 位于
com.zxuqian.notebook.webservice 包中。

@Path("/user")
public class UserResource {

    private static final Logger logger = Logger.getLogger(UserResource.class.getCanonicalName());

    @EJB
    private IUserServiceLocal userService;

    @GET
    @Path("/{id}")
    @Produces({MediaType.APPLICATION_JSON})
    public User getUserById(@PathParam("id") Long id) {

        User user = this.userService.getUserById(id);
        return user;

    }

    @PUT
    @Consumes(MediaType.APPLICATION_JSON)
    public Response createUser(User user) {
        this.userService.addUser(user);

        return Response.ok().build();
    }

    @DELETE
    @Path("/{id}")
    public Response deleteUser(@PathParam("id") Long id) {

        logger.severe("The id is: " + id);

        User user = this.userService.getUserById(id);

        logger.severe("The user is: " + user);

        this.userService.deleteUser(user);

        return Response.ok().build();
    }


}
  • @Path 类级注解定义了访问UserResource的根路径,例如,如果访问Web
    Service的路径为
    http://localhost:8080/notebook-rest,那么访问User资源的URI就会以
    http://localhost:8080/notebook-rest/user 开头。 @Path
    方法级注解定义了对资源进行操作的具体路径,如果要根据id查询某一特定用户,则相应的URI为
    http://localhost:8080/notebook-rest/user/1 ,然后就会匹配到
    getUserById 方法。 @Path("/{id}"}
    中的{id}为通用匹配符,它会取URI最后一段的值,例如上述URI中的
    1,然后它的值会赋给用 @PathParam("id") 参数级注解标记的 id
    参数。
  • Web service对资源的操作用HTTP动词来标记,在Web
    Service中共有7种,常用的为 POST, GET, PUT
    DELETE,分别用来更新,获取,新增和删除。
    Java中使用同名的注解来使用这些动词。
  • @Consume@Produce
    分别表示接收什么类型的数据和生成什么样的数据。getUserbyId()
    方法生成json格式的数据,而createUser() 方法需要接收 json
    类型的数据。
  • 注意,deleteUser()
    方法是先根据id查询出user对象然后再删除。但是这样会有问题,因为通过
    getUserById()
    查询出来的user为detached(脱管)对象,不能直接使用JPA的API进行删除。因而,在
    notebookDomain 模块的 deleteUser() 方法第一行添加
    user = this.entityManager.merge(user);
    来把脱管的对象变为持久对象:

public void deleteUser(User user) {
        user = this.entityManager.merge(user);
        this.entityManager.remove(user);

}

UserResource
中各个方法中的代码都很简单明了,无非查询和添加数据,然后返回HTTP响应码。Response.ok()
返回 200 表示成功。当然更正确的做法应该在 @PUT 方法中返回 201
并返回新创建的资源的URI。

@Column
import javax.persistence.Column;

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

图片 3

JAXB

JAXB 是Java
XML绑定API,可以把普通Java对象(POJO)自动映射为XML或者JSON。使用此API需要转换
notebookDomain 模块中的User
类为JAXB类。只需要简单的添加一个类级注解 @XmlRootElement
即可,所有的成员变量的名字将作为XML的标签或JSON的key。

User.java

@Entity
// 'from' unexpected error. Add JPA facet by project structure, then specify the persistent.xml and provider.
@NamedQuery(name = "getAllUsersQuery", query = "from User u")
@XmlRootElement
public class User implements Serializable {

@Id
import javax.persistence.Id;

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

选择 search for project recursively,
因为我们的项目是模块化,pom.xml在子文件夹下,所以我们需要递归查找子目录。再勾选
import Maven projects automatically。点击 next.

配置模块

现在,在 WebContent/WEB-INF 文件夹下创建 web.xml 文件,内容为:

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

  <servlet-mapping>
    <servlet-name>javax.ws.rs.core.Application</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

</web-app>

javax.ws.rs.core.Application 是负责映射URL到Web
Service资源的servlet。/* 代表所有访问此模块的URL都会映射到web
serivce资源的URI。

最后在 notebookEAR 模块中的 pom.xml 文件中配置web
service模块的上下文路径(此处为关键代码,全文请参考github源码):

<webModule>
      <groupId>com.zxuqian</groupId>
      <artifactId>notebookRESTful</artifactId>
      <contextRoot>/notebook-rest</contextRoot>
</webModule>

@GeneratedValue
import javax.persistence.GeneratedValue;

public class UserDao implements IUserDao, Serializable { private EntityManager entityManager; public UserDao(EntityManager entityManager) { this.entityManager = entityManager; } @Override public User getUserById { 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; return user.getId(); } @Override public void deleteUser(User user) { this.entityManager.remove; }}

图片 4

运行程序

notebookRoot 模块执行Maven install 然后在 notebookEAR
模块中执行 wildfly:run 来启动wildfly server并部署EAR文件。

这里我用了一个叫 Postman 的工具来测试Web Service,可在这里下载:
https://www.getpostman.com/
打开后工具界面如下:

图片 5

image.png

在工具右侧,可选择HTTP动词,输入Web Service URI,定义HTTP
headers。然后点击 Send 就会发起HTTP请求到Web Service。 例如访问id为
1
的用户,可以看到返回的JSON数据(为了测试目的,用户的密码明文返回到了这里,在生产环境中应坚决避免)。

新增一个用户:

图片 6

image.png

选择 PUT 方式。 然后在 Body 选项中选择
JSON(application/JSON) 作为content type(橙色部分)。
可以直接copy从GET中返回的JSON结构,然后删除 id
属性,并修改其他属性为新的用户数据,然后点击 Send。成功之后就会看到
Status 显示为 200

可以从数据库中验证结果:

图片 7

image.png

可以看到,数据库中多了一条id为 5 的用户数据。

删除用户:

图片 8

image.png

选择 DELETE 方式,按图指定URI,然后点击 Send 按钮。
成功后可在数据库中查询结果:

图片 9

image.png

最后,如果把 UserResource 类中的 getUserById() 方法的注解
@Produces 改成 APPLICATION_XML 就会产生 XML 格式的数据:

图片 10

image.png

有关Web Service的基本知识就介绍到这里,如果有问题,欢迎留言评论!

@Version
import javax.persistence.Version;

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

勾选搜索出来的项目: com.zxuqian:notebookRoot:0.0.1-SNAPSHOT,
此为所有Maven模块的根模块,用来统一管理依赖。稍后详细介绍。

@OrderBy
import javax.persistence.OrderBy;

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

图片 11

@Transient
import javax.persistence.Transient;

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

选择 JDK 并点击 next。

@Lob
import javax.persistence.Lob;

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

图片 12

Hibernate Association Mapping
Annotations

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

给项目命名并选择项目文件存储目录,这里保持默认。点击Finish。

@OneToOne
import javax.persistence.OneToOne;

Persistence.xml 需要进行一些改动:

图片 13

@ManyToOne
import javax.persistence.ManyToOne;

<?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>

现在进入到IntelliJ主界面,会发现项目下存在Eclipse相关配置文件:
.settings, build, .classpath, .project。这些可直接安全删除。

@OneToMany
import javax.persistence.OneToMany;

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

图片 14

@ManyToMany
import javax.persistence.ManyToMany;

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

Maven是一个强大的依赖管理和项目构建工具,并且pom.xml文件可以继承,以方便管理依赖的版本和定义通用的属性。首先,创建一个新的maven模块,给定group
id为com.zxuqian, artifact id为 notebookRoot, 版本为:0.0.1-SNAPSHOT。
此为其他所有模块的根模块,所以在此模块执行 maven clean 或 install
为所有子模块执行相同的操作。Pom.xml文件如下所示:

@PrimaryKeyJoinColumn
import javax.persistence.PrimaryKeyJoinColumn;

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

<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"> <modelVersion>4.0.0</modelVersion> <groupId>com.zxuqian</groupId> <artifactId>notebookRoot</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>pom</packaging> <dependencyManagement> <dependencies> <dependency> <groupId>org.wildfly.bom</groupId> <artifactId>wildfly-javaee7</artifactId> <scope>import</scope> <type>pom</type> <version>10.1.0.Final</version> </dependency> </dependencies> </dependencyManagement> <dependencies> </dependencies> <modules> <module>../notebook</module> <module>../notebookDomain</module> <module>../notebookEAR</module> </modules></project>

@JoinColumn
import javax.persistence.JoinColumn;

pom.xml 文件内容为:

<packaging>此项目为pom形式,只用来提供pom.xml文件供子模块继承。<dependencyManagement>_使用wildfly
bom 来管理wildfly的依赖库。Wildfly
bom也是一个pom类型的项目,其中定义了wildfly所有的依赖库。import scope
是说此pom继承自wildfly
bom。这样做可以避免在每个模块的pom.xml中定义版本号,以免引起混乱。<modules>
标签引用其他子模块,运行maven的目标也会在这些子模块中同时运行
notebookDomain 和notebookEAR 模块会在下面创建。

@JoinTable
import javax.persistence.JoinTable;

<?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>

由于更新了项目结构,上次创建的JSF模块需要做一些改动,新的pom.xml文件:

@MapsId
import javax.persistence.MapsId;

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

<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"> <modelVersion>4.0.0</modelVersion> <groupId>com.zxuqian</groupId> <artifactId>notebook</artifactId> <packaging>war</packaging> <parent> <groupId>com.zxuqian</groupId> <artifactId>notebookRoot</artifactId> <version>0.0.1-SNAPSHOT</version> <relativePath>../notebookRoot/pom.xml</relativePath> </parent> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.5.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>3.0.0</version> <configuration> <warSourceDirectory>WebContent</warSourceDirectory> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>org.jboss.spec.javax.faces</groupId> <artifactId>jboss-jsf-api_2.2_spec</artifactId> <scope>provided</scope> </dependency> </dependencies></project>

Hibernate Inheritance Mapping
Annotations

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

注意 <parent> 标签, 引用自notebookRoot根模块。 依赖更改为wildfly
bom中提供的 org.jboss.spec.javax.faces.jboss-jsf-api_2.2_spec

@Inheritance
import javax.persistence.Inheritance;

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

请参考MySQL官方网站如何安装:

5.7。

@DiscriminatorColumn
import javax.persistence.DiscriminatorColumn;

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

首先下载mysql jdbc 连接驱动:

mysql-connector-java-5.1.40-bin.jar
即为驱动。首次启动Wildfly之前需要添加用户才能登录管理界面,在命令行中运行:/Users/zxuqian/development/tools/wildfly-10.1.0.Final/bin/add-user.sh根据提示创建管理用户。从命令行中启动Wildfly:/Users/zxuqian/development/tools/wildfly-10.1.0.Final/bin/standalone.sh登录到管理界面:

@DiscriminatorValue
import javax.persistence.DiscriminatorValue;

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

图片 15

@Entity,@Table,@Colume,@Id,@GeneratedValue略

@OrderBy(用于One-to-Many等关联表中集合的排序)

@OrderBy("firstName asc")
private Set contacts;

@Transient 字段不会更新到数据库

@Lob:二进制数据

hibernate的注解就这么完了吗?

Paste_Image.png

@Localpublic interface IUserServiceLocal extends IUserService {}

在欢迎界面点击Deploy an Application旁边的
start。我们需要先把MySQL的jdbc驱动部署到Wildfly。在随后出现的页面中,点击
Add 。

hibernate注解的<u>重难点</u>在于多表之间的关系:

栗子来了:

Paste_Image.png

company – companydetail : one-to-one,共享相同主键
contact – contactDetail : 通过外键连接的one-to-one
contact-company : 外键连接的many-to-one, contact作为owner
company-companyStatus : 外键连接的many-to-one,company作为owner

创建一个远程bean接口, IUserServiceRemote

图片 16

one-to-one tips:

共享相同主键的用@PrimaryKeyJoinColumn
一方持有另一方外键,用 @JoinColumn & @OneToOne和 mappedBy属性
两个Entity通过一个中间表联系,使用@JoinTable and mappedBy
共享键用@MapsId

@Remotepublic interface IUserServiceRemote extends IUserService {}

选择Upload a new deployment。点击Next。

one-to-one

@Entity
@Table(name = "company")
public class Company implements Serializable {

  @Id
  @Column(name = "id")
  @GeneratedValue
  private int id;

  @OneToOne(cascade = CascadeType.MERGE)
  @PrimaryKeyJoinColumn
  private CompanyDetail companyDetail;

  ...
}

@Entity
@Table(name = "companyDetail")
public class CompanyDetail implements Serializable {

  @Id
  @Column(name = "id")
  private int id;

  ...
}



@Entity
@Table(name = "contactDetail")
public class ContactDetail implements Serializable {

  @Id
  @Column(name = "id")
  @GeneratedValue
  private int id;

  @OneToOne
  @MapsId
  @JoinColumn(name = "contactId")
  private Contact contact;

  ...
}

@Entity
@Table(name = "contact")
public class Contact implements Serializable {

  @Id
  @Column(name = "ID")
  @GeneratedValue
  private Integer id;

  @OneToOne(mappedBy = "contact", cascade = CascadeType.ALL)
  private ContactDetail contactDetail;

  ....
}

cascade(级联)级联在编写触发器时经常用到,触发器的作用是当
主控表信息改变时,用来保证其关联表中数据同步更新。若对触发器来修改或删除关联表相记录,必须要删除对应的关联表信息,否则,会存有脏数据。所以,适当的做法是,删除主表的同时,关联表的信息也要同时删除,在hibernate中,只需设置cascade属性值即可。

CascadeType.PERSIST:级联新增(又称级联保存):对order对象保存时也对items里的对象也会保存。对应EntityManager的presist方法

例子:只有A类新增时,会级联B对象新增。若B对象在数据库存(跟新)在则抛异常(让B变为持久态)

CascadeType.MERGE:级联合并(级联更新):若items属性修改了那么order对象保存时同时修改items里的对象。对应EntityManager的merge方法

例子:指A类新增或者变化,会级联B对象(新增或者变化)

CascadeType.REMOVE:级联删除:对order对象删除也对items里的对象也会删除。对应EntityManager的remove方法

例子:REMOVE只有A类删除时,会级联删除B类;

CascadeType.REFRESH:级联刷新:获取order对象里也同时也重新获取最新的items时的对象。对应EntityManager的refresh(object)方法有效。即会重新查询数据库里的最新数据
(用的比较少)

CascadeType.ALL:以上四种都是

综上所述:一般的,用CascadeType.MERGE:级联合并(级联更新)就能达到级更新同时又稳定不报错。

@ManyToOne

tips:
Use @JoinColumn when foreign key is held by one of the entities.
Use @JoinTable for entities linked through an association table.

@Entity
@Table(name = "contact")
public class Contact implements Serializable {

  @ManyToOne
  @JoinColumn(name = "companyId")
  private Company company;

  ...

 }

@Entity
@Table(name = "company")
public class Company implements Serializable {

  @ManyToOne
  @JoinColumn(name = "statusId")
  private CompanyStatus status;

  ...

 }

@OneToMany

tips:
Use mappedBy attribute for bi-directional associations with ManyToOne
being the owner.
OneToMany being the owner or unidirectional with foreign key – try to
avoid such associations but can be achieved with @JoinColumn
@JoinTable for Unidirectional with association table

//Please see the many-to-one relationship between Contact and Company above. Company to Contact will be a one-to-many relationship. The owner of this relationship is Contact and hence we will use 'mappedBy' attribute in Company to make it bi-directional relationship.
@Entity
@Table(name = "company")
public class Company implements Serializable {

  @OneToMany(mappedBy = "company", fetch = FetchType.EAGER)
  @OrderBy("firstName asc")
  private Set contacts;

  ...

 }

@ManyToMany

tips:
Use @JoinTable for entities linked through an association table.
Use mappedBy attribute for bi-directional association.

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

图片 17

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

选择解压得到的jar文件。点击Next。

<?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>

图片 18

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

保持默认,点击Finish。

<?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>

图片 19

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

回到首页,点击Create a datasource旁边的start链接。在随后出现的页面中选择
Subsystems -> Datasources -> Non-XA。点击右边的 Add。

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

图片 20

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

选择MySQL datasource. 点击Next。

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; } public String register() { this.userService.addUser(this.user); return this.getAllUsers(); } public String getAllUsers() { this.users = this.userService.getAllUsers(); return "user_list"; }}

图片 21

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

指定一个Datasource名称和JNDI名称。 JNDI 名称十分重要,会在JPA连接到JDBC
Resource的时候用到。点击Next。

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

图片 22

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

选择Detected driver并选择第一个检测出来的驱动。点击next。

<?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 value="#{userBackBean.user.username}" /><br /> <label for="password">Password: </label> <h:inputSecret value="#{userBackBean.user.password}" /><br /> <label for="date_of_birth">Date of birth: </label> <input type="date" jsf: value="#{userBackBean.user.dateOfBirth}"> <f:convertDateTime pattern="yyyy-MM-dd"/> </input> <br /> <label for="email">Email: </label> <input type="email" jsf: 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>

图片 23

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

定义连接
URL。这里我创建的数据库名称为notebook,在配置URL之前,先在mysql中使用“create
database
notebook”命令创建此数据库。指定MySQL用户名和密码之后,点击next。

再创建页面 user_list.xhtml

图片 24

<!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>

查看配置概要,如果正确点击finish。

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

图片 25

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>

图片 26

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

如果成功连接到数据库,会弹出下图提示框:

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

图片 27

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

从IntelliJ中创建一个Maven 模块。使用下图所示的groupId, artifactId,
并选择择之前所创建的notebookRoot做为父项目:
com.zxuqian:notebookRoot:0.0.1-SNAPSHOT

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

图片 28

<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>

创建完成后,修改pom.xml文件内容:

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

<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"> <modelVersion>4.0.0</modelVersion> <groupId>com.zxuqian</groupId> <artifactId>notebookDomain</artifactId> <packaging>jar</packaging> <parent> <groupId>com.zxuqian</groupId> <artifactId>notebookRoot</artifactId> <version>0.0.1-SNAPSHOT</version> <relativePath>../notebookRoot/pom.xml</relativePath> </parent> <build> <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> <dependencies> <dependency> <groupId>org.hibernate.javax.persistence</groupId> <artifactId>hibernate-jpa-2.1-api</artifactId> <scope>provided</scope> </dependency> </dependencies></project>
<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>

此模块打包为Jar项目,为Java
EE所要求的JPA模块打包结构。因为wildfly运行环境提供JPA实现,所以这里JPA依赖的scope为provided,仅在编译时提供。

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

Entity是Java对象和数据库表建立关系的桥梁,使用JPA注解来配置如何生成对应的数据库表。首先创建一个class,类名为User,并填入如下代码:

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

package com.zxuqian.notebook.domain;import java.io.Serializable;import java.util.Date;import javax.persistence.*;/** * Entity implementation class for Entity: User * */@Entitypublic class User implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private long id; private String username; private String password; private Date dateOfBirth; private String email; private String phone; public User() { super(); } public long getId() { return id; } public void setId { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Date getDateOfBirth() { return dateOfBirth; } public void setDateOfBirth(Date dateOfBirth) { this.dateOfBirth = dateOfBirth; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } }
<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>

此类为POJO(simple plain old java
bean),意思为此类提供一组属性,并提供用这些属性初始化对象的构造方法,以及访问和设置这些属性的getter和setter方法。Entity需要实现Serializable接口以便JPA把对象序列化到数据库和从数据库中反序列化出来。@Entity注解表明此类为Entity,默认使用类名作为表名。@Id注解声明此成员变量为数据库中的主键,使用IDENTITY生成策略,即JPA负责生成ID的数值。在src/META-INF
目录, 创建名为 persistence.xml 的文件,填入如下内容:

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

<?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"> <jta-data-source>java:/MySqlDS</jta-data-source> <properties> <property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/> <property name="javax.persistence.schema-generation.scripts.action" value="none"/> </properties> </persistence-unit></persistence>

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

此文件为JPA的配置文件。 <jta-data-source>
使用在wildfly下创建的JNDI名称。
javax.persistence.schema-generation.database.action
属性的值,drop-and-create ,表示数据库表会在每次项目部署后重新创建。
javax.persistence.schema-generation.scripts.action 表示是否生成SQL
文件。

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

因为JSF模块和JPA模块为独立的两个项目,所以我们需要把它们打包成一个EAR包部署到wildfly服务器中。它包含的JSF和JPA模块会自动被wildfly识别并部署。创建此项目(groupId和artifacitId请参考下方pom文件),并在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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.zxuqian</groupId> <artifactId>notebookEAR</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>ear</packaging> <build> <plugins> <plugin> <artifactId>maven-ear-plugin</artifactId> <version>2.10</version> <configuration> <earSourceDirectory>EarContent</earSourceDirectory> <version>7</version> <defaultLibBundleDir>lib</defaultLibBundleDir> <modules> <webModule> <groupId>com.zxuqian</groupId> <artifactId>notebook</artifactId> <contextRoot>/notebook</contextRoot> </webModule> </modules> </configuration> </plugin> <plugin> <groupId>org.wildfly.plugins</groupId> <artifactId>wildfly-maven-plugin</artifactId> <version>1.2.0.Alpha2</version> <configuration> <jbossHome>/Users/zxuqian/development/tools/wildfly-10.1.0.Final</jbossHome> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>com.zxuqian</groupId> <artifactId>notebook</artifactId> <version>0.0.1-SNAPSHOT</version> <type>war</type> </dependency> <dependency> <groupId>com.zxuqian</groupId> <artifactId>notebookDomain</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies></project>

点击 Register 链接,

<webModule> 指定JSF模块为web模块,可定义context
root,即访问项目的URL根路径。这里使用了 wildfly-plugin
插件,可以方便的启动、关闭服务器,部署,卸载项目。在<jbossHome>中指定wildfly
主目录,否则它会自动下载一个新的wildfly以供使用。<denpendencies>指定了此项目编译需要依赖的模块,即JSF和JPA,它需要把这两个项目打包进EAR包。

图片 29ScreenShot2.png

在IntelliJ中,点击View – Tool Windows – Maven Projects 来显示Maven视图:

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

图片 30

图片 31ScreenShot3.png

双击notebookRoot的install来构建项目。双击notebookEAR下的wildfly:run来启动服务器并部署项目。如果需要重新部署项目,只需双击执行wildfly:redeploy即可。验证数据库是否创建成功,请连接到MySQL数据库,并查看notebook数据库中是否存在User表。使用describe

  • 表名可显示表结构:

    use notebook;describe user;

如果看到如下结果即表示创建成功。

图片 32

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图