- 准备工作
- 设计数据库
- 封装数据库操作
- 创建实体类
- 数据库增删查改操作(写法几乎很相似)
- 前后端交互
 
准备工作
博客管理系统前端部分在学习前端、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。降序的按时间查询



















