Spring之路(32)–SpringMVC+SpringJDBC+jQuery+Bootstrap博客系统完整实例

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
云数据库 RDS PostgreSQL,高可用系列 2核4GB
简介: 本文目录1. 前情回顾2. 概述3. 新建项目4. 新建包及目录、文件5. 完整开发过程5.1 封装数据对象5.2 封装数据访问层,并注册为bean5.3 编写服务类,封装对博客的操作方法5.4 编写控制器,提供http接口5.5 编写html页面,调用api接口完成操作5.6 编写springmvc-config.xml5.7 编写web.xml6. 测试

1. 前情回顾

之前我们已经用SpringMVC+JSP+Boostrap+原生JDBC实现过博客系统。


然后我们将其改为了前后端分离的Spring Restful+jQuery+Bootstrap+原生JDBC实现的博客系统。


本篇我们将完整的实现一个Spring+SpringMVC+SpringJDBC+jQuery+Bootstrap的博客系统,说的很复杂,实际上后端还是Restful风格的API,前端还是通过jQuery调用后端,Bootstrap仅负责页面样式。


OK,通过本篇希望大家将之前的知识点都串起来,同时我们的项目规范上也更加贴近项目实战多一些。


OK,前情回顾完毕,开整~~


2. 概述

功能:很简单,就是实现博客的增删改查。


数据库:MySQL,表结构如下:

CREATE TABLE `blog` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '唯一标志',
  `title` varchar(255) DEFAULT '' COMMENT '标题',
  `author` varchar(255) DEFAULT '' COMMENT '作者姓名',
  `content` longtext COMMENT '内容',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=35 DEFAULT CHARSET=utf8;


/

项目分层:

视图层(Html+Bootstrap+jQuery),负责页面显示

控制层(Controller),负责接受页面请求,并调用服务层完成业务逻辑,最后返回数据

服务层(Service),负责调用数据访问层,完成对数据库的增删改查操作,并封装业务逻辑

数据访问层(DAO),负责操作数据库


3. 新建项目

新建Dynamic web project,项目名称【springjdbcblog】,然后将Spring相关jar包以及下面几个jar包放入lib目录下:


commons-logging-1.2.jar 日志相关

jackson-annotations-2.8.0.jar json相关

jackson-core-2.8.0.jar json相关

jackson-databind-2.8.0.jar json相关

mysql-connector-java-5.1.48.jar mysql驱动

druid-1.1.21.jar 数据库连接池

4. 新建包及目录、文件

建立如下图项目结构:

image.png

5. 完整开发过程

本次我们按照从底层到外层的开发方式,也就是从数据对象–数据访问层–服务层–控制器层–视图页面的顺序。


5.1 封装数据对象

数据对象与数据库表是一一对应关系,根据表中的列设计数据对象类的属性即可,代码如下:

package org.maoge.sjblog.xdo;
/**
 * @theme 数据对象--博客
 * @author maoge
 * @date 2020-01-29
 */
public class BlogDo {
  private Long id;
  private String title;
  private String author;
  private String content;
  // 省略get get
}
5.2 封装数据访问层,并注册为bean
我们将博客数据访问层封装为BlogDao,并通过@Repositoy注册为bean,然后通过@Autowired注入jdbcTemplate对象。
package org.maoge.sjblog.dao;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.maoge.sjblog.xdo.BlogDo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;
/**
 * @theme DAO--博客
 * @author maoge
 * @date 2020-01-29
 */
@Repository // 注册为组件
public class BlogDao {
  @Autowired // 自动注入
  private NamedParameterJdbcTemplate namedTemplate;
  /**
  * 新增
  */
  public void insert(BlogDo blog) {
  Map map = new HashMap<>();
  map.put("author", blog.getAuthor());
  map.put("content", blog.getContent());
  map.put("title", blog.getTitle());
  // 注意使用:xxx占位
  namedTemplate.update("insert into blog(author,content,title)values(:author,:content,:title)", map);
  }
  /**
  * 删除
  */
  public void delete(Long id) {
  Map map = new HashMap<>();
  map.put("id", id);
  namedTemplate.update("delete from blog where id =:id", map);
  }
  /**
  * 更新
  */
  public void update(BlogDo blog) {
  Map map = new HashMap<>();
  map.put("author", blog.getAuthor());
  map.put("content", blog.getContent());
  map.put("title", blog.getTitle());
  map.put("id", blog.getId());
  namedTemplate.update("update blog set author=:author,content=:content,title=:title where id=:id", map);
  }
  /**
  * 按id查询
  */
  public BlogDo getById(Long id) {
  Map map = new HashMap<>();
  map.put("id", id);
  return namedTemplate.queryForObject("select * from blog where id=:id", map, new RowMapper() {
    @Override
    public BlogDo mapRow(ResultSet rs, int rowNum) throws SQLException {
    BlogDo blog = new BlogDo();
    blog.setAuthor(rs.getString("author"));
    blog.setContent(rs.getString("content"));
    blog.setId(rs.getLong("id"));
    blog.setTitle(rs.getString("title"));
    return blog;
    }
  });
  }
  /**
  * 查询列表
  */
  public List getList() {
  return namedTemplate.query("select * from blog", new RowMapper() {
    @Override
    public BlogDo mapRow(ResultSet rs, int rowNum) throws SQLException {
    BlogDo blog = new BlogDo();
    blog.setAuthor(rs.getString("author"));
    blog.setContent(rs.getString("content"));
    blog.setId(rs.getLong("id"));
    blog.setTitle(rs.getString("title"));
    return blog;
    }
  });
  }
}




5.3 编写服务类,封装对博客的操作方法

直接我们是在控制器中直接调用数据接口层,这种封装会导致当业务逻辑比较复杂时,控制器层会有很多操作代码。


实际上控制器层应该只负责接受输入,调用方法完成业务逻辑,然后返回结果。


DAO层方法只是简单的对数据源完成增删改查等操作,显然不足以支撑业务逻辑。


所以一般在控制器层和DAO层之间,有一个服务层,封装业务逻辑和对数据源的操作,此处编写一个BlogService类实现该部分功能。

package org.maoge.sjblog.service;
import java.util.List;
import org.maoge.sjblog.dao.BlogDao;
import org.maoge.sjblog.xdo.BlogDo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
 * @theme 服务--博客
 * @author maoge
 * @date 2020-01-27
 */
@Service//替代@Component
public class BlogService {
  @Autowired // 自动注入BlogDao组件
  private BlogDao blogDao;
  /**
  * 获取博客列表
  */
  public List getBlogList() {
  return blogDao.getList();
  }
  /**
  * 按id获取博客信息
  */
  public BlogDo getBlogById(Long id) {
  return blogDao.getById(id);
  }
  /**
  * 新增博客
  */
  public void addBlog(BlogDo blog) {
  blogDao.insert(blog);
  }
  /**
  * 根据博客id更新博客信息
  */
  public void updateBlog(BlogDo blog) {
  blogDao.update(blog);
  }
  /**
  * 根据博客id删除对应博客
  */
  public void deleteBlog(Long id) {
  blogDao.delete(id);
  }
}
5.4 编写控制器,提供http接口
然后我们编写BlogController,按照Restful风格提供http接口,代码如下:
package org.maoge.sjblog.controller;
import java.util.List;
import org.maoge.sjblog.service.BlogService;
import org.maoge.sjblog.xdo.BlogDo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
 * @theme 控制器--博客
 * @author maoge
 * @date 2020-01-28
 */
@RestController // 通过该注解,第一将BlogController注册为控制器,第二将其中方法返回值转换为json
public class BlogController {
  @Autowired // 自动装配blogService
  private BlogService blogService;
  /**
  * 查询博客信息
  * 1、@GetMapping表示可以使用get方法请求该api
  * 2、"/blog/{id}"表示请求路径为/blog/{id}的形式,其中{id}为占位符
  * 3、@PathVariable("id")表示将占位符{id}的值传递给id
  * 4、也就是说/blog/123请求的话,会将123传递给参数id
  */
  @GetMapping(value="/blog/{id}")
  public BlogDo getOne(@PathVariable("id") long id) {
  return blogService.getBlogById(id);
  }
  /**
  * 查询博客列表,使用get方法
  */
  @GetMapping("/blog")
  public List getList(){
  return blogService.getBlogList();
  }
  /**
  * 新增博客
  * 1、@PostMapping表示使用post方法
  * 2、@RequestBody表示将请求中的json信息转换为BlogDo类型的对象信息,该转换也是由SpringMVC自动完成的
  */
  @PostMapping("/blog")
  public void add(@RequestBody BlogDo blog) {
  blogService.addBlog(blog);
  }
  /**
  * 修改博客
  * 实际上此处也可以不在路径中传递id,而是整个使用json传递对象信息,但是我查询了一些文档,貌似使用路径传递id更加规范一些,此处不用纠结
  */
  @PutMapping("/blog/{id}")
  public void update(@PathVariable("id") long id,@RequestBody BlogDo blog) {
  //修改指定id的博客信息
  blog.setId(id);
  blogService.updateBlog(blog);
  }
  /**
  * 删除博客
  */
  @DeleteMapping("/blog/{id}")
  public void delete(@PathVariable("id") long id) {
  blogService.deleteBlog(id);
  }
}



5.5 编写html页面,调用api接口完成操作

编写html页面,通过jQuery的ajax调用BlogController里面的api方法,代码如下:

integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<div> integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></div><div>
</div><div> //浏览博客</div><div> function viewBlogs() {</div><div>  var row = "";</div><div>  //先清空表格</div><div>  $('#blogTable').find("tr:gt(0)").remove();</div><div>  $.ajax({</div><div>   type: "GET",</div><div>   url: "/restfulblog/blog",</div><div>    dataType: "json",</div><div>    contentType: "application/json; charset=utf-8",</div><div>    success: function (res) {</div><div>    $.each(res, function (i, v) {</div><div>      row = "<tr>";</div><div>      row += "<td>" + v.id + "</td>";</div><div>      row += "<td>" + v.title + "</td>";</div><div>     row += "<td>" + v.author + "</td>";</div><div>      row +=</div><div>      "<td><a class='btn btn-primary btn-sm' href='#' οnclick='editBlog(" + v.id +</div><div>      ")'>编辑</a>";</div><div>     row +=</div><div>      "<a class='btn btn-danger btn-sm' href='#' οnclick='deleteBlog(" + v.id +</div><div>      ")'>删除</a></td>";</div><div>     row += "</tr>";</div><div>      $("#blogTable").append(row);</div><div>    });</div><div>   },</div><div>   error: function (err) {</div><div>    console.log(err);</div><div>    }</div><div>  });</div><div>  }</div><div>  //新增</div><div> function addBlog() {</div><div>  $('#blogAddModal').modal('show');</div><div> }</div><div>  //新增提交</div><div> function addBlogSubmit() {</div><div>  var data = {</div><div>    id: '',</div><div>    title: $("#blogAddModal input[name='title']").val(),</div><div>   author: $("#blogAddModal input[name='author']").val(),</div><div>   content: $("#blogAddModal textarea[name='content']").val()</div><div>  };</div><div>  $.ajax({</div><div>   type: "POST",</div><div>    url: "/restfulblog/blog",</div><div>    //dataType: "json",</div><div>    contentType: "application/json; charset=utf-8",</div><div>    data: JSON.stringify(data), //需要将对象转换为字符串提交</div><div>    success: function () {</div><div>    //新增后重新加载</div><div>    viewBlogs();</div><div>    //关闭弹窗</div><div>    $('#blogAddModal').modal('hide');</div><div>   },</div><div>   error: function (err) {</div><div>    console.log(err);</div><div>    }</div><div>  });</div><div>  }</div><div>  //编辑</div><div> function editBlog(id) {</div><div>  //查询博客信息</div><div>  $.ajax({</div><div>    type: "GET",</div><div>   url: "/restfulblog/blog/" + id,</div><div>    dataType: "json",</div><div>    contentType: "application/json; charset=utf-8",</div><div>    success: function (res) {</div><div>    console.log(res);</div><div>    //为编辑框赋值</div><div>    $("#blogEditModal input[name='id']").val(res.id);</div><div>    $("#blogEditModal input[name='title']").val(res.title);</div><div>    $("#blogEditModal input[name='author']").val(res.author);</div><div>    $("#blogEditModal textarea[name='content']").val(res.content);</div><div>    //显示编辑弹窗</div><div>    $('#blogEditModal').modal('show');</div><div>    },</div><div>   error: function (err) {</div><div>    console.log(err);</div><div>    }</div><div>  });</div><div>  }</div><div>  //编辑提交</div><div> function editBlogSubmit() {</div><div>  var data = {</div><div>   id: $("#blogEditModal input[name='id']").val(),</div><div>    title: $("#blogEditModal input[name='title']").val(),</div><div>    author: $("#blogEditModal input[name='author']").val(),</div><div>    content: $("#blogEditModal textarea[name='content']").val()</div><div>  };</div><div>  $.ajax({</div><div>    type: "PUT",</div><div>   url: "/restfulblog/blog/" + data.id,</div><div>   //dataType: "json",</div><div>    contentType: "application/json; charset=utf-8",</div><div>    data: JSON.stringify(data), //需要将对象转换为字符串提交</div><div>    success: function () {</div><div>    //新增后重新加载</div><div>    viewBlogs();</div><div>    //关闭弹窗</div><div>    $('#blogEditModal').modal('hide');</div><div>    },</div><div>   error: function (err) {</div><div>    console.log(err);</div><div>    }</div><div>  });</div><div>  }</div><div>  //删除</div><div> function deleteBlog(id) {</div><div>  $.ajax({</div><div>   type: "DELETE",</div><div>    url: "/restfulblog/blog/" + id,</div><div>    //dataType: "json",//由于删除方法无返回值,所以此处注释掉</div><div>    contentType: "application/json; charset=utf-8",</div><div>    success: function () {</div><div>    //删除后重新加载</div><div>    viewBlogs();</div><div>    },</div><div>   error: function (err) {</div><div>    console.log(err);</div><div>    }</div><div>  });</div><div>  }</div><div>

5.6 编写springmvc-config.xml

上面我们使用到的bean包括:namedParameterJdbcTemplate、blogDao、blogService、blogController,此外namedParameterJdbcTemplate还需要注入dataSource。


其中blogDao、blogService、blogController是使用注解标识的,我们可以直接开启扫描即可。namedParameterJdbcTemplate和dataSource是Spring提供的类,我们需要在xml手工配置。


所以springmvc-config.xml编写如下:

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:mvc="http://www.springframework.org/schema/mvc"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
  base-package="org.maoge.sjblog" />
  class="com.alibaba.druid.pool.DruidDataSource">
    value="com.mysql.jdbc.Driver">
    value="jdbc:mysql://127.0.0.1:3306/myblog?useUnicode=true&characterEncoding=utf-8">
  class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">



5.7 编写web.xml

最后我们实现下web.xml,当web服务启动时配置好加载的DispatcherServlet及对应的容器配置信息。

xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
  id="WebApp_ID" version="3.1">
  springjdbcblog
 springmvc
 org.springframework.web.servlet.DispatcherServlet
  contextConfigLocation
  /WEB-INF/springmvc-config.xml
 1
 springmvc
 /*




6. 测试

部署到Tomcat上启动,界面如下,还是不错滴。


哈哈,虽然跟之前一版的博客系统从界面上讲没任何差别,但是后端代码变化不少啊,数据库操作引擎已经由自定义的DbHelper变为SpringJDBC封装的NamedParameterJdbcTemplate,而且还使用了Druid这样先进滴数据库连接池,稳啊!


相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
7月前
|
存储 Java 数据库
Spring Boot 注册登录系统:问题总结与优化实践
在Spring Boot开发中,注册登录模块常面临数据库设计、密码加密、权限配置及用户体验等问题。本文以便利店销售系统为例,详细解析四大类问题:数据库字段约束(如默认值缺失)、密码加密(明文存储风险)、Spring Security配置(路径权限不当)以及表单交互(数据丢失与提示不足)。通过优化数据库结构、引入BCrypt加密、完善安全配置和改进用户交互,提供了一套全面的解决方案,助力开发者构建更 robust 的系统。
221 0
|
4月前
|
存储 人工智能 自然语言处理
用Spring AI搭建本地RAG系统:让AI成为你的私人文档助手
想让AI帮你读懂PDF文档吗?本文教你用Spring AI和Ollama搭建一个本地RAG系统,让AI成为你的私人文档助手。无需GPU,无需云端API,只需几行代码,你的文档就能开口说话了!
|
7月前
|
存储 人工智能 Java
Spring AI与DeepSeek实战四:系统API调用
在AI应用开发中,工具调用是增强大模型能力的核心技术,通过让模型与外部API或工具交互,可实现实时信息检索(如天气查询、新闻获取)、系统操作(如创建任务、发送邮件)等功能;本文结合Spring AI与大模型,演示如何通过Tool Calling实现系统API调用,同时处理多轮对话中的会话记忆。
1282 57
|
消息中间件 存储 Java
📨 Spring Boot 3 整合 MQ 构建聊天消息存储系统
本文详细介绍了如何使用Spring Boot 3结合RabbitMQ构建高效可靠的聊天消息存储系统。通过引入消息队列,实现了聊天功能与消息存储的解耦,解决了高并发场景下直接写入数据库带来的性能瓶颈问题。文章首先分析了不同MQ产品的特点及适用场景,最终选择RabbitMQ作为解决方案,因其成熟稳定、灵活路由和易于集成等优势。接着,通过Docker快速部署RabbitMQ,并完成Spring Boot项目的配置与代码实现,包括生产者发送消息、消费者接收并处理消息等功能。最后,通过异步存储机制,既保证了消息的即时性,又实现了可靠持久化。
549 0
📨 Spring Boot 3 整合 MQ 构建聊天消息存储系统
|
5月前
|
Java 调度 流计算
基于Java 17 + Spring Boot 3.2 + Flink 1.18的智慧实验室管理系统核心代码
这是一套基于Java 17、Spring Boot 3.2和Flink 1.18开发的智慧实验室管理系统核心代码。系统涵盖多协议设备接入(支持OPC UA、MQTT等12种工业协议)、实时异常检测(Flink流处理引擎实现设备状态监控)、强化学习调度(Q-Learning算法优化资源分配)、三维可视化(JavaFX与WebGL渲染实验室空间)、微服务架构(Spring Cloud构建分布式体系)及数据湖建设(Spark构建实验室数据仓库)。实际应用中,该系统显著提升了设备调度效率(响应时间从46分钟降至9秒)、设备利用率(从41%提升至89%),并大幅减少实验准备时间和维护成本。
315 0
|
8月前
|
人工智能 自然语言处理 Java
对话即服务:Spring Boot整合MCP让你的CRUD系统秒变AI助手
本文介绍了如何通过Model Context Protocol (MCP) 协议将传统Spring Boot服务改造为支持AI交互的智能系统。MCP作为“万能适配器”,让AI以统一方式与多种服务和数据源交互,降低开发复杂度。文章以图书管理服务为例,详细说明了引入依赖、配置MCP服务器、改造服务方法(注解方式或函数Bean方式)及接口测试的全流程。最终实现用户通过自然语言查询数据库的功能,展示了MCP在简化AI集成、提升系统易用性方面的价值。未来,“对话即服务”有望成为主流开发范式。
5885 7
|
8月前
|
JSON Java 数据格式
微服务——SpringBoot使用归纳——Spring Boot中的全局异常处理——处理系统异常
本文介绍了在Spring Boot项目中如何通过创建`GlobalExceptionHandler`类来全局处理系统异常。通过使用`@ControllerAdvice`注解,可以拦截项目中的各种异常,并结合`@ExceptionHandler`注解针对特定异常(如参数缺失、空指针等)进行定制化处理。文中详细展示了处理参数缺失异常和空指针异常的示例代码,并说明了通过拦截`Exception`父类实现统一异常处理的方法。虽然拦截`Exception`可一劳永逸,但为便于问题排查,建议优先处理常见异常,最后再兜底处理未知异常,确保返回给调用方的信息友好且明确。
1106 0
微服务——SpringBoot使用归纳——Spring Boot中的全局异常处理——处理系统异常
|
8月前
|
Java 微服务 Spring
微服务——SpringBoot使用归纳——Spring Boot中使用拦截器——拦截器使用实例
本文主要讲解了Spring Boot中拦截器的使用实例,包括判断用户是否登录和取消特定拦截操作两大场景。通过token验证实现登录状态检查,未登录则拦截请求;定义自定义注解@UnInterception实现灵活取消拦截功能。最后总结了拦截器的创建、配置及对静态资源的影响,并提供两种配置方式供选择,帮助读者掌握拦截器的实际应用。
280 0
|
10月前
|
存储 安全 Java
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
715 8
|
11月前
|
Java 数据库 数据安全/隐私保护
轻松掌握Spring依赖注入:打造你的登录验证系统
本文以轻松活泼的风格,带领读者走进Spring框架中的依赖注入和登录验证的世界。通过详细的步骤和代码示例,我们从DAO层的创建到Service层的实现,再到Spring配置文件的编写,最后通过测试类验证功能,一步步构建了一个简单的登录验证系统。文章不仅提供了实用的技术指导,还以口语化和生动的语言,让学习变得不再枯燥。
205 2