java web project--learning through building
a full-stack web application for users to understand java web system
- HTML/CSS
- form tag
- <em><strong>t1: design a form with head, 3 rows 3 cols with frame</strong></em>
- <em><strong>t2: modify the form width, height,format align, cellspacing</strong></em>
- <em><strong>5 row 5 col, the first row and col A cell can span 2 cols</strong></em>
- <em><strong>the second row first col cell spans 2 row, the forth row and forth col cell spans two rows and 2 cols</strong></em>
- form
- jQuery
- Servlet
- JavaEE three-tier system, decouple
- servlet thymeleaf
- 引进 dispatchServlet 后代码改变
- mvc service 和 dao 详细区分
- mvc ioc 耦合问题
- IOC - 控制反转 / DI - 依赖注入
- QQZone 业务需求
- three-tier system of the project
- file upload and download
- Cookie && Session
- Order module
- Filter
- 事务管理
- listener
- JSON
- AJAX
- use AJAX invalidate the username
- use AJAX to add the item to the cart
HTML/CSS
超链接
form tag
t1: design a form with head, 3 rows 3 cols with frame
/**
* table
* border
* width
* align
* cellspacing
* tr row tag
* th from head tag
* td table space tag
*/
<table align="center" border="1" width="300" height="300" cellspacing="0">
<tr>
<th>1.1</th>
<th>1.2</th>
<th>1.3</th>
</tr>
<tr>
<td>2.1</td>
<td>2.2</td>
<td>2.3</td>
</tr>
<tr>
<td>3.1</td>
<td>3.2</td>
<td>3.3</td>
</tr>
</table>
t2: modify the form width, height,format align, cellspacing
5 row 5 col, the first row and col A cell can span 2 cols
the second row first col cell spans 2 row, the forth row and forth col cell spans two rows and 2 cols
/**
* table
* border
* width
* align
* cellspacing
* tr row tag
* th from head tag
* td table space tag
*/
<table align="center" border="1" width="300" height="300" cellspacing="0">
<tr>
<td colspan="2">1.1</td>
<td>1.3</td>
<td>1.4</td>
<td>1.5</td>
</tr>
<tr>
<td rowspan="2">2.1</td>
<td>2.2</td>
<td>2.3</td>
<td>2.4</td>
<td>2.5</td>
</tr>
<tr>
<td>3.2</td>
<td>3.3</td>
<td>3.4</td>
<td>3.5</td>
</tr>
<tr>
<td>4.1</td>
<td>4.2</td>
<td>4.3</td>
<td colspan="2" rowspan="2">4.4</td>
</tr>
<tr>
<td>5.1</td>
<td>5.2</td>
<td>5.3</td>
</tr>
</table>
form
t1: create a register form, inlucing the username, password, gender, hobbies(multiple), nations(roll down) hidden field
- form
- input type=text value (display default content)
- input type=password value
- input type=radio (single selection) name (for group) checked=“checked” (default selected)
- input type=checkbox (multiple selection) checked=“checked”
- input type=reset value
- input type=submit value
- input type=button value
- input type=file (used for uploading files)
- input type=hidden (hidden message for sending to service)
- select
- option selected=“selected"设置默认选中
- textarea
- rows
- cols
<body>
<form>
username:<input type="text" value="default"/><br/>
password:<input type="password" value="abc"/><br/>
password:<input type="password" value="abc"/><br/>
gender:<input type="radio" name="sex"/>male<input type="radio" name="sex" checked="checked" />female<br/>
hobbies:<input type="checkbox" checked="checked" />Java<input type="checkbox" />JavaScript<input
type="checkbox" />C++<br/>
nations:<select>
<option>--please select your nation--</option>
<option selected="selected">China</option>
<option>U.S.</option>
<option>Japan</option>
</select><br/>
introduce yourself:<textarea rows="10" cols="20">xxxxx</textarea><br/>
<input type="reset" value="abc" />
<input type="submit"/>
</form>
</body>
jQuery
attr()
prop() (checked, readOnly, selected, disabled)
xml
Servlet
HttpServlet
in real life always use HttpServlet instead of the GenericServlet. HttpServlet is easier to work with, and has more methods to work with than GenericServlet.
/
/ decode by web
/ / is a absolute path: http:ip:port/
/ decode by service
/servlet / is a absolute path: http:ip:port/project
servletContext.getRealPath("/")
request.getRequestDispatcher("/")
reponse.sendRediect("/") // send / to web get http:ip:port/
garbled languagee
resp.setContentType(“text/html; charset=UTF-8”);
JavaEE three-tier system, decouple
client(Web Browser)
⬇️
JaveEE three-tier system
⬇️
1️⃣ web/view
-
- get request parameter, encapsulate into a single standardized Bean object;
-
- invoke Service layer to deal with business;
-
- response to client, sendRediect, getRequestDispatcher;
⬇️ ⬇️
2️⃣ Serveice
-
- deal with business layer
-
- invoke Dao save date
⬇️ ⬇️
3️⃣ Dao persistence layer
- only interact with Sql
- CRUD
- Create
- Read
- Update
- Delete
⤵️
MySQL, Oracle, SqlServer
↩️
- Dao
- Jdbc
- DbUtile
- JdbcTemplate
- Mybatis
- Hiberante
- JPA
↩️
- Serveice
- Spring frame
↩️
- Web
- Service
- Webwork
- Strtus1.x/2.x
- SpringMVC
servlet thymeleaf
index
index -》 IndexServlet (service() -》 doGet() ) -》《- xxDAOImpl -》 《- BaseDAO -》 《- 向数据库发起请求 -》 IndexServlet (service() -》 doGet() ) -》《- xxDAOImpl -》 《- BaseDAO -》 《- 向数据库发起请求
Thymeleaf 视图模板技术
- 添加 Thymeleaf jar 包
- web.xml 添加配置
- 新建一个 ViewBaseServlet
- 配置前缀 prefix
- 配置后缀 suffix
- servlet 继承 ViewBaseServlet
ViewBaseServlet
public class ViewBaseServlet extends HttpServlet {
private TemplateEngine templateEngine;
@Override
public void init() throws ServletException {
// 1.获取ServletContext对象
ServletContext servletContext = this.getServletContext();
// 2.创建Thymeleaf解析器对象
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(servletContext);
// 3.给解析器对象设置参数
// ①HTML是默认模式,明确设置是为了代码更容易理解
templateResolver.setTemplateMode(TemplateMode.HTML);
// ②设置前缀
String viewPrefix = servletContext.getInitParameter("view-prefix");
templateResolver.setPrefix(viewPrefix);
// ③设置后缀
String viewSuffix = servletContext.getInitParameter("view-suffix");
templateResolver.setSuffix(viewSuffix);
// ④设置缓存过期时间(毫秒)
templateResolver.setCacheTTLMs(60000L);
// ⑤设置是否缓存
templateResolver.setCacheable(true);
// ⑥设置服务器端编码方式
templateResolver.setCharacterEncoding("utf-8");
// 4.创建模板引擎对象
templateEngine = new TemplateEngine();
// 5.给模板引擎对象设置模板解析器
templateEngine.setTemplateResolver(templateResolver);
}
protected void processTemplate(String templateName, HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 1.设置响应体内容类型和字符集
resp.setContentType("text/html;charset=UTF-8");
// 2.创建WebContext对象
WebContext webContext = new WebContext(req, resp, getServletContext());
// 3.处理模板数据
templateEngine.process(templateName, webContext, resp.getWriter());
}
}
TemplateEngine 用法
index th:if && th:less 用法
th:if 是条件为 true
<tr th:if="${#lists.isEmpty(session.bookList)}">
<td colspan="4">库存为空</td>
</tr>
th:unless 是条件为 false
<tr th:unless="${#lists.isEmpty(session.bookList)}">
<td>book1</td>
td>200</td>
td>30</td>
</tr>
@WebServlet("/edit.do”) put/get 问题
先理解只要不是 form 表单,修改也是 get
游览器只能发送 get 和 post
引进 dispatchServlet 后代码改变

springmvc
servlets 合并
- AddServlet.java

servlets
@WebServlet("/add.do")
public class AddServlet extends ViewBaseServlet {
private FruitDAO fruitDAO = new FruitDAOImpl();
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
String fname = request.getParameter("fname");
Integer price = Integer.parseInt(request.getParameter("price")) ;
Integer fcount = Integer.parseInt(request.getParameter("fcount"));
String remark = request.getParameter("remark");
Fruit fruit = new Fruit(0,fname , price , fcount , remark ) ;
fruitDAO.addFruit(fruit);
response.sendRedirect("index");
}
}
- Servelt 过多

servlets
在方法中用
switch{ case “index” }

servlets
@WebServlet("/fruit.do")
public class FruitServlet extends ViewBaseServlet {
private FruitDAO fruitDAO = new FruitDAOImpl();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置编码
request.setCharacterEncoding("UTF-8");
String operate = request.getParameter("operate");
if(StringUtil.isEmpty(operate)){
operate = "index" ;
}
switch(operate){
case "index":
index(request,response);
break;
case "add":
add(request,response);
break;
case "del":
del(request,response);
break;
case "edit":
edit(request,response);
break;
case "update":
update(request,response);
break;
default:
throw new RuntimeException("operate值非法!");
}
}
private void update(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.设置编码
request.setCharacterEncoding("utf-8");
//2.获取参数
String fidStr = request.getParameter("fid");
Integer fid = Integer.parseInt(fidStr);
String fname = request.getParameter("fname");
String priceStr = request.getParameter("price");
int price = Integer.parseInt(priceStr);
String fcountStr = request.getParameter("fcount");
Integer fcount = Integer.parseInt(fcountStr);
String remark = request.getParameter("remark");
//3.执行更新
fruitDAO.updateFruit(new Fruit(fid,fname, price ,fcount ,remark ));
//4.资源跳转
//super.processTemplate("index",request,response);
//request.getRequestDispatcher("index.html").forward(request,response);
//此处需要重定向,目的是重新给IndexServlet发请求,重新获取furitList,然后覆盖到session中,这样index.html页面上显示的session中的数据才是最新的
response.sendRedirect("fruit.do");
}
private void edit(HttpServletRequest request , HttpServletResponse response)throws IOException, ServletException {
String fidStr = request.getParameter("fid");
if(StringUtil.isNotEmpty(fidStr)){
int fid = Integer.parseInt(fidStr);
Fruit fruit = fruitDAO.getFruitByFid(fid);
request.setAttribute("fruit",fruit);
super.processTemplate("edit",request,response);
}
}
private void del(HttpServletRequest request , HttpServletResponse response)throws IOException, ServletException {
String fidStr = request.getParameter("fid");
if(StringUtil.isNotEmpty(fidStr)){
int fid = Integer.parseInt(fidStr);
fruitDAO.delFruit(fid);
//super.processTemplate("index",request,response);
response.sendRedirect("fruit.do");
}
}
private void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
String fname = request.getParameter("fname");
Integer price = Integer.parseInt(request.getParameter("price")) ;
Integer fcount = Integer.parseInt(request.getParameter("fcount"));
String remark = request.getParameter("remark");
Fruit fruit = new Fruit(0,fname , price , fcount , remark ) ;
fruitDAO.addFruit(fruit);
response.sendRedirect("fruit.do");
}
private void index(HttpServletRequest request , HttpServletResponse response)throws IOException, ServletException {
HttpSession session = request.getSession() ;
// 设置当前页,默认值1
Integer pageNo = 1 ;
String oper = request.getParameter("oper");
//如果oper!=null 说明 通过表单的查询按钮点击过来的
//如果oper是空的,说明 不是通过表单的查询按钮点击过来的
String keyword = null ;
if(StringUtil.isNotEmpty(oper) && "search".equals(oper)){
//说明是点击表单查询发送过来的请求
//此时,pageNo应该还原为1 , keyword应该从请求参数中获取
pageNo = 1 ;
keyword = request.getParameter("keyword");
//如果keyword为null,需要设置为空字符串"",否则查询时会拼接成 %null% , 我们期望的是 %%
if(StringUtil.isEmpty(keyword)){
keyword = "" ;
}
//将keyword保存(覆盖)到session中
session.setAttribute("keyword",keyword);
}else{
//说明此处不是点击表单查询发送过来的请求(比如点击下面的上一页下一页或者直接在地址栏输入网址)
//此时keyword应该从session作用域获取
String pageNoStr = request.getParameter("pageNo");
if(StringUtil.isNotEmpty(pageNoStr)){
pageNo = Integer.parseInt(pageNoStr); //如果从请求中读取到pageNo,则类型转换。否则,pageNo默认就是1
}
//如果不是点击的查询按钮,那么查询是基于session中保存的现有keyword进行查询
Object keywordObj = session.getAttribute("keyword");
if(keywordObj!=null){
keyword = (String)keywordObj ;
}else{
keyword = "" ;
}
}
// 重新更新当前页的值
session.setAttribute("pageNo",pageNo);
FruitDAO fruitDAO = new FruitDAOImpl();
List<Fruit> fruitList = fruitDAO.getFruitList(keyword , pageNo);
session.setAttribute("fruitList",fruitList);
//总记录条数
int fruitCount = fruitDAO.getFruitCount(keyword);
//总页数
int pageCount = (fruitCount+5-1)/5 ;
/*
总记录条数 总页数
1 1
5 1
6 2
10 2
11 3
fruitCount (fruitCount+5-1)/5
*/
session.setAttribute("pageCount",pageCount);
//此处的视图名称是 index
//那么thymeleaf会将这个 逻辑视图名称 对应到 物理视图 名称上去
//逻辑视图名称 : index
//物理视图名称 : view-prefix + 逻辑视图名称 + view-suffix
//所以真实的视图名称是: / index .html
super.processTemplate("index",request,response);
}
}
反射方法,这样可以增加更多的 method,而不用 switch 来增加代码
@WebServlet("/fruit.do")
public class FruitServlet extends ViewBaseServlet {
private FruitDAO fruitDAO = new FruitDAOImpl();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置编码
request.setCharacterEncoding("UTF-8");
String operate = request.getParameter("operate");
if(StringUtil.isEmpty(operate)){
operate = "index" ;
}
// 获取当前类中所有方法
Method[] methods = this.getClass().getDeclareMethods();
for (Method m : methods) {
String methodName = m.getName();
if (operate.equals(methodName)) {
try {
// 找到和 operate 同名的方法, 那么通过反射技术调用它
m.invoke(this, request, response);
return;
} catch(IllegalAcc....)
e.printS
}
}
throw new RuntimeException("operate值非法!");
}
// switch(operate){
// case "index":
// index(request,response);
// break;
// case "add":
// add(request,response);
// break;
// case "del":
// del(request,response);
// break;
// case "edit":
// edit(request,response);
// break;
// case "update":
// update(request,response);
// break;
// default:
// throw new RuntimeException("operate值非法!");
// }
}
private void update(HttpServletRequest request, HttpServletResponse response) throws
dispatcherServlet 引入

servlets
用 dispatcherServelet 控制 controller, 把反射放到父类
核心控制器统一获取参数

servlets
mvc service 和 dao 详细区分

servlets

servlets
mvc ioc 耦合问题
+ 耦合/依赖注入
在软件系统中,层与层之间是存在依赖的。我们也称之为耦合。
我们系统架构或者是设计的一个原则是: 高内聚低耦合。
层内部的组成应该是高度聚合的,而层与层之间的关系应该是低耦合的,最理想的情况 0 耦合(就是没有耦合)
public class FruitController {
private FruitService fruitService = new FruitServiceImpl() ;
private String update(Integer fid , String fname , Integer price , Integer fcount , String remark ){
}
- step 1:
private FruitService fruitService = null ;
- 解决空指针问题:
1. 配置 3 个 bean,对应 3 个组件
<beans>
<bean id="fruitDAO" class="com.atguigu.fruit.dao.impl.FruitDAOImpl"/>
<bean id="fruitService" class="com.atguigu.fruit.service.impl.FruitServiceImpl">
<!-- property标签用来表示属性;name表示属性名;ref表示引用其他bean的id值-->
<property name="fruitDAO" ref="fruitDAO"/>
</bean>
<bean id="fruit" class="com.atguigu.fruit.controllers.FruitController">
<property name="fruitService" ref="fruitService"/>
</bean>
</beans>
2. 配置 bean 工厂,实现根据 id 获得一个类的功能
public interface BeanFactory {
Object getBean(String id);
}
3. 配置方法实现 bean 工厂
public class ClassPathXmlApplicationContext implements BeanFactory {
private Map<String,Object> beanMap = new HashMap<>();
public ClassPathXmlApplicationContext(){
try {
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applicationContext.xml");
//1.创建DocumentBuilderFactory
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
//2.创建DocumentBuilder对象
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder() ;
//3.创建Document对象
Document document = documentBuilder.parse(inputStream);
//4.获取所有的bean节点
NodeList beanNodeList = document.getElementsByTagName("bean");
for(int i = 0 ; i<beanNodeList.getLength() ; i++){
Node beanNode = beanNodeList.item(i);
if(beanNode.getNodeType() == Node.ELEMENT_NODE){
Element beanElement = (Element)beanNode ;
String beanId = beanElement.getAttribute("id");
String className = beanElement.getAttribute("class");
Class beanClass = Class.forName(className);
//创建bean实例
Object beanObj = beanClass.newInstance() ;
//将bean实例对象保存到map容器中
beanMap.put(beanId , beanObj) ;
//到目前为止,此处需要注意的是,bean和bean之间的依赖关系还没有设置
}
}
//5.组装bean之间的依赖关系
for(int i = 0 ; i<beanNodeList.getLength() ; i++){
Node beanNode = beanNodeList.item(i);
if(beanNode.getNodeType() == Node.ELEMENT_NODE) {
Element beanElement = (Element) beanNode;
String beanId = beanElement.getAttribute("id");
NodeList beanChildNodeList = beanElement.getChildNodes();
for (int j = 0; j < beanChildNodeList.getLength() ; j++) {
Node beanChildNode = beanChildNodeList.item(j);
if(beanChildNode.getNodeType()==Node.ELEMENT_NODE && "property".equals(beanChildNode.getNodeName())){
Element propertyElement = (Element) beanChildNode;
String propertyName = propertyElement.getAttribute("name");
String propertyRef = propertyElement.getAttribute("ref");
//1) 找到propertyRef对应的实例
Object refObj = beanMap.get(propertyRef);
//2) 将refObj设置到当前bean对应的实例的property属性上去
Object beanObj = beanMap.get(beanId);
Class beanClazz = beanObj.getClass();
Field propertyField = beanClazz.getDeclaredField(propertyName);
propertyField.setAccessible(true);
propertyField.set(beanObj,refObj);
}
}
}
}
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
@Override
public Object getBean(String id) {
return beanMap.get(id);
}
}
IOC - 控制反转 / DI - 依赖注入
控制反转
- 之前在 Servlet 中,我们创建 service 对象 , FruitService fruitService = new FruitServiceImpl(); 这句话如果出现在 servlet 中的某个方法内部,那么这个 fruitService 的作用域(生命周期)应该就是这个方法级别;(需要大量的创建销毁过程)
如果这句话出现在 servlet 的类中,也就是说 fruitService 是一个成员变量,那么这个 fruitService 的作用域(生命周期)应该就是这个 servlet 实例级别 (不需要大量的创建销毁过程,但生命周期扩大,有线程不安全问题)
- 之后我们在 applicationContext.xml 中定义了这个 fruitService。然后通过解析 XML,产生 fruitService 实例,存放在 beanMap 中,这个 beanMap 在一个 BeanFactory 中因此,我们转移(改变)了之前的 service 实例、dao 实例等等他们的生命周期。控制权从程序员转移到 BeanFactory。这个现象我们称之为控制反转
依赖注入
-
之前我们在控制层出现代码:FruitService fruitService = new FruitServiceImpl();那么,控制层和 service 层存在耦合。
-
之后,我们将代码修改成 FruitService fruitService = null ; 然后,在配置文件中配置:
<bean id="fruit" class="FruitController">
<property name="fruitService" ref="fruitService"/>
</bean>
QQZone 业务需求
1) 用户登录
2) 登录成功,显示主界面。左侧显示好友列表;上端显示欢迎词。如果不是自己的空间,显示超链接:返回自己的空间;下端显示日志列表
3) 查看日志详情:
- 日志本身的信息(作者头像、昵称、日志标题、日志内容、日志的日期)
- 回复列表(回复者的头像、昵称、回复内容、回复日期)
- 主人回复信息
4) 删除日志
5) 删除特定回复
6) 删除特定主人回复
7) 添加日志、添加回复、添加主人回复
8) 点击左侧好友链接,进入好友的空间
2.数据库设计
1) 抽取实体 : 用户登录信息、用户详情信息 、 日志 、 回贴 、 主人回复
2) 分析其中的属性:
- 用户登录信息:账号、密码、头像、昵称
- 用户详情信息:真实姓名、星座、血型、邮箱、手机号.....
- 日志:标题、内容、日期、作者
- 回复:内容、日期、作者、日志
- 主人回复:内容、日期、作者、回复
3) 分析实体之间的关系
- 用户登录信息 : 用户详情信息 1:1 PK
- 用户 : 日志 1:N
- 日志 : 回复 1:N
- 回复 : 主人回复 1:1 UK
- 用户 : 好友 M : N
3.数据库的范式:
1) 第一范式:列不可再分
2) 第二范式:一张表只表达一层含义(只描述一件事情)
3) 第三范式:表中的每一列和主键都是直接依赖关系,而不是间接依赖
4.数据库设计的范式和数据库的查询性能很多时候是相悖的,我们需要根据实际的业务情况做一个选择:
- 查询频次不高的情况下,我们更倾向于提高数据库的设计范式,从而提高存储效率
- 查询频次较高的情形,我们更倾向于牺牲数据库的规范度,降低数据库设计的范式,允许特定的冗余,从而提高查询的性能
5.QQZone登录功能实现出现的四个错误:
1) URL没修改,用的还是fruitdb
2)
3)rsmd.getColumnName() 和 rsmd.getColumnLabel()
4)Can not set com.atguigu.qqzone.pojo.UserBasic field com.atguigu.qqzone.pojo.Topic.author to java.lang.Integer
5) left.html页面没有样式,同时数据也不展示,原因是:我们是直接去请求的静态页面资源,那么并没有执行super.processTemplate(),也就是thymeleaf没有起作用
(之前的表单也是这个原因)
解决方法:
- 新增PageController,添加page方法:
public String page(String page){
return page ; // frames/left
}
目的是执行super.processTemplate()方法,让thymeleaf生效
three-tier system of the project
sp1. create sql forms
- database book
- t_user
sp2. JaveBean User (id, username, password, email)
sp3. JdbcUtils
- jar package
- jdbc.properties in src dir
username=root
password=root
url=jdbc:mysql://localhost:3306/book
driverClassName=com.mysql.jdbc.Driver
initialSize=5
maxActive=1
- Jdbc tool
sp4. BaseDao
- commons-dbutils jar
- BaseDao
public abstract class BaseDao {
private QueryRunner queryRunner = new QueryRunner();
/**
* update() : insert/update/delete
*
*/
public int update(String sql, Object... args) {
Connection connection = JdbcUtils.getConnection();
try {
return queryRunner.update(connection, sql, args);
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.close(connection);
}
return -1;
}
}
/**
* query() :
* return javaBean
*/
public <T> T queryForOne(Class<T> type, String sql, Object... args) {
Connection con = JdbcUtils.getConnection();
try {
return queryRunner.query(con, sql, new BeanHandler<T>(type), args);
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.close(connection);
}
return null;
}
}
/**
* query() :
* return multiplate javaBean
*/
public <T> T queryForList(Class<T> type, String sql, Object... args) {
Connection con = JdbcUtils.getConnection();
try {
return queryRunner.query(con, sql, new BeanListHandler<T>(type), args);
}
/**
* query() :
* return sql
*/
public Object queryForSingleValue(String sql, Object... args) {
Connection conn = JdbcUtils.getConnection();
try {
return queryRunner.query(conn, sql, new ScalarHandler(), args);
}
sp5. UserDao && test
- UserDao interface
public intercface UserDao {
/**
* @param username
*
*/
public User queryUserByUsername(String username);
public User queryUserByUsernameAndPassword(String username, String password);
public int saveUser(User user);
}
- UserDaoImpl
public class UserDaoImpl extend BaseDap implements UserDao {
/**
* @param username
*
*/
public User queryUserByUsername(String username) {
String sql = "select `id`,`username`,`password`,`email` from t_user where username = ?";
return queryForOne(User.class, sql, username);
}
public User queryUserByUsernameAndPassword(String username, String password){
}
public int saveUser(User user){
}
}
- UserDao test
UserDao userDao = new UserDaoImpl();
@Test
public void queryUserByUsername() {
if (userDao.queryUserByUsername("admin1234") == null ){
System.out.println("aaaaaa!");
} else {
System.out.println("bbbbbbbbb");
}
sp6. UserService && test
- UserService interface
public intercface UserService {
/**
* @param username
*
*/
public void registUser(User user);
public User login(User
public boolean existsUsername(String username);
}
-
UserServiceImpl
-
UserService test
sp7. web
-
regist
-
when user finish regist, click sumbit button, send the parameter to service and save user data
-
modify regist.html and regist_success.html page
-
RegistServlet
-
public class RegistServlet extends HttpServlet {
private UserService userService = new UserServiceImpl();
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
IOException {
// 1. get parameter
String username = req.getParameter("username");
String password = req.getParameter("password");
String email = req.getParameter("email");
String code = req.getParameter("code");
// 2. check the code
if ("abcde".equalsIgnoreCase(code)) {
// 3、check the username
if (userService.existsUsername(username)) {
System.out.println("username[" + username + "]exist!");
// back to regist page
req.getRequestDispatcher("/pages/user/regist.html").forward(req, resp);
} else {
// available
// invoke Sservice save to SQL database
userService.registUser(new User(null, username, password, email));
// go to the regist_success.html
req.getRequestDispatcher("/pages/user/regist_success.html").forward(req, resp);
}
} else {
System.out.println("code[" + code + "]error");
req.getRequestDispatcher("/pages/user/regist.html").forward(req, resp);
}
}
}
file upload and download
file upload
- form, method = post
- form encType: multipart/form-data
- form input type=file
- servlet reveice and deal with data upload
commos-fileupload.jar
- commons-fileupload-x.x.x.jar
- commons-io-x.x.jar
These two jars contains classes we can use:
- ServletFileUpload: decode upload data
- boolean ServletFileUpload.isMultipartContent(HttpServletRequest request);
- public List
parseRequest(HttpServletRequest request); - boolean FileItem.isFormField(), check is ordinary form or not
- String FileItem.getFieldName()
- String FileItem.getString()
- String FileItem.getName()
- void FileItem.write(file)
fileupload
<form action="http://192.168.1.68://8080/book/uploadServlet" method="post" enctype="multipart/form-data">
username: <input type="text" name="username" /><br>
username: <input type="file" name="photo" /><br>
<input type="submit" value="upload" /><br>
</form>
decode data upload
/**
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
IOException {
// 1. check the data is multipart
if (ServletFileUpload.isMultipartContent(req)) {
FileItemFactroy fileItemFactroy = new DiskFileItemFactory();
ServletFileUpload servletFileUpload = new ServletFileUpload(fileItemFactroy);
try {
// decode data, get FileItem
List<FileItem> list = servletFileUpload.parseRequest(req);
for (FileItem fileItem : list) {
if (fileItem.isFormField()) {
System.out.println(" name value:" + fileItem.getFieldName());
// 参数 UTF-8.解决乱码问题
System.out.println("form value value:" + fileItem.getString("UTF-8"));
} else {
// 上传的文件
System.out.println("name value:" + fileItem.getFieldName());
System.out.println("upload file name:" + fileItem.getName());
fileItem.write(new File("e:\\" + fileItem.getName()));
}
}
}
file download
- response.getOutputStream();
- servletContext.getResourceAsStream();
- servletContext.getMimeType();
- response.getContentType();
- response.setHeader(“Content-Disposition”, “attachment;fileName=1.jpg”);
String us = request.getHeader("User-Agent");
// whether is Fire fox web
if (us.contains("Firefox")) {
String str = "attachment;fileName" + "=?utf-8?B?" + new BASE64Encoder().encode("中文.jpg".getBytes("utf-8"))
+ "?=";
response.setHeader("Content-Disposition", str);
} else {
String str = "attachment;fileName" + URLEncode.encode("中文.jpg".getBytes("utf-8"))
+ "?=";
response.setHeader("Content-Disposition", str);
}
Cookie && Session
use Cookie to save user’s information
chorme: the first time tomcat
-
usename: 〰️ ▶️ login.jsp
-
password ◀️ 〰️ back to login page
-
submit 〰️ ▶️ LoginServlet 1. get username and password 2. validate
. ❌ not allow to login display username Set-Cookie:username ◀️ 〰️ ✔️: save username as Cookie send to server
<form action="http://localhost:8080/13_cookie_session/loginServlet" method="get">
用户名:<input type="text" name="username" value="${cookie.username.value}"> <br>
密码:<input type="password" name="password"> <br>
<input type="submit" value="登录">
</form>
LoginServlet
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
IOException {
String username = req.getParameter("username");
String password = req.getParameter("password");
if ("wzg168".equals(username) && "123456".equals(password)) {
//login success
Cookie cookie = new Cookie("username", username);
cookie.setMaxAge(60 * 60 * 24 * 7);// cookie is available in a week
resp.addCookie(cookie);
System.out.println("login success");
} else {
// login fails
System.out.println("login fails");
}
}
Session to invalidate the user infomation
UserServlet
protected void logout(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
IOException {
// 1、invalidate Session username
req.getSession().invalidate();
// 2、redirect to login page
resp.sendRedirect(req.getContextPath());
}
Order module
Order page
Order page
- orderId
- createTime
- price
- status
- userId
Order Item
- id
- name
- count
- price
- totalPrice
- orderId
Order function
- create order
- show all orders
- send order
- show my order
- totalPrice
- receive order
Order Servlet
OrderService
OrderDao
- saveOrder(order)
- queryOrders()
- changeOrderStatus(OrderId, status);
- queryOrderByUserId(userId);
OrderItemDao
- saveOrderItem(orderItem)
- queryOrdersItemByOrderId(orderId)
Filter

servlets
A ->B -> C demo3.service() -> C -> B -> A
过滤器 Filter
-
Filter 也属于 Servlet 规范
-
Filter 开发步骤:新建类实现 Filter 接口,然后实现其中的三个方法:init、doFilter、destroy
配置 Filter,可以用注解@WebFilter,也可以使用 xml 文件 -
Filter 在配置时,和 servlet 一样,也可以配置通配符,例如 @WebFilter("*.do")表示拦截所有以.do 结尾的请求
-
过滤器链
- 执行的顺序依次是: A B C demo03 C2 B2 A2
- 如果采取的是注解的方式进行配置,那么过滤器链的拦截顺序是按照全类名的先后顺序排序的
- 如果采取的是 xml 的方式进行配置,那么按照配置的先后顺序进行排序
chorme: tomcat
- http://ip:port/project/resource 〰️ ▶️ Filter target rescouce . authorized ▶️ html, jsp, Servlet
. ◀️ 〰️ unauthorized
@WebFilter(filterName ="ManagerFilter", urlPatterns = {"/pages/manager/*", "/manager/bookServlet"})
public class AdminFilter implements Filter {
/**
* doFilter method
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain
filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpSession session = httpServletRequest.getSession();
Object user = session.getAttribute("user");
//
if (user == null) {
servletRequest.getRequestDispatcher("/login.jsp").forward(servletRequest,servletResponse);
return;
} else {
//
filterChain.doFilter(servletRequest,servletResponse);
}
}
}
事务管理
涉及到的组件
- OpenSessionInViewFilter
- TransactionManager
- ThreadLocal
- ConnUtil
- BaseDAO
ThreadLocal
- get() , set(obj)
- ThreadLocal 称之为本地线程 。 我们可以通过 set 方法在当前线程上存储数据、通过 get 方法在当前线程上获取数据
- set 方法源码分析:
public void set(T value) {
Thread t = Thread.currentThread(); //获取当前的线程
ThreadLocalMap map = getMap(t); //每一个线程都维护各自的一个容器(ThreadLocalMap)
if (map != null)
map.set(this, value); //这里的key对应的是ThreadLocal,因为我们的组件中需要传输(共享)的对象可能会有多个(不止Connection)
else
createMap(t, value); //默认情况下map是没有初始化的,那么第一次往其中添加数据时,会去初始化
}
- get 方法源码分析
public T get() {
Thread t = Thread.currentThread(); //获取当前的线程
ThreadLocalMap map = getMap(t); //获取和这个线程(企业)相关的ThreadLocalMap(也就是工作纽带的集合)
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); //this指的是ThreadLocal对象,通过它才能知道是哪一个工作纽带
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value; //entry.value就可以获取到工具箱了
return result;
}
}
return setInitialValue();
}

servlets

servlets

servlets
ThreadLocal and Filter manage transactions
modify JdbcUtils
private static ThreadLocal<Connection> conns = new ThreadLocal<>();
// Connection conn = null;
Connection conn = conns.get();
BaseDao
Filter add try-catch to Service
@WebFilter(filterName ="TransactionFilter", urlPatterns = "/*")
public class TransactionFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain
filterChain) throws IOException, ServletException {
try {
filterChain.doFilter(servletRequest,servletResponse);
JdbcUtils.commitAndClose();// 提交事务
} catch (Exception e) {
JdbcUtils.rollbackAndClose();//回滚事务
e.printStackTrace();
}
}
throw new RunException() in BaseServlet to Filter
public abstract class BaseServlet extends HttpServlet {
try {
//
Method method = this.getClass().getDeclaredMethod(action, HttpServletRequest.class,
HttpServletResponse.class);
// System.out.println(method);
//
method.invoke(this, req, resp);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);// throw new RunException Filter
}
}
tomcat display error page
<error-page>
<!--error-code -->
<error-code>500</error-code>
<!--location-->
<location>/pages/error/error500.jsp</location>
</error-page>
<!--error-page -->
<error-page>
<!--error-code -->
<error-code>404</error-code>
<!--location -->
<location>/pages/error/error404.jsp</location>
</error-page>
listener
- ServletContextListener - 监听 ServletContext 对象的创建和销毁的过程
- HttpSessionListener - 监听 HttpSession 对象的创建和销毁的过程
- ServletRequestListener - 监听 ServletRequest 对象的创建和销毁的过程
- ServletContextAttributeListener - 监听 ServletContext 的保存作用域的改动(add,remove,replace)
- HttpSessionAttributeListener - 监听 HttpSession 的保存作用域的改动(add,remove,replace)
- ServletRequestAttributeListener - 监听 ServletRequest 的保存作用域的改动(add,remove,replace)
- HttpSessionBindingListener - 监听某个对象在 Session 域中的创建与移除
- HttpSessionActivationListener - 监听某个对象在 Session 域中的序列化和反序列化
ContextLoaderListener
//监听上下文启动,在上下文启动的时候去创建IOC容器,然后将其保存到application作用域
//后面中央控制器再从application作用域中去获取IOC容器
@WebListener
public class ContextLoaderListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
//1.获取ServletContext对象
ServletContext application = servletContextEvent.getServletContext();
//2.获取上下文的初始化参数
String path = application.getInitParameter("contextConfigLocation");
//3.创建IOC容器
BeanFactory beanFactory = new ClassPathXmlApplicationContext(path);
//4.将IOC容器保存到application作用域
application.setAttribute("beanFactory",beanFactory);
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}

servlets
JSON
JSON && JavaBean
Person person = new Person(1,"AA");
Gson gson = new Gson();
String personJsonString = gson.toJson(person);
System.out.println(personJsonString);
Person person1 = gson.fromJson(personJsonString, Person.class
JSON && List
List<Person> personList = new ArrayList<>();
personList.add(new Person(1, "AA"));
personList.add(new Person(2, "BB"));
Gson gson = new Gson();
// transfer to json string
String personJsonString = gson.toJson(personList);
System.out.println(personJsonString);
List<Person> list = gson.fromJson(personJsonString, new PersonListType().getType());
JSON && Map
Map<Integer,Person> personMap = new HashMap<>()
personList.add(new Person(1, "AA"));
personList.add(new Person(2, "BB"));
Gson gson = new Gson();
// transfer to json string
String personJsonString = gson.toJson(personMap);
System.out.println(personJsonString);
Map<Integer,Person> personMap2 = gson.fromJson(personJsonString, new TypeToken<HashMap<Integer,Person>>(){}.getType());
AJAX
use AJAX invalidate the username
protected void ajaxExistsUsername(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
// get username
String username = req.getParameter("username");
// userService.existsUsername();
boolean existsUsername = userService.existsUsername(username);
//
Map<String,Object> resultMap = new HashMap<>();
resultMap.put("existsUsername",existsUsername);
Gson gson = new Gson();
String json = gson.toJson(resultMap);
resp.getWriter().write(json);
}
$("#username").blur(function () {
//1 get username
var username = this.value;
$.getJSON("http://localhost:8080/book/userServlet","action=ajaxExistsUsername&username=" +
username,function (data) {
if (data.existsUsername) {
$("span.errorMsg").text("xxxx!");
} else {
$("span.errorMsg").text("xxxx!");
}
});
});
use AJAX to add the item to the cart
protected void addItem(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// System.out.println("bookId: " + request.getParameter("id"));
System.out.println("request head reference value: " + request.getHeader("Referer"));
// 2.1 : 获取请求的参数,商品编号
int id = WebUtils.parseInt(request.getParameter("id"), 0);
// 2.2: 调用bookService.queryBookById(id): Book得到图书的信息
Book book = bookService.queryBookById(id);
// 2.3: 把图书信息,转换成CartItem商品项
CartItem cartItem = new CartItem(book.getId(), book.getName(), 1, book.getPrice(), book.getPrice());
// 2.4: 调用Cart.addItem(CartItem);添加商品项
Cart cart = (Cart) request.getSession().getAttribute("cart");
if (cart == null) {
cart = new Cart();
request.getSession().setAttribute("cart", cart);
}
cart.addItem(cartItem);
// 2.5: 重定向回商品列表页面
System.out.println(cart);
request.getSession().setAttribute("lastName", cartItem.getName());
response.sendRedirect(request.getContextPath());
//use AJAX to add the item to the cart
Map<String,Object> resultMap = new HashMap<String,Object>();
resultMap.put("totalCount", cart.getTotalCount());
resultMap.put("lastName",cartItem.getName());
Gson gson = new Gson();
String resultMapJsonString = gson.toJson(resultMap);
resp.getWriter().write(resultMapJsonString);
}