- 准备工作
- 设计数据库
- 封装数据库操作
- 创建实体类
- 数据库增删查改操作(写法几乎很相似)
- 前后端交互
准备工作
博客管理系统前端部分在学习前端、css、js部分实现;现在我们将完成后端工作;并且部署云服务上;使其能让所有联网的人使用。
创建maven项目;先导入三个依赖;jackson;mysql;servlet;
<dependencies>
<!-- 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/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
</dependencies>
把前端代码;复制进去webapp;如果直接复制blog到时候代码结构比较难写路径;所以直接把这里面复制进去简单
还有给tomcat的投名状;不然怎么让tomcat带着我们项目跑呢;
设计数据库
分析:博客系统涉及两个实体;博客、用户。创建两张表;表示博客和用户;关系是一对多:一个用户可以写多篇博客;一个博客只能属于一个用户。
blog(blogId,title,content,postTime,userId)
user(userId,username,password)
有了上次表白墙的经验:这一次就知道把sql语句保存在文件里;当我们要部署云服务器上直接复制、粘贴就能完成建库建表操作。
-- 写sql语句;把这些保存下来;当需要部署在其它机器就直接复制粘贴即可
create database if not exists liao_blog;
--删除旧;创建新的;防止之前的残留数据对我们的影响;严谨起见
--创建这个数据库和选中这个数据库
use liao_blog;
drop table if exists user;
drop table if exists blog;
--创建表;
create table blog(
--设置主键
blogId int primary key auto_incremaet,
title varchar(128),
--正文
content varchar(4096),
--发布时间
postTome datetime,
userId int
);
create table user(
userId int primary key auto_incremaet,
username varchar(20) unique,--要求用户名不重复
password varchar(20)
);
封装数据库操作
前面的数据库版本表白墙程序也是有这个操作:一模一样的代码封装;以后我们使用的是框架;不需要再像这样子手动封装。
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
//数据库的连接;把连接的过程封装一下;作为工具类;提供一些static让我们使用更方便
public class DBlianjie {
// 静态成员相当于饿汉单例模式
public static DataSource dataSource=new MysqlDataSource();
//初始化DataSource
static {
((MysqlDataSource)dataSource).setURL("jdbc:mysql://127.0.0.1:3306/liao?characterEncoding=utf8&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("111111");
}
// 通过这个方法建立连接
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
//通过这个方法释放资源
public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet){
// 此处的三个 try catch 分开写更好, 避免前面的异常导致后面的代码不能执行.
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();
}
}
}
}
创建实体类
实体类:和表中的记录对应的类;和表的结构 (列)密切相关
blog表:Blog类对应的一个对象;对应是表中的一条记录;就是一篇博客。
user表:User类对应的一个对象;对应是表中的一个记录;就是一个用户
import java.sql.Timestamp;
//都生成get和set方法;用来获取博客的内容。
public class Blog {
private int blogId;
private String tatle;
private String content;
private Timestamp postTimel;
private int userId;
public int getBlogId() {
return blogId;
}
public void setBlogId(int blogId) {
this.blogId = blogId;
}
public String getTatle() {
return tatle;
}
public void setTatle(String tatle) {
this.tatle = tatle;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Timestamp getPostTimel() {
return postTimel;
}
public void setPostTimel(Timestamp postTimel) {
this.postTimel = postTimel;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
}
public class User {
private int userId;
private String username;
private String password;
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
数据库增删查改操作(写法几乎很相似)
我们需要想清楚;哪些需要存数据在数据库;哪里需要从数据库取数据库;存的时候传blog对象是存的对象。取的时候就把数据赋值于一个blog对象;到时候我们根据这个方法获取到这个对象就能取到我们想要的东西。
(比如点击发布文章;点击查看全文;博客列表页的内容;删除博客)我们通过的就是实体类来当一个中介的角色;要存什么放这个类的对象里再存;要取什么取到的结果放这个对象里。
Dao:Data Access Object访问数据的对象。
博客表:创建BlogDao
用户表:创建UserDao
import java.util.List;
//通过这个类对博客表的基本操作
public class BlogDao {
//1.新增博客
public void add(Blog blog){
//这两个的声明在这里为了提升作用域;免得在finally里没法用这个对象.
Connection connection=null;
PreparedStatement statement=null;
try{
//1:创建数据库连接
connection=DBlianjie.getConnection();
//2:构造sql;自增主键不用我们设置;所以就是null;
String sql="insert into blog values(null,?,?,?,?)";
//进行替换;来源;存的过程我们要在Blog获取。
// Blog的初始化内容来自用户前端博客;就是我们调用这个方法;会传一个Blog对象;是已经初始化后的
statement.setString(1,blog.getTitle());
statement.setString(2,blog.getContent());
statement.setTimestamp(3,blog.getPostTimel());
statement.setInt(4,blog.getUserId());
//3:执行sql;把这个东西存到数据库
statement.executeUpdate();
}catch (SQLException e){
e.printStackTrace();
}finally {
//4:释放资源;没用到的写null
DBlianjie.close(connection,statement,null);
}
}
// 2.根据博客id查询指定博客(博客详情页);
// 就是对应我们查看全文的按钮后显示结果(相当于从数据库取东西;根据博客id进行条件查询想要的博客)
public Blog selectById(int blogId){
Connection connection=null;
PreparedStatement statement=null;
ResultSet resultSet=null;
try{
// //1:建立连接
connection=DBlianjie.getConnection();
//2:构造sql
String sql = "select * from blog where blogId = ?";
statement=connection.prepareStatement(sql);
// //进行替换
statement.setInt(1,blogId);
//3:执行
resultSet = statement.executeQuery();
// 4. 遍历结果集合. 由于 blogId 在 blog 表中是唯一的. (主键);
// 说明我们这里查到数据只可能是一条;不需要循环。if就好了;
// 我们就把结果用来初始化一个blog对象;进行返回。我们就根据这个对象获取里面博客的内容
if(resultSet.next()){
Blog blog = new Blog();
blog.setBlogId(resultSet.getInt("blogId"));
blog.setTitle(resultSet.getString("title"));
blog.setContent(resultSet.getString("content"));
blog.setPostTime(resultSet.getTimestamp("postTime"));
blog.setUserId(resultSet.getInt("userId"));
return blog;
}
}
catch (SQLException e){
e.printStackTrace();
}
finally {
DBlianjie.close(connection,statement,resultSet);
}
//没有查到这个博客id的博客
return null;
}
// 3.查询数据库中的所以博客列表(博客列表页)
public List<Blog> selectAll(){
Connection connection=null;
PreparedStatement statement=null;
ResultSet resultSet=null;
//创建一个list储存这很多篇博客
List<Blog> blogs=new ArrayList<>();
try {
//1:建立连接
connection=DBlianjie.getConnection();
//2:构造sql语句;这里是查询所有博客的内容取前100个字进行摘要;所以是全列查询
String sql="select * from blog";
statement= connection.prepareStatement(sql);
//3:执行sql
resultSet=statement.executeQuery();
//4:遍历结果集;把遍历的结果每一篇都放一个blog对象;
// 每循环一次就创建这样子的一个对象;最后放list<blog>
while (resultSet.next()){
Blog blog=new Blog();
blog.setBlogId(resultSet.getInt("blogId"));
blog.setTitle(resultSet.getString("title"));
//这里正文部分;因为是粗略显示;全部显示是在博客详情页;所以我们对读取的内容进行截取
String content=resultSet.getString("content");
if(content.length()>=100){
content=content.substring(0,100)+" ……";
}
blog.setContent(content);
blog.setPostTime(resultSet.getTimestamp("postTime"));
blog.setUserId(resultSet.getInt("userId"));
blogs.add(blog);
}
}catch (SQLException e){
e.printStackTrace();
}
finally {
DBlianjie.close(connection,statement,resultSet);
}
return blogs;
}
//4.删除指定博客
public void delete(int blogId){
Connection connection=null;
PreparedStatement statement=null;
try{
//1:建立连接
connection=DBlianjie.getConnection();
//2:构造sql
String sql="delete from blog where blogId=?";
statement=connection.prepareStatement(sql);
//3:执行sql
statement.executeUpdate();
}catch (SQLException e){
e.printStackTrace();
}finally {
DBlianjie.close(connection,statement,null);
}
}
// 暂时不涉及修改博客;(修改、可以通过删除和新增;前端没有这样的功能)
}
userDao:这个实体类的对应是和创建表时的密切相关
因为当前注册功能待开发中:注册和登录实现差不多;先不搞add先;也没有用户删号功能. 也就不必 delete
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
//针对用户表的基本操作
public class UserDao {
// 1. 根据 userId 来查用户信息;我好像还不知道这个是干嘛的;后面就知道了
public User selectById(int userId){
Connection connection=null;
PreparedStatement statement=null;
ResultSet resultSet=null;
try{
//1:建立连接
connection =DBlianjie.getConnection();
//2:构造sql
String sql="select * from user where userId=?";
statement=connection.prepareStatement(sql);
statement.setInt(1,userId);
//3:执行sql
resultSet=statement.executeQuery();
//4:遍历结果集;一条记录还是用if就可以
if(resultSet.next()){
User user=new User();
user.setUserId(resultSet.getInt("userId"));
user.setUsername(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
return user;
}
}catch (SQLException e){
e.printStackTrace();
}finally {
DBlianjie.close(connection, statement, resultSet);
}
return null;
}
// 2. 根据 username 来查用户信息 (登录的时候)
public User SelectByUsername(String username){
Connection connection=null;
PreparedStatement statement=null;
ResultSet resultSet=null;
try{
//1:建立连接
connection =DBlianjie.getConnection();
//2:构造sql
String sql="select * from user where username=?";
statement=connection.prepareStatement(sql);
statement.setString(1,username);
//3:执行sql
resultSet=statement.executeQuery();
//4:遍历结果集;一条记录还是用if就可以
if(resultSet.next()){
User user=new User();
user.setUserId(resultSet.getInt("userId"));
user.setUsername(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
return user;
}
}catch (SQLException e){
e.printStackTrace();
}finally {
DBlianjie.close(connection, statement, resultSet);
}
return null;
}
}
上述就把后续会用到的操作封装成方法;后面就能直接使用。
前后端交互
第一个页面实现:
在进行前端的学习时;写的这个博客列表页数据是写死的。正确的做法是通过数据库读取数据显示页面上。
前后端交互;让博客列表页;加载这个页面的时候通过ajax等给服务器发请求;服务器查数据库获取博客列表页数据;返回给浏览器’;浏览器根据这些数据构造页面内容。(在没有前后端分离;我们页面是后端生成;返回整个页面给浏览器;前后端分离;解耦的同时;我们只需要返回数据由前端构造页面;大大提升效率;也减轻服务器的工作量)
三步走:不同情况可能开发前端和开发后端代码的顺序是不一样的
1:约定前后端交互接口
有什么功能;前端要发什么请求;后端要返回什么响应;交互的格式。这里需要注意五个部分;地址栏URL(html页面)、查看全文按钮、右上角的三个按钮。
URL:我们希望通过输入这个html页面然后就能发送一个请求给服务器;获得响应。这个只是普通的页面;这里的URL并不是关联到服务器的注解的路径(如果是直接关联;那就不输入URL就直接是发送一个GET请求);所以我们得在前端页面里构建这个请求。
请求 GET /blog(/blog是对应我们doGet注解的路径)
响应:使用json组织;数组的形式返回
[
{
blogId:1,
title:“我的第一篇博客”
contet:“第一次变成这样的我;让我怎么去否认……”
postTime:“2023-5-6 17:12:00”
userId:1
},
{
blogId:2,
title:“我的第二篇博客”
contet:“第二次变成这样的我;让我怎么去否认……”
postTime:“2023-5-6 17:12:00”
userId:1
},
{
blogId:3,
title:“我的第三篇博客”
contet:“第三次变成这样的我;让我怎么去否认……”
postTime:“2023-5-6 17:12:00”
userId:1
}
]
2:开发后端代码
处理这个输入URL的get请求;查数据库的过程。(写到这里就发现一个问题;光是管理和操作数据的类就有五个;再把servlet类写上去不就乱套了吗;分类一下;把这些类都放包里‘分别加上package model’)
所以创建多两个包:model;模型;管理和操作数据的部分。api:存放servle相关的类
package api;
import com.fasterxml.jackson.databind.ObjectMapper;
import model.Blog;
import model.BlogDao;
import model.User;
import javax.servlet.ServletException;
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.util.List;
@WebServlet(("/blog"))
public class BlogServlet extends HttpServlet {
//定义在这里;别的方法也能用这个对象
public ObjectMapper objectMapper=new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//现在需要拿到数据库的数据;然后转成json格式;写回给前端
//这里转成json格式就需要用到我们在准备工作时导入jackson依赖
BlogDao blogDao = new BlogDao();
//这个方法就会把数据库查到的所有博客返回List里
List<Blog> blogs=blogDao.selectAll();
//转成json
String respjson=objectMapper.writeValueAsString(blogs);
//一定不要忘记设置;告诉浏览器你返回的是什么类型和数据字符集,不然浏览器是不知道;还以为你的是普普通通字符串
resp.setContentType("application/json;charset=utf8");
resp.getWriter().write(respjson);
}
}
3:开发前端代码;
请求的构造;
响应的结果重新构造页面:把我们之前写死的数据进行重新构造;我们自己用ajax构造GET请求(地址栏输入也是能触发这个GET请求)
留下一份作为参考;这是我们最终要构造成的文章样式
正文部分完成;
经过一波三折的测试;调试取得
目前两个bug:
1:时间戳问题;是应该显示系统时间;而不是时间戳。
这里格式化字符串;一定不要去背;因为不同语言、不同库;格式化时间的操作可能不同;上网查一查才是最稳的。
想想有什么办法能解决这个问题:
方法1:不用数据库的时间戳;使用idea的;但是这样子会数据库时间设置不进去;我们数据库的类型修改才string存进去就好了。或者是再写一个设置的时间是时间戳格式。一个用来设置进入数据库;一个后来返回响应。
这样子时间还是同一份;不同的get方法获取到的格式是不一样的;这个方法虽然显示没有调用;但是在objectMapper.writeValueAsString把这个对象转成json时;这个get方法会有用的。
最好的方法还是;获取idea的时间戳;然后转成string格式化;设置进数据库时使用statement.setString();数据库里就不用postTime类型;存的时候也不用存now()。直接是当个字符串.。取就按照字符串来取。
2:新的博客应该在上面的;老的在下面。
博客顺序问题简单:加个order by postTime desc。降序的按时间查询