继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

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

程序员大阳
关注TA
已关注
手记 357
粉丝 1.5万
获赞 1523

前情回顾

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

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

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

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

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

概述

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

数据库: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),负责操作数据库

新建项目

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

  1. commons-logging-1.2.jar 日志相关
  2. jackson-annotations-2.8.0.jar json相关
  3. jackson-core-2.8.0.jar json相关
  4. jackson-databind-2.8.0.jar json相关
  5. mysql-connector-java-5.1.48.jar mysql驱动
  6. druid-1.1.21.jar 数据库连接池

新建包及目录、文件

建立如下图项目结构:
在这里插入图片描述

完整开发过程

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

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
}
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<String, Object> 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<String, Object> map = new HashMap<>();
		map.put("id", id);
		namedTemplate.update("delete from blog where id =:id", map);
	}
	/**
	 * 更新
	 */
	public void update(BlogDo blog) {
		Map<String, Object> 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<String, Object> map = new HashMap<>();
		map.put("id", id);
		return namedTemplate.queryForObject("select * from blog where id=:id", map, new RowMapper<BlogDo>() {
			@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<BlogDo> getList() {
		return namedTemplate.query("select * from blog", new RowMapper<BlogDo>() {
			@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;
			}
		});
	}
}
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<BlogDo> 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);
	}
}
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<BlogDo> 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、编写html页面,调用api接口完成操作

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

<!DOCTYPE html>
<html>

<head>
	<meta charset="UTF-8">
	<title></title>
	<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
	<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"
		integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>

<body>
	<nav class="navbar navbar-inverse">
		<div class="container-fluid">
			<ul class="nav navbar-nav">
				<li><a href="#" onclick="viewBlogs()">浏览博客</a></li>
				<li><a href="#" onclick="addBlog()">新增博客</a></li>
			</ul>
		</div>
	</nav>
	<table id="blogTable" class="table table-striped">
		<tr>
			<th>ID</th>
			<th>标题</th>
			<th>作者</th>
			<th>操作</th>
		</tr>
	</table>
	<!-- 新增弹窗 -->
	<div id="blogAddModal" class="modal fade" tabindex="-1" role="dialog">
		<div class="modal-dialog" role="document">
			<div class="modal-content">
				<div class="modal-header">
					<h4 class="modal-title">新增博客</h4>
				</div>
				<div class="modal-body" style="padding:16px;">
					<!-- 新增博客的表单 -->
					<form>
						<div class="form-group">
							<label>标题</label>
							<input name="title" type="text" class="form-control">
						</div>
						<div class="form-group">
							<label>内容</label>
							<textarea name="content" class="form-control" rows="3"></textarea>
						</div>
						<div class="form-group">
							<label>作者</label>
							<input name="author" type="text" class="form-control">
						</div>
					</form>
				</div>
				<div class="modal-footer">
					<button type="button" class="btn btn-primary" onclick="addBlogSubmit()">提交</button>
					<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
				</div>
			</div>
		</div>
	</div>
	<!-- 编辑弹窗 -->
	<div id="blogEditModal" class="modal fade" tabindex="-1" role="dialog">
		<div class="modal-dialog" role="document">
			<div class="modal-content">
				<div class="modal-header">
					<h4 class="modal-title">编辑博客</h4>
				</div>
				<div class="modal-body" style="padding:16px;">
					<!-- 编辑博客的表单 -->
					<form>
						<div class="form-group">
							<label>ID</label>
							<input name="id" type="text" class="form-control" readonly>
						</div>
						<div class="form-group">
							<label>标题</label>
							<input name="title" type="text" class="form-control">
						</div>
						<div class="form-group">
							<label>内容</label>
							<textarea name="content" class="form-control" rows="3"></textarea>
						</div>
						<div class="form-group">
							<label>作者</label>
							<input name="author" type="text" class="form-control">
						</div>
					</form>
				</div>
				<div class="modal-footer">
					<button type="button" class="btn btn-primary" onclick="editBlogSubmit()">提交</button>
					<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
				</div>
			</div>
		</div>
	</div>
</body>
<!--jQuery-->
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"
	integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous">
</script>
<script>
	//浏览博客
	function viewBlogs() {
		var row = "";
		//先清空表格
		$('#blogTable').find("tr:gt(0)").remove();
		$.ajax({
			type: "GET",
			url: "/restfulblog/blog",
			dataType: "json",
			contentType: "application/json; charset=utf-8",
			success: function (res) {
				$.each(res, function (i, v) {
					row = "<tr>";
					row += "<td>" + v.id + "</td>";
					row += "<td>" + v.title + "</td>";
					row += "<td>" + v.author + "</td>";
					row +=
						"<td><a class='btn btn-primary btn-sm' href='#' onclick='editBlog(" + v.id +
						")'>编辑</a>";
					row +=
						"<a class='btn btn-danger btn-sm' href='#' onclick='deleteBlog(" + v.id +
						")'>删除</a></td>";
					row += "</tr>";
					$("#blogTable").append(row);
				});
			},
			error: function (err) {
				console.log(err);
			}
		});
	}
	//新增
	function addBlog() {
		$('#blogAddModal').modal('show');
	}
	//新增提交
	function addBlogSubmit() {
		var data = {
			id: '',
			title: $("#blogAddModal input[name='title']").val(),
			author: $("#blogAddModal input[name='author']").val(),
			content: $("#blogAddModal textarea[name='content']").val()
		};
		$.ajax({
			type: "POST",
			url: "/restfulblog/blog",
			//dataType: "json",
			contentType: "application/json; charset=utf-8",
			data: JSON.stringify(data), //需要将对象转换为字符串提交
			success: function () {
				//新增后重新加载
				viewBlogs();
				//关闭弹窗
				$('#blogAddModal').modal('hide');
			},
			error: function (err) {
				console.log(err);
			}
		});
	}
	//编辑
	function editBlog(id) {
		//查询博客信息
		$.ajax({
			type: "GET",
			url: "/restfulblog/blog/" + id,
			dataType: "json",
			contentType: "application/json; charset=utf-8",
			success: function (res) {
				console.log(res);
				//为编辑框赋值
				$("#blogEditModal input[name='id']").val(res.id);
				$("#blogEditModal input[name='title']").val(res.title);
				$("#blogEditModal input[name='author']").val(res.author);
				$("#blogEditModal textarea[name='content']").val(res.content);
				//显示编辑弹窗
				$('#blogEditModal').modal('show');
			},
			error: function (err) {
				console.log(err);
			}
		});
	}
	//编辑提交
	function editBlogSubmit() {
		var data = {
			id: $("#blogEditModal input[name='id']").val(),
			title: $("#blogEditModal input[name='title']").val(),
			author: $("#blogEditModal input[name='author']").val(),
			content: $("#blogEditModal textarea[name='content']").val()
		};
		$.ajax({
			type: "PUT",
			url: "/restfulblog/blog/" + data.id,
			//dataType: "json",
			contentType: "application/json; charset=utf-8",
			data: JSON.stringify(data), //需要将对象转换为字符串提交
			success: function () {
				//新增后重新加载
				viewBlogs();
				//关闭弹窗
				$('#blogEditModal').modal('hide');
			},
			error: function (err) {
				console.log(err);
			}
		});
	}
	//删除
	function deleteBlog(id) {
		$.ajax({
			type: "DELETE",
			url: "/restfulblog/blog/" + id,
			//dataType: "json",//由于删除方法无返回值,所以此处注释掉
			contentType: "application/json; charset=utf-8",
			success: function () {
				//删除后重新加载
				viewBlogs();
			},
			error: function (err) {
				console.log(err);
			}
		});
	}
</script>
</html>
6、编写springmvc-config.xml

上面我们使用到的bean包括:namedParameterJdbcTemplateblogDaoblogServiceblogController,此外namedParameterJdbcTemplate还需要注入dataSource

其中blogDaoblogServiceblogController是使用注解标识的,我们可以直接开启扫描即可。namedParameterJdbcTemplatedataSource是Spring提供的类,我们需要在xml手工配置。

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

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	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">
	<!-- 扫描org.maoge.sjblog下所有包 -->
	<context:component-scan
		base-package="org.maoge.sjblog" />
	<!--注解驱动,开启通过注解配置访问路径与方法的匹配 -->
	<mvc:annotation-driven />
	<!--静态资源映射 -->
	<mvc:resources mapping="/static/**" location="/static/" />
	<!-- 数据源 -->
	<bean id="dataSource"
		class="com.alibaba.druid.pool.DruidDataSource">
		<property name="driverClassName"
			value="com.mysql.jdbc.Driver"></property>
		<!-- 注意xml中使用&amp;替代& -->
		<property name="url"
			value="jdbc:mysql://127.0.0.1:3306/myblog?useUnicode=true&amp;characterEncoding=utf-8"></property>
		<property name="username" value="root"></property>
		<property name="password" value="XXX"></property>
	</bean>
	<!--注册namedTemplate组件 -->
	<bean id="namedTemplate"
		class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
		<!-- 注入dataSource,注意org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate需要通过构造方法注入dataSource,没有提供setter注入方法 -->
		<constructor-arg index="0" ref="dataSource"></constructor-arg>
	</bean>
</beans>
7、编写web.xml

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

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	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">
	<display-name>springjdbcblog</display-name>
	<servlet>
		<servlet-name>springmvc</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>/WEB-INF/springmvc-config.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>springmvc</servlet-name>
		<url-pattern>/*</url-pattern>
	</servlet-mapping>
</web-app>

测试

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

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

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP