springboot-综合案例
通过上面内容的学习,我们完成一个综合案例:
采用 Springboot + mybatis + redis 实现对数据库的增删改查、分页、缓存操作。
具体需求如下:
主要目的是练习Springboot如何集成各类技术进行项目开发
/*
Navicat Premium Data Transfer
Source Server : 127.0.0.1mysql5.7.27
Source Server Type : MySQL
Source Server Version : 50727
Source Host : localhost:3306
Source Schema : springbootdb
Target Server Type : MySQL
Target Server Version : 50727
File Encoding : 65001
Date: 31/08/2022 20:25:11
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id主键',
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '账号\r\n',
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码',
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '姓名',
`age` int(11) NOT NULL COMMENT '年龄',
`sex` int(11) NOT NULL COMMENT '性别',
`birthday` date NOT NULL COMMENT '时间',
`xl` int(11) NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `username`(`username`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of t_user
-- ----------------------------
INSERT INTO `t_user` VALUES (1, 'zs', '123456', '张三', 23, 1, '2022-06-13', 1);
INSERT INTO `t_user` VALUES (2, 'ls', '123456', '李四', 24, 2, '2022-06-06', 2);
SET FOREIGN_KEY_CHECKS = 1;
勾选
-热部署插件-DevTools
-web工程
-Thymeleaf
-Mysql
-Mybatis
-Redis
点击Finish 完成
添加mysql的版本
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
<version>5.1.46version>
dependency>
修改springboot版本-(将2.7.3的版本改成 2.3.12.RELEASE 低版本的)
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.12.RELEASEversion>
<relativePath/>
parent>
添加mybatis的插件
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
<plugin>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-maven-pluginartifactId>
<version>1.3.6version>
<configuration>
<configurationFile>GeneratorMapper.xmlconfigurationFile>
<verbose>trueverbose>
<overwrite>trueoverwrite>
configuration>
plugin>
plugins>
<resources>
<resource>
<directory>src/main/javadirectory>
<includes>
<include>**/*.xmlinclude>
includes>
resource>
resources>
build>
删除 spring-boot-maven-plugin
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
配置数据库连接
#配置数据库连接四要素
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springbootdb?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
配置redis连接
#配置Redis的连接信息(单机版)
## redis所在的服务器IP
spring.redis.host=127.0.0.1
## 配置端口
spring.redis.port=6379
##密码,我这里没有设置,所以不填
spring.redis.password=
## 设置最大连接数,0为无限
spring.redis.pool.max-active=8
3.配置thymeleaf
#关闭缓冲,否则可能不能看到实时信息,开发阶段建议关闭,上线后建议开启
spring.thymeleaf.cache=false
DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<classPathEntry location="D:\javaTools\maven_repository\mysql\mysql-connector-java\5.1.46\mysql-connector-java-5.1.46.jar"/>
<context id="tables" targetRuntime="MyBatis3">
<commentGenerator>
<property name="suppressAllComments" value="true" />
commentGenerator>
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://127.0.0.1:3306/springbootdb"
userId="root"
password="root">
jdbcConnection>
<javaModelGenerator targetPackage="com.springboot.model" targetProject="src/main/java">
<property name="enableSubPackages" value="false" />
<property name="trimStrings" value="false" />
javaModelGenerator>
<sqlMapGenerator targetPackage="com.springboot.mapper" targetProject="src/main/java">
<property name="enableSubPackages" value="false" />
sqlMapGenerator>
<javaClientGenerator type="XMLMAPPER" targetPackage="com.springboot.mapper" targetProject="src/main/java">
<property name="enableSubPackages" value="false" />
javaClientGenerator>
<table tableName="t_user"
domainObjectName="User"
enableCountByExample="false"
enableUpdateByExample="false"
enableDeleteByExample="false"
enableSelectByExample="false"
selectByExampleQueryId="false"/>
context>
generatorConfiguration>
@MapperScan(basePackages = "com.springboot.mapper")
将命名空间拷贝进去
xmlns:th="http://www.thymeleaf.org"
完整代码
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h1>index.htmlh1>
body>
html>
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.annotation.Resource;
@Controller
public class UserController {
@Resource
private UserService userService;
@RequestMapping("/")
public String index(){
return "index";
}
}
空的
import com.springboot.service.UserService;
@Service
public class UserServiceImpl implements UserService {
}
localhost:8080
@RequestMapping("/")
public String index(Model model) {
List<User> list = userService.getUserList();
model.addAttribute("userList", list);
return "index";
}
/*获取Userlist列表*/
List<User> getUserList();
/*注入userMapper*/
@Resource
private UserMapper userMapper;
/*获取Userlist列表*/
@Override
public List<User> getUserList() {
List<User>list= userMapper.selectAll();
return list;
}
/*查询列表*/
List<User> selectAll();
<select id="selectAll" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from t_user
select>
<table>
<tr>
<td>序号td>
<td>账号td>
<td>姓名td>
<td>年龄td>
<td>性别td>
<td>生日td>
<td>学历td>
<td>td>
tr>
<tr th:each="user:${userList}" th:object="${user}">
<td th:text="${userStat.count}">序号td>
<td th:text="*{username}">账号td>
<td th:text="*{name}">姓名td>
<td th:text="*{age}">年龄td>
<td th:text="*{sex==1?'男':'女'}">性别td>
<td th:text="*{#dates.format(birthday,'yyyy-MM-dd')}">生日td>
<td th:switch="*{xl}">
<span th:case="1">小学span>
<span th:case="2">初中span>
<span th:case="3">高中span>
<span th:case="4">本科span>
<span th:case="5">其他span>
td>
<td>
<a th:href="@{|/toUpdate?id=*{id}|}">修改a>
<a th:href="@{|/delete?id=*{id}|}">删除a>
td>
tr>
table>
如果每次我们都要从数据库读,肯定会影响效率, 那我们是不是就会想到用缓存技术,那我们学到了什么缓存技术呢,是不是redis,.那我们就讲Redis使用进来
注入redisTemplate对象
/*注入redisTemplate*/
@Resource
private RedisTemplate redisTemplate;
修改getUserList方法
/*获取Userlist列表*/
@Override
public List getUserList() {
Listlist= (List) redisTemplate.opsForValue().get("userAll");
if(list==null||list.isEmpty()){
synchronized (this){
list= (List) redisTemplate.opsForValue().get("userAll");
if(list==null||list.isEmpty()){
list= userMapper.selectAll();
redisTemplate.opsForValue().set("userAll",list);
}
}
}
return list;
}
在User 实现序列化接口
public class User implements Serializable{
缓存成功:
/*根据id查询用户*/
@RequestMapping("/private/toUpdate")
public String toUpdate(Integer id,Model model){
User user= userService.getUserById(id);
model.addAttribute("user",user);
return "update";
}
/*根据id查询用户*/
User getUserById(Integer id);
/*根据id查询用户*/
@Override
public User getUserById(Integer id) {
return userMapper.selectByPrimaryKey(id);
}
/*增加两个/private */
<a th:href="@{|/private/toUpdate?id=*{id}|}">修改a>
<a th:href="@{|/private/delete?id=*{id}|}">删除a>
将命名空间(xmlns:th=“http://www.thymeleaf.org”)
Title
<h1>修改数据h1>
<form th:action="@{|/private/update|}" method="post" th:object="${user}">
账号:<input type="text" th:value="*{username}" ><br>
姓名:<input type="text" th:value="*{name}" ><br>
年龄:<input type="text" th:value="*{age}"><br>
性别:
<input type="radio" value="1" >男
<input type="radio" value="2" >女
<br>
生日:<input type="text" th:value="*{#dates.format(birthday,'yyyy-MM-dd')}" ><br>
学历:
<select >
<option value="1" >小学option>
<option value="2" >初中option>
<option value="3" >高中option>
<option value="4" >本科option>
<option value="5" >其他option>
select><br>
<input type="submit" value="修改">
form>
测试:http://localhost:8080/ 访问–>修改
性别:
<input type="radio" value="1" th:checked="*{sex==1}" >男
<input type="radio" value="2" th:checked="*{sex==2}" >女
<br>
学历:
<select name="xl">
<option value="1" th:selected="*{xl==1}">小学option>
<option value="2" th:selected="*{xl==2}">初中option>
<option value="3" th:selected="*{xl==3}">高中option>
<option value="4" th:selected="*{xl==4}">本科option>
<option value="5" th:selected="*{xl==5}">其他option>
select><br>
再次测试:http://localhost:8080/ 访问–>修改
//默认这里会出现异常,因为表单有日期格式的数据需要设置到User对象中。解决方案参考User类中的birthday属性的注解
/*修改方法*/
@RequestMapping("/private/update")
public String update(User user){
userService.update(user);
return "redirect:/";
}
//将指定格式的字符串格式化成Date对象
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
/*修改用户信息*/
void update(User user);
/*修改用户信息*/
@Override
public void update(User user) {
//由于参数User对象中的属性username和password没有值,因此需要调用updateByPrimaryKeySelective方法,进行选择列否改
//否则会将表中的username和password数据修改为null
userMapper.updateByPrimaryKeySelective(user);
//更新数据库的数据后,Redis中存在的数据和数据库不一致,Redis中的数据为脏数据解决方案如下
//1. 将Redis的数据删除,需要时再从数据库中读取存入Redis。可以及时更新Redis中的数据,但如果数据频繁修改效率不高且Redis缓冲利用率比较低
//2. 配置定时任务,定期更新例如1秒或10秒钟更新一次。不能及时更新Redis,但是可以在频繁修改的数据中使用,缓冲利用率比较高
redisTemplate.delete("userAll");
}
<form th:action="@{|/private/update|}" method="post" th:object="${user}">
账号:<input type="text" th:value="*{username}" readonly><br>
姓名:<input type="text" th:value="*{name}" name="name"><br>
年龄:<input type="text" th:value="*{age}" name="age"><br>
性别:
<input type="radio" value="1" th:checked="*{sex==1}" name="sex">男
<input type="radio" value="2" th:checked="*{sex==2}" name="sex">女
<br>
生日:<input type="text" th:value="*{#dates.format(birthday,'yyyy-MM-dd')}" name="birthday"><br>
学历:
<select name="xl">
<option value="1" th:selected="*{xl==1}">小学option>
<option value="2" th:selected="*{xl==2}">初中option>
<option value="3" th:selected="*{xl==3}">高中option>
<option value="4" th:selected="*{xl==4}">本科option>
<option value="5" th:selected="*{xl==5}">其他option>
select><br>
<input type="hidden" th:value="*{id}" name="id">
<input type="submit" value="修改">
form>
测试:http://localhost:8080/ 访问–>修改–修改成功
<a th:href="@{|/private/delete?id=*{id}|}" onclick="return confirm('确认要删除数据码?')">删除a>
/*删除数据*/
//默认这里会出现异常,因为表单有日期格式的数据需要设置到User对象中。解决方案参考User类中的birthday属性的注解
@RequestMapping("/private/delete")
public String delete(Integer id){
userService.deleteById(id);
return "redirect:/";
}
/*根据id删除数据*/
void deleteById(Integer id);
/*根据id删除数据*/
@Override
public void deleteById(Integer id) {
userMapper.deleteByPrimaryKey(id);
//更新数据库的数据后,Redis中存在的数据和数据库不一致,Redis中的数据为脏数据解决方案如下
//1. 将Redis的数据删除,需要时再从数据库中读取存入Redis。可以及时更新Redis中的数据,但如果数据频繁修改效率不高且Redis缓冲利用率比较低
//2. 配置定时任务,定期更新例如1秒或10秒钟更新一次。不能及时更新Redis,但是可以在频繁修改的数据中使用,缓冲利用率比较高
redisTemplate.delete("userAll");
}
测试:http://localhost:8080/ 访问–>删除–删除成功
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h1>添加数据h1>
body>
html>
<a th:href="@{|/private/toAdd|}">添加数据a>
/*因为无法直接访问templates 下的add页面.所以从controller跳转*/
@RequestMapping("/private/toAdd")
public String toAdd(){
return "add";
}
/*新增用户*/
@RequestMapping("/private/add")
public String add(User user,Model model){
//插入数据,返回0表示添加成功 ,返回1 表示账号重复
int result=userService.addUser(user);
if(result==1){
model.addAttribute("msg","账号已经存在请更换!例如"+user.getUsername()+"123");
return "add";
}
return "redirect:/";
}
/*新增用户*/
int addUser(User user);
/*新增用户*/
@Override
public int addUser(User user) {
userMapper.insert(user);
//更新数据库的数据后,Redis中存在的数据和数据库不一致,Redis中的数据为脏数据解决方案如下
//1. 将Redis的数据删除,需要时再从数据库中读取存入Redis。可以及时更新Redis中的数据,但如果数据频繁修改效率不高且Redis缓冲利用率比较低
//2. 配置定时任务,定期更新例如1秒或10秒钟更新一次。不能及时更新Redis,但是可以在频繁修改的数据中使用,缓冲利用率比较高
redisTemplate.delete("userAll");
return 0;
}
(因为update页面的数据和新增页面的基本数据一致.)
<h1>修改数据h1>
<form th:action="@{|/private/update|}" method="post" th:object="${user}">
账号:<input type="text" th:value="*{username}" readonly><br>
姓名:<input type="text" th:value="*{name}" name="name"><br>
年龄:<input type="text" th:value="*{age}" name="age"><br>
性别:
<input type="radio" value="1" th:checked="*{sex==1}" name="sex">男
<input type="radio" value="2" th:checked="*{sex==2}" name="sex">女
<br>
生日:<input type="text" th:value="*{#dates.format(birthday,'yyyy-MM-dd')}" name="birthday"><br>
学历:
<select name="xl">
<option value="1" th:selected="*{xl==1}">小学option>
<option value="2" th:selected="*{xl==2}">初中option>
<option value="3" th:selected="*{xl==3}">高中option>
<option value="4" th:selected="*{xl==4}">本科option>
<option value="5" th:selected="*{xl==5}">其他option>
select><br>
<input type="hidden" th:value="*{id}" name="id">
<input type="submit" value="修改">
form>
账号:<input type="text" name="username"><br>
密码:<input type="text" name="password"><br>
<form th:action="@{|/private/add|}" method="post" >
<input type="submit" value="添加">
<h1>添加数据h1>
<form th:action="@{|/private/add|}" method="post" >
账号:<input type="text" name="username"><br>
密码:<input type="text" name="password"><br>
姓名:<input type="text" name="name"><br>
年龄:<input type="text" name="age"><br>
性别:
<input type="radio" value="1" name="sex">男
<input type="radio" value="2" name="sex">女
<br>
生日:<input type="text" name="birthday"><br>
学历:
<select name="xl">
<option value="1" >小学option>
<option value="2" >初中option>
<option value="3" >高中option>
<option value="4" >本科option>
<option value="5" >其他option>
select><br>
<input type="submit" value="添加">
form>
测试: http://localhost:8080/ 测试添加-当重复提交相同的username的时候,爆出异常
org.springframework.dao.DuplicateKeyException:
/*新增用户*/
@Override
public int addUser(User user) {
try {
//插入数据,可能会抛出DuplicateKeyException异常,这个异常是Spring提供的,表示违反数据库的唯一约束,代表账号重复
userMapper.insert(user);
//更新数据库的数据后,Redis中存在的数据和数据库不一致,Redis中的数据为脏数据解决方案如下
//1. 将Redis的数据删除,需要时再从数据库中读取存入Redis。可以及时更新Redis中的数据,但如果数据频繁修改效率不高且Redis缓冲利用率比较低
//2. 配置定时任务,定期更新例如1秒或10秒钟更新一次。不能及时更新Redis,但是可以在频繁修改的数据中使用,缓冲利用率比较高
redisTemplate.delete("userAll");
} catch (DuplicateKeyException e) {
return 1;
}
return 0;
}
<span style="color: red">[[${msg}]]span>
测试:http://localhost:8080/ 重复添加账号
成功回显重复账号提示信息
model.addAttribute("user",user);
由于我们回到add页面有两个途径,分别是/pricate/toAdd 和 /pricate/add 两个控制器.由于toAdd页面没有数据会报错,所以我们要这样实现前端页面.我们要先判null
<form th:action="@{|/private/add|}" method="post" >
账号:<input type="text" name="username" th:value="${user==null?'':user.username}"><br>
密码:<input type="text" name="password"><br>
姓名:<input type="text" name="name" th:value="${user==null?'':user.name}"><br>
年龄:<input type="text" name="age" th:value="${user==null?'':user.age}"><br>
性别:
<input type="radio" value="1" name="sex" th:checked="${user==null?false:user.sex==1}">男
<input type="radio" value="2" name="sex" th:checked="${user==null?false:user.sex==2}">女
<br>
生日:<input type="text" name="birthday" th:value="${user==null?'':#dates.format(user.birthday,'yyyy-MM-dd')}"><br>
学历:
<select name="xl">
<option value="1" th:selected="${user==null?false:user.xl==1}">小学option>
<option value="2" th:selected="${user==null?false:user.xl==2}">初中option>
<option value="3" th:selected="${user==null?false:user.xl==3}">高中option>
<option value="4" th:selected="${user==null?false:user.xl==4}">本科option>
<option value="5" th:selected="${user==null?false:user.xl==5}">其他option>
select><br>
<input type="submit" value="添加">
form>
测试: http://localhost:8080/ 提交重复的账号数据
package com.springboot.interceptors;
import com.springboot.model.User;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/*拦截器类*/
@Component
public class IsLoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
User user= (User) request.getSession().getAttribute("userSession");
if(user==null){
//没有登录,转向到noLogin控制器
request.getRequestDispatcher("/noLogin").forward(request,response);
return false;
}
return true;
}
}
/*用户未登录跳转*/
@RequestMapping("/noLogin")
public String noLogin(Model model){
model.addAttribute("msg","请登录后在操作");
return "login";
}
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h1>用户登录h1>
<span style="color: red">[[${msg}]]span>
body>
html>
package com.springboot.config;
import com.springboot.interceptors.IsLoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
/*拦截器配置类*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
//注入拦截器类对象
@Resource
private IsLoginInterceptor isLoginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
/*添加拦截器类*/
registry.addInterceptor(isLoginInterceptor)
.addPathPatterns("/private/**");//指定拦截/private/下的所有方法
}
}
测试: http://localhost:8080/ 点击任意请求,都将跳转到登录页面
增加命名空间和form 表单
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<form th:action="@{|/login|}" method="post">
账号:<input type="text" name="username"><br>
密码:<input type="text" name="password"><br>
<input type="submit" value="登录">
form>
/*用户登录方法*/
@RequestMapping("/login")
public String login(User user, Model model, HttpSession session){
int result= userService.login(user);
if(result==1){
model.addAttribute("msg","账号错误");
return "login";
}
if(result==2){
model.addAttribute("msg","密码错误");
return "login";
}
session.setAttribute("userSession",user);
return "redirect:/";
}
/*用户登录*/
int login(User user);
/*用户登录*/
@Override
public int login(User user) {
//根据用户账号查询用户
User dbUser= userMapper.selectByUsername(user.getUsername());
if(dbUser==null){
return 1;
}
if(!dbUser.getPassword().equals(user.getPassword())){
return 2;
}
//将数据库中的user数据拷贝到当前方法的形参中
BeanUtils.copyProperties(dbUser,user);
return 0;
}
//根据用户账号查询用户
User selectByUsername(String username);
<select id="selectByUsername" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from t_user
where username=#{username}
select>