第二讲 系统框架设计-后端篇
一、前序准备
1、开发环境推荐
开发工具:IDEA
最新版
服务器:tomcat8
以上(tomcat8
的 servlet api
版本不能太高,下面会提到)
项目管理工具:maven
2、My开发环境
开发工具:IDEA2021
tomcat服务器:tomcat8.5.69
项目管理工具:maven
二、新建项目
1、新建项目
打开 IDEA 2021
, 点击新建,选择 Java Enterprise
, 输入项目名 news_frontend
,点击下一步,然后点击完成
注意:IDEA2021
新建Java Web项目必须使用项目管理工具maven
或者gradle
,我也推荐大家使用这两种工具中的其中一种管理项目依赖
2、目录结构调整
- 删除
src
下的test
目录 - 删除
webapp
下的index.jsp
- 删除 项目包下的
HelloServlet
- 在
java
目录下新建以下包dao
:存放数据库访问对象filter
:存放过滤器servlet
:存放servlettest
:存放测试类utils
:存放工具类vo
:存放返回的值对象
- 在
resources
目录下新建db.properties
文件,方便配置数据库连接信息
3、配置依赖
注意:servlet-api
,现在用4.0.1没问题,后续出问题后我们会更换,为了演示问题,所以我们这里不改
<properties>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>
<dependencies>
<!-- servlet,现在用4.0.1没问题,后续出问题后我们会更换,为了演示问题,所以我们这里不改 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!-- junit单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- mysql驱动,如果是8.0请导入8.0的版本,同时记得修改db.properities -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- gson,用于对象转json字符串 -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
</dependencies>
复制代码
4、配置数据库连接信息 db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/news_demo?characterEncoding=utf8&useSSL=false&serverTimezone=UTC
username=root
password=root
复制代码
5、编写数据库工具类
在 utils
包下新建类 DatabaseUtil
注意:DatabaseUtil
的代码与之前相比有所变化,主要是读取resources目录下的 db.properties
逻辑有所改变,现在使用 maven
项目的话,我们采用类加载器的方式获取 db.properties
package com.example.demo.utils;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
/**
* 数据库连接工具类
*/
public class DatabaseUtil {
/**
* 获得数据库的连接
*
* @return 数据库的连接
*/
public static Connection getConnection() throws IOException {
// 获取配置文件
InputStream in = DatabaseUtil.class.getClassLoader().getResourceAsStream("db.properties");
// 解析配置文件
Properties properties = new Properties();
properties.load(in);
assert in != null;
in.close();
// 获取配置文件中数据库连接相应字段的值
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
String username = properties.getProperty("username");
String password = properties.getProperty("password");
// 获取数据库连接
Connection connection = null;
try {
Class.forName(driver);
connection = DriverManager.getConnection(url, username, password);
} catch (Exception e) {
e.printStackTrace();
}
return connection;
}
/**
* 关闭数据库连接
*
* @param resultSet 结果集
* @param statement sql执行器
* @param connection 连接
*/
public static void close(ResultSet resultSet, Statement statement, Connection connection) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws IOException {
// 测试数据库连接
System.out.print(DatabaseUtil.getConnection());
}
}
复制代码
6、编写 Gson
工具类
这里使用 gson
,同类型的还有 fastjson
和 jackson
,大家都可以尝试
package com.example.demo.utils;
import com.google.gson.Gson;
/**
* Gson工具类,主要用于获取单例gson
*/
public class GsonUtil {
private static final Gson gson = new Gson();
public static Gson getGson() {
return gson;
}
}
复制代码
此处运用类似单例模式的设计模式,确保运行时只有一个gson
,避免重复new
,因为Gson
只是个工具,不会经常变化,所以整个应用有一个就够了
7、编写值对象ValueObject
在vo
包下新建 ArticleVo
类和 SearchResultVo
类,并生成getter
、 setter
、 toString
和有参空参构造方法
ArticleVo
:
package com.example.demo.vo;
public class ArticleVo {
private long id;
private String url;
private String docno;
private String title;
private String content;
@Override
public String toString() {
return "ArticleVo{" +
"id=" + id +
", url='" + url + '\'' +
", docno='" + docno + '\'' +
", title='" + title + '\'' +
", content='" + content + '\'' +
'}';
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getDocno() {
return docno;
}
public void setDocno(String docno) {
this.docno = docno;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
复制代码
SearchResultVo
:
package com.example.demo.vo;
import java.util.List;
public class SearchResultVo {
private long duration;
private int size;
private List<ArticleVo> list;
public SearchResultVo() {
}
public SearchResultVo(long duration, int size, List<ArticleVo> list) {
this.duration = duration;
this.size = size;
this.list = list;
}
@Override
public String toString() {
return "SearchResultVo{" +
"duration=" + duration +
", size=" + size +
", list=" + list +
'}';
}
public long getDuration() {
return duration;
}
public void setDuration(long duration) {
this.duration = duration;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public List<ArticleVo> getList() {
return list;
}
public void setList(List<ArticleVo> list) {
this.list = list;
}
}
复制代码
8、编写数据库访问类DAO
在 dao
包下新建 ArticleDao
类
在开发dao
过程中发现经常会写大量重复的代码,比如数据库连接,查询一个或者多个,此时可以借助一些工具如 jdbcTemplate
, DBUtils
以及连接池,比如阿里的 druid
,不过我不打算引入这么多工具,增加学习成本,而且由于业务不是很多很复杂,所以还是尽量用自己写的方式,感兴趣或者有能力的同学可以自行升级~
我们可以封装两个常用的方法,一个是查询一个的,一个是查询多个的
- 查询一个:
/**
* 查询一个ArticleVo的通用方法
*
* @param sql 查询语句
* @return 查询到的新闻
*/
public ArticleVo selectOne(String sql) throws Exception {
try {
connection = DatabaseUtil.getConnection();
ps = connection.prepareStatement(sql);
resultSet = ps.executeQuery();
ArticleVo articleVo = new ArticleVo();
while (resultSet.next()) {
articleVo.setId(resultSet.getLong(1));
articleVo.setUrl(resultSet.getString(2));
articleVo.setDocno(resultSet.getString(3));
articleVo.setTitle(resultSet.getString(4));
articleVo.setContent(resultSet.getString(5));
}
return articleVo;
} finally {
DatabaseUtil.close(resultSet, ps, connection);
}
}
复制代码
- 查询多个
/**
* 查询多个ArticleVo通用方法
*
* @param sql 查询语句
* @return 查询到的新闻列表
*/
public List<ArticleVo> selectList(String sql) throws Exception {
try {
connection = DatabaseUtil.getConnection();
ps = connection.prepareStatement(sql);
resultSet = ps.executeQuery();
List<ArticleVo> articleVoList = new ArrayList<>();
while (resultSet.next()) {
ArticleVo articleVo = new ArticleVo();
articleVo.setId(resultSet.getLong(1));
articleVo.setUrl(resultSet.getString(2));
articleVo.setDocno(resultSet.getString(3));
articleVo.setTitle(resultSet.getString(4));
articleVo.setContent(resultSet.getString(5));
articleVoList.add(articleVo);
}
return articleVoList;
} finally {
DatabaseUtil.close(resultSet, ps, connection);
}
}
复制代码
然后定义三个业务方法,并且执行相应的查询语句
/**
* 根据id查询
*
* @param id 新闻id
* @return 新闻
*/
public ArticleVo getOneById(long id) throws Exception {
String sql = "select * from article where id = " + id;
return selectOne(sql);
}
/**
* 随机获取n条新闻
*
* @param n 新闻数量
* @return 随机获取得到的n条新闻
*/
public List<ArticleVo> getRandomList(int n) throws Exception {
String sql = "select * from article where id >= (select floor(RAND() * (select MAX(id) from article))) order by id limit " + n;
return selectList(sql);
}
/**
* 根据关键词模糊查询新闻
*
* @param word 关键词
* @return 与关键词匹配的文章
*/
public List<ArticleVo> getArticleListByWord(String word) throws Exception {
String sql = "select * from article where title like '%" + word + "%' or content like '%" + word + "%'";
return selectList(sql);
}
复制代码
怎么样,系不系比较简洁亿点了
9、测试一下 DAO
这么好的代码怎么能不测试一下?赶紧写几个单元测试~
package com.example.demo.test;
import com.example.demo.dao.ArticleDao;
import org.junit.Test;
public class ArticleDaoTest {
/**
* 测试根据id查询
*/
@Test
public void testGetOneById() throws Exception {
ArticleDao articleDao = ArticleDao.getArticleDao();
System.out.println(articleDao.getOneById(1));
}
/**
* 测试随机推荐10条新闻
*/
@Test
public void testRandomList() throws Exception {
ArticleDao articleDao = ArticleDao.getArticleDao();
System.out.println(articleDao.getRandomList(10));
}
/**
* 测试根据关键词模糊查询新闻
*/
@Test
public void testGetArticleListByWord() throws Exception {
ArticleDao articleDao = ArticleDao.getArticleDao();
System.out.println(articleDao.getArticleListByWord("河源").size());
}
}
复制代码
结果就不截图了,反正都通过了
10、编写 Servlet
由于我们采用注解的方式配置 Servlet
,比较方便,所以呢,web.xml
可以不配置,Servlet
的逻辑也比较简单,主要就是获取请求参数,利用DAO
查询数据库,然后利用 GSON
把那个结果转为 JSON
,返回结果即可
1、ArticleSearchServlet
package com.example.demo.servlet;
import com.example.demo.dao.ArticleDao;
import com.example.demo.utils.GsonUtil;
import com.example.demo.vo.ArticleVo;
import com.example.demo.vo.SearchResultVo;
import com.google.gson.Gson;
import java.io.*;
import java.util.List;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
@WebServlet(value = "/article-search")
public class ArticleSearchServlet extends HttpServlet {
// 根据关键词查询数据库
public List<ArticleVo> searchByWord(String word) throws Exception {
ArticleDao articleDao = ArticleDao.getArticleDao();
return articleDao.getArticleListByWord(word);
}
// 计时、查询和返回查询结果
public String getSearchResult(String word) throws Exception {
// 开始计时
long start = System.currentTimeMillis();
Gson gson = GsonUtil.getGson();
List<ArticleVo> articleVoList = searchByWord(word);
// 结束计时
long duration = System.currentTimeMillis() - start;
return gson.toJson(new SearchResultVo(duration, articleVoList.size(), articleVoList));
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("application/json;charset=utf8");
// 获取关键词
String word = request.getParameter("word");
PrintWriter out = response.getWriter();
try {
out.println(getSearchResult(word));
} catch (Exception e) {
e.printStackTrace();
} finally {
out.flush();
out.close();
}
}
}
复制代码
response.setContentType("application/json;charset=utf8");
:设置响应体的格式,我们响应的是JSON
所以这里设置为application/json
,字符编码是utf8
;
request.getParameter(key)
:获取key
参数的值
2、ArticleViewServlet
package com.example.demo.servlet;
import com.example.demo.dao.ArticleDao;
import com.example.demo.utils.GsonUtil;
import com.example.demo.vo.ArticleVo;
import com.google.gson.Gson;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet(value = "/article-view")
public class ArticleViewServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("application/json;charset=utf8");
// 获取参数,要获取的新闻id
long id = Long.parseLong(request.getParameter("id"));
Gson gson = GsonUtil.getGson();
ArticleDao articleDao = ArticleDao.getArticleDao();
PrintWriter out = response.getWriter();
try {
// 根据id获取新闻
ArticleVo articleListByWord = articleDao.getOneById(id);
out.println(gson.toJson(articleListByWord));
} catch (Exception e) {
e.printStackTrace();
} finally {
out.flush();
out.close();
}
}
}
复制代码
3、ArticleRecommendByRandomServlet
package com.example.demo.servlet;
import com.example.demo.dao.ArticleDao;
import com.example.demo.utils.GsonUtil;
import com.example.demo.vo.ArticleVo;
import com.google.gson.Gson;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
@WebServlet(value = "/article-random")
public class ArticleRecommendByRandomServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("application/json;charset=utf8");
// 获取参数,要获取的随机新闻条数
int size = Integer.parseInt(request.getParameter("size"));
Gson gson = GsonUtil.getGson();
ArticleDao articleDao = ArticleDao.getArticleDao();
PrintWriter out = response.getWriter();
try {
// 获取随机新闻
List<ArticleVo> articleListByWord = articleDao.getRandomList(size);
out.println(gson.toJson(articleListByWord));
} catch (Exception e) {
e.printStackTrace();
} finally {
out.flush();
out.close();
}
}
}
复制代码
细心的同学可能会发现,有些代码挺相似的,比如设置返回的内容类型和编码,对象转成json
字符串等,而且,对于一些错误情况也没有考虑,只是简单假设情况都是理想的,至于该怎么改进,大家可以动动脑筋,虽说手把手,但是也要留点自行完善和改进的空间~
11、测试
首先测试随机推荐
http://localhost:8080/demo/article-random?size=10
复制代码
其次测试根据新闻id获取新闻
http://localhost:8080/demo/article-view?id=1
复制代码
最后测试根据关键词模糊查询新闻
http://localhost:8080/demo/article-search?word=河源
复制代码
咦,我的页面怎么和你们不一样,那是因为我装了csdn
的 Chrome
插件,会自动格式化 json
,当然,装 FeHelper Web
前端助手也可以~