目录
增删改查
以“增”举例
Servlet的功能:(MVC中的C,控制层,一般包名servlet或者web)
1)把用户提交到服务器的表单数据,封装(创建)为domain包中的 Customer对象(对应一张数据库表),所以对象是在运行在服务器上的Servlet时创建的(而不是在用户的浏览器的机器上)。(最终传到DAO层中的Customer对象就产生于此时)
2)对用户信息,生成 uuid,因为domain域的实体中有该字段,但没有值。
3)接着把第1)步中得到的Customer对象,作为调用service方法的参数传入。
4)回显到一个页面,提示成功信息。
浏览器:
1)表单数据相关检测。
2)发送增加条目的请求时,URL要带上method对应的方法,从而给servlet用于识别区分,通过这样来调用不同的增删改查Servlet
domain层(实体层,又称POJO),对应于数据库表,它是最底层,也是首先应该写出来的代码。(用excel表的思维去思考更简单)
注意,一个POJO类的对象,是代表对应数据库表中的一行,(每行都有表的结构中的所有列。)
DAO层: 这层的类中,应该都持有一个POJO类(domain包中的对应类)的对象作为属性,对于“增”对应的DAO方法void add(Customer c),是无返回值void的。
“删”对应DAO方法void delete(String cid);
“改”对应DAO方法void edit(Customer c); 改的时候,也需要按c.getCid,填到where cid = ? ,才能改。
"查"对应DAO方法List<Customer> findAll(); 这是对应select * from t_customer语句,查询出表中所有的行(所有的对象)
下面是“增”的DAO代码。
业务层: (一般包名是service)
add增加业务
1)这里没有复杂的多个操作的业务,直接把servlet传过来的Customer对象,再次直接传递给底层的DAO中的add方法。
持久层 DAO层:持久层是为了增删改查数据库表(MVC中的M,模型层,直接使用JavaBean操作数据库表,一般包名是dao,也可以把domain中的实体也算作M,因为他们是对应的)
调用add方法,把Servlet(控制)层传来的Customer对象,按数据库的对应字段,填入相应的字段。
注意,如果是查询操作,那么是按照sql语句中要查询的字段,利用反射去调用JavaBean中相应的getXXX获取值。
add方法,类比下应该也类似,在DAO层中,使用Servlet封装好的、业务层传下来的Customer对象,去调用对应的getXXX方法得到返回值,作为数据值放入SQL代码中。
业务层代码:
持有一个底层的CustomerDao类引用,将控制层传入自己add方法的参数:Customer类引用,直接传给DAO层的add方法。
把数据(这里是Customer对象)看作水流,这里的业务层Service等于用方法的参数去接收了这个数据,再次将其作为参数放入DAO层方法中。
数据就被直接打入到DAO层的方法中。
servlet层代码(对应MVC中的C,控制层):注意,这里我们使用了继承BaseServlet类
附上BaseServlet代码:
package cn.itcast.web.servlet;
import java.io.IOException;
import java.lang.reflect.Method;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public abstract class BaseServlet extends HttpServlet {
public void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
/*
* 1. 获取参数,用来识别用户想请求的方法
* 2. 然后判断是否哪一个方法,是哪一个我们就调用哪一个
*/
String methodName = req.getParameter("method");
if(methodName == null || methodName.trim().isEmpty()) {
throw new RuntimeException("您没有传递method参数!无法确定您想要调用的方法!");
}
/*
* 得到方法名称,是否可通过反射来调用方法?
* 1. 得到方法名,通过方法名再得到Method类的对象!
* * 需要得到Class,然后调用它的方法进行查询!得到Method
* * 我们要查询的是当前类的方法,所以我们需要得到当前类的Class
*/
Class c = this.getClass(); //得到继承自BaseServlet 的 某个Servlet类的class对象。
// 注意上面,在Servlet中使用this的用法,将来哪个Servlet对象来跑这段代码,这个this就代表谁!
Method method = null;
try {
/*下面,通过客户端请求的方法名字符串,得到方法对象 。
原型是下面,从第二个参数开始,是要获取的方法的形参类型。因为仅仅一个方法名,无法唯一的确定下来一个方法,还需要参数。
返回值就不需要了,因为唯一确定一个方法的就是:方法名、形参的(列表),getMethod方法说明:
Method getMethod(String name, Class<?>... parameterTypes)
比如本例中,是要获取一个双参数,第一参数是HttpServletRequest类型,第二参数是HttpServletResponse类型的方法的对象。
实际上就是Servlet层里的add方法,上面有add方法的代码截图。
*/
method = c.getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
} catch (Exception e) {
throw new RuntimeException("您要调用的方法:" + methodName + "(HttpServletRequest,HttpServletResponse),它不存在!");
}
/*
* 下面利用反射,调用起method方法对象
*/
try {
String result = (String)method.invoke(this, req, resp); //this是将来跑起来时的Servlet对象,req是将来的请求,resp是将来的响应
/*
* 获取请求处理方法执行后return的字符串,它表示转发或重定向的路径!可以参照上面Servlet层的代码截图
* 帮它完成转发或重定向!
*/
/*
* 如果用户返回的是字符串为null,或为"",那么我们什么也不做!
*/
if(result == null || result.trim().isEmpty()) {
return;
}
/*
* 查看返回的字符串中是否包含冒号,如果没有,表示转发
* 如果有,使用冒号分割字符串,得到前缀和后缀!
* 其中前缀如果是f,表示请求转发;
* 如果是r表示重定向
* 后缀就是要转发或重定向的路径了!
*/
if(result.contains(":")) {
// 使用冒号分割字符串,得到前缀和后缀
int index = result.indexOf(":");//获取冒号的位置
String s = result.substring(0, index);//截取出前缀,表示操作
String path = result.substring(index+1);//截取出后缀,表示路径
if(s.equalsIgnoreCase("r")) {//如果前缀是r,那么重定向!
resp.sendRedirect(req.getContextPath() + path);
} else if(s.equalsIgnoreCase("f")) {
req.getRequestDispatcher(path).forward(req, resp);
} else {
throw new RuntimeException("你指定的操作:" + s + ",当前版本还不支持!");
}
} else {//没有冒号,默认为转发!
req.getRequestDispatcher(result).forward(req, resp);
}
} catch (Exception e) {
System.out.println("您调用的方法:" + methodName + ", 它内部抛出了异常!");
throw new RuntimeException(e);
}
}
}
CommonUtils类代码,在day14的资料中:
package cn.itcast.commons;
import java.util.Map;
import java.util.UUID;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;
/**
* 小小工具
* @author qdmmy6
*
*/
public class CommonUtils {
/**
* 返回一个不重复的字符串
* @return
*/
public static String uuid() {
return UUID.randomUUID().toString().replace("-", "").toUpperCase(); //UUID默认会带横线,替换掉即可,推荐使用UUID
}
/**
* 把map转换成对象
* @param map
* @param clazz
* @return
*
* 把Map转换成指定类型,该map一般来自于 request.getParakmeterMap()方法
*/
@SuppressWarnings("rawtypes")
public static <T> T toBean(Map map, Class<T> clazz) {
try {
/*
* 1. 通过参数clazz创建实例
* 2. 使用BeanUtils.populate把map的数据封闭到bean中
*/
T bean = clazz.newInstance();
ConvertUtils.register(new DateConverter(), java.util.Date.class);
BeanUtils.populate(bean, map);
return bean;
} catch(Exception e) {
throw new RuntimeException(e);
}
}
}
add.jsp (对应MVC中的V)
这里要注意的用法,就是输入框的隐藏,就是用来传参数method的,CustomerServlet中有对其的处理。
这是POST提交表单的常见用法。
<input type = "hidden" name="method" value="add">
如果是GET的请求,直接在 /CustomerServlet?method=add
msg.jsp(成功后,跳转到该页面)
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>My JSP 'msg.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head>
<body>
<h1 style="color:green;" align="center">恭喜,${msg }</h1>
</body>
</html>
domain域 实体代码:
package cn.itcast.cstm.domain;
/**
* 领域对象 与表单和数据库表在设计上要对应
*
* @author cxf
*
*/
public class Customer {
/*
* 对应数据库表
*/
private String cid; // 主键
private String cname; // 客户名称
private String gender; // 客户性别
private String birthday; // 客户生日
private String cellphone; // 客户手机
private String email; // 客户邮箱
private String description; // 客户的描述
public String getCid() {
return cid;
}
public void setCid(String cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
public String getCellphone() {
return cellphone;
}
public void setCellphone(String cellphone) {
this.cellphone = cellphone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@Override
public String toString() {
return "Customer [cid=" + cid + ", cname=" + cname + ", gender="
+ gender + ", birthday=" + birthday + ", cellphone="
+ cellphone + ", email=" + email + ", description="
+ description + "]";
}
}
查询
接下来是查询功能的几个实现模块:
top.jsp 这是主页,用户的输入,即要查询的人,就在这个页面上输入。
list.jsp 这是负责显示查询结果的页面
我们写代码的时候,从底层DAO开始写,之后写 Service层,最后写Servlet层。
但开始运行的时候,调用是最先Servlet;在Servlet中持有Service的对象; 在Service中持有DAO的对象。
查询的逻辑很简单:
1、Servlet上,一步步地利用各层中持有的底层对象,最终利用DAO层中的findAll()方法查到 List<Customer> ,一步步返回到Servlet
2、保存到request域的变量"cstmList"中
3、转发到list.jsp页面,在该页面上显示出request域的变量"cstmList"
DAO层查询模块,下面图是我拼出来的:DAO层需要持有 QueryRuner类的对象
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.QueryRunner;
import cn.itcast.jdbc.TxQueryRunner; // TxQueryRunner extends QueryRunner 自己设计的类
这个SQL语句中没有问号,所以查询方法query中,没有第三参数。
这里的查询方法,在运行时,是不需要Service层 乃至Servlet层传入参数的。如果要接收用户指定信息的查询,还是需要带参数的DAO层查询方法。
Service层代码: 持有DAO层对象,并调用DAO层的方法。( 还有你;一切拜托你)
Servlet层代码:
1、持有Service层对象,并调用Service层方法(还有你;一切拜托你)
2、把得到的查询结果(一个List<Customer>对象),存到request域变量中,以便让jsp负责拿出显示,因为查询的最终目的是让jsp页面进行展示,这点与增加、修改、删除,Servlet层与jsp的交互处理不太一样。
top.jsp中,对应的查询部分,之前搭建时,写的是 <a href="<c:url value='list.jsp'/>">查询客户</a>
改为: <a href="<c:url value='/CustomerServlet?method=findAll'/>">查询客户</a>
以便用户点击后,转入执行 CustomerServlet 中的指定 findAll() 方法。
list.jsp 显示功能代码: 需要注意的是,list.jsp原本被设置为静态的,静态的显示效果好了之后,再往里添加显示域对象
<c:forEach>遍历显示
<c:forEach>标签用于遍历,items是要遍历的request域中对象,var是每次遍历时的对象中元素的赋值变量
list.jsp 完整代码
完整的前端list.jsp代码如下:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>客户列表</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head>
<body>
<h3 align="center">客户列表</h3>
<table border="1" width="70%" align="center">
<tr>
<th>客户姓名</th>
<th>性别</th>
<th>生日</th>
<th>手机</th>
<th>邮箱</th>
<th>描述</th>
<th>操作</th>
</tr>
<c:forEach items="${requestScope.cstmList}" var="cstm">
<tr>
<td>${cstm.cname }</td>
<td>${cstm.gender }</td>
<td>${cstm.birthday }</td>
<td>${cstm.cellphone }</td>
<td>${cstm.email }</td>
<td>${cstm.description }</td>
<td>
<a href="<c:url value='/CustomerServlet?method=preEdit&cid=${cstm.cid }'/>">编辑</a>
<a href="<c:url value='/msg.jsp'/>">删除</a>
</td>
</tr>
</c:forEach>
</table>
</body>
</html>
改
编辑用户
编辑用户,需要两次请求过程:
1、在查询结果list.jsp 的基础上,点击要编辑的用户。这是一个request请求。对应的Servlet中处理的方法叫preEidt
该preEdit方法查询到Customer对象,然后转发到edit.jsp,这是用户真正编辑的界面。
2、第二个过程才是真正编辑的过程,对应Servlet上的方法edit。
关键在第一个过程中,list.jsp怎么带出请求参数cid,这是通过在查询的那次请求request的结果时,就有的 ${cstm.cid} :
所以编辑的动作,要求先有查询。
CustomerServlet 代码:
调用Service层的load方法报错,因为这里是从上层往下层写的。
从list.jsp发来的请求中,抽取 cid 的请求参数值,将这个CID的字符串,往下经过Service层、层层传入DAO层对应的方法中。
这里Servlet层需要 request.setAttribute的原因,是因为要把预处理的查询得到的对象,通过请求转发给编辑页面edit.jsp
____ _________ ______ ________ ______________
CustomerService 中 load方法,一样因为底层没有写。
该层的load要求传入的String cid ,来自于Servlet层的传入, 再次调用DAO层方法,把 cid 传入下层DAO层。
___ ___________ ___________ _________________
CustomerDao类,持有TxQueryRunner类的属性;查询方法query中的结果集参数,是单行的BeanHandler;?号只有1处,为cid的值;
上面总体来说,Servlet 的preEdit方法加载到一个结果存在request域中后,将请求request转发到edit.jsp页面;
下面edit.jsp页面要承担显示load方法的加载当前结果到页面的任务;还要承担提供一个“保存、提交、编辑用户”按钮,把用户修改的值传入Servlet的edit方法。
下面就是edit.jsp的代码:
下面65、66两行,为了向/CustomerServlet发送参数 method、cid
参数method为了指明调用Servlet中的edit方法; 参数cid指明了要修改(编辑)的数据库中的Customer对象。
————————————————————————————————————————————————
Servlet中 编辑 的第二步: edit方法的编写
接受用户请求中的数据,封装为Customer对象,用了CommonUtils类
那么接下来,再继续往下层的edit方法中,层层向下传递Customer对象:
数据库表叫做 t_customer
——————————————————————————————————
BaseServlet
查询与修改总结
如果分析几个Servlet方法中参数的流向:
1、首先,当用户在top.jsp页面输入好了值,点击”查询“按钮后,Servlet中的查询方法findAll() 被调用 ,从数据库中查找到一个Customer的对象存入request域的一个变量名中,再将这个request请求转发 (forward )到list.jsp,去显示出来。
<本过程的Service层中的findAll()方法是无参的,上层Servlet并不向底层DAO传入参数;因为这里要查全部的人员,以List的返回值得到对象的列表>
2、然后,用户可以点击list.jsp上某一个用户对应的"编辑"按钮,该操作会在requst中携带名为cid的参数,同时会调用Servlet中的preEdit方法(本项目中,Servlet中的方法名,不一定与下层的方法名一致!!),进而层层调用下层的load方法,查询到该cid为该值的Customer对象,放在request域的一个变量名中,再将这个请求转发到edit.jsp页面,去显示出来。
<本过程中,被层层传入下层的是一个字符串cid,因为Service层中的load(String cid)是有参数的,得到的返回值是一个Customer对象类型>
3、再然后,用户填写对该Customer对象准备更新的所有数据,写完后点击 “保存、提交、编辑用户” 按钮,同时会调用Servlet的edit方法,该方法是把最新的Customer对象的值,传入底层的DAO,从而调用QueryRunner类 的update方法完成对数据库的更新,完成编辑功能。
<本过程中,被层层传入下层的是一个Customer对象,因为Service层中的edit(Customer c)是有参数的 ,编辑(写入)方法返回值类型是void>
所以,查询,是从数据库中查; 编辑,是编辑数据库。
Servlet类中方法的返回值都是String,是为了在执行后,可以把request请求转发到另外的页面,这是自定义的设计。
——————————————————————————————————
删除
删除也是建立在查询findAll()的结果集list.jsp之上的。
在list.jsp上面,每一行查询结果记录右边,不仅有一个编辑按钮,还有一个删除按钮。
在删除按钮上做两个隐藏的输入框,各带一个参数。一个用来指明点击链接后调用的Servlet中的方法delete(String cid),另一个用来携带cid号。
注意删除的方法名,在Servlet层、Service层、DAO层都是一样的。
Servlet层的方法还是:
1、持有Service类属性对象;调用void delete(String cid);
2、设置request域对象的值,在这里就是一个字符串“删除成功!”
3、返回一个字符串,转发到msg.jsp提示成功(msg.jsp中去读取上面设置的域对象);
Service层往下,都是void delete(String cid)
_________________________________________________
高级搜索
其实并不麻烦,多条件组合查询而已。
思路:
按照需求,在query.jsp页面上给出可以按何种字段查询的文本框,接收用户的输入。
每一个文本框,都自带一个参数name,在request请求中,传递给Servlet的query方法以备获取;
之后被Servlet获取到,压入调用组合查询的Service层的方法中,层层传递到DAO层的组合查询方法中。
<
Service、DAO层中query方法的参数就是一个Customer对象。对象的有些属性是默认值,非默认值的就是Servlet接收jsp页面的输入,构造而成的Customer对象;
非默认值属性的个数,应该与jsp页面上提供的可接收用户输入的字段个数相同,这是由CommonUtils.toBean() 方法支持的!
CommonUtils.toBean() 可以支持jsp页面上的输入字段,是domain的实体中字段的子集。
>
DAO层需要数据库多表查询的知识。在用户没有对某个字段有查询要求时,应该对该字段不给予where的筛选条件。
DAO层返回值当然与findAll查询一样,是一个List<Customer>数组,因为结果可能不是单行的。
测试时,可能需要造些写测试数据,添加进数据库:
造出300个人
高级搜索流程:
高级搜索的Servlet层:
高级搜索的Service层:
高级搜索的DAO层的query方法:
由于防止用户变更查询业务逻辑,我们使用StringBuiler来处理字符串,先给出一定不变SQL查询字符串的部分:
select * from t_customer where 1=1
之后往后追加where语句。追加时不需要考虑where会不会被执行,全部都是and了。因为上面的where一定会被执行,这是技巧经验!
<拿一个废条件把where占了,之后就一定都是and了,执行不执行都是and>
因为用户不一定会填写Jsp上的每个查询条件,所以,在DAO里要对jsp页面上有的字段做判断:
如果传过来的Customer对象的指定属性非空(并且不是空串),就要在sql模板上追加一个 “ and 对应属性 like ? ”
注意,List<Object> params作为参数列表
qr.query方法的执行,需要 sql模板、结果集、sql模板中的传入值。
最后List<Object> params作为参数列表的处理中,cname甚至支持查到中间是输入的姓名的记录。其他字段也类似,只有性别是精确的。
如果不想模糊匹配,想精确匹配可以用下面:其他的左匹配、右匹配都是一个意思: