一、分析需求【先只实现核心内容】
- 注册新用户
- 登录已有用户
- 展示博客列表(每一项包含了文章的标题、作者)【点击标题及刽跳转到文章详情页】
- 文章详情页中,可以看到文章的标题、作者、文章内容,其他评论功能、分类功能等先不考虑
- 发布新的博客(不考虑富文本编辑-> 嵌入图片、嵌入视频、嵌入代码等)
- 删除自己的博客
二、创建项目 maven app
可以参考创建 maven 项目来创建好 maven 项目
要注意的是 pom.xml 中需要添加的依赖有两个,一个是 servlet API 另一个是 mysql connector,将下面的内容复制进去就好,如果版本不一致可以自行去 maven 仓库 搜索一下
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
三、数据库设计
设计当前的代码中用到的数据库有几张表,每个表有哪些字段,每个表之间有什么关系
3.1 提取出需求中的“实体”有哪些
确定了需要有两张表 User 、 Article
3.2 想清楚关注的属性
确定两张表中都需要有哪些字段
用户:名字、密码;
文章:标题、内容
3.3 实体和实体的关系
一对多的关系
User 表
userId | name | passWord |
1 | zyt | 123 |
2 | zyt2 | 123 |
Article 表
id | title | content | userId |
1 | 标题 | 正文 | 1 |
2 | 标题2 | 正文 | 1 |
3 | 标题3 | 正文 | 1 |
先创建一个 db.sql 文件夹,编辑好需要的数据库的配置操作,将这些粘贴到 mysql 命令行中
drop database if exists java_blogdemo;
create database java_blogdemo;
drop table if exists user;
use java_blogdemo;
create table user (
userId int primary key auto_increment,
name varchar(50) unique ,
password varchar(50)
);
drop table if exists article;
create table article (
articleId int primary key auto_increment,
tilte varchar(255),
content text,
userId int,
foreign key(userId) references user(userId)
);
4.开始写代码
先实现数据库的一些基本操作,把两张表所涉及到的一些基本的增删改查操作稍微封装一下
4.1 创建一个类,管理数据库的连接
在 java 目录下面创建一个 model 包,用于表示和管理数据,把数据库相关的操作都放到这个 model 中
在 model 包中创建一个 DButil.class 文件,进行数据库连接管理
- 建立连接
- 断开连接
JDBC 中使用 DataSource 来管理连接
懒汉实现的单例模式有一个重要的问题:线程不安全
首次使用 getDataSource ,如果是多线程调用,就可能会产生线程不安全问题
如何保证该线程安全?
- 合适的位置加锁
- 双重 if 判定
- volatile
package model;
import java.sql.Connection;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Create with IntelliJ IDEA
* Description:管理数据库连接
* 1、建立连接
* 2、断开连接
* JDBC 使用 DataSource来管理连接
* DBUtil 相当于是对 DataSource 再稍微包装一层
* DataSource 每个程序只应该有一个实例(单例)
* DBUtil 本质上就是实现了一个单例模式,管理了唯一的一个 DataSource 实例
* 此处使用懒汉模式
* User:Zyt
* Date:2020-07-13
*/
public class DButil {
private static volatile DataSource dataSource = null;
private static final String URL = "jdbc:mysql://127.0.0.1:3306/java_blogdemo?characterEncoding=utf-8&useSSL=true";
private static final String USERNAME = "root";
private static final String PASSWORD = "";
public static DataSource getDataSource() {
if (dataSource == null) {
synchronized (DButil.class) {
if (dataSource == null) {
dataSource = new MysqlDataSource();
// 给 DataSource 设置一些属性
((MysqlDataSource)dataSource).setURL(URL);
((MysqlDataSource)dataSource).setUser(USERNAME);
((MysqlDataSource)dataSource).setPassword(PASSWORD);
}
}
}
return dataSource;
}
//通过这个方法获取连接
public static Connection getConnection(){
try {
return getDataSource().getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
//通过这个方法断开连接
public static void close(Connection connection, PreparedStatement statement,
ResultSet resultSet){
try {
if (resultSet != null){
resultSet.close();
}
if (statement != null){
statement.close();
}
if (connection != null){
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
4.2 创建实体类
创建一个 User 类和 Article 类,这两个类应该和表结构对应
package model;
/**
* Create with IntelliJ IDEA
* Description:
* User:Zyt
* Date:2020-07-13
*/
public class Article {
private int articleId;
private String title;
private String content;
private int userId;
public int getArticleId() {
return articleId;
}
public void setArticleId(int articleId) {
this.articleId = articleId;
}
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;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
package model;
/**
* Create with IntelliJ IDEA
* Description:
* User:Zyt
* Date:2020-07-13
*/
public class User {
private int userId;
private String name;
private String password;
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
4.3 实现数据库的基本增删改查
4.3.1 对 user 表的基本功能操作
创建一个 UserDao 类,和一个 ArticleDao 类,分别负责实现 user 表和 article 表的增删改查操作,DAO 表示数据访问层
这里主要涉及的功能有
- 新增用户(注册)
- 按照名字查找用户(登录)
package model;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ResourceBundle;
/**
* Create with IntelliJ IDEA
* Description:通过 UserDao 这个类来在完成用户的数据表操作
* User:Zyt
* Date:2020-07-15
*/
public class UserDao {
//1、新增用户
//2、按照名字查找用户(登录)
void add(User user){
//1.获取数据库连接
Connection connection = DButil.getConnection();
//2.拼装 SQL 语句
String sql = "insert into user values (null,?,?)";
PreparedStatement statement = null;
try {
statement = connection.prepareStatement(sql);
statement.setString(1,user.getName());
statement.setString(2,user.getPassword());
//3.执行 SQL 语句
int ret = statement.executeUpdate();
if (ret != 1){
System.out.println("插入新用户失败!");
return;
}
System.out.println("插入新用户成功!");
} catch (SQLException e) {
e.printStackTrace();
} finally {
//4.释放数据库的连接
DButil.close(connection,statement,null);
}
}
public User selectByName(String name){
Connection connection = DButil.getConnection();
String sql = "select * from user where name = ?";
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
statement = connection.prepareStatement(sql);
statement.setString(1,name);
resultSet = statement.executeQuery();
if (resultSet.next()){
User user = new User();
user.setUserId(resultSet.getInt("userID"));
user.setName(resultSet.getString("name"));
user.setPassword(resultSet.getString("Password"));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
DButil.close(connection,statement,null);
}
return null;
}
public static void main(String[] args) {
UserDao userDao = new UserDao();
User user = new User();
user.setName("zzz");
user.setPassword("123");
userDao.add(user);
}
}
和数据库建立连接
一个常见问题:数据库连接不上(中间就会抛异常),结合着异常的信息,来分析原因。
常见原因:
- url 写错了(数据库的 IP 号、端口号、数据库名错了、url 格式错了……)
- 用户名、密码错了
- 数据库服务器没有正确启动
- 连其他主机的数据库,会被 mysql 的安全策略拦截
4.3.2 对 article 表的基本功能操作
涉及到的功能有:
- 新增文章
- 查看文章列表
- 查看文章详情
package model;
import sun.security.pkcs11.Secmod;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
* Create with IntelliJ IDEA
* Description:
* User:Zyt
* Date:2020-07-15
*/
public class ArticleDao {
public void add(Article article){
Connection connection = DButil.getConnection();
String sql = "insert into article values (null,?,?,?)";
PreparedStatement statement = null;
try {
statement = connection.prepareStatement(sql);
statement.setString(1,article.getTitle());
statement.setString(2,article.getContent());
statement.setInt(3,article.getUserId());
int ret = statement.executeUpdate();
if (ret != 1){
System.out.println("插入文章操作失败!");
return;
}else {
System.out.println("插入文章成功!");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
DButil.close(connection,statement,null);
}
}
//查看文章列表
public List<Article> selectAll(){
List<Article> articles = new ArrayList<>();
Connection connection = DButil.getConnection();
String sql = "select articleId,title,userId from article";
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
statement = connection.prepareStatement(sql);
resultSet = statement.executeQuery();
while (resultSet.next()){
Article article = new Article();
article.setArticleId(resultSet.getInt("articleId"));
article.setTitle(resultSet.getString("title"));
article.setUserId(resultSet.getInt("userId"));
articles.add(article);
}
} catch (SQLException e) {
e.printStackTrace();
}
return articles;
}
public Article selectById(int articleId){
Connection connection = DButil.getConnection();
String sql = "select * from article where article = ?";
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
statement = connection.prepareStatement(sql);
statement.setInt(1,articleId);
resultSet = statement.executeQuery();
if (resultSet.next()){
Article article = new Article();
article.setArticleId(resultSet.getInt("articleId"));
article.setTitle(resultSet.getString("title"));
article.setContent(resultSet.getString("content"));
article.setUserId(resultSet.getInt("userId"));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
DButil.close(connection,statement,null);
}
return null;
}
public void delete(int articleId){
Connection connection = DButil.getConnection();
String sql = "delete form article where articleId = ?";
PreparedStatement statement = null;
try {
statement = connection.prepareStatement(sql);
statement.setInt(1,articleId);
int ret = statement.executeUpdate();
if (ret != 1){
System.out.println("删除文章失败!");
}else {
System.out.println("删除文章成功!");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
DButil.close(connection,statement,null);
}
}
public static void main(String[] args) {
ArticleDao articleDao = new ArticleDao();
}
}
5. 针对刚才的数据库操作进行简单的测试
单元测试:把一些类/方法(拥有相对完整的功能),视为一个单元,针对这个单元进行测试
单元测试一般是由开发人员进行的,测试人员很少涉及单元测试,写代码过程中,针对某些胆码,写完了之后,立刻来测一下功能。
最大的好处能够尽早发现 bug
5.1 对 userDao 进行测试
将下面注释部分代码插入到 main 函数中,注释部分解除后查看效果,测试完成后再将注释添加上去
public static void main(String[] args) {
UserDao userDao = new UserDao();
/*User user = new User();
user.setName("zyt");
user.setPassword("123");
userDao.add(user);
User user = userDao.selectByName("zyt");
System.out.println(user);*/
}
5.1.1插入新用户操作
5.1.2 测试查询操作
5.2 对 articleDao 进行测试
同理,将下面注释部分代码添加到 main 函数中查看效果
public static void main(String[] args) {
ArticleDao articleDao = new ArticleDao();
//测试新增文章
/* Article article = new Article();
article.setTitle("我是标题");
article.setContent("正文在这里正文在这里正文在这里正文在这里正文在这里正文在这里" +
"正文在这里正文在这里正文在这里正文在这里正文在这里");
article.setUserId(1);
//articleDao.add(article);
//测试查看文章列表
List<Article> articles = articleDao.selectAll();
System.out.println(articles);
//测试查看指定文章内容
Article article1 = articleDao.selectById(1);
System.out.println(article1);
//删除操作
articleDao.delete(1);*/
}
5.2.1 添加文章操作
5.2.2 查看文章列表操作
5.2.3查询文章操作
这个部分我查出了一些问题,就是发现自己在写 sql 语句的时候出现了失误,经过调试后正常运行
5.2.4删除文章操作
总结
这种测试方式还有很多不合理的地方:
- 测试过的代码需要注释掉
- 有些测试需要结合数据库来查看(不能自动检测出结果)
- 想进行一些更复杂的操作(重复执行 N 次,打乱顺序执行用例等)都不能完成
JUint 可以解决以上问题
6. 进行前后端接口操作
6.1设计接口
6.1.1获取注册页面
请求:GET / register.html
响应:注册页面上带有两个输入框(输入用户名和密码),一个提交按钮
6.1.2 实现注册功能(注册页面中提交的form 表单数据,需要服务器处理)
请求:POST / register
name=zzz&password=123
响应:返回一个提示页面,告诉用户注册成功还是失败,并且能够跳转登录页面
6.1.3 获取登录页面
请求:GET / login.html
响应:返回一个登录页面,包含两个输入框和登录按钮
6.1.4 实现登录功能(登录页面提交的数据,需要服务器来处理)
请求:POST / login
name=zzz&password=123
响应:返回一个提示页面,告诉用户登录还是失败
6.1.5 获取文章列表
请求:GET / article
响应:返回文章的列表页(包含文章的标题,点击标题,进入到详情页)
6.1.6 获取文章详细内容
请求:GET / atticle?articleId=1
响应:返回文章的详情页(包含文章的内容)
6.1.7 发布文章
请求:POST / article
title=xxx&content=xxxxxx
响应:返回一个提示页面,告诉用户发布成功还是失败
6.1.8 删除文章
请求:GET / deleteArticle?articleId=1
响应:返回一个提示页面,告诉用户删除成功还是失败
接口设计和数据库设计类似,都是从“需求”中提取出来的,当产品需求确定了,就可以进行这样的接口设计以及数据库设计了。