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. 新建包及目录、文件
建立如下图项目结构:
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这样先进滴数据库连接池,稳啊!