EL : Expression Language, 是可以替代JSP中的Java代码
传统的在JSP中用Java代码显示数据的弊端:
- 类型转换
- 需要处理null
- 代码参杂
EL示例
- $
${域对象.域对象中的属性.属性.属性.级联属性} - EL 操作符
- 点操作符 . 使用方便
- 中括号操作符 [] 功能强大; 可以包含特殊字符(. \ - ); 也可以获取变量名(如: 变量name, 则可以${requestScope[name]}); 可以访问数组
- 通过el操作符可以直接拿数组, Map的值
- ${requestScope.hobbies[0]}; 即可拿到String数组的第一个元素的值
- $ 即可拿到Map类型的key为cn的value值
- EL 关系运算符
- 大于 >(或: gt); 大于或等于 >=(或: ge); 等于 == (或: eq); 小于或等于 <=(或: le); 小于 <(或: lt); 不等于 !=(或: ne)
- EL 逻辑运算符
- 逻辑或 ||(或: or); 逻辑与 &&(或: and); 逻辑非 !(或: not)
- empty 运算符
- 判断一个值是否为unull, 或者判断这个值不存在; 如果这个值为null, 或者不存在, 则返回true
EL表达式的隐式对象
-
隐式对象就是不需要new就可以使用的对象, 自带的对象
-
作用域访问对象(EL域对象)
- pageScope, requestScope, sessionScope, applicationScope
- 如果不指定域对象, 则默认会根据从小到大的顺序, 依次取值
-
参数访问对象
- 获取表单数据 $ 或 ${paramValues.hobbies[0]}, 即可拿到表单提交的数据
- 获取超链接中传递的值: 如超链接: <\a href="el.jsp?fileName=jiefxff">jefxff</\a>; 使用 $ 即可拿到超链接中URL重写拿到的值
-
JSP隐式对象
- pageContent: 在JSP中可以通过pageContent获取JSP的其他隐式对象
- EL 的隐式对象含有一个JSP的一个隐式对象pageContent, 通过该隐式对象即可拿到JSP的其他隐式对象; 因此在EL中使用JSP隐式对象, 就可以通过pageContent间接获取
- 模板: ${pageContent.方法名去掉()和get并且首字母小写}
- ${pageContent.getSession()} --简化--> $
- 也可以使用此方法级联获取对象: &
JSTL: 比EL更加强大
- 需要引入两个jar包 jstl.jar(1.2), stardard.jar(1.1.2)
- 在Intellij 中还要再settings->schemas and DTDs 中, 将URL映射到对应的Location(即点击右边加号再URL中填入"http://java.sun.com/jsp/jstl/core"; 再File中选择standard-1.1.2.jar中的c.tld); 这样才可以使用
- 引入一个 taglib 指令(引入一个库, 并且加一个前缀): <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>; 其中: prefix="c" : 前缀
核心标签库
通用标签库(prefix="c" 那么前缀就是c)
- 赋值语句: <c:set />
- 在某个作用域中给变量赋值; <c:set var="变量名" value="变量值" scope="4个范围的作用域其中一个" />
- 在某个作用域中给某个对象的属性赋值(不能指定scope="request");<c:set target="${对象}" propery="对象的属性" value="赋值"/>
- 显示: <c:out /> 相当于EL, 但是比EL强大(结合例子实践)
- c:out显示的好处就是, 不存在的数据, 直接显示空白; 也可以通过default="Xxx"来设置当数据不存在时显示default中的值
- escaleXml属性为true可以使c:out中value里面的数据原封不动的显示出来; 而为false的话, value中的数据会被浏览器解析
- 删除: <c:remove />
条件标签库
- 单重if条件
- <\c:if test="这里写条件判断语句" var="var变量是保存结果的" scope="这里指定域" > </\c:if>
- 多重选择(类似与switch或多重if,else if, else结构)
- <c:choose> <c:when test="..." > </c:when> <c:when test="..." > </c:when> <c:otherwise> </c:otherwise> </c:choose>
- c:choose 标签是选择标签, 只能配合其子标签 c:when 和 c:otherwise 来使用; 当c:when 中test="${判断条件}"为真时执行其中的代码
迭代标签库
- 循环
- c:forEach 基础迭代标签, 接收多种集合类型
核心标签库代码实例
- 通用标签库代码示例
<body>
<%-- 下面这句话的意思就是在request作用域里面赋了一个变量名:name, 变量值:zs
相当于 reqest.setAtttibute("name", "zs")--%>
<%-- 相当于给不存在的变量赋值 --%>
---------------------------给某个变量赋值---------------------------
<c:set var="name" value="zs" scope="request"/>
EL获取JSTL的值: ${requestScope.name}
<br>
---------------------------给普通对象的属性赋值---------------------------
<br>
<%-- EL获取对象的属性 --%>
${requestScope.student.sname}
<br>
<%-- JSTL 通过target配合EL拿到student对象, 通过 propery拿到属性名, 通过 value进行新的赋值 --%>
<c:set target="${requestScope.student}" property="sname" value="zxs"/>
<br>
<%-- 测试是否覆盖了sname的值 --%>
${requestScope.student.sname}
<br>
---------------------------给Map对象的属性赋值---------------------------
<br>
<%-- 拿到map对象 --%>
${requestScope.map.cn}
<br>
<%-- JSTL 通过target配合EL拿到map对象, 通过 propery拿到key为cn, 通过 value进行新的赋值 --%>
<c:set target="${requestScope.map}" property="cn" value="CHINA"/>
<br>
<%-- 拿到map对象 --%>
${requestScope.map.cn}
<br>
================================<\c:out />===================================
<br>
传统的EL模式: ${requestScope.student}
<br>
c:out模式: <c:out value="${requestScope.student}"/>
<br>
<%-- c:out显示的好处就是, 不存在的数据, 直接显示空白; 也可以显示default的值--%>
c:out显示不存在的数据: <c:out value="${requestScope.stud}" default="zs-23"/>
<br>
<a href="www.baidu.com">百度</a><br>
<%-- escapeXml属性为true可以使c:out中value里面的数据原封不动的显示出来; 而为false的话, value中的数据会被浏览器解析 --%>
<c:out value='<a href="https://www.baidu.com">百度</a>' escapeXml="true" />
<br>
<c:out value='<a href="https://www.baidu.com">百度</a>' escapeXml="false" />
<br>
================================c:remove删除对象===================================
<br>
通过c:set设置一个值: <c:set var="a" value="b" scope="request"/>
<br>
通过EL显示这个值: ${requestScope.a}
<br>
通过c:remove删除这个值: <c:remove var="a" scope="request"/>
<br>
测试是否删除这个值: ${requestScope.a}
</body>
- JSTL单重的if
<%--<c:if test="这里写条件判断语句" var="va变量是保存结果的" scope="这里指定域" >--%>
<c:if test="${10>2}" var="result" scope="request" >
真
<br>
<c:out value="${requestScope.result}" default="没有赋值, 这是显示的默认值" />
</c:if>
Filter 过滤器
FIlter实现
- 要想将一个普通的Class变成一个具有功能的类(过滤器, 拦截器...), 要么继承父类, 要么添加一个注解
- 实现 javax.servlet.Filter 接口后, 要重写其中的 init(), doFilter(), destory() 三个方法
- 过滤器需要在web.xml中配置, 配置方法和web.xml配置Servlet是一样的
- 通过配置后, 会使用doFilter()方法拦截请求和响应, 并且可以通过 servletChain.doFilter(request, resopnse)方法放行
Filter 映射
- 通过使用通配符配置 web.xml中的url-pattern可以拦截任何请求; 如
/* - doFilter中方法参数是ServlertRequest, 而在Servlet中是HTTPServletRequest
- dispatcher请求方式:
- REQUEST: 拦截HTTP请求 get post
- FORWAED: 只拦截通过请求转发方式的请求
- INCLUDE: 只拦截通过 request.getRequestDispatcher("").include(),通过 <jsp: include page="" />此种 方式发出的请求
- ERROR: 只拦截
发出的请求
FIlter 过滤器链条
- 可以有多个 Filter 类
- 在web.xml中 哪个Filtre-mapping在前, 那么那个拦截就在前; 即Filter拦截的顺序是由
在web.xml中的顺序决定的 - 拦截顺序: 客户端发出请求 --> Filter1拦截请求 --(放行)--> Filter2拦截请求 --(放行)--> 服务器响应 --> Filter2拦截响应 --(放行)--> Filter1拦截响应 --(放行)--> 客户端页面
监听器
- 每个监听器各自提供了两个方法: 监听开始, 监听结束
- ServletContext在Servlet容器创建时创建, 在容器关闭时销毁
监听对象的创建和销毁
- request: 实现 ServletRequestListener 接口
- response: 实现 HttpSessionListener 接口
- applocation: 实现 ServletContextListener 接口
监听对象中属性的变更
- request: 实现 ServletRequestAttributeListener 接口
- response: 实现 HttpSessionAttributeListener 接口
- applocation: 实现 ServletContextAttributeListener 接口
监听器开发步骤
- 编写监听器, 实现接口
- 配置 web.xml
Session 的钝化和活化
- 钝化: 内存 --> 硬盘
- 活化: 硬盘 --> 内存
Session对象的四种状态
监听绑定和解绑: HttpSessionBindingListener (不需要配置web.xml)
- session.setAttribute("a", Xxx) ; 将对象a绑定到session中
- session.removeAttribute("a") ; 将对象a从session中解绑
监听session的钝化和活化: HttpSessionActivationListener
-
钝化活化的本质就是序列化和反序列化
-
钝化
- 编写类实现HttpSessionActivationListener和Serializable接口, 创建该类的实例, 然后将对象添加到Session中,
- 配置Tomcat 安装目录下的/conf/context.xml
- 运行项目, 当超过设定的时间之后, 添加到session中的属性会随着Session钝化到指定的目录中
-
活化
- 活化不需要什么实际的操作, 直接通过 EL 取值就可以
- session中获取某一个对象时, 如果该对象在内存中不存在, 则直接尝试从之前该对象钝化的文件中去获取
-
总结
- 钝化活化实际执行是通过 context.xml中进行配置而进行的
- HttpSessionActivationListener 只负责在session钝化和活化时予以监听
- 需要实现Serializable接口
如何钝化, 活化
- 配置 Tomcat 安装目录下的/conf/context.xml
<!-- 通过配置实现钝化和活化
className="org.apache.catalina.session.PersistentManager" : 固定写法
MaxIdleSwap: 最大空闲时间, 如果超过该时间, 将会被钝化
FileStore: 通过该类是实现具体的钝化操作
directory: (相对路径)绝对路径(相对路径是相当于Tomcat中work里面此次项目的路径); 但是最好是自己配置的专门存放Sessiond的路径
-->
<Manager className="org.apache.catalina.session.PersistentManager" MaxIdleSwap="1" >
<Store className="org.apache.catalina.session.FileStore" directory="jeff" />
</Manager>
Ajax: 异步js和xml
- 异步刷新技术: 如果网页中某一个地方需要修改, 异步刷新可以使, 只刷新需要修改的地方, 而页面的其他地方保持不变,
实现:
js : 依赖 XMLHttpRequest对象
- XMLHttpRequest对象的方法:
- open(方法名(提交方式get|post), 服务器的地址, true) : 与服务端建立连接(为true: 异步刷新, false:全局刷新)
- send()
- get : send(null)
- post : send(参数值)
- setRequestHeader(header, value)
- get: 不需要设置此方法
- post: 需要设置
- 如果请求元素中包含了文件上传: setRequestHeader("Content-Type", "multipart/form-data")
- 如果没有文件上传: setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
- XMLHttpRequest对象的属性
- readystate: 请求状态(只有状态为4标识完成)
- 0 : 表示 XMLHttpRequest 对象没有初始化
- 1 : 表示 XMLHttpRequest 对象开始发送请求, 已经执行了 open() 方法并完成了相关资源的准备
- 2 : 表示 XMLHttpRequest 对象已将请求发送完毕, 已经执行了send()方法来发送请求, 但是还没有收到响应
- 3 : 表示 XMLHttpRequest 对象开始读取响应信息, 已经接收到HTTP响应的头部信息, 但是还没有将响应体接收完毕
- 4 : 表示 XMLHttpRequest 对象将响应信息全部读取完毕
- status: 响应状态(只有200代表正常)
- 200 : 服务器正常响应
- 400 : 无法找到请求的资源
- 403 : 没有访问权限
- 404 : 访问的资源不存在
- 500 : 服务器内部错误(服务器内部代码错误)
- onreadystatechange: 回调函数
- responseText / responseXML : 响应格式为Text或者XML
- readystate: 请求状态(只有状态为4标识完成)
jquery: 推荐
通用方式既可以是post, 也可以是get
$.ajax({
url:服务器地址,
get|post: 请求方式,
data: 请求数据,
success:function(result, testStatus){
}
error:function(xhr,errorMessage, e){
}
});
get方式
$.get(
服务器地址,
请求数据,
function (result) {
// body...
}
"text"| "xml"| "json");// 直接写括号里的
post方式
$.post(
服务器地址,
请求数据,
function (result) {
// body...
}
"text"| "xml"| "json");// 直接写括号里的
load 方式(load: 将服务端的返回值, 直接加载到$(选择器)所选择的元素中)
$(选择器).load(
服务器地址,
请求数据
);
JSON方式
$.getJSON(
服务器地址,
请求数据,
JSON格式的请求数据,
function(result){
});
js 代码示例
- post请求方式
<script type="text/javascript">
// index.jsp 是通过post方式将值传递给服务端
function register() {
var mobile = document.getElementById("mobile").value;
// 通过Ajax异步的方式提交服务端
// 创建 XMLHttpRequest 对象
xmlHttpRequest = new XMLHttpRequest();
// 设置xmlHttpRequest的回调函数
xmlHttpRequest.onreadystatechange = callBack;
// 建立链接
xmlHttpRequest.open("post", "mobileServlet", true);
// 设置post方式的头消息
xmlHttpRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
// 注意:
// 注意send方法: send("mobile= " + mobile);和 send("mobile=" + mobile);是有大区别, 这个方法中不能有空格
xmlHttpRequest.send("mobile=" + mobile); // 发送的方式为: k=v
}
// 定义回调函数(接收服务端的返回值)
function callBack(){
if(xmlHttpRequest.readyState == 4 && xmlHttpRequest.status == 200){
// 接收服务端返回的数据
var data = xmlHttpRequest.responseText; // 服务端返回值为String格式
if(data == "true"){
alert("此号码已存在, 请更换!")
} else {
alert("注册成功!")
}
}
}
</script>
Jquery 方式
<script type="text/javascript">
// index.jsp 是通过post方式将值传递给服务端
function register()
{
var $mobile = $("#mobile").val();
// ajax 方式
/*
$.ajax({
url:"mobileServlet",
请求方式:"post",
data:"mobile="+$mobile,
success:function(result, testStatus)
{
if(result == "true"){
alert("号码已存在, 注册失败!");
}else{
alert("注册成功!");
}
},
error:function(xhr, errorMessage, e) {
alert("系统异常!");
}
});
*/
// get 方式
/*
$.get(
"mobileServlet",
"mobile="+$mobile,
function(result){
if(result == "true"){
alert("号码已存在, 注册失败!");
}else{
alert("注册成功!");
}
},
"text"
);
*/
// post 方式
$.post(
"mobileServlet",
"mobile="+$mobile,
function (result) {
if(result == "true"){
alert("号码已存在, 注册失败!");
}else{
alert("注册成功!");
}
},
"text"
);
}
</script>
idea中Tomcat乱码:
- file - settings - 搜索 File Encodings, 全部改为UTF-8
- 打开IEDA安装目录, 在 idea64.exe.vmoptions 和 idea.exe.vmoptions 最后追加 -Dfile.encoding=UTF-8
- 配置tomcat页面中, VMoptions设置: -Dfile.encoding=UTF-8
idea热部署问题
- 还是在run下面的部署页面操作
- On 'Update' action: 选择 Update classes and resources
- on frame deactovation: 选择 Update classes and resources
- java 代码页生效
- run启动: 仅JSP等静态资源有效
- debug启动: java和jsp等均有效
idea中jar包问题
问题
- Java项目: 直接将jar复制到工程中, 右键 -Add as Library
- web项目:
- Eclipse: Web-Content/lib/*.jar, Eclipse会将 Web-Content/lib/中的所有jar存放再项目的全部生命周期中
- idea: 会将web/lib/*.jar 中的所有jar,只在项目运行阶段生效, 其他阶段不生效
解决方案
- 如果通过gradle/maven, 则不用管这些
- 如果不是, 则需要手工解决
- jar包本身只在运行阶段有效(如: 数据库连接jar):
- ①只需要将jar复制到web/lib/中(可能会延迟)
- ② 在 Structure -> Artifacts -> output 中在创建一个lib, 将 jar放到里面
- jar包再各个阶段都有效(如: commons-dbcp.jar 在开发, 运行均有效)
- ① 将 commons-dbcp.jar 粘贴在 web/lib中
- ② 将jar复制到工程src中, 右键 -Add as Library
- jar包本身只在运行阶段有效(如: 数据库连接jar):
总结:
- Java项目: 直接将jar复制到工程中, 右键 -Add as Library
- web项目:
- jar包本身只在运行阶段有效(如: 数据库连接jar):
- ①只需要将jar复制到web/lib/中(可能会延迟)
- jar包再各个阶段都有效
- ①只需要将jar复制到web/lib/中(可能会延迟)
- ②将jar复制到工程src中, 右键 -Add as Library
- jar包本身只在运行阶段有效(如: 数据库连接jar):
JNDI : Java命名目录接口
- 多项目共享同一个变量
- pageContext(当前页面) < request(一次请求) < session(一次会话) < application(项目运行期间)
- JNDI, 就是将某一类资源(对象), 以配置文件(tomcat/conf/cotext.xml)的形式写入
- 固定前缀: 通过上下文对象的lookup()方法查找变量是, 方法名参数有个固定写法: lookup("java:comp/env/xxxxName")
使用步骤
- 配置文件(tomcat/conf/cotext.xml)中添加:
- JSP中通过获取上下文对象, 调用上下文对象的lookup()方法就可以
连接池
- 普通的数据库连接在打开和关闭连接时比较损耗性能, 而且是和数据库的直接交互, 连接池就是一个维护数据库连接的管理员, 手中经常有数据库的连接, 当需要连接的时候, 直接从管理员手中拿, 用完了之后还给管理员, 而且不需要直接和数据库交互
- 常见的连接池: Tomcat-dbcp, dbcp, c3p0, druid
- 数据源(javax.sql.Datasource)包含了数据池, 数据源可以管理数据池
- 以前需要Class.forName()加载驱动, 通过DriverManager直接从数据库获取连接; 而用连接池的核心就是: 将连接的指向改了, 现在指向的是数据源而不是数据库
- 数据库访问的核心 --> pstmt/stmt -> Connection -> 1. 直连数据库 2. 数据源 (ds.getConnection())
Tomcat-dbcp:
- 1.类似于 jndi, 在Context.xml中配置下面的MySQL Resource
- 2.MySQL配置
<Resource
<!-- name指定Resource的JNID名字 -->
name="jdbc/mysql"
<!-- 指定Resource的管理者, 有两个可选: Container和Applocation;Container:由容器来创建Resource; Application: 由Web应用来创建和管理Resource -->
auth="Container"
<!-- type: 指定Resource的类型 -->
type="javax.sql.DataSource"
<!-- 指定连接池中, 处于活动状态的数据库连接的最大数量, 如果值为0, 标识不受限制 -->
maxActive="100"
<!-- 指定连接池中, 处于空闲状态的数据库连接的最大数量, 如果值为0, 标识不受限制 -->
maxIdle="30"
<!-- 指定连接池中,连接处于空闲状态的最长时间(单位为毫秒), 如果超出此最长时间将会抛出异常; 如果值为-1, 表示允许无限制等待 -->
maxWait="10000"
<!-- 指定数据库访问名 -->
username="root"
<!-- 指定数据库访问密码 -->
password="123456"
<!-- 指定连接数据库的驱动程序的类名 -->
driverClassName="com.mysql.cj.jdbc.Driver"
<!-- 指定连接数据库的RUL -->
url="jdbc:mysql://127.0.0.1:3306/blog?useSSL=false&serverTimezone=UTC"
/>
- 3.在项目里面的web.xml中指定context.xml里面配置的数据源; 就是指定name,type, auth三个属性的值
- 4.在DBUtils.java 中将之前通过DriverManager.getConnection(URL, NAME, PWD)获取连接改为通过 Context ctx = new InitialContext();先获取Context.xml; 再通过 DataSource ds = (DataSource)ctx.lookup("java:comp/env/jdbc/mysql");获取DataSource对象, 再通过DataSource对象获取连接 Connection conn = ds.getConnection();
dbcp 连接池
- 需要引入jar包 commons-dbcp-1.4.jar, commons-pool.jar
- 获取ds的核心类(二选一): BasicDataSource , BasicDataSourceFactory
BasicDataSource 方式(硬编码方式)
- 1.创建 BasicDataSource dbcp = new BasicDataSource();
- 2.通过下面方法设置各种属性即可拿到 dbcp
-
- 通过 dbcp.getConnrction()获取数据库连接即可
常用方法
- void serDriverClassName(String driverClassName) : 设置连接数据库的驱动名
- void setUrl(String url) : 设置连接数据库的URL
- void setUsername(Stirng Username) : 设置连接数据库的用户名
- void setPassword(Stirng Password) : 设置连接数据库的木马
- void setInitialiSize(int initialSize) : 设置初始化时, 连接数据池中的连接数量
- void setMaxActive(int maxActive) : 设置连接池中, 处于活动状态的数据库连接最大数量
- void setMinIdle(int minIdle) : 设置连接池中, 处于空闲状态的数据库连接的最小数量
- Collection getConnection() : 从连接池中获取一个数据库连接
BasicDataSourceFactory 配置方式(dbcpconfig.properties配置文件; 全部是k=v)
- 编写dbcpconfig.properties配置文件(格式k=v中间不要由空格)
- 通过创建 Properties 类的实例, 调用该实例的load()方法将配置文件加载进来; 需要注意的是load()方法的参数是流的形式
- 创建 Properties 实例: Properties props = new Properties();
- 将字符串编程输入流: InputStream input = new DBCPDemo().getClass().getClassLoader().getResourceAsStream("dbcpconfig.properties");
- 调用load()方法: props.load(input);
- 通过 DataSource dbcp = BasicDataSourceFactory.createDataSource(prop); 方法即可创建dbcp
C3P0 连接池
- 核心类 ComboPooledDataSource
- C3P0是将DBCP的两种方式(硬编码和配置文件)合二为一; 通过ComboPooledDataSource的构造参数来区分, 无参数的就是硬编码; 而有参数的就是通过配置文件的方式
- 无参数构造方法步骤:
- 创建 ComboPooledDataSource c3p0 = new ComboPooledDataSource(); 对象
- 通过 c3p0的set方法设置Driver,Url,User,Passwd等
- return c3p0; 即可
- 连接时 通过 c3p0.getConnection()即可
- 有参数构造方法, 配置文件(c3p0-config.xml)
- 在src中编写c3p0-config.xml文件, 注意的点: Url中出现分号(;)时, 应该使用(amp;)这种形式
- 使用时直接 return new ComboPooledDataSource("jefxff"); 即可返回 c3p0
C3P0的核心类ComboPooledDataSource常用方法
- public ComboPooledDataSource() : 无参构造方法(硬编码发方式)
- public ComboPooledDataSource(String configName) : 加载配置文件的构造方法
- void setDriverClass(String driverClass) : 设置数据库连接的驱动
- void setJdbcUrl(String jdbcUrl) : 设置连接数据库的URL
- void setUser(Stirng User) : 设置数据库连接的用户名
- void setPassword(String password) : 设置数据库连接的密码
- void setMaxPoolSize(int maxPoolSize) : 设置连接池的最大连接数目
- void setMinPoolSize(int minPoolSize) : 设置连接池的最小连接数目
- void setInitiaPoolSize(int initiaPoolSize) : 设置初始化时, 连接池中的连接数量
- Connection getConnection() : 从连接池中获取一个数据库连接, 该方法由ComboPooledDataSource 的父类 AbstractPoolBackedDataSource提供
连接池总结
- 硬编码: 获取某个连接池数据源的对象 ds = new XxxDataSource(); ds.setXxx(); return ds;
- 配置文件: 编写配置文件, ds = new XxxDataSource(); 加载配置文件; return ds;
Apache DButils
- 下载 commons-dbutils-1.7-bin.zip 文件, 解压, 主要使用 commons-dbutils-1.7.jar 文件, 包括几个重点类: DbUtils, QueryRunner, ResultSetHandler
- DbUtils: 打开关闭连接, 提交事务
- QueryRunner : 增删改查方法的基础, 必须创建该类的实例, 通过该类的实例来调用query或者update方法来执行增删改查; 其中该实例参数可以是一个DataSource实例, 又DS实例后是自动提交事务
- updte()
- query()
- ResultSetHandler接口 : 有很多的实现类, 一个实现类对应于一种不同 的查询结果类型
通过 ResultSetHandler接口的实现类来实现查询
- ArrayHandler实现类: 返回结果集中的第一行数据, 并用Object[]接收
- ArrayListhandler实现类: 返回结果集的多行数据, 接收类型是 List<Obkect[]>
- BeanHandler实现类: 返回结果集的第一行数据, 并将结果集放在Bean里,即对象Student里面. (反射会通过无参构造来创建对象)
- BeanListHandler实现类: 返回结果集的多行数据,接收类型为 List
- BeanMapHandler实现列: 返回结果集的多行数据, 接收类型为 Map<>(如果是Oracle数据库, Java中对应的Oracle默认的数值类型 BigDecimal(数字通过 new BigDecimal(int) 方法转换为BigDecimal))
- MapHandler实现类: 返回一行以Map形式包装的数据
- MapListHandler实现类: 返回多行以List<\map>形式包装的数据
- KeyedHandler: 返回多行数据, 并且给每个添加数据添加字段
- ColumnListHandler : 把结果集的 某一列保存在List中
- ScalarHandler: 查询单值结果型
增删改
- 自动提交事务; QueryRunner runner = new QueryRunner(DataSourceUtil.getDataSourceWithC3P0ByXml());即创建QueryRunner时指定数据源; 直接通过runner调用update方法, 调用时具体的实现和之前学习PreparedStatement的增删改操作一样, 即写SQL语句, 通过 Object[] 数组指定参数即可
- runner.update(sql); 和 runner.update(sql, 参数);
- 手动提交事务, QueryRunner runner = new QueryRunner(); 即创建QueryRunner实例时并没有传入ds, 那么通过runner调用update()方法时,第一个参数需要传入一个Connention对象
- runner.update(Connection, sql, 参数);
关于数据库Connection连接的问题
- 问题: 如果创建Connection的方法是static修饰的, 那么conn是共享的, 就会存在线程不安全问题, 即A刚拿到连接, 而B刚好关闭连接, 这样就会线程不安全; 解决这种现象的方法:如果不用static修饰, 每个Connecttion都是私有的, 每次使用都需要new一个, 那么性能太低; 如果加锁, 或者synchronized修饰, 就会存在排队获取连接的现象, 造成性能下降;
- 解决: ThreadLocal : 既可以保证数据安全, 又可以保证性能
- ThreadLocal: 可以为每个线程复制一个副本, 每个线程可以访问自己内部的副本, 所以有个别名: 线程本地变量
- 常用方法:
- get(); 从ThreadLocal中获取一个变量(副本)
- set(); 给ThreadLocal中放入一个变量
- remove(); 删除副本
关于事务安全的问题
-
问题:
- 对于数据库来说, 一个连接对用于一个事务, 一个事务可以包含多个DML(增删改)操作
- 如果给每个DAO操作都创建一个Connection, 则多个DAO操作对应于多个事务, 但是一般来讲, 一个业务(Servuce)中的多个DAO操作应该包含在一个事务中
-
解决: ThreadLocal : 在第一次DAO操作时, 真正的创建一个Connection对象, 然后在其他几次DAO操作时, 借助于ThreadLocal的自身特性, 自动将改Connection复制多个(Connection只创建了一个,因此该Connection中的所有操作必然对应于同一个事务; 并且ThreadLocal在使用层面复制了多个, 因此可以完成多个DAO操作)
-
事务流程: 开启事务(将自动提交关闭, 变成手动提交) -> 进行各种DML操作 -> 正常: 将刚才的所有DML全部提交 (全部成功)
-> 失败(异常), 将刚才的所有DML操作回滚 (全部失败)
元数据 (描述数据的数据)
元数据分类 (主要记怎么来的)
- 数据库元数据 DataBaseMeteData dmd = conn.conn.getMetaData();
- 参数元数据 ParamentMeteData pmd = pstmt.getParamentMeteData();
- mysql使用参数元数据需要在URL后面追加: ?generateSimpleParamentMetedate=true
- 结果集元数据 ResultSetMeteData = rs.getResultSetMeteData();
自定义标签
- 传统方式(JSP1.0) : 实现Tag接口
- 简单方式(JSP2.0) : 实现SimpleTag接口
传统方式自定义标签步骤
- 编写标签处理类
- 传统方式(JSP1.0) : 实现Tag接口: 实现 javax.servlet.jsp.tagext.Tag接口; 主要方法: doStartTag()
- 简单方式(JSP2.0) : 实现SimpleTag接口 实现 javax.servlet.jsp.tagex.SimpleTag 接口; 主要方法: doTag()
- 如果JSP在编译阶段发现了自定义标签xx:yyy, 他就交给doStartTag()或者doTag()方法
- 编写标签描述符(tld文件)
- 找一个别人的标签语言(el, jstl)的tld文件抄一下
- 导入并使用
-
位置: myTag.tld只能放在WEB-INF目录或者其子目录下(除了 lib, classes目录之外)
-
使用: 引入具体要使用的tld文件: <%@ taglib uri="..." prefix="c" %>; uri:每个tld文件的唯一描述符; prefix:使用tld文件的前缀
-
具体的使用:
- 空标签:没有标签体<c:foreach></c:foreach>
- 带标签体: 带标签体<c:foreach>xxx</c:foreach>
- 代属性: <c:foreach collection="$">xxx</c:foreach>
-
Tag接口中的所有方法的执行逻辑
- 当JSP容器(Tomcat, jetty)在将jsp文件翻译成.java文件并编译执行时, 如果遇到JSP中有标签, 就会依次执行 setPageContext(), setParent(), doStartTag(), doEndTag(), realease()方法用于解析标签中的执行逻辑
-
IterationTag接口(Tag接口的子接口)
- doAfterBody(): 方法有两个返回值:
- 返回 (EVAL_BODY_AGAIN = 2): 重复执行
- 返回 (SKIP_BODY = 0) : 结束执行
- doAfterBody(): 方法有两个返回值:
-
BodyTag: 在标签体被显示之前, 进行一些其他"额外"的操作
- 属性 int EVAL_BODY_BUFFER = 2; 是 doStartTag()方法的第三个返回值, 代表一个缓冲区(BodyContent); BodyContent是一个抽象类, 具体的实现类是 BodyContentImpl(Tomcat包下的jasper.jar, 需要导入此包)
- BodyContent的用途:
- 如果返回值是 EVAL_BODY_BUFFER, 则服务区会自动将标签体需要显示的内容放入缓冲区(BodyContent), 因此, 如果要更高改显示结果, 只需要从缓冲区获取原来的数据进行修改即可
- 通过缓冲区获取数据: getBodyContent().getString();自己通过.toUpperCase()方法转大写, 通过bodyContent.getEnclosingWriter().write(content);输出即可
-
实例
- 编写自定义标签, 将hello循环输出指定次数
- 编写自定义标签, 将hello转换大写输出
简单方式自定义标签核心
- 最大的简化: 将传统的的 doStartTag(), doEndTag(), doAfterBody() 等方法简化为一个通用的 doTag()方法
- doTag(): 传统方式可以对标签的最终结果显示进行修改, 核心是 缓冲区(BodyContent); 而简单方式没有缓冲区, 是通过 javax.servlet.jsp.tagext.JspFragment类: 代表一块JSP元素(该块不包含scriptlet, 因此简单方式的tld文件中
不能选JSP, 而是选 scriptless) - JspFragment中有一个invoke(writer var1)方法, 参数是流, 如果要修改显示内容, 只需要修改次流即可
- invoke(Writer var1): doTag()每调用一次invoke()方法, 标签体就执行一次
- 简单标签的核心是 SimpleTag接口, 其实现类是 SimpleTagSupport, 而SimpleTag接口中的核心是 JspFragment; JspFragment是通过SimpleTagSupport实现类获得的
部署实施时使用的集群
- Apache Nginx
- Tomcat: 理论上单节点Tomcat能够稳定的处理请求并发量200-300
- 使用集群: 负载均衡, 失败迁移
- 服务端集群:
- 水平集群(失败迁移)将服务器安装在各个不同的计算机上;
- 垂直集群(负载均衡): 一台计算机上安装多个服务
1. 搭建Apache集群:
- apache: 特点是处理静态资源)(HTML, img, js)
- tomcat: 特点是处理动态资源
- apache + tomcat: 实现动静分离
1.1 Apache 请求的分流操作
1.2 配置 Apache
-
- 打开 conf/httpd.conf 文件; 修改: Define SRVROOT "D:\dev\cluster\Apache24"
-
- 将Apache配置成Windows服务
- 以管理员身份打开CMD, 执行命令
- "D:\dev\cluster\Apache24\bin\httpd.exe" -k install -n apache24
- 可能报错丢失 VCRUNTIME140.DLL, 需要下载安装 vc_redist.x64.exe 下载地址
- 删除Apache服务: sc delete apache24
-
- 注册成功后, 查看localhost是否注册成功
1.3 配置tomcat
- 将Tomcat复制两份, 分别命名tomcat-a, tomcat-b 均放在D:\dev\cluster
- 规划端口(/server.xml):
- Tomcat-a:server端口: 1005; http端口:1080; ajp端口: 1009
- TOmcat-b:server端口: 2005; http端口:2080; ajp端口: 2009
- 配置Engine(/server.xml):
- Tomcat-a:
- Tomcat-b:
- Tomcat-a:
- 打开集群开关(/server.xml)
- 将以下注释打开:
- 将以下注释打开:
1.4 将配置好的Apache和Tomcat结合起来
-需要下载mod_jk.so, 下载后解压将mod_jk.so文件复制到Apache的modules目录下
- 在Apache/conf目录下新建 workers.properties文件
- workers.properties文件内容
worker.list=controller,tomcat-a,tomcat-b
# tomcat-a
# 配置端口号
worker.tomcat-a.port=1009
# 配置ip地址, 部署时就是IP地址, 本机演示就是localhost
worker.tomcat-a.host=localhost
# 配置
worker.tomcat-a.type=ajp13
# 配置负载均衡的比重
worker.tomcat-a.lbfactor=1
# tomcat-b
# 配置端口号
worker.tomcat-b.port=2009
# 配置ip地址, 部署时就是IP地址, 本机演示就是localhost
worker.tomcat-b.host=localhost
# 配置
worker.tomcat-b.type=ajp13
# 配置负载均衡的比重
worker.tomcat-b.lbfactor=2
# controller
# 配置分流器的类型(负载均衡)
worker.controller.type=lb
# 配置两个需要分流的列表
worker.controller.balanced_workers=tomcat-a,tomcat-b
# true: session使用sticky策略(固定在一个服务器); false: session使用session广播的策略
worker.controller.sticky_session=false
1.4.1 配置worker.properties是session策略:
- sticky策略: 固定将每一个用户的请求分配个特定的服务器, 后期的请求不会分给其他的服务器; 弊端是无法失败迁移(理解就是x第一次通过Apache访问了tomcat-a, tamcat-a保存了x的session, 后期x再来时, 指定是配分配到tomcat-a, 而不会分给别的服务器)
- session广播: 自动同步session; 弊端是容器造成广播风暴(理解就是x第一次通过Apache访问了tomcat-a, tamcat-a保存了x的session,tomcat-a会自动将x的session信息广播给其他的tamcat服务器, 结果就是每个服务器都保留的x的session信息, 这样x下次在来时, controller会按照指定的策略分配x到对应的服务器, 而不是分配给tomcat-a)
- 集中管理方式(重点): 理解就是将各个服务器的session集中到一个数据库或者容器进行管理, 下次来访问时, 服务器去这个专门管理session的数据库或容器去验证session是否存在, 以此来做判断
1.5 在Apache/conf下面配置mod_jk.conf(用于加载mod_jk.so和workers.properties)
- 内容
# 加载mod_jk.so
LoadModule jk_module modules/mod_jk.so
# 加载workers.properties
JkWorkersFile conf/workers.properties
# /* 表示分流所有的(jsp, servlet, html) /*.jsp 表示只分流jsp, /*.html 表示拦截.html文件, servlet可以自加后缀来拦截
JkMount /* controller
1.5 将mod_jk.conf加载到httpd.conf
- 这就会使apache启动时加载mod_jk.conf, mod_jk.conf再连接Apache和tomcat, z最终完成配置
- 再httpd.conf最后面加一句代码: include conf/mod_jk.conf
注意:
-
- 需要再项目的web.xml中加一句
- 需要再项目的web.xml中加一句
-
- CATALINA_HOME 会使启动tomcat时, 自动启动CATALINA_HOME指定的tomcat, 而集群中需要开启多个不同的tomcat, 因此再单机环境下, 需要删除 CATALINA_HOME 环境变量
-
- 依次启动Apache, tomcat-a, tomcat-b
-
- Apache中: worker.list-controller,tomcata,tomcatb; Tomcat配置中: jvmRoute="'tomcat-a",jvmRoute="'tomcat-a"; 以及tomcat服务器的目录名tomcata,tomcatb他们可以不一样, 之间没有什么关系