SpringBoot集成Shiro+JWT(Hutool)完整代码示例

2025-08-07 21:50

本文主要是介绍SpringBoot集成Shiro+JWT(Hutool)完整代码示例,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《SpringBoot集成Shiro+JWT(Hutool)完整代码示例》ApacheShiro是一个强大且易用的Java安全框架,提供了认证、授权、加密和会话管理功能,在现代应用开发中,Shiro因...

一、背景介绍

1.1 为什么使用Shiro?

Apache Shiro 是一个强大且易用的 Java 安全框架,提供了认证、授权、加密和会话管理功能。在现代应用开发中,Shiro 因其简单性和灵活性而被广泛采用:

  • ​简单易用​​:相比 Spring Security,Shiro 的 API 更加直观和简单
  • ​功能全面​​:提供认证、授权、会话管理、加密等企业级安全功能
  • ​轻量级​​:不依赖任何容器,可以独立运行
  • ​业界规范​​:被众多企业采用,有丰富的社区支持和文档

1.2 为什么需要双Token?

在原有单Token方案基础上引入 ​Access Token(访问令牌)​​ 和 ​​Refresh Token(刷新令牌)​​ 的组合,解决以下问题:

  • ​安全性​​:Access Token 短期有效降低泄露风险,Refresh Token 独立存储且过期时间长
  • ​用户体验​​:自动刷新 Access Token,用户无感知续期
  • ​合规性​​:符合 OAuth 2.0 标准流程kKAdR

二、技术栈组成

技术组件作用版本要求
SpringBoot基础框架3.x
Apache Shiro认证和授权核心2.0.0+
Hutool-JWT令牌生成与验证5.8.24+

三、环境准备

3.1 创建 SpringBoot 项目

<!-- pom.XML -->
<dependencies>
    <!-- Shiro核心依赖 -->
    <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <classifier>jakarta</classifier>
            <version>${shiro.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <classifier>jakarta</classifier>
            <version>${shiro.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.shiro</groupId>
                    <artifactId>shiro-core</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.shiro</groupId>
                    <artifactId>shiro-web</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <classifier>jakarta</classifier>
            <version>${shiro.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.shiro</groupId>
                    <artifactId>shiro-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    <!-- Hutool-JWT -->
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.8.39</version>
    </dependency>
</dependencies>

四、核心代码实现

4.1 JWT工具类(JwtUtil.java)

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTUtil;
import cn.hutool.core.date.DateTime;
import org.springframework.data.Redis.core.RedisTemplate;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class JwtUtil {
    private static final String TOKEN_KEY = "sys:token:";
    private static final long ACCESS_EXPIRE = 1000 * 60 * 15; // 15分钟
    /**
     * 获取密钥(可选,我这里做的是动态配置的,可以根据需要写死就行)
     * @return 密钥
     */
    private static byte[] getJwtSSecret() {
        SysParamsService sysParamsService = SpringUtil.getBean(SysParamsService.class);
        String jwtSecret =  sysParamsService.getValue("jwt.secret", true);
        return jwtSecret.getBytes();
    }
    /**
     * 获取刷新token过期时间-单位天(可选,我这里做的是动态配置的,可以根据需要写死就行)
     * @return 过期时间-单位天
     */
    private static int getRefreshExp() {
        SysParamsService sysParamsService = SpringUtil.getBean(SysParamsService.class);
        String refreshExp =  sysParamsService.getValue("jwt.exp", true);
        return Integer.parseInt(refreshExp);
    }
    // 生成双Token
    public static Map<String, String> generateTokens(Long userId,String username) {
        Map<String, String> tokens = new HashMakKAdRp<>();
        // Access Token
        tokens.put("accessToken", createToken(userId,username));
        // Refresh Token
        tokens.put("refreshToken", createRefreshToken(userId));
        return tokens;
    }
    public static String createToken(Long userId,String username) {
        Map<String, Object> payload = new HashMap<>();
        payload.put("userId", userId);
        payload.put("username", username);
        payload.put("type", "access");
        payload.put("exp", new DateTime(System.currentTimeMillis() + ACCESS_EXPIRE).getTime());
        return JWTUtil.createToken(payload, getJwtSSecret());
    }
    public static String createRefreshToken(Long userId) {
        Map<String, Object> payload = new HashMap<>();
        payload.put("userId", userId);
        payload.put("type", "refresh");
        String refreshToken = JWTUtil.createToken(payload, getJwtSSecret());
        RedisTemplate<String, String> redisTemplate = SpringUtil.getBean("redisTemplate", RedisTemplate.class);
        redisTemplate.opsForValue().set(TOKEN_KEY+userId, refreshToken, getRefreshExp(), TimeUnit.DAYS);
        return refreshToken;
    }
    // 刷新Token
    public static void refreshAccessToken(String refreshToken) {
        Assert.isTrue(JWTUtil.verify(refreshToken, getJwtSSecret()), "非法Token错误");
        JWT jwt = JWTUtil.parseToken(refreshToken);
        Assert.isTrue(ObjectUtil.equals("refresh", jwt.getPayload("type")), "非法Token错误");
        Long userId = (Long) jwt.getPayload("userId");
        RedisTemplate<String, String> redisTemplate = SpringUtil.getBean("redisTemplate", RedisTemplate.class);
        String redis_refreshToken = redisTemplate.opsForValue().get(TOKEN_KEY + userId);
        Assert.isTrue(ObjectUtil.equals(redis_refreshToken, refreshToken), "Token已过期");
        //可选 用于延长缓存时间
        long expire = redisTemplate.getExpire(TOKEN_KEY + userId, TimeUnit.DAYS);
        if(expire == 0){
            redisTemplate.expire(TOKEN_KEY + userId, 7, TimeUnit.DAYS);
        }
    }
    /**
     * 从Token中获取用户Id
     * @param refreshToken JWT Token字符串
     * @return 用户Id
     */
    public static Long getUserIdFromRefreshToken(String refreshToken) {
        Assert.isTrue(JWTUtil.verify(refreshToken, getJwtSSecret()), "非法Token错误");
        JWT jwt = JWTUtil.parseToken(refreshToken);
        Assert.isTrue(ObjectUtil.equals("refresh", jwt.getPayload("type")), "非法Token错误");
        Long userId = (Long) jwt.getPayload("userId");
        RedisTemplate<String, String> redisTemplate = SpringUtil.getBean("redisTemplate", RedisTemplate.class);
        long expire = redisTemplate.getExpire(TOKEN_KEY + userId, TipythonmeUnit.DAYS);
        Assert.isTrue(expire >= 0, "Token已过期");
        String redis_refreshToken = redisTemplate.opsForValue().get(TOKEN_KEY + userId);
        Assert.isTrue(ObjectUtil.equals(redis_refreshToken, refreshToken), "Token已过期");
        return (Long) jwt.getPayload("userId");
    }
    /**
     * 从Token中获取用户Id
     * @param token JWT Token字符串
     * @return 用户Id
     */
    public static Long getUserIdFromToken(String token) {
        Assert.isTrue(JWTUtil.verify(token, getJwtSSecret()), "非法Token错误");
        JWT jwt = JWTUtil.parseToken(token);
        Assert.isTrue(ObjectUtil.equals(jwt.getPayload("type"),"access"), "非法Token错误");
        Assert.isTrue(jwt.getPayload("exp")!=null && jwt.validate(0),"token已失效");
        return (Long) jwt.getPayload("userId");
    }
    /**
     * 从Token中获取用户名
     * @param token JWT Token字符串
     * @return 用户名
     */
    public static String getUsernameFromToken(String token) {
        Assert.isTrue(JWTUtil.verify(token, getJwtSSecret()), "非法Token错误");
        JWT jwt = JWTUtil.parseToken(token);
        Assert.isTrue(jwt.getPayload("exp")!=null && jwt.validate(0),"token已失效");
        return jwt.getPayload("username").toString();
    }
}

4.2 Shiro配置类(ShiroConfig.java)

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.config.ShiroFilterConfiguration;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import jakarta.servlet.Filter;
@Configuration
public class ShiroConfig {
    @Bean
    public DefaultWebSessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionValidationSchedulerEnabled(false);
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        return sessionManager;
    }
    @Bean("securityManager")
    public SecurityManager securityManager(Oauth2Realm oAuth2Realm, SessionManager sessionManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(oAuth2Realm);
        securityManager.setSessionManager(sessionManager);
        securityManager.setRememberMeManager(null);
        return securityManager;
    }
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager, SysParamsService sysParamsService) {
        ShiroFilterConfiguration config = new ShiroFilterConfiguration();
        config.setFilterOncePerRequest(true);
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        shiroFilter.setShiroFilterConfiguration(config);
        Map<String, Filter> filters = new HashMap<>();
        // oauth过滤
        filters.put("oauth2", new Oauth2Filter());
        Map<String, String> filterMap = new LinkedHashMap<>();
        filterMap.put("/v3/api-docs/**", "anon");
        filterMap.put("/doc.html", "anon");
        filterMap.put("/favicon.ico", "anon");
        filterMap.put("/refreshToken", "anon");
        filterMap.put("/login", "anon");
        filterMap.put("/**", "oauth2");
        shiroFilter.setFilterChainDefinitionMap(filterMap);
        return shiroFilter;
    }
    @Bean("lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}

4.3 注册过滤器

@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean<DelegatingFilterProxy> shiroFilterRegistration() {
        FilterRegistrationBean<DelegatingFilterProxy> registration = new FilterRegistrationBean<>();
        registration.setFilter(new DelegatingFilterProxy("shiroFilter"));
        // 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理
        registration.addInitParameter("targetFilterLifecycle", "true");
        registration.setEnabled(true);
    php    registration.setOrder(Integer.MAX_VALUE - 1);
        registration.addUrlPatterns("/*");
        return registration;
    }
}

4.4 注册oauth2过滤器

public class Oauth2Filter extends AuthenticatingFilter {
    private static final Logger logger = LoggerFactory.getLogger(Oauth2Filter.class);
    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
        // 获取请求token
        String token = getRequestToken((HttpServletRequest) request);
        if (StringUtils.isBlank(token)) {
            logger.warn("createToken:token is empty");
            return null;
        }
        return new Oauth2Token(token);
    }
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        if (((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())) {
            return true;
        }
        return false;
    }
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        // 获取请求token,如果token不存在,直接返回401
        String token = getRequestToken((HttpServletRequest) request);
        if (StringUtils.isBlank(token)) {
            logger.warn("onAccessDenied:token is empty");
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.setContentType("application/json;charset=utf-8");
            httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
            httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
            String json = JsonUtils.toJsonString(new Result<Void>().error(ErrorCode.UNAUTHORIZED));
            httpResponse.getWriter().print(json);
            return false;
        }
        return executeLogin(request, response);
    }
    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request,
            ServletResponse response) {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setContentType("application/json;charset=utf-8");
        httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
        httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
        try {
            Throwable throwable = e.getCause() == null ? e : e.getCause();
            Result<Void> r = new Result<Void>().error(ErrorCode.UNAUTHORIZED, throwable.getMessage());
            String json = JsonUtils.toJsonString(r);
            httpResponse.getWriter().print(json);
        } catch (IOException e1) {
        }
        return false;
    }
    /**
     * 获取请求的token
     */
    private String getRequestToken(HttpServletRequest httpRequest) {
        String token = null;
        // 从header中获取token
        String authorization = httpRequest.getHeader(Constant.AUTHORIZATION);
        if (StringUtils.isNotBlank(authorization) && authorization.startsWith("Bearer ")) {
            token = authorization.replace("Bearer ", "");
        }
        return token;
    }
}

4.5 认证类

import java.util.HashSet;
import java.util.Set;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import jakarta.annotation.Resource;
@Component
public class Oauth2Realm extends AuthorizingRealm {
    @Resource
    private UserService userService;
    private static final Logger logger = LoggerFactory.getLogger(Oauth2Realm.class);
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof Oauth2Token;
    }
    /**
     * 授权(验证权限时调用)
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        UserDetail user = (UserDetail) principajavascriptls.getPrimaryPrincipal();
        // 用户权限列表
        Set<String> permsSet = new HashSet<>();
        if (user.getSuperAdmin() == SuperAdminEnum.YES.value()) {
            permsSet.add("sys:role:superAdmin");
            permsSet.add("sys:role:normal");
        } else {
            permsSet.add("sys:role:normal");
        }
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setStringPermissions(permsSet);
        return info;
    }
    /**
     * 认证(登录时调用)
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String accessToken = (String) token.getPrincipal();
        // 根据accessToken,查询用户信息
        Long userId = JwtUtil.getUserIdFromToken(accessToken);
        Assert.notNull(userId, "token已过期,请重新登入");
        // 查询用户信息
        SysUserEntity userEntity = userService.getUser(userId);
        // 转换成UserDetail对象
        UserDetail userDetail = ConvertUtils.sourceToTarget(userEntity, UserDetail.class);
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(userDetail, accessToken, getName());
        return info;
    }
}

4.6 token类

public class Oauth2Token implements AuthenticationToken {
    private String token;
    public Oauth2Token(String token) {
        this.token = token;
    }
    @Override
    public String getPrincipal() {
        return token;
    }
    @Override
    public Object getCredentials() {
        return token;
    }
}

4.7 Shiro获取用户信息工具类

public class SecurityUser {
    public static Subject getSubject() {
        try {
            return SecurityUtils.getSubject();
        } catch (Exception e) {
            return null;
        }
    }
    /**
     * 获取用户信息
     */
    public static UserDetail getUser() {
        Subject subject = getSubject();
        if (subject == null) {
            return new UserDetail();
        }
        UserDetail user = (UserDetail) subject.getPrincipal();
        if (user == null) {
            return new UserDetail();
        }
        return user;
    }
    public static String getToken() {
        return getUser().getToken();
    }
    /**
     * 获取用户ID
     */
    public static Long getUserId() {
        return getUser().getId();
    }
}

五、双Token实现原理

5.1 Token结构对比

Token类型有效期存储位置包含信息
Access Token15分钟客户用户ID、用户名称,类型,过期时间,角色(可选)、权限(可选)
Refresh Token7天客户端,Redis用户ID、类型

5.2 核心流程图

六、完整代码示例

6.1 登录控制器(AuthController.java)

@RestController
public class AuthController {
    @PostMapping("/login")
    public Result login(@RequestBody LoginRequest request) {
        User user = userService.findByUsername(request.getUsername());
        if (user == null || !user.getPassword().equals(request.getPassword())) {
            return Result.error("账号或密码错误");
        }
        String accessToken = JwtUtil.createAccessToken(
            user.getUsername(), 
            user.getId()
        );
        String refreshToken = JwtUtil.createRefreshToken(user.getUsername(),user.getId());
        return Result.success(Map.of(
            "accessToken", accessToken,
            "refreshToken", refreshToken
        ));
    }
    @PostMapping("/refreshToken")
    public Result<String> refreshToken(@RequestHeader("refreshToken") String refreshToken) {
        Long userId = JwtUtil.getUserIdFromRefreshToken(refreshToken);
        //if(userId==null)userId=1904748826795986946L;
        SysUserDTO user = sysUserService.getByUserId(userId);
        Assert.notNull(user, "token异常,非法登入");
        String newAccessToken = JwtUtil.createToken(user.getId(),user.getUsername());
        JwtUtil.refreshAccessToken(refreshToken);//自动续租
        return new Result<String>().ok(newAccessToken);
    }
}

七、双Token优势总结

​维度​​单Token方案​​双Token方案​
​安全性​单一Token泄露风险高Access Token短期有效,Refresh Token双存储(可自动延迟过期时间,并保证安全性)
​用户体验​频繁登录/重新认证自动续期,用户无感知
​合规性​不符合OAuth2.0标准完全遵循OAuth2.0标准流程

八 、补充

为确保系统的安全性,本方案采用了​​单刷新Token绑定机制​​。具体而言,每个用户的刷新Token(Refresh Token)与单一设备或终端绑定。当同一用户在其他设备或终端上登录时,新的登录操作将导致之前设备的刷新Token失效(通常伴随Access Token的到期登入失效)。这种机制有效防止了同一账户在多设备间的异常并行登录,增强了账户的安全性。

然而,根据不同的业务需求和安全策略,开发者可以根据实际情况对Token管理机制进行调整。例如,若业务场景允许多设备同时在线,可以修改Token绑定策略,支持多刷新Token共存。此外,开发者们也欢迎在下方评论区提出自己的建议,共同探讨和优化

到此这篇关于SpringBoot集成Shiro+JWT(Hutool)完整代码示例的文章就介绍到这了,更多相关SpringBoot Shiro JWT集成内容请搜索China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程China编程(www.chinasem.cn)!

这篇关于SpringBoot集成Shiro+JWT(Hutool)完整代码示例的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1155628

相关文章

springboot项目中集成shiro+jwt完整实例代码

《springboot项目中集成shiro+jwt完整实例代码》本文详细介绍如何在项目中集成Shiro和JWT,实现用户登录校验、token携带及接口权限管理,涉及自定义Realm、ModularRe... 目录简介目的需要的jar集成过程1.配置shiro2.创建自定义Realm2.1 LoginReal

Java 与 LibreOffice 集成开发指南(环境搭建及代码示例)

《Java与LibreOffice集成开发指南(环境搭建及代码示例)》本文介绍Java与LibreOffice的集成方法,涵盖环境配置、API调用、文档转换、UNO桥接及REST接口等技术,提供... 目录1. 引言2. 环境搭建2.1 安装 LibreOffice2.2 配置 Java 开发环境2.3 配

在 Spring Boot 中连接 MySQL 数据库的详细步骤

《在SpringBoot中连接MySQL数据库的详细步骤》本文介绍了SpringBoot连接MySQL数据库的流程,添加依赖、配置连接信息、创建实体类与仓库接口,通过自动配置实现数据库操作,... 目录一、添加依赖二、配置数据库连接三、创建实体类四、创建仓库接口五、创建服务类六、创建控制器七、运行应用程序八

基于Spring Boot 的小区人脸识别与出入记录管理系统功能

《基于SpringBoot的小区人脸识别与出入记录管理系统功能》文章介绍基于SpringBoot框架与百度AI人脸识别API的小区出入管理系统,实现自动识别、记录及查询功能,涵盖技术选型、数据模型... 目录系统功能概述技术栈选择核心依赖配置数据模型设计出入记录实体类出入记录查询表单出入记录 VO 类(用于

深入解析Java NIO在高并发场景下的性能优化实践指南

《深入解析JavaNIO在高并发场景下的性能优化实践指南》随着互联网业务不断演进,对高并发、低延时网络服务的需求日益增长,本文将深入解析JavaNIO在高并发场景下的性能优化方法,希望对大家有所帮助... 目录简介一、技术背景与应用场景二、核心原理深入分析2.1 Selector多路复用2.2 Buffer

Java中数组与栈和堆之间的关系说明

《Java中数组与栈和堆之间的关系说明》文章讲解了Java数组的初始化方式、内存存储机制、引用传递特性及遍历、排序、拷贝技巧,强调引用数据类型方法调用时形参可能修改实参,但需注意引用指向单一对象的特性... 目录Java中数组与栈和堆的关系遍历数组接下来是一些编程小技巧总结Java中数组与栈和堆的关系关于

MySQL 升级到8.4版本的完整流程及操作方法

《MySQL升级到8.4版本的完整流程及操作方法》本文详细说明了MySQL升级至8.4的完整流程,涵盖升级前准备(备份、兼容性检查)、支持路径(原地、逻辑导出、复制)、关键变更(空间索引、保留关键字... 目录一、升级前准备 (3.1 Before You Begin)二、升级路径 (3.2 Upgrade

SpringBoot利用树形结构优化查询速度

《SpringBoot利用树形结构优化查询速度》这篇文章主要为大家详细介绍了SpringBoot利用树形结构优化查询速度,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一个真实的性能灾难传统方案为什么这么慢N+1查询灾难性能测试数据对比核心解决方案:一次查询 + O(n)算法解决

Python跨文件实例化、跨文件调用及导入库示例代码

《Python跨文件实例化、跨文件调用及导入库示例代码》在Python开发过程中,经常会遇到需要在一个工程中调用另一个工程的Python文件的情况,:本文主要介绍Python跨文件实例化、跨文件调... 目录1. 核心对比表格(完整汇总)1.1 自定义模块跨文件调用汇总表1.2 第三方库使用汇总表1.3 导

SpringBoot实现虚拟线程的方案

《SpringBoot实现虚拟线程的方案》Java19引入虚拟线程,本文就来介绍一下SpringBoot实现虚拟线程的方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录什么是虚拟线程虚拟线程和普通线程的区别SpringBoot使用虚拟线程配置@Async性能对比H