Appli Web课程笔记
前言
本文章是根据法国国立高等电力技术、电子学、计算机、水力学与电信学校 (E.N.S.E.E.I.H.T.) 第八学期课程*“Application Web”* 总结而来的【部分课程笔记】。碍于本人学识有限,部分叙述难免存在纰漏,请读者注意甄别。
第一部分:Introduction
Web 基于 客户端-服务器
模型, 这里客户端是浏览器,服务器是 Web 服务器(如 Apache)。
主要包括三个方面:
1. 带有 URI/URN/URL 的文件的指定和位置
- URI (Uniform Resource Identifier):统一资源标识符,就是在某一规则下能把一个资源独一无二地标识出来。
- URL (Uniform Resource Locator):统一资源定位符。标识一个互联网资源,并指定对其进行操作或获取该资源的方法。
- URN (Uniform Resource Name):统一资源名称。用于标识唯一书目的 ISBN系统是一个典型的 URN 使用范例
2. 使用 HTML 和 CSS 语言对文档进行编码
HTML
HTML (HyperText Markup Language) 语言由**包含内容的标签 (composé de balises)**组成
1 |
|
CSS
层叠样式表(Cascading Style Sheets) 是一种用来为结构化文档(如HTML文档或XML应用)添加样式(字体、间距和颜色等)的计算机语言
1 | body { |
- 如何在
HTTP
文件里使用CSS
文件
1 | <head> |
3. 浏览器和 Web 服务器之间的通信协议 (HTTP)
HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网服务器传输超文本到本地浏览器的传送协议,基于TCP/IP通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)。面向连接,安全。
HTTP协议工作于 C/S
架构为上。浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。Web服务器根据接收到的请求后,向客户端发送响应信息:
graph LR Client --Request--> Server Server --Reponse--> Client
HTTP使用URL来传输数据和建立连接
1 | http:// 127.0.0.1 :8080 /enseeiht/ index.html ?name=dai&age=18 #detail |
HTTP 请求数据格式
1 | <!-- 请求行 --> |
或 json
格式的 HTTP 请求数据
1 | POST http://www.example.com HTTP/1.1 |
或 xml
格式的 HTTP 请求数据
1 | POST http://www.example.com HTTP/1.1 |
HTTP 的请求方法
-
GET
:请求指定的页面信息,并返回实体主体。在请求行中请求 (
/example/index.html?username=xxx&password=123456
),有长度限制。 -
HEAD
:类似于GET
请求,只不过返回的响应中没有具体的内容,用于获取报头 -
POST
:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST
请求可能会导致新的资源的建立和/或已有资源的修改。在请求体中请求,没有长度限制。 -
PUT
:从客户端向服务器传送的数据取代指定的文档的内容。 -
DELETE
:请求服务器删除指定的页面。 -
CONNECT
:HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。 -
OPTIONS
:允许客户端查看服务器的性能。 -
TRACE
:回显服务器收到的请求,主要用于测试或诊断。
HTTP 响应数据格式
1 | <!-- 响应行 --> |
HTTP 状态码
状态代码有三位数字组成,第一个数字定义了响应的类别,共分五种类别:
1xx
:指示信息。表示请求已接收,继续处理2xx
:成功。表示请求已被成功接收、理解、接受3xx
:重定向。要完成请求必须进行更进一步的操作4xx
:客户端错误。请求有语法错误或请求无法实现5xx
:服务器端错误。服务器未能实现合法的请求
常见状态码:
1 | 200 OK //客户端请求成功 |
4. HTTP 表单 (formulaire)
网站怎样与用户进行交互?答案是使用HTML表单(form)。表单是可以把浏览者输入的数据传送到服务器端,这样服务器端程序就可以处理表单传过来的数据。
1 | <form action="url" method="method"> |
get
与 post
的差别
本质上的区别是语义的区别,根据HTTP规范,POST
的语义是根据请求负荷(报文主体)对指定的资源做出处理。POST
方法是安全、不可缓存的。GET
的语义是请求获取指定的资源,具体的处理方式视资源类型而不同。GET
不安全,可缓存。具体差别如下:
- get在后退刷新时是无害的,post会重新提交请求;
- get参数通过URL传递,post放在Request body中;
- get请求参数保留在浏览器历史记录中,post参数不会保留;
- get产生的URL地址可以被存为书签,而post不可以;
- 对参数的数据类型,get只接受ASCII字符,而post没有限制;
- get比post更不安全,因为发送的数据显示在URL上,在发送密码或其他敏感信息时绝不要使用get;
- get请求只能进行url编码,而post支持多种编码方式。
表单元素:input
1 | <input type="type" name="input" size="10pd" value="value"> |
常见的 input
标签:
-
submit
用于数据提交 -
text
可输入文本 -
password
用于输入密码,输入内容会呈现为小圆点,进行隐藏 -
checkbox
多选框 -
radio
多选框 -
select
下拉选择 并和option
标签一起使用1
2
3
4
5<select name="choice">
<option value="value1">选择1</option>
<option value="value2">选择2</option>
<option value="value3">选择3</option>
</select> -
file
上传文件 -
hidden
隐藏组件 -
bottom
按钮 -
reset
重置表单 -
textarea
文本区域1
2
3<textarea name="teatarea" rows="4" cols="4">
这是文本区域,可以键入
</textarea>
5. CGI的工作原理
CGI(Common Gateway Interface)公共网关接口,根据CGI标准,编写外部扩展应用程序,可以对客户端浏览器输入的数据进行处理,完成客户端与服务器的交互操作。CGI规范定义了Web服务器如何向扩展应用程序发送消息,在收到扩展应用程序的信息后又如何进行处理等内容。
CGI 应用程序能与浏览器进行交互,还可通过数据API与数据库服务器等外部数据源进行通信,从数据库服务器中获取数据。格式化为HTML文档后,发送给浏览器,也可以将从浏览器获得的数据放到数据库中。
graph RL subgraph "C/S" 浏览器 --"get/post"--> Web服务器 Web服务器 --"generated.html"--> 浏览器 end Web服务器 --stdin--> CGI Web服务器 --$QUERY_STRING--> CGI CGI --stdout--> Web服务器 subgraph "动态网页" CGI --传入数据--> 程序 程序 --产生数据--> CGI end
<form>
标签的 METHOD
属性来决定具体使用哪一种方法。
- 在
METHOD=GET
时,向 CGI 传递表单编码信息的是通过命令来进行的。表单编码信息大多数是通过环境变量QUERY_STRING
来传递的。 - 若
METHOD=POST
,表单信息通过标准输入stdin
来读取。
6. HTTP Cookies
Cookie
是一个保存在客户机中的简单的文本文件, 这个文件与特定的 Web 文档关联在一起, 保存了该客户机访问这个Web 文档时的信息, 当客户机再次访问这个 Web 文档时这些信息可供该文档使用。
由服务器创建,保存在客户端中
组成
Cookie是一段不超过4KB的小型文本数据,由一个名称(Name)、一个值(Value)和其它几个用于控制Cookie有效期、安全性、使用范围的可选属性组成。其中:
Name/Value
:设置Cookie的名称及相对应的值,对于认证Cookie,Value
值包括Web服务器所提供的访问令牌。Expires
属性:设置Cookie的生存期。有两种存储类型的Cookie:会话性与持久性。Expires属性缺省时,为会话性Cookie,仅保存在客户端内存中,并在用户关闭浏览器时失效;持久性Cookie会保存在用户的硬盘中,直至生存期到或用户直接在网页中单击“注销”等按钮结束会话时才会失效 [3] 。Path
属性:定义了Web站点上可以访问该Cookie的目录。Domain
属性:指定了可以访问该 Cookie 的 Web 站点或域。Cookie 机制并未遵循严格的同源策略,允许一个子域可以设置或获取其父域的 Cookie。当需要实现单点登录方案时,Cookie 的上述特性非常有用,然而也增加了 Cookie受攻击的危险,比如攻击者可以借此发动会话定置攻击。因而,浏览器禁止在Domain
属性中设置.org
、.com
等通用顶级域名、以及在国家及地区顶级域下注册的二级域名,以减小攻击发生的范围Secure
属性:指定是否使用HTTPS
安全协议发送Cookie。使用HTTPS安全协议,可以保护Cookie在浏览器和Web服务器间的传输过程中不被窃取和篡改。该方法也可用于Web站点的身份鉴别,即在HTTPS的连接建立阶段,浏览器会检查Web网站的SSL证书的有效性。但是基于兼容性的原因(比如有些网站使用自签署的证书)在检测到SSL证书无效时,浏览器并不会立即终止用户的连接请求,而是显示安全风险信息,用户仍可以选择继续访问该站点。由于许多用户缺乏安全意识,因而仍可能连接到Pharming攻击所伪造的网站HTTPOnly
属性 :用于防止客户端脚本通过document.cookie属性访问Cookie,有助于保护Cookie不被跨站脚本攻击窃取或篡改。但是,HTTPOnly的应用仍存在局限性,一些浏览器可以阻止客户端脚本对Cookie的读操作,但允许写操作;此外大多数浏览器仍允许通过XMLHTTP
对象读取HTTP
响应中的Set-Cookie头 。
第二部分:动态网页
2.1 Servlet
Servlet是 Java 提供的一门动态web资源开发技术。是JavaEE 的规范之一,其实就是一个接口,我们需要定义 Servlet
的类实现 Servlet
接口。
1 | interface Servlet{ |
但是在上述 Servlet
接口中只有 void service(ServletRequest req, ServletResponse res)
方法最为常用,我们将 Servlet
接口封装为 HttpServlet
抽象类:
1 | public abstract class HttpServlet extends GenericServlet { |
Request 和 Response
Request 获取请求数据
请求数据分为3部分:
- 请求行:
GET /request-demo/req1?username=xxx HTTP/1.1
String getMethod()
:获取请求方式:GET/POST
String getContextPath()
:获取虛拟目录(项目访问路径):/request-demo
String Buffer getRequestURL()
:获取URL(统一资源定位符):http://localhost:8080/request-demo/req1
String getRequestURI()
:获取URI(统一资源标识符):/request-demo/req1
String getQueryString()
:获取请求参数(GET
方式):username=zhangsan&password=1
- 请求头:
User-Agent: Mozilla/5.0 Chrome/91.0.4472.106
- String getHeader(String name):根据请求头名称,获取值
- 请求体 (
POST
方式):username=xxx&password=123456
ServletlnputStream getinputStream()
:获取字节输入流BufferedReader getReader()
:获取字符输入流
getParameter()
:我们将 GET
方式中获取请求参数 String getQueryString()
和 POST
方式中获取请求参数 BufferedReader getReader()
同一为一种通用的获取请求参数的方法:getParameter(key)
。这其实是将所有的参数解析后存在在一个 MAP
中,通过 key
来获取 value
。
【请求转发】request.getRequestDispatcher("资源B的路径").forword(request,respond)
:如果服务器有两个资源,资源A 和资源B,资源A处理一部分后跳转 (forword) 到资源B继续处理。地址栏不变,一次请求
- 请求转发资源间共享数据:使用
Request
对象void setAttribute(String name, Object o)
:存储数据到request
域中Object getAttribute (String name)
:根据 key,获取值void removeAttribute(String name)
:根据 key,删除该键值对
Response 设置响应数据
响应数据分为3部分:
-
响应行:
HTTP/1.1 200 OK
void setStatus(int statusCode)
:设置响应状态码
-
响应头:
Content-Type: text/html
void setHeader(String name, String value)
:设置响应头 键值对
-
响应体:
<html></html>
Printwriter writer = response.getWriter()
:获取宇符输出流writer.write("<html>")
Servletoutputstream outputStream = response.getoutputStream()
:获取字节输出流
【重定向 Redierct】当前资源A无法处理(statusCode=302
),重定向到别的资源B (location
) 处理。地址栏变化,两次不同的请求
1 | response.sendRedierct("资源B的路径"); |
例:
web_app/demo.html
:
1 | <html> |
- 由上述代码可见,
action
指定的请求路径是web_app/demo
method
指定的方法是post
servletDemo.java
:
1 | // 一个路径可以配置多个 urlPattern = {"/demo1", "/demo2"} |
Cookie
客户端的会话构造技术:将数据保存在客户端,每次请求都携带 Cookies 数据进行访问。
- Cookie 存活时间:
- 默认情况下,Cookie 存储在浏览器内存中,当浏览器关闭,内存释放,则 Cookie 被销毁。
setMaxAge(int seconds)
:设置存活时间。(+表示存入硬盘,到期删除;-表示默认情况;0表示删除对应Cookie)
1 | // 1.创建Cookie对象,设置数据 |
Session
服务器端的会话跟踪技术:将数据保存在服务端。Session的是现实基于Cookies的。
JavaEE 提供了 HttpSession
接口,来实现一次回话的多次请求间的数据共享功能
1 | // 1. 获取 Session 对象 |
2.2 JSP
Java Server Page,即Java服务端页面。是一种动态网页技术,既可以定义 HTML、JS、CSS 等静态内容,还可以定义 Java 代码的动态内容。JSP = HRTML + Java
1 | <html> <!-- html 代码 --> |
JSP 原理
JSP 本质上是一个 Servlet。JSP 在被访问时,由 JSP 容器(Tomcat)将其转换为 Java 文件(Servlet),再由 JSP 容器将其编译,最终对外提供服务的就是这个字节码文件:
JSP 脚本 (Scriptlet)
JSP 脚本用于在 JSP 页面内定义 Java 代码。
JSP 脚本的分类:
<% ... %>
:Java 脚本。内容会直接放到_jspService()
方法之中;<%= ... %>
:表达式。内容会直接放到out.print()
方法之中,作为out.print()
的参数;<%! ... %>
:声明。内容会放到_jspService()
方法之外,被类直接包含。定义成员变量、方法等
1 | <html> |
1 | public final class hello_jsp extends HttpJspBase implements JspSourceDependent { |
JSP 的指令标签
JSP指令用来设置整个JSP页面相关的属性,如网页的编码方式和脚本语言。
语法格式如下:
1 | <%@ directive attribute="value" %> |
指令可以有很多个属性,它们以键值对的形式存在,并用逗号隔开。
JSP中的三种指令标签:
(1)<%@ page ... %>
Page指令为容器提供当前页面的使用说明。一个JSP页面可以包含多个page指令。
Page指令的语法格式:
1 | <%@ page attribute="value" %> |
等价的XML格式:
1 | <jsp:directive.page attribute="value" /> |
属性 | 描述 |
---|---|
buffer |
指定out对象使用缓冲区的大小 |
autoFlush |
控制out对象的 缓存区 |
contentType |
指定当前JSP页面的MIME类型和字符编码 |
errorPage |
指定当JSP页面发生异常时需要转向的错误处理页面 |
isErrorPage |
指定当前页面是否可以作为另一个JSP页面的错误处理页面 |
extends |
指定servlet从哪一个类继承 |
import |
导入要使用的Java类 |
info |
定义JSP页面的描述信息 |
isThreadSafe |
指定对JSP页面的访问是否为线程安全 |
language |
定义JSP页面所用的脚本语言,默认是Java |
session |
指定JSP页面是否使用session |
isELIgnored |
指定是否执行EL表达式 |
isScriptingEnabled |
确定脚本元素能否被使用 |
(2)包含:<%@ include ... %>
使用包含操作,可以将一些重复的代码包含进来继续使用,从正常的页面组成来看,有时可能分为几个区域。而其中的一些区域可能是一直不需要改变的,改变的就其中的一个具体内容区域。现在有两种方法可以实现上述功能。
- 方法一:在每个JSP 页面 (HTML)都包含工具栏、头部信息、尾部信息、具体内容
- 方法二:将工具栏、头部信息、尾部信息都分成各个独立的文件,使用的时候直接导入
很明显,第二种方法比第一种更好,第一种会存在很多重复的代码,并旦修改很不方便,在JSP 中如果要想实现包含的操作,有两种做法:静态包含、动态包含,静态包含使用 include
指令即可,动态包含则需要使用 include
动作标签。
-
静态包含:
<%@ include file="要包含文件的相对路径" %>
-
动态包含:
<jsp:include file="要包含文件的相对路径"> </jsp:include>
(3)Taglib 指令
JSP API允许用户自定义标签,一个自定义标签库就是自定义标签的集合。Taglib指令引入一个自定义标签集合的定义,包括库路径、自定义标签。
Taglib指令的语法:
1 | <%@ taglib uri="uri" prefix="prefixOfTag" %> |
2.3 MVC 模式
MVC 是一种分层开发的模式,其中:
- M:Model,业务模型,处理业务
- V:View,视图,界面展示
- C:Controller,控制器,处理请求,调用模型和视图,也可以在一个控制器中调用另一个控制器
三层架构:软件设计架构
- 数据访问层:对数据库的CRUD基本操作
- 业务逻辑层:对业务逻辑进行封装,组合数据访问层层中基本功能,形成复杂的业务逻辑功能
- 表现层:接收请求,封装数据,调用业务逻辑层,响应数据
2.4 案例
我们使用一个简单的例子实现上述 MVC + 三层结构,其中我们不需要连接数据库,所以我们把 “数据访问层”和“数据库”用一个类 Compte
取代:
Compte.java
(DATA)
1 | public class Compte { |
Facade.java
(Service)
1 | public class Facade { |
Controller.java
(servlet)
1 |
|
Banque.jsp
(View)
1 | <%@ page language="java" import="java.util.*" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> |
第三部分:EJB
EBJ (Enterprise JavaBean) 运行在容器 (Container) 中。EJB 实际上就是一个封装了业务逻辑的 Java 类。
- EJB 支持分布式。分布式对象之间实现分布透明性,即在客户端代码中无需指定分布式对象的位置;
- EJB 支持分布式对象之间的事物(RMI 不支持)
- 支持不同类型的客户端
Session Bean
Session Bean 可以执行业务逻辑操作,比如注册用户、订单登记等。在上级部分所讲的“三层结构”中位于业务逻辑层。
何为 Session?
从客户端获得 EJB 对象开始,多次调用 EJB 对象的方法,直到客户端生命周期结束,或客户端释放了 EJB 队象为止,称为一次会话 (Session)。与返回的 EJB 对象有关,如果不是同一个对象(内存地址不同),则不是同一个会话。
sequenceDiagram participant c as Client participant ec as EJB容器 participant eo as EJB对象 c ->> ec :1. JNDI 查找 EJB对象 ec ->> c :2. 返回 EJB对象1 c ->> eo :3. 调用 EJB对象1 的方法 c ->> eo :4. 调用 EJB对象1 的方法 c ->> eo :5. 调用 EJB对象1 的方法 Note over eo :以上操作均在一个 Session1 中 Note over c :客户端生命周期结束 c ->> ec :6. JNDI 查找 EJB对象 ec ->> c :7. 返回 EJB对象2 c ->> eo :8. 调用 EJB对象2 的方法 c ->> eo :9. 调用 EJB对象2 的方法 c ->> eo :10. 调用 EJB对象2 的方法 Note over eo :以上操作均在一个 Session2 中
Session Bean 的状态
何为对象的状态?
对象的状态是由其实例变量(即成员变量)的值组成。
- 实例变量:在不同的实例中,变量的值可以是不同的。非静态变量。
- 类变量:这个类的所有实例都一个值。
static
变量
Stateful Session Bean (有状态的 Session Bean)
EJB 能够为同一个客户端在多次请求(方法调用)之间保持与其对应的状态信息
从 HTTP Session 类比 Stateful Session Bean:
客户动作 | 服务器响应 |
---|---|
1、打开浏览器 | |
2、访问购物网站 | 3、创建 HTTP Session 对象 |
4、返回 jsessionid | |
5、将 jsessionid 写入 cookie | |
6、往购物车内添加商品 | |
7、向系统提交商品信息,以及 jsessionid 的值 | 8、服务器根据 jsessionid 找到对应的 HTTP Session对象,同时,创建购物车对象,与 Session 绑定 |
9、 继续添加商品,或删除商品 | |
10、每次向服务器提交数据的时候,都会带着一个 jsessionid 的信息 | 11、服务器通过 jsessionid 来辨认不同的客户端,以及维护这些客户端的状态信息 |
EJB 实例池通过 Token 令牌区分不同的客户端,从而实现 Stateful Session Bean:
Stateless Session Bean (无状态的 Session Bean)
是指 EJB 容器不会对 EJB 的状态进行管理。容器会使用实例池的方式,甚至单例 (Singleton) 的方式来实现无状态的 Session Bean。
是否可以理解成:在有状态的 Session Bean 中,实例池不再根据 Token 区分不同的客户端?
Singleton Session Bean (单例的 Session Bean)
实例池中只有一个 EJB对象,不再对客户端的状态进行区别管理,而统一使用一个 EJB 对象
客户端访问接口
@Remote
远程客户端接口
- 客户端与其调用的 EJB 对象不在同一个 JVM 进程中;
- 可以是 web组件、应用客户端、或是其他的 EJB;
- 对于远程客户端来说,EJB 的位置是透明的;
- 为了创建一个可以被远程客户端访问的 EJB,需要用
@Remote
注解来定义这些 EJB
@Local
本地客户端接口
- 客户端与其调用的 EJB 对象在同一个 JVM 进程中;
- 可以是 web组件、或是其他的 EJB;
- 为了创建一个可以被本地访问的 EJB,需要用
@Local
注解来定义这些 EJB - 一个 EJB 可以同时被定义为
@Remote
和@Local
@webMethod
客户端接口
Web Service 客户端可以访问无状态 Session Bean 的接口,只有在业务逻辑方法被标识为 @WebMethod
的时候,Web Service 客户端才可以访问到。
客户端访问方式
远程访问方式 (Remote Access)
在进程间通行的时候需要将参数序列化和反序列化,传值。
sequenceDiagram participant c as Client participant est as EJB Stub 客户端代理 participant esk as EJB Skeleton 服务器代理 participant j as JNDI 服务 participant eo as EJB对象 c ->> +j :1. JNDI lookup(查找) EJB对象 j ->> -est :2. 创建 est ->> c :3. 给客户端返回 stub 对象 c ->> +est :4. 调用方法(参数) est ->> est :5. 将参数序列化 est ->> -esk :6. 底层网络通信 esk ->> esk :7. 将参数反序列化 esk ->> eo :8. 调用相应的方法(参数)
本地访问方式 (Local Access)
与远程访问方式相比,本地服务方式没有将参数序列化和反序列化的内容。可以直接访问地址(传址)
sequenceDiagram participant c as Client participant est as EJB Stub 客户端代理 participant esk as EJB Skeleton 服务器代理 participant j as JNDI 服务 participant eo as EJB对象 c ->> +j :1. JNDI lookup(查找) EJB对象 j ->> -est :2. 创建 est ->> c :3. 给客户端返回 stub 对象 c ->> +est :4. 调用方法(参数) est ->> -esk :5. 底层网络通信 esk ->> eo :6. 调用相应的方法(参数)
客户端访问接口类型与访问方式
graph LR subgraph "客户端接口" 远程客户端接口 本地客户端接口 WebService客户端接口 end subgraph "访问方式" Remote方式 Local方式 WebMethod方式 end 远程客户端接口 --- Remote方式 本地客户端接口 --- Remote方式 本地客户端接口 --- Local方式 WebService客户端接口 --- WebMethod方式
方法的参数和访问方式
不同的访问方式 (Remote、Local、Web Service) 会影响到 EJB 方法的参数及其返回值。
独立性
- 如果是远程调用,客户端操纵的 EJB 的参数,其实是一份参数值的拷贝。因此,对参数的修改,不会影响到EJB,
- 但是对于本地调用来说,客户端操纵的 EJB 的参数,就是一个直接引用,它对参数的修改,将会影响到 EJB。
- 所以,不管在哪种情况下,请避免修改参数的值!
粗粒度的数据访问
因为远程调用的速度比较慢,所以在设计的时候,请尽量使用粗粒度的接口设计。即尽量减少方法的调用,并尽可能在一次方法调用中传输完毕所需要的数据!
案例
我们依旧沿用上一个部分的案例。
Compte.java
( 实体类 )
1 |
|
@Remote
远程客户端访问 和 @Local
本地客户端访问
<interface>: BankLocal
(本地访问接口)
1 |
|
<interface>: BankRemote
(远程访问接口)
1 |
|
BankImpl.java
( Facade/Service)
1 | // Or @Stateful, or @Stateless |
RemoteClientEJB.java
(远程客户端app)
1 | public class RemoteClientEJB { |
jboss-ejb-client.properties
( 为了客户端可以远程访问,配置 JNDI
)
1 | endpoint.name = client-endpoint |
@WebMethod
客户端访问:Servlet
Controller.java
(以 @WebServlet
方式访问的客户端:Servlet)
1 |
|
<interface>: BankRemote
(远程访问接口)
1 |
|
BankImpl.java
( Facade/Service)
1 | // Or @Stateful, or @Stateless |
第四部分:JPA
JPA,Java持久化API:持久化 Bean 与普通的 Java Bean 无异,区别在于他们要用 EJB 的 Annotation 进行标记。
- 一个实体类将其表示为
@Entity
; - 实体类必须有主键,一般用
@Id
标识; - 在
/META-INF/
目录下,有persistence.xml
文件,其主要作用是定义实体类映射的相关配置信息,比如指定数据源、都有哪些实体类、以及跟持久化相关的其它的一些属性。
Entity Bean 实体类
我们可以用 Entity Bean 实体类来记录数据,每个实体类都关联一个数据库的表。
Entity Bean 基本映射规则(映射到数据库表)
(默认的表名和字段名与属性名一致)
-
实体类和表的映射关系:
@Entity
:声明实体类;@Table(name="数据库表的名称")
:配置实体类和表的映射关系
-
实体类中属性和数据库表中字段的映射关系:
@Id
:声明主键的配置@GeneratedValue(strategy)
:配置主键的生成策略strategy = GenerationType.IDENTITY
:id主键自增strategy = GenerationType.AUTO
:缺省,默认方式strategy = GenerationType.SEQUENCE
:通过序列生产主键,与@SequenceGenerator
配合使用strategy = GenerationType.TABLE
:通过其他表生成主键
@Column(name="数据库表中字段的名称")
:配置属性和数据库表中字段的映射关系name = "数据库表中字段的名称"
;unique = true/false
:是否唯一;nullable = true/false
:是否可空;length = 字段长度
;
@Basic
:默认缺省,所有配置全部设为默认@Transient
:该属性不需要映射为数据库表的字段;@Temporal(TemporalType)
:定义Date
类型的精度TemporalType.DATE
:年-月-日
TemporalType.TIME
:时:分:秒
TemporalType.TIMESTAMP
:年-月-日 时:分:秒
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class Compte extends Serializable { // 需要将其序列化
private int num;
private String nom;
private int solde;
public Compte() {} // 空构造函数
public Compte(int num, String nom, int solde) {
this.num = num;
this.nom = nom;
this.solde = solde;
}
public int getNum() {
return this.num;
}
public void setNum(int num) {
this.num = num;
}
public String getNom() {
return this.nom;
}
public void setNom(String nom) {
this.nom = nom;
}
public int getSolde() {
return this.solde;
}
public void setSolde(int solde) {
this.solde = solde;
}
public String toString() {
rerturn "Compte [num=" + num + ",nom=" + nom + ",solde=" + solde + "]";
}
}
EntityManager
实体类管理器
在 JPA 规范中,EntityManager是真正对数据库操作的对象。
实体作为普通 Java 对象,只有在调用EntityManager 将其持久化后才会变成持久化对象。EntityManager对象在一组实体类与底层数据源之间进行 O/R 映射的管理。它可以用来管理和更新 Entity Bean,根椐主键查找 Entity Bean,还可以通过 JPQL 语句查询实体。
实体的状态:
- 新建状态:新创建的对象,尚未拥有持久性主键。
- 持久化状态:已经拥有持久性主键并和持久化建立了上下文环境
- 游离状态 :拥有持久化主键,但是没有与持久化建立上下文环境
- 删除状态:拥有持久化主键,已经和持久化建立上下文环境,但是从数据库中删除
EntityManager
方法
1 |
|
-
find()
:根据id查找1
2int num; // 主键
Compte c = em.find(Compte.class, num); -
getReference()
:根据id查找,先返回实体对象的代理,在使用代理对象前关闭 EntityManager 会出现懒加载异常LazyInitializationException
。 -
presist()
:保存 (INSERT
),使对象由临时状态变为持久化状态。如果实体类有主键,在presist()
会抛出异常1
2Compte c = new Compte(null, "tom", "200"); // 自动分配主键
em.presist(c); -
merge()
:更新UPDATE or INSERT
-
如传入的是一个临时对象(无主键id),会先创建一个新的对象(有主键),把临时对象的属性复制到新对象中,再对这个新对象
INSERT
数据库持久化操作。所以新对象有主键,而临时对象没有主键。1
2
3Compte c = new Compte(null, "theo", "1000"); // 自动分配主键
Compte c_temp = em.merge(c);
// c.getNum(); -> null c_temp.getNum(); -> JPA 按照策略分配的 #Id 的值 -
如果传入的是一个游离对象(有主键id),若 EntityManager 缓存中没有该对象、且数据库中也没有相应的记录,JPA 则会先创建一个新的对象,把游离对象的属性复制到新对象中,再对这个新对象
INSERT
数据库持久化操作。1
2
3Compte c = new Compte(100, "enzo", "1000"); // 自动分配主键
Compte c_temp = em.merge(c);
// c.getNum(); -> 100 c_temp.getNum(); -> 4 -
如果传入的是一个游离对象(有主键id),若 EntityManager 缓存中没有该对象,但数据库中有相应的记录,JPA 则会查询相应记录后返回该记录的一个对象,把游离对象的属性复制到查询对象中,再对这个查询对象
UPDATE
数据库持久化操作。1
2
3
4Compte c = new Compte(null, "enzo", "500"); // 自动分配主键
c.setNum(4);
Compte c_temp = em.merge(c);
// c.toString(); -> Compte [num=4, nom=enzo, solde=500]
-
-
remove()
:删除 -
flush()
:同步数据表的记录和内存中对象的状态setFlushMode(FlushModeType.AUTO)
:自动同步到数据库setFlushMode(FlushModeType.COMMIT)
:直到事物提交时才同步到数据库getFlushMode
-
transaction = em.getTransaction()
:获取事务对象transaction.begin();
transaction.commit();
transaction.rollback();
映射关联关系
@ManyToOne
单向多对一关联映射
在单向多对一的关联映射里,在“多”的一端添加一个外键 (Foreign Key) 指向“一”的一端,而且可以指定字段名称。
1 | // 一 |
1 | // 多 |
@OneToMany
一对多关联映射
@OneToMany(fetch=FetchType.xxx)
:修改默认加载策略,默认懒加载 (LAZY
);(EAGER
)@OneToMany(cascade=CascadeType.remove)
:- 默认情况下,若删除“一”的一端,会先将“一”的一端中关联“多”的外键置空,然后删除
- 可以通过
@OneToMany
的cascade
属性来修改默认的删除策略 CascadeType.PERSIST (级联新建)
CascadeType.REMOVE (级联删除)
CascadeType.REFRESH (级联刷新)
CascadeType.MERGE (级联更新)中选择一个或多个
还有一个选择是使用CascadeType.ALL ,表示选择全部四项
一对多单向关联映射:
一对多单向关联映射 有两个映射策略,即 外键关联 和 表关联
- 外键关联:会创建一个外键 ,用外键记录
Client
和Account
之间的单向关联。
1 | // 一 |
1 | // 多 |
- 表关联:会创建一个中间表 ,用中间表记录
Client
和Account
之间的单向关联。
1 | // 一 |
1 | // 多 |
一对多双向关联映射:
一对多双向关联映射 = 多对一双向关联映射
没有中间表,但会在“多”的一端加入一个外键,两端的外键名保持一致。
1 | // 一 |
1 | // 多 |
@OneToOne
一对一双向关联映射
有两种策略:主键(Primary Key)关联 和 唯一外键(Foreign Key)关联
主键关联
一端的主键 (@Id
) 依赖于另一端的主键 (@Id
)
1 |
|
1 |
|
唯一外键关联
- 使用
@OneToOne
来映射一对一关联关系; - 若需要在当前表中添加外键,则需要使用
@JoinColumn(unique=true)
来映射
1 |
|
1 |
|
@ManyToMany
多对多关联映射
通过新建一个中间表,达到多对多的关联映射。
1 |
|
1 |
|
JPQL
JPQL 语言,即 Java Persistence Query Language 的简称。
JPQL 是一种和 SQL 非常类似的中间性和对象化查询语言,它查询的内容是实体类和类中的属性。它最终会被编译成针对不同底层数据库的 SQL查询,从而屏蔽不同数据库的差异。
JPQL 语言的语句可以是 select
语句,update
语句或 delete
语句,它们都通过 Query 接口封装执行。调用 EntityManager
的 createQuery()
、createNamedQuery()
、createNativeQuery()
以获得查询对象,进而可调用 Query 接口的相关方法来执行查询操作
例:
1 | public Collection<Orders> searchByAge() { |
-
SELECT-FROM
子句:select
用于指定返回的结果实体或实体的某些属性;from
子句声明查询源实体类,并指定标识符变量(相当于 SQL 表的别名)。如果不希望返回重复实体,可以使用关键字distinct
。FROM Orders WHERE o.id=:o_id
当查询整个实体类时可省略select *
-
WHERE
子句:用于指定查询条件,SELECT o FROM Orders o WHERE o.id =:o_id
SELECT o FROM Orders o WHERE o.id > 4
-
GROUP BY
子句:用于对查询结果分组统计,通常需要使用聚合函数,如AVG, SUM, COUNT, MAX, MIN
等SELECT MAX(o.id) FROM Orders o
-
ORDER BY
子句:用于对查询结果进行排序,ASC
升序,DESC
降序SELECT o FROM Orders ORDER BY o.id DESC
-
UPDATE
子句:用于执行数据的更新操作。主要针对于单个实体类的更新1
2
3
4
5String jpql = "UPDATE Order o SET o.age = ? WHERE o.id = ?";
Query query = em.creatQuery(jpql);
query.setParameter(1,25);
query.setParameter(2,2);
query.executeUpdate(); -
DELETE
子句:1
2
3
4String jpql = "DELETE Order o WHERE o.id = ?";
Query query = em.creatQuery(jpql);
query.setParameter(1,2);
query.executeUpdate();
当然还有很多其他的用法,但在此我们不作深究。