文章目录
接着上一篇文章的内容,首先了解了一下 JDBC 和 JSP 的 JSTL 如何配合 MySQL 进行数据库相关的基本操作,然后用之前写的一个辣鸡留言板 xjb 改了改,成功实现了这个简易留言板
JDBC 与 MySQL
数据库操作
JDBC(Java Database Connectivity) 是 Java 连接和访问数据库用的一个 API,如果数据库用的是 MySQL 的话需要首先从 MySQL 官网 下载一个驱动包,实际的下载地址是 mysql-connector-java-5.1.45.tar.gz,解压之后可以看到里面有一个 mysql-connector-java-5.1.45-bin.jar
文件,把它的文件路径(即精确到 .jar
的路径)添加到 CLASSPATH 环境变量里即可,我的做法是先放进 Tomcat 安装目录的 /lib
文件夹下再添加进环境变量,这样就不用另外找文件夹放了
在 MySQL 里创建一个数据库,然后可以写一个简单的 Java 代码测试,test.java
:
import java.sql.*;
public class test {
public static void main(String[] args) {
try {
Class.forName("com.mysql.jdbc.Driver");
Connection con = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/" +
"DATABASE_NAME" +
"?useUnicode=true&characterEncoding=UTF-8",
"DATABASE_USERNAME",
"DATABASE_PASSWORD"
);
Statement s = con.createStatement();
s.executeUpdate(
"CREATE TABLE test (" +
"ID INTEGER NOT NULL AUTO_INCREMENT," +
"PRIMARY KEY(ID)" +
") ENGINE = InnoDB DEFAULT CHARSET = utf8;"
);
s.close(); con.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
把里面的三个变量(数据库名、用户名、密码)改成对应的值,然后编译运行一下,可以看到数据库下有一个新的名为 test
的表
下面这一行代码:
Class.forName("com.mysql.jdbc.Driver");
就是加载刚刚的那个 MySQL 驱动包的操作,如果没有成功添加进 CLASSPATH 里的话编译会报错,然后 jdbc:mysql://localhost:3306/
是用 JDBC 连接 MySQL 的地址,后面的编码设置主要是防止中文(或者其他 Unicode 符号)乱码,数据库连接时的编码设置是很重要的,以及 UTF-8 大法好(
XML 配置文件读取
为了方便后续的一些操作或者移植的方便,习惯性用程序通过读取单独的配置文件来设置数据库相关的变量,然后 Java 处理 JSON 似乎需要单独下载的包,所以就直接用 XML 了
新建一个 config.xml
,内容如下:
<?xml version = "1.0"?>
<config>
<database>DATABASE_NAME</database>
<username>DATABASE_USERNAME</username>
<password>DATABASE_PASSWORD</password>
</config>
然后用 Java 进行读取的测试,reader.java
:
import java.io.File;
import javax.xml.parsers.*;
import org.w3c.dom.*;
public class reader {
public static void main(String[] args) {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document config = db.parse(new File("config.xml"));
System.out.println(
config.getElementsByTagName("database").item(0).getTextContent()
);
System.out.println(
config.getElementsByTagName("username").item(0).getTextContent()
);
System.out.println(
config.getElementsByTagName("password").item(0).getTextContent()
);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行之后控制台显示的如果正是配置文件里的那些变量值的话就没问题了,组合一下两个文件然后加点 INSERT
语句向表内添加一些数据即可,具体代码可见 init.java
,顺便也在 initDB.sql
里放了初始化数据表的 SQL 源码
JSTL 与 MySQL
数据库内容获取与渲染
JSTL(JSP Standard Tag Library) 是 JSP 的标准标签库,通过它就可以以标签的形式用 JSP 实现一些基本的操作逻辑,而不需要直接在页面文件内编写 Java 代码
使用 JSTL 同样需要下载相关的库,地址是 jakarta-taglibs-standard-1.1.2.zip,当然需要其他版本的可以自己在 文件列表 里找,下载下来以后解压,把它的 /lib
文件夹下的两个文件 jstl.jar
和 standard.jar
放进项目文件夹的 /WEB-INF/lib/
目录里,接下来就可以使用 JSTL 了
因为这里还需要用到 MySQL 相关的操作,所以得把上面提到的 mysql-connector-java-5.1.45-bin.jar
也放进 /WEB-INF/lib/
目录里,没有很深入了解这方面,估计是因为 JSTL 使用的外部包都从这个文件夹里找,而不会管加入 CLASSPATH 的那些?
然后在 JSP 文件头部导入这些 JSTL,以及设置好页面类型和编码方式,注意如果不写这个而只在 meta
标签里指定 charset
是没用的:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
接下来把之前一个项目里的辣鸡留言板代码找出来,稍微整理一下去掉不需要的,基本的页面结构就完成了,代码比较长,可以直接到 index(initial).jsp
文件看,然后使用 JSTL 连接 MySQL 数据库并获取需要的数据:
<sql:setDataSource var="snapshot" driver="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/DATABASE_NAME?useUnicode=true&characterEncoding=utf-8" user="DATABASE_USERNAME" password="DATABASE_PASSWORD"/>
<sql:query dataSource="${snapshot}" var="result">
SELECT * FROM forum ORDER BY ID DESC
</sql:query>
记得改数据库相关的三个配置变量,可以看到里面的 driver
和 url
的值和使用 Java 的时候是相同的,然后用 var
属性设置变量供后面的 JSTL 访问,比如 snapshot
作为数据库连接用于 <sql:query>
里,然后 SELECT 出的数据又存到 result
变量里,接下来用 <c:forEach>
标签去处理获取到的这些数据:
<c:forEach items="${result.rows}" var="row">
<c:out value="${row.id}"/>
<c:out value="${row.postTime}"/>
<c:out value="${row.nickname}"/>
<c:out value="${row.message}"/>
</c:forEach>
其实也可以直接这样写:
<c:forEach items="${result.rows}" var="row">
${row.id}
${row.postTime}
${row.nickname}
${row.message}
</c:forEach>
这样就获得并显示出了数据库里的这些留言数据,具体怎么排版和显示就看自己的想法了,一般还是用 HTML 布局的吧……然后 <c:out value="${row.id}"/>
或者 ${row.id}
这种就直接视做纯文本的内容来用即可
<fmt>
可以用来格式化输出数据,这里用来分割日期和时间:
<fmt:formatDate pattern="yyyy-MM-dd" value="${row.postTime}"/>
<fmt:formatDate pattern="HH:mm:ss" value="${row.postTime}"/>
从 XML 配置文件读取并设置变量值
一些配置相关的变量值当然还是最好从单独的配置文件读取啦,同样新建一个 config.xml
,注意要放到 /WEB-INF/
文件夹下,不然就能直接在浏览器里访问和获取了 = =
用 JSTL 解析 XML 还要用到额外的 jar 包,分别是 Xerces-J-bin.2.11.0.zip 里的 xml-apis.jar
和 xercesImpl.jar
和 xalan-j27_0-bin.zip 里的 xalan.jar
,把这三个文件解压出来同样放进 /WEB-INF/lib
目录里,然后在 JSP 文件开头加上引用它的语句:
<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x"%>
接下来可以用相关的操作进行解析和设置变量值了:
<c:import var="XMLfile" url="/WEB-INF/config.xml"/>
<x:parse xml="${XMLfile}" var="configXML"/>
<c:set var="url">
jdbc:mysql://localhost:3306/<x:out select="$configXML/config/database"/>?useUnicode=true&characterEncoding=utf-8
</c:set>
<c:set var="username">
<x:out select="$configXML/config/username"/>
</c:set>
<c:set var="password">
<x:out select="$configXML/config/password"/>
</c:set>
这部分耗了我蛮长时间的地方就是,<x:out>
里 select
获取的到的值不知道怎么用来设置成变量,因为后面还需要用到它,想了好多种办法,一开始用 <x:set>
但是这个设置的是一个 XPath 表达式的值,而且后面不能直接用 ${xxx}
的方式获取到真正的变量值,然后又试了好多种奇葩的实现,最接近的一次大概就是 <c:set var="password" value="<x:out select=\"$configXML/config/password\"/>">
了,然而这个看起来就那么奇怪,试了一下果然并没有用
最后发现 <c:set>
竟然是可以把标签对里的内容设置为 var 的……这个就很劲了,用 <x:out>
把值放在这个标签对里就可以成功设置好一个变量,然后 url 那个值因为需要加些参数什么的,不方便在用到的地方拼接,也就单独拿出来了
你们给我搞的这个 <c:set>
啊,excited!
后面需要改的也就是使用到这些变量的 <sql:setDataSource>
了:
<sql:setDataSource var="snapshot" driver="com.mysql.jdbc.Driver" url="${url}" user="${username}" password="${password}"/>
其他的具体代码在 index.jsp
里,访问前记得参照第一篇文章内容把 Tomcat 的 Web 根目录从 /HelloWorld
改到 /General
并重启服务,首页效果大致是这样的:
手机上的模拟预览图:
向数据库写入新数据
用 JSTL 和 Java 分别实现了一下添加留言的功能,前面两个部分如果做好了的话这一块还是很简单的,然后发表留言的 HTML 元素还是使用之前那个项目里的,效果图如下:
把提交表单的标签稍作修改,然后在对应的 insert.jsp
里写好处理代码即可:
<form method="POST" action="insert.jsp">
Note: General
文件夹中,JSTL 的实现在 insert.jsp
里,Java 的实现在 insert(java).jsp
里
JSTL 的实现
其实就是一样设置好 dataSource,然后在 <sql:update>
里写好 INSERT 语句就行了:
<sql:update dataSource="${snapshot}">
INSERT INTO forum (nickname,message) VALUES('${param.nickname}', '${param.message}')
</sql:update>
注意两个单引号,因为变量本身是不带引号的,JSP处理的时候会直接进行内容的替换
编码的问题还没有找到能只用 JSTL 实现的方案,现在的方式是在文件头部加上下面这行处理语句:
<% request.setCharacterEncoding("UTF-8"); %>
Java 的实现
首先在 JSP 文件头部加上基本的设置和导入语句:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="java.sql.*,java.util.*"%>
<%@ page import="java.io.File"%>
<%@ page import="javax.xml.parsers.*,org.w3c.dom.*"%>
和上面 JDBC 与 MySQL 部分的内容一样,写好 INSERT 处理的代码即可,但是 Java 代码需要包括在一对 <%
、%>
之间,操作部分的代码如下:
request.setCharacterEncoding("UTF-8");
Statement st = con.createStatement();
st.executeUpdate(
"INSERT INTO forum (nickname, message)" +
"VALUES(" +
"'" + request.getParameter("nickname") + "'," +
"'" + request.getParameter("message") + "')"
);
编码问题的解决使用的是相同的处理方式,不过这里写的本来就是 Java 所以看起来更自然一点
还有一个问题是这种情况下配置文件名不能直接写相对路径,因为 JSP 处理这些 Java 代码的时候是编译到 Tomcat 的相关目录下的,所以需要用下面这行代码:
request.getServletContext().getRealPath("/WEB-INF/config.xml")
来获取真实的地址,它返回的也就是配置文件在计算机中的实际位置,如果 getRealPath()
的实参是 ""
的话,就会直接返回当前项目文件夹在计算机中的路径
防 SQL 注入
带用户变量的 SQL 语句,用拼接字符串的方式实现肯定是很危险的,常见的防注入还是过滤特殊字符和用 PREPARE 语句等,这里简单提一下上面的两种实现方式分别如何用 PREPARE 的方式改进
Java 的实现就是直接使用 PreparedStatement
类进行替换,然后用 setString()
绑定对应的参数:
PreparedStatement s = con.prepareStatement(
"INSERT INTO " +
"forum (nickname, message)" +
"VALUES (?, ?)"
);
s.setString(1, request.getParameter("nickname"));
s.setString(2, request.getParameter("message"));
s.executeUpdate();
其实一开始我是以为 JSTL 不提供 PREPARE 的方式的,但是转念想想这毕竟是非常重要的安全问题,果然一看文档就发现了有 <sql:param>
标签,直接在 <sql:update>
里把变量的位置换成 ?
然后在接下来按顺序设置对应的值即可:
<sql:update dataSource="${snapshot}">
INSERT INTO forum (nickname,message) VALUES(?, ?)
<sql:param value="${param.nickname}"/>
<sql:param value="${param.message}"/>
</sql:update>
异常处理
两个文件都有做异常处理,以及操作成功后倒计时 5s 自动返回首页,也简单提一下吧
提示是用 Bootstrap 的 Modal 实现的,也就是一个对话框,所以需要加载框架的 CSS 和 JS 之类的,但是这些文件本来也就在显示首页的时候加载好了,所以没什么影响,主要是不想自己写 CSS
操作成功的效果图如下:
用了一点 JavaScript 来进行 5s 的倒计时,时间到了就自动返回首页,或者点击确定按钮也会直接返回
如果出现了错误,Java 实现那里当然用 catch 然后处理就行了,JSTL 的实现就需要用到 <c:catch>
标签对,将具体处理部分的代码放进 <c:catch var="exception">
、</c:catch>
里,如果出现了异常就会设置 exception
变量,然后用以下类似 if..else..
的语句进行处理:
<c:choose>
<c:when test="${exception != null}">
${exception}
</c:when>
<c:otherwise>
<h3>Success!</h3>
</c:otherwise>
</c:choose>
这里的一个小问题是 JSP 对 ${exception}
是直接进行替换的,我是用的 JS 进行处理,不能直接进行拼接字符串,需要考虑转义的问题
为了能不用 Java 完成对特殊字符的过滤,用了一对自定义的 <exception>
标签,将异常内容放进它的 msg
属性里然后用 jQuery 获取:
<exception msg="${exception}"></exception>
errMsg = $("exception").attr("msg");
这样得到的 errMsg 变量里存的就是操作过程中抛出的异常信息,可以用于后续的操作
因为 Java 的实现版本最后的显示部分都不太清真地用 out.println()
向页面输出 HTML 代码,尤其是 <script>
标签对内的内容,所以也有类似的转义等问题,不过只要进行一下全文替换就行了:
String errMsg = e.toString().replaceAll("'", "\\\\'");
四个斜杠应该是似曾相识的吧,一次由 Java 转义生成两个斜杠,然后由 JS 转义生成一个斜杠也就是实际上的转义符
出错信息显示的效果如下:
当然把报错信息全部显示出来也是一个安全隐患,可以让攻击者知道出错信息然后相应地调整攻击策略= =
一般这种出错信息还是自己留着调试用或者做线上环境的记录的吧,不过毕竟这是自己做着玩的东西也就不太多考虑这一块了
整理一下一些操作和它们对应的源文件:
Operation | Related Source Code Files |
---|---|
Initialize database with SQL File | /initDB.sql |
Initialize database with Java |
/jdbc-test/init.java
/jdbc-test/config.xml
|
List Messages: Initial Version (URL: localhost:8080/index(initial).jsp ) |
/index(initial).jsp
/WEB-INF/lib/*
|
List Messages: Version With Configuration File (URL: localhost:8080/ or localhost:8080/index.jsp ) |
/index.jsp
/WEB-INF/config.xml
/WEB-INF/lib/*
|
Insert Message: Java Implementation (Via Submitting Insert Modal Form in index page) |
/insert(java).jsp
/WEB-INF/config.xml
|
Insert Message: JSTL Implementation (Via Submitting Insert Modal Form in index page) |
/insert.jsp
/WEB-INF/config.xml
/WEB-INF/lib/*
|
系列文章地址
这些 Java Web 相关的内容是 Java 课的最后一次实验做的,因为觉得多少有一些总结的意义,就稍微整理了一下并分成三篇博客文章发布:
- 初识 JSP、Servlet 与 Java Bean:Tomcat 的配置和几个基本的 Hello World
- 初识 JDBC 与 JSTL:配合 MySQL 实现一个简易留言板
- 再探 JSP、Servlet 与 Java Bean:用 MVC 模式实现一个简易留言板