Spring Boot实践八--用户管理系统

一,技术介绍

技术选型功能说明
springboot是一种基于 Spring 框架的快速开发应用程序的框架,它的主要作用是简化 Spring 应用程序的配置和开发,同时提供一系列开箱即用的功能和组件,如内置服务器、数据访问、安全、监控等,使开发者可以更加高效地构建和部署应用程序
Maven快速的引入jar包进行开发,自动构建部署
tomcatweb服务器,快速部署发布web 服务。Tomcat是一个开源的Java Servlet容器,它可以运行Java Servlet和JavaServer Pages(JSP)应用程序。作为一个Web服务器,它可以处理HTTP请求和响应,并将它们传递给Java Servlet和JSP应用程序进行处理。
ThymeleafThymeleaf是一种Java模板引擎,它可以将HTML、XML、JavaScript等文件转换为可执行的模板。在开发Web应用程序时,通常会使用Tomcat作为Web服务器,而Thymeleaf可以作为模板引擎来生成动态的Web页面。因此,Thymeleaf和Tomcat可以一起使用来构建动态Web应用程序。
junit单元测试框架
mybatis将Java对象与关系数据库进行映射,实现数据的持久化操作。mybatis的mapper文件是储存sql语句的一个xml文件,他替代了JDBC在类中写多条语句的问题,简化了步骤。
redis用作缓存。它的读写速度非常快,每秒可以处理超过10万次读写操作。高并发访问数据时直接走内存,和直接查询数据库相比,redis的高效性、快速性优势明显
mysql关系型数据库

Spring、Spring Boot和Spring Cloud的关系

在这里插入图片描述

我们可以这样理解:正是由于 IoC (控制反转,把创建好的对象给Spring进行管理)和 AOP(面向切面编程,不修改源代码的情况下进行功能增加) 这两个强大的功能才有了强大的轻量级开源JavaEE框架 Spring;Spring 生态不断地发展才有了 Spring Boot;Spring Boot 开发、部署的简化,使得 Spring Cloud 微服务治理方案彻底落地。

Spring Boot 在 Spring Cloud 中起到了承上启下的作用:

  • Springboot 将原有的 xml 配置,简化为 java 注解
  • 使用 IDE 可以很方便的搭建一个 springboot 项目,选择对应的 maven 依赖,简化Spring应用的初始搭建以及开发过程
  • springboot 有内置的 tomcat 服务器,可以 jar 形式启动一个服务,可以快速部署发布 web 服务
  • springboot 使用 starter 依赖自动完成 bean 配置,解决 bean 之间的冲突,并引入相关的 jar 包

Mybatis和Redis缓存的区别

Mybatis和Redis缓存的区别在于:

  • Mybatis缓存是基于内存的,而Redis缓存是基于磁盘的。即Mybatis缓存是在应用程序内部实现的,而Redis缓存是在外部服务器上实现的,这意味着Redis缓存可以在多个应用程序之间共享,而Mybatis缓存只能在单个应用程序实例中使用。
  • Mybatis缓存是局部缓存,只能缓存查询结果,而Redis缓存可以缓存任何类型的数据,包括对象、列表、哈希表等。
  • Mybatis缓存是默认开启的,但需要手动配置,而Redis缓存需要安装和配置Redis服务器。
  • Mybatis缓存是基于时间和空间的限制,而Redis缓存可以设置过期时间和最大内存使用量。

二,项目介绍

本项目是一个基于SpringBoot的用户权限管理系统,主要实现用户的注册、登录、角色管理、权限管理等功能。

数据库设计

表设计作用
用户表用户表(user)用于存储系统中的用户信息,包括用户ID、用户名、密码、邮箱、手机号等字段。
角色表角色表(role)用于存储系统中的角色信息,包括角色ID、角色名称、角色描述等字段
权限表权限表(permission)用于存储系统中的权限信息,包括权限ID、权限名称、权限描述等字段。
用户角色关联表用户角色关联表(user_role)用于存储系统中用户与角色之间的关系,包括用户ID、角色ID等字段。
角色权限关联表角色权限关联表(role_permission)用于存储系统中角色与权限之间的关系,包括角色ID、权限ID等字段。

功能模块

用户注册:提供用户注册功能。
用户登录:提供用户登录功能。
角色管理:提供角色的增删改查功能。
权限管理:提供权限的增删改查功能。
用户角色管理:提供用户与角色的关联管理功能。
角色权限管理:提供角色与权限的关联管理功能。

代码结构

 SpringBootRedis 工程项目结构如下:controller - Controller 层dao - 数据操作层model - 实体层service - 业务逻辑层Application - 启动类resources 资源文件夹application.properties - 应用配置文件,应用启动会自动读取配置generatorConfig.xml - mybatis 逆向生成配置(这里不是本文只要重点,所以不进行介绍)mapper 文件夹StudentMapper.xml - mybatis 关系映射 xml 文件

三,项目实现

1,配置依赖

pom.xml依赖

demospringboot\pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.15-SNAPSHOT</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>demospringboot</artifactId><version>0.0.1-SNAPSHOT</version><name>demospringboot</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-commons</artifactId></dependency><!-- 添加mybatis依赖 --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.1</version></dependency><!-- 添加redis依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency><!-- 添加mysql依赖 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version><scope>runtime</scope><!-- MySQL5.x时,请使用5.x的连接器(cmd执行mysql -V确定)<groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.45</version>--></dependency><!-- 添加thymeleaf依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!-- 添加swagger依赖 --><dependency><groupId>com.spring4all</groupId><artifactId>swagger-spring-boot-starter</artifactId><version>1.9.0.RELEASE</version></dependency><!-- 添加junit依赖 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.20</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build><repositories><repository><id>spring-milestones</id><name>Spring Milestones</name><url>https://repo.spring.io/milestone</url><snapshots><enabled>false</enabled></snapshots></repository><repository><id>spring-snapshots</id><name>Spring Snapshots</name><url>https://repo.spring.io/snapshot</url><releases><enabled>false</enabled></releases></repository></repositories><pluginRepositories><pluginRepository><id>spring-milestones</id><name>Spring Milestones</name><url>https://repo.spring.io/milestone</url><snapshots><enabled>false</enabled></snapshots></pluginRepository><pluginRepository><id>spring-snapshots</id><name>Spring Snapshots</name><url>https://repo.spring.io/snapshot</url><releases><enabled>false</enabled></releases></pluginRepository></pluginRepositories></project>

application.properties配置

demospringboot\src\main\resources\application.properties:

# mysql 指定使用的数据库
spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase?createDatabaseIfNotExist=true
spring.datasource.username=root
spring.datasource.password=1234
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# mysql5: spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# 执行初始化sql
spring.datasource.initialize=true
spring.datasource.schema=classpath:schema.sql# redis
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.min-idle=0
spring.redis.lettuce.shutdown-timeout=100ms# 默认线程池
spring.task.execution.pool.core-size=2
spring.task.execution.pool.max-size=5
spring.task.execution.pool.queue-capacity=10
spring.task.execution.pool.keep-alive=60
spring.task.execution.pool.allow-core-thread-timeout=true
spring.task.execution.shutdown.await-termination=false
spring.task.execution.shutdown.await-termination-period=
spring.task.execution.thread-name-prefix=task-# mybatis 指定mapper xml映射文件
mybatis.mapper-locations=classpath:mybatis/*.xml
# 打印mybatis的执行sql
# mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl# swagger
swagger.title=spring-boot-starter-swagger
swagger.description=Starter for swagger 2.x
swagger.version=1.4.0.RELEASE
swagger.license=Apache License, Version 2.0
swagger.licenseUrl=https://www.apache.org/licenses/LICENSE-2.0.html
swagger.termsOfServiceUrl=https://github.com/xxx/spring-boot-starter-swagger
swagger.contact.name=didi
swagger.contact.url=http://blog.xxx.com
swagger.contact.email=xxx.com
swagger.base-package=com.example.demospringboot
swagger.base-path=/**spring.mvc.pathmatch.matching-strategy=ant_path_matcher

2,数据库初始化

在前面的application.properties的sql配置中,我们指定了会自动创建mydatabase,并且指定了初始化sql文件:

spring.datasource.initialize=true
spring.datasource.schema=classpath:schema.sql

对应的demospringboot\src\main\resources\schema.sql主要用来初始化设计的5张表,sql语句如下:


-- 测试表初始化
drop database if exists mydatabase;
create database if not exists mydatabase character set utf8;
use mydatabase;
drop table if exists user;
drop table if exists role;
drop table if exists permission;
drop table if exists user_role;
drop table if exists role_permission;-- 用户表
CREATE TABLE `user` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID',`username` varchar(32) NOT NULL COMMENT '用户名',`password` varchar(64) NOT NULL COMMENT '密码',PRIMARY KEY (`id`),UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';-- 角色表
CREATE TABLE `role` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '角色ID',`name` varchar(32) NOT NULL COMMENT '角色名称',`description` varchar(128) DEFAULT NULL COMMENT '角色描述',`create_time` datetime NOT NULL COMMENT '创建时间',`update_time` datetime NOT NULL COMMENT '更新时间',PRIMARY KEY (`id`),UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';-- 权限表
CREATE TABLE `permission` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '权限ID',`name` varchar(32) NOT NULL COMMENT '权限名称',`description` varchar(128) DEFAULT NULL COMMENT '权限描述',`create_time` datetime NOT NULL COMMENT '创建时间',`update_time` datetime NOT NULL COMMENT '更新时间',PRIMARY KEY (`id`),UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限表';-- 用户角色关联表
CREATE TABLE `user_role` (`user_id` int(11) NOT NULL COMMENT '用户ID',`role_id` int(11) NOT NULL COMMENT '角色ID',PRIMARY KEY (`user_id`,`role_id`),KEY `role_id` (`role_id`),CONSTRAINT `user_role_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,CONSTRAINT `user_role_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表';-- 角色权限关联表
CREATE TABLE `role_permission` (`role_id` int(11) NOT NULL COMMENT '角色ID',`permission_id` int(11) NOT NULL COMMENT '权限ID',PRIMARY KEY (`role_id`,`permission_id`),KEY `permission_id` (`permission_id`),CONSTRAINT `role_permission_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,CONSTRAINT `role_permission_ibfk_2` FOREIGN KEY (`permission_id`) REFERENCES `permission` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色权限关联表';INSERT INTO user VALUES(1,'admin','123456');
INSERT INTO user VALUES(2,'admin2','123456');
INSERT INTO user VALUES(3,'guanyu','1234');
INSERT INTO user VALUES(4,'zhangsan','1235');

然后主类实现CommandLineRunner run接口,执行initDatabase进行初始化:

进行如下调用:

package com.example.demospringboot;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.init.ScriptUtils;
import org.springframework.scheduling.annotation.EnableAsync;import org.springframework.cache.annotation.EnableCaching;import com.example.demospringboot.workmanager.WorkManager;
import com.example.demospringboot.workmanager.AsyncWorkManager;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import com.spring4all.swagger.EnableSwagger2Doc;@EnableCaching
@EnableAsync
@EnableSwagger2Doc
@SpringBootApplication
// 需要指定扫描的类,并在配置文件指定mybatis.mapper-locations为对应的xml路径
@MapperScan(value = {"com.example.demospringboot.dao"})
public class DemospringbootApplication implements CommandLineRunner {@Autowiredprivate DataSource dataSource;public static void main(String[] args) {SpringApplication.run(DemospringbootApplication.class, args);}@Overridepublic void run(String... strings) throws SQLException {initDatabase();}private void initDatabase() throws SQLException {System.out.println("======== 自动初始化数据库开始 ========");Resource initData = new ClassPathResource("schema.sql");Connection connection = null;try {connection = dataSource.getConnection();ScriptUtils.executeSqlScript(connection, initData);} catch (SQLException e) {throw new RuntimeException(e);} finally {if (connection != null) {connection.close();}}System.out.println("======== 自动初始化数据库结束 ========");}
}

如上我们通过DataSource.getConnection()总是从datasource或连接池返回一个新的连接,并通过ScriptUtils.executeSqlScript执行了我们的sql脚本。需要注意如果开发者没有手工释放这连接(显式调用 Connection.close() 方法),则这个连接将永久被占用(处于 active 状态),造成连接泄漏!

2,数据库增删改查接口实现

首先,我们基于mabatis+redis+mysql实现一个user类数据库增删改查的基本功能。

实体类bean.User实现 Serializable 接口,因为 Spring 会将对象先序列化再存入 Redis

package com.example.demospringboot.bean;import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;@Data
@NoArgsConstructor
public class User implements Serializable {private int id;private String username;private String password;
}

dao层,定义UserMapper接口:

package com.example.demospringboot.dao;import com.example.demospringboot.bean.User;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;@Repository
@CacheConfig(cacheNames = "users")
public interface UserMapper {User findUserById(@Param("id") int id);User findUserByName(@Param("username") String username);String findPassword(String username);@CacheableList<User> findAllUsers();void deleteUserById(@Param("id") int id);void deleteAllUsers();int insertUser(@Param("user") User user);void updateUserPassword(@Param("user") User user);
}

对应的demospringboot\src\main\resources\mybatis\UserMapper.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!-- mapper标签要指定namespace属性,和实际的mapper文件一致-->
<mapper namespace="com.example.demospringboot.dao.UserMapper"><select id="findUserById" resultType="com.example.demospringboot.bean.User">select * from t_user where id = #{id}</select><select id="findUserByName" resultType="com.example.demospringboot.bean.User">select * from t_user where username = #{username}</select><select id="findAllUsers" resultType="com.example.demospringboot.bean.User">select * from t_user</select><delete id="deleteAllUsers" >delete from t_user</delete><delete id="deleteUserById" parameterType="int">delete from t_user where id=#{id}</delete><insert id="insertUser" parameterType="com.example.demospringboot.bean.User">insert into t_user(id,username,password) values(#{user.id},#{user.username},#{user.password})</insert><update id="updateUserPassword" parameterType="com.example.demospringboot.bean.User">update t_user set password=#{user.password} where id=#{user.id}</update>
</mapper>

主启动类:

package com.example.demospringboot;import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Repository;import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import org.springframework.cache.annotation.EnableCaching;
import com.spring4all.swagger.EnableSwagger2Doc;@EnableCaching
@EnableAsync
@EnableSwagger2Doc
@SpringBootApplication
// 需要指定扫描的类,并在配置文件指定mybatis.mapper-locations为对应的xml路径
@MapperScan(value = {"com.example.demospringboot.dao"})
public class DemospringbootApplication {public static void main(String[] args) {SpringApplication.run(DemospringbootApplication.class, args);}}

3,Web页面接口实现

用户注册和登录

我们基于springboot内嵌的tomcat,用Thymeleaf 模版实现一个存储用户账号密码的web界面。在UsreController中设计如下接口:

接口作用
@GetMapping(value = {“/register”})
public String registerPage()
返回注册界面html
@GetMapping(value = {“/login”})
public String loginPage()
返回登录界面html
@GetMapping(value = {“/success”})
public String UserPage(HttpSession session, Model model)
返回登录以后的界面html
@PostMapping(“register”)
public String Register(User user, Model model)
提交注册信息进行注册
@PostMapping(“login”)
public String login(User user, HttpSession session, Model model)
提交登录信息进行登录

对应的3个html放置在demospringboot\src\main\resources\templates\目录下:

success.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><title>Thymeleaf Spring Boot Example</title>
</head>
<body>
<h1 th:text="'Welcome,'+${user}+'!'"></h1>
<a>You have successfully logged in !</a>
</body>
</html>

login.html

访问http://localhost:8080/login时返回login.html页面。

点击登录按钮,通过html的form表单method="post" th:action="@{/login}跳到Controller的@PostMapping("login"),查询成功后重定向到@GetMapping("success"),返回success.html

点击注册按钮,通过html的form表单method="get" th:action="@{/register}跳到Controller的@GetMapping("register"),返回registe.html页面。

<!DOCTYPE html >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>登录注册界面</title><link rel="stylesheet" href="../static/style.css">
</head><body>
<!-- 整体布局 -->
<div class="container right-panel-active"><!-- 登录框 --><div class="container_from container_signin"><form class="form" id="form" method="post" th:action="@{/login}"><h2 class="form_title">欢迎登录</h2><div class="row"><span>用户名:</span><input type="text" name="username" placeholder="请输入您的账号" class="input"></div><div class="row"><span>&emsp;码:</span><input type="password" name="password" placeholder="请输入您的密码" class="input"></div><div class="row"><span th:text="${msg}"></span></div><input type="submit" class="btn" value="登录"/></form><form class="form" method="get" id="form1" th:action="@{/register}"><label id="register" class="form-label" >没有账号?请点击<input class="btn" type="submit" value="注册"/></form></div>
</div>
<script src="../static/login.js"></script>
</body>
</html>

register.html

填入信息后点击注册,通过html的form表单method="post" th:action="@{/register}跳到Controller的@PostMapping("register"),进行注册

<!DOCTYPE html >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>登录注册界面</title><link rel="stylesheet" href="../static/style.css">
</head><body>
<!-- 整体布局 -->
<div class="container right-panel-active"><!-- 注册框 --><div class="container_from container_signup"><form class="from" method="post" id="from" th:action="@{/register}"><h2 class="form_title">注册账号</h2><div class="row"><span>用户名:</span><input type="text" id="username" name="username" placeholder="请输入账号" class="input"></div><div class="row"><span>&emsp;码:</span><input type="password" name="password" placeholder="请输入密码" class="input"></div><!-- 提示注册信息${tip} --><div class="row"><span th:text="${tip}"></span></div><input class="btn" type="submit" value="注册"/></form></div>
</div>
<script src="../static/login.js"></script>
</body>
</html>

实现userController:

package com.example.demospringboot.controller;import com.example.demospringboot.bean.User;
import com.example.demospringboot.dao.UserMapper;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.ui.Model;import javax.servlet.http.HttpSession;@Controller
public class UserController {@AutowiredUserMapper userService;@GetMapping(value = {"/login"})public String loginPage() {// 返回login.htmlreturn "login";}@GetMapping("register")public String registerPage() {// 返回register.htmlreturn "register";}@PostMapping("register")public String Register(User user, Model model) {try {User userName = userService.findUserByName(user.getUsername());//没有用户可以进行注册if (userName == null) {if (user.getPassword().equals("") || user.getUsername().equals("")) {model.addAttribute("tip", "请填写信息");return "register";} else {int ret = userService.insertUser(user);if (ret > 0) {model.addAttribute("tip", "注册成功,请返回登录页面进行登录");}return "register";}} else {model.addAttribute("tip", "用户已存在,请返回登录页面进行登录");return "register";}} catch (Exception e) {e.printStackTrace();return e.getMessage();}}@PostMapping("login")public String login(User user, HttpSession session, Model model) {try {//先查找一下有没有该账号User userReturn = userService.findUserByName(user.getUsername());if (userReturn != null) {//如果有账号则判断账号密码是否正确if (userReturn.getPassword().equals(user.getPassword())) {//添加到session保存起来session.setAttribute("loginUser", user);//重定向到@GetMapping("success")return "redirect:/success";} else {//如果密码错误,则提示输入有误model.addAttribute("msg", "账号或者密码有误");return "login";}} else {model.addAttribute("msg", "账号或者密码有误");return "login";}} catch (Exception e) {e.printStackTrace();return e.getMessage();}}@GetMapping("success")public String UserPage(HttpSession session, Model model) {User loginUser = (User)session.getAttribute("loginUser");if (loginUser != null) {model.addAttribute("user", loginUser.getUsername());// 返回success.htmlreturn "success";} else {model.addAttribute("msg", "请登录");return "login";}}}

界面效果如下:
在这里插入图片描述

step3:多线程task

首先,实现两个UserService和AsyncUserService两个服务接口:

接口:

package com.example.demospringboot.service;public interface UserService {void checkUserStatus();
}
package com.example.demospringboot.service;public interface AsyncUserService {void checkUserStatus();
}

对应实现:

package com.example.demospringboot.service.impl;import com.example.demospringboot.bean.User;
import com.example.demospringboot.service.UserService;
import com.example.demospringboot.dao.UserMapper;import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Slf4j
@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Overridepublic void checkUserStatus() {List<User> AllUsers = userMapper.findAllUsers();for (User u : AllUsers) {// System.out.println(ThreadUtils.getThreadName() + ": " + u);log.info("{}", u);}};
}
package com.example.demospringboot.service.impl;import com.example.demospringboot.task.AsyncTasks;
import com.example.demospringboot.service.AsyncUserService;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class AsyncUserServiceImpl implements AsyncUserService {@Autowiredprivate AsyncTasks asyncTasks;@Overridepublic void checkUserStatus() {asyncTasks.doTaskOne("1");asyncTasks.doTaskOne("2");asyncTasks.doTaskOne("3");};
}

用到的task类如下:

package com.example.demospringboot.task;import com.example.demospringboot.utils.ThreadUtils;import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;import java.util.Random;
import java.util.concurrent.CompletableFuture;@Slf4j
@Component
public class AsyncTasks {public static Random random = new Random();// @Async注解中的参数就是异步任务的线程池@Async("taskExecutor")public CompletableFuture<String> doTaskOne(String taskNo){log.info("开始任务:{}", taskNo);long start = System.currentTimeMillis();ThreadUtils.sleepUtil(random.nextInt(10000));long end = System.currentTimeMillis();log.info("完成任务:{},耗时:{} 毫秒", taskNo, end - start);return CompletableFuture.completedFuture("任务完成");}}

(1)异步任务通过方法上的@Async("taskExecutor")和启动类的@EnableAsync注解实现,@Async中的参数指定了异步任务使用的的线程池。调用异步方法时不会等待方法执行完,调用即过,被调用方法在自己的线程池中奔跑。
(2)多线程执行的返回值是Future类型或void。Future是非序列化的,微服务架构中有可能传递失败。spring boot推荐使用的CompletableFuture来返回异步调用的结果。

用到的thread工具类如下:

package com.example.demospringboot.utils;import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Repository;import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;@Repository
public class ThreadUtils {public static final int MAX_POOL_SIZE = 2;public static final String EXECUTOR_POOL_PREFIX = "exe-" + MAX_POOL_SIZE + "-";public static final String ASYNC_EXECUTOR_POOL_PREFIX = "async-exe-" + MAX_POOL_SIZE + "-";public static final String ASYNC_TASK_POOL_PREFIX = "async-task-" + MAX_POOL_SIZE + "-";// 自定义AsyncTask线程池@Beanpublic ThreadPoolTaskExecutor taskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(MAX_POOL_SIZE);executor.setMaxPoolSize(MAX_POOL_SIZE);executor.setQueueCapacity(MAX_POOL_SIZE);executor.setKeepAliveSeconds(0);executor.setThreadNamePrefix(ASYNC_TASK_POOL_PREFIX);// 如果添加到线程池失败,那么主线程会自己去执行该任务executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());return executor;}// 启动Executor的线程池public static ThreadPoolTaskExecutor getThreadPool(String threadNamePrefix) {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(MAX_POOL_SIZE);executor.setMaxPoolSize(MAX_POOL_SIZE);executor.setQueueCapacity(MAX_POOL_SIZE);executor.setKeepAliveSeconds(0);executor.setThreadNamePrefix(threadNamePrefix);executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());executor.initialize();return executor;}public static void sleepUtil(long millis) {try {Thread.sleep(millis);} catch (InterruptedException e) {System.out.println(e);}}
}

线程池用的是ThreadPoolTaskExecutor 。Executor 顾名思义是专门用来处理多线程相关的一个接口,所有线程相关的类都实现了这个接口,里面有一个execute()方法,用来执行线程,线程池主要提供一个线程队列,队列中保存着所有等待状态的线程。避免了创建与销毁的额外开销,提高了响应的速度。

ThreadPoolTaskExecutor是对ThreadPoolExecutor进行了封装处理,是spring core包中提供的,而ThreadPoolExecutor是JDK中的JUC。

参数说明:

  • corePoolSize:核心线程数
  • queueCapacity:任务队列容量(阻塞队列)
  • maxPoolSize:最大线程数
  • keepAliveTime:线程空闲时间
  • rejectedExecutionHandler:任务拒绝处理器
    异步任务会先占用核心线程,核心线程满了其他任务进入队列等待;在缓冲队列也满了之后才会申请超过核心线程数的线程来进行处理。当线程数已经达到maxPoolSize,且队列已满,线程池可以调用这四个策略处理:
    • AbortPolicy策略:默认策略,如果线程池队列满了丢掉这个任务并且抛出RejectedExecutionException异常。
    • DiscardPolicy策略:如果线程池队列满了,会直接丢掉这个任务并且不会有任何异常。
    • DiscardOldestPolicy策略:如果队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列。
    • CallerRunsPolicy策略:如果添加到线程池失败,那么主线程会自己去执行该任务,不会等待线程池中的线程去执行。
    • 也可以自己实现RejectedExecutionHandler接口,可自定义处理器

为了控制异步任务的并发不影响到应用的正常运作,我们必须要对线程池做好相应的配置,防止资源的过渡使用。需考虑好默认线程池的配置和多任务情况下的线程池隔离。

上述服务我们就用不同线程池的两个WorkManager进行管理:

package com.example.demospringboot.workmanager;import com.example.demospringboot.service.UserService;
import com.example.demospringboot.utils.ThreadUtils;import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;@Slf4j
@Component
public class WorkManager {private static final ThreadPoolTaskExecutor EXECUTOR_POOL =ThreadUtils.getThreadPool(ThreadUtils.EXECUTOR_POOL_PREFIX);@Autowiredprivate UserService userService;public void startExecutor() {EXECUTOR_POOL.execute(new Executor(userService));}static class Executor implements Runnable {private UserService userService;public Executor(UserService userService) {this.userService = userService;}@Overridepublic void run() {while (true) {userService.checkUserStatus();// sleep 1sThreadUtils.sleepUtil(1000L);}}}
}
package com.example.demospringboot.workmanager;import com.example.demospringboot.service.AsyncUserService;
import com.example.demospringboot.utils.ThreadUtils;import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;@Slf4j
@Component
public class AsyncWorkManager {private static final ThreadPoolTaskExecutor ASYNC_EXECUTOR_POOL =ThreadUtils.getThreadPool(ThreadUtils.ASYNC_EXECUTOR_POOL_PREFIX);@Autowiredprivate AsyncUserService asyncUserService;public void startSyncExecutor() {ASYNC_EXECUTOR_POOL.execute(new AsyncExecutor(asyncUserService));}static class AsyncExecutor implements Runnable {private AsyncUserService asyncUserService;public AsyncExecutor(AsyncUserService asyncUserService) {this.asyncUserService = asyncUserService;}@Overridepublic void run() {while (true) {asyncUserService.checkUserStatus();// sleep 1sThreadUtils.sleepUtil(1000L);}}}
}

主类如下:

package com.example.demospringboot;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;import org.springframework.cache.annotation.EnableCaching;import com.example.demospringboot.workmanager.WorkManager;
import com.example.demospringboot.workmanager.AsyncWorkManager;@EnableCaching
@EnableAsync
@SpringBootApplication
@MapperScan(value = {"com.example.demospringboot.dao"})
public class DemospringbootApplication implements CommandLineRunner {@Autowiredprivate WorkManager workManager;@Autowiredprivate AsyncWorkManager asyncWorkManager;public static void main(String[] args) {SpringApplication.run(DemospringbootApplication.class, args);}@Overridepublic void run(String... strings) {//workManager.startExecutor();asyncWorkManager.startSyncExecutor();}
}

主启动类实现了CommandLineRunner 接口,会直接执行run方法。
我们在其中调用了WorkManager的startExecutor方法,用线程池execute方法启动了对应线程类的run方法。

test

package com.example.demospringboot;import com.example.demospringboot.dao.UserMapper;
import com.example.demospringboot.bean.User;
import com.example.demospringboot.task.AsyncTasks;import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
import java.util.concurrent.CompletableFuture;
import org.springframework.cache.CacheManager;import java.util.List;@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
@Rollback(value = false)
public class DemospringbootApplicationTests {@Autowired()private UserMapper userMapper;@Autowiredprivate CacheManager cacheManager;@Testpublic void testUserMapper() throws Exception {// deleteAllUsersuserMapper.deleteAllUsers();// insertUser 插入2条User user = new User();user.setId(100);user.setUsername("Jacky");user.setPassword("1000");userMapper.insertUser(user);user.setId(200);user.setUsername("Mike");user.setPassword("2000");userMapper.insertUser(user);// findUserByIduser = userMapper.findUserById(100);Assert.assertEquals("Jacky", user.getUsername());// updateUserPassworduser.setPassword("1500");userMapper.updateUserPassword(user);Assert.assertEquals("1500", user.getPassword());// deleteUserByIduserMapper.deleteUserById(100);// findAllUsersList<User> AllUsers = userMapper.findAllUsers();for (User u : AllUsers) {System.out.println(u);}//Assert.assertEquals(1, AllUsers.size());System.out.println("CacheManager type : " + cacheManager.getClass());}@Autowiredprivate AsyncTasks asyncTasks;@Testpublic void testTasks() throws Exception {long start = System.currentTimeMillis();// 线程池1CompletableFuture<String> task1 = asyncTasks.doTaskOne("1");CompletableFuture<String> task2 = asyncTasks.doTaskOne("2");CompletableFuture<String> task3 = asyncTasks.doTaskOne("3");// 线程池2CompletableFuture<String> task4 = asyncTasks.doTaskTwo("4");CompletableFuture<String> task5 = asyncTasks.doTaskTwo("5");CompletableFuture<String> task6 = asyncTasks.doTaskTwo("6");// 一起执行CompletableFuture.allOf(task1, task2, task3, task4, task5, task6).join();long end = System.currentTimeMillis();log.info("任务全部完成,总耗时:" + (end - start) + "毫秒");}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/101772.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

win11安装ubuntu 子系统安装过程及注意事项

第一步 &#xff1a;安装系统必须组件 由于子系统是系统自带组件&#xff0c;需要安装软件支持 第二步&#xff1a;应用商店安装 ubuntu 编辑 编辑 这个时候打开会报错 第三步&#xff0c;运行linux子系统 选择Windows PowerShell 以管理员身份运行&#xff09; 输入&#…

简单计算器的实现(含转移表实现)

文章目录 计算器的一般实现使⽤函数指针数组的实现&#xff08;转移表&#xff09; 计算器的一般实现 通过函数的调用&#xff0c;实现加减乘除 # define _CRT_SECURE_NO_WARNINGS#include<stdio.h>int Add(int x, int y) {return x y; }int Sub(int x, int y) {retur…

Ros noetic Move_base 监听Move状态 实战使用教程

前言: 承接上一篇文章,在上一文中我们了解到move_base有几种监听的状态,我一文章中我将开源全部监听代码,本文将从0开始建立监听包,并覆上全部的工程代码,和仿真实操结果。 本文,还将解决当临时障碍物与机身相交时,机器人回人为自己被“卡住”,局部规划器规划的速度为…

Linux socket网络编程

一、主机字节序列和网络字节序列 主机字节序列分为大端字节序列和小端字节序列&#xff0c;不同的主机采用的字节序列可能不同。大端字节序列是指一个整数的高位字节存储在内存的低地址处&#xff0c;低位字节存储在内存的高地址处。小端字节序列是指整数的高位字节存储在内存…

【Windows 常用工具系列 10 -- linux ssh登录脚本输入密码】

文章目录 脚本输入SSH登录密码scp 脚本免密传输 脚本输入SSH登录密码 sshpass 是一个用于运行时非交互式ssh密码提供的工具&#xff0c;它允许你直接在命令行中为ssh命令提供密码。这就意味着你可以在脚本中使用ssh命令&#xff0c;而不需要用户交互地输入密码。 一般来说&am…

技术分享| WebRTC之SDP详解

一&#xff0c;什么是SDP WebRTC 是 Web Real-Time Communication&#xff0c;即网页实时通信的缩写&#xff0c;是 RTC 协议的一种Web实现&#xff0c;项目由 Google 开源&#xff0c;并和 IETF 和 W3C 制定了行业标准。 WebRTC是点对点通讯&#xff0c;他的通话建立需要交换…

【数据结构练习】链表面试题锦集一

目录 前言&#xff1a; 1. 删除链表中所有值为key的节点 方法一&#xff1a;正常删除&#xff0c;头结点另外讨论 方法二:虚拟头结点法 方法三&#xff1a;递归 2.反转链表 方法一&#xff1a;双指针迭代 方法二&#xff1a;递归法解析&#xff1a; 3.链表的中间结点 方法…

AWVS安装~Windows~激活

目录 1.下载安装包 2.双击acunetix_15.1.221109177.exe进行安装 3.配置C:\Windows\System32\drivers\etc\hosts 4.复制wvsc.exe到C:\Program Files (x86)\Acunetix\15.1.221109177下 5.复制license_info.json与wa_data.dat到C:\ProgramData\Acunetix\shared\license下&…

CentOS中Oracle11g进程有哪些

最近遇到Oracle数据库运行过程实例进程由于某种原因导致中止的问题&#xff0c;专门看了下正常Oracle数据库启动后的进程有哪些&#xff0c;查阅资料了解了下各进程的作用&#xff0c;记录如下。 oracle 3032 1 0 07:36 ? 00:00:00 ora_pmon_orcl oracle …

解决 node-gyp 错误问题

gyp verb check python checking for Python executable “python2.7” in the PATH gyp verb which failed Error: not found: python2.7 安装老项目老是报错Python找不到&#xff0c;以为是自己node版本高过了node-sass导致的&#xff0c;把node版本降下来还是不行。然后找到…

ONLYOFFICE协作空间服务器如何一键安装自托管私有化部署

ONLYOFFICE协作空间服务器如何一键安装自托管私有化部署 如何在 Ubuntu 上部署 ONLYOFFICE 协作空间社区版&#xff1f;https://blog.csdn.net/m0_68274698/article/details/132069372?ops_request_misc&request_id&biz_id102&utm_termonlyoffice%20%E5%8D%8F%E4…

vue3+element下拉多选框组件

<!-- 下拉多选 --> <template><div class"select-checked"><el-select v-model"selected" :class"{ all: optionsAll, hidden: selectedOptions.data.length < 2 }" multipleplaceholder"请选择" :popper-app…

ui设计师简历自我评价(合集)

UI设计最新面试题及答案 1、说说你是怎么理解UI的? UI是最直观的把产品展示展现在用户面前的东西&#xff0c;是一个产品的脸面。人开始往往是先会先喜欢上美好的事物后&#xff0c;在去深究内在的东西的。 那么也就意味着一个产品的UI首先要做的好看&#xff0c;无论风格是…

智慧班牌云平台源码 (人脸识别、信息发布、校园风采、家校互通、教务管理、考勤管理)

电子班牌是一款智慧校园的管理工具&#xff0c;也是校园的多媒体展示平台&#xff0c;智慧电子班牌系统是专为学校智慧教育设计的一款智慧校园的管理工具&#xff0c;融合了多媒体信息发布、校园风采、家校互通、教务管理、考勤管理、日常办公等一系列应用。具备智慧教育功能和…

自定义VIEW之SeekBar

先放效果 实现代码 import android.annotation.SuppressLint import android.content.Context import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.graphics.Rect import android.util.AttributeSet import android…

H3C 无线网络vlan pool架构案例三层组网web配置

实验的是目标就是要实现华为vlan pool那种应用&#xff0c; 整个园区发一种ssid信号&#xff0c;但是连接的客户端可以随机连上后进入不同的vlan&#xff0c;在这大型园区网非常有用。 这种方法也适合同一个ssid情况下&#xff0c;在不同的位置关联不同的vlan 开启自动固化、…

会议剪影|思腾合力受邀参加2023中国算力大会并作主题演讲

共享数字经济合作新机遇&#xff0c;携手开创算力产业新未来。8月18日-19日&#xff0c;由工业和信息化部、宁夏回族自治区人民政府主办的2023中国算力大会、第二届“西部数谷”算力产业大会在银川盛大启幕。思腾云计算总经理庞志刚、思腾合力市场总监徐莉受邀参会。 ▲思腾云计…

IDEA项目实践——Element UI概述

系列文章目录 IDEA项目实践——JavaWeb简介以及Servlet编程实战 IDEA项目实践——Spring当中的切面AOP IDEA项目实践——Spring框架简介&#xff0c;以及IOC注解 IDEA项目实践——动态SQL、关系映射、注解开发 IDEWA项目实践——mybatis的一些基本原理以及案例 文章目录 …

学习平台助力职场发展与提升

近年来&#xff0c;随着互联网技术的发展&#xff0c;学习平台逐渐成为了职场发展和提升的必备工具。学习平台通过提供丰富的课程内容、灵活的学习时间和个性化的学习路径&#xff0c;帮助职场人士更好地提升自己的技能和知识储备&#xff0c;为职场发展打下坚实的基础。 学习…

UML 类图

1、概述 目录 1、概述 1.1、UML概念 1.2、类图的概念 2、类的表示方式 2.1、普通类 2.2、抽象类 2.3、接口 3、类与类关系的表示 3.1、关联关系&#xff08;Association&#xff09; 3.1.1、单向关联 3.1.2、双向关联 3.1.3、自关联 3.2、聚合关系&#xff08;aggre…