Java学习笔记

编程知识 行业动态 更新时间:2024-06-13 00:21:18

文章目录

  • 1、项目创建方式
  • 2、pom.xml
  • 3、yaml配置注入
      • 3.1 配置文件
      • 3.2 JSR303数据校验(好像没什么用)
      • 3.3 多环境切换
  • 4、整合Druid
  • 5、整合Mybatis-plus
      • 5.1 导入 MyBatis-plus 的相关依赖
      • 5.2 配置mybatis-plus
      • 5.3 代码自动生成器
      • 5.4 配置 MapperScan 注解
      • 5.5 Service CRUD 接口
      • 5.6 Page分页
      • 5.7 常用注解
          • @TableId
          • @TableField
  • 6、 乐观锁
      • 6.1 配置插件
      • 6.2 在实体类的字段上加上`@Version`注解
  • 7、逻辑删除
      • 7.1 在数据表中增加一个 deleted 字段
      • 7.2 在yml中进行配置
      • 7.3 实体类字段上加上`@TableLogic`注解
  • 8、条件构造器
      • 8.1 QueryWrapper
      • 8.2 UpdateWrapper
          • 1. set
          • 2. setSql
          • 3. 通用条件
  • 9、自动填充功能
      • 9.1 注解填充字段 `@TableField(fill = FieldFill.INSERT)` `...
      • 9.2 自定义实现类 MyMetaObjectHandler
      • 9.3 自动填充遇到的问题
          • 1. 调用update方法时,自动填充字段不生效问题
          • 2. 逻辑删除时,自动填充字段不生效问题
          • 3. 举例(删除用户)
  • 10、用原生的mybatis完成sql语句
      • 10.1 整体步骤
      • 10.2 XML映射文件
          • 1. sql
          • 2. 集合collection
      • 10.3 动态sql
          • 1. if
          • 2. choose、when、otherwise
          • 3. trim、where、set
          • 4. foreach
  • 11、shiro
      • 11.1 MD5、Salt的注册流程
          • 1. 导入Spring整合Shiro依赖
          • 2. UserController.java
          • 3. SaltUtil.java
          • 4. 修改ShiroConfig.java
      • 11.2 MD5、Salt的认证流程
          • 1. 自定义Realm方式 CustomerRealm
          • 3. CustomerByteSource.java
          • 4. 配置Shiro ShiroConfig
          • 5. 设置登录、退出接口
      • 11.3 Shiro请求授权
          • 1. 授权方式
          • 2. 授权
            • 基于角色的访问控制(隐式角色)
            • 基于资源的访问控制(显示角色)
      • 11.4 EhCache实现缓存
          • 1. 修改pom.xml
          • 2. 修改ShiroConfig.java中getRealm
      • 11.5 集成Redis实现Shiro缓存
          • 1. 修改pom.xml
          • 2. 修改application.yml
          • 3. 启动redis服务
          • 4. RedisCacheManager.java与RedisCache.java
          • 5. 修改ShiroConfig.java
  • 12、Spring Boot 集成 Swagger


1、项目创建方式


使用 IDEA 直接创建项目

1、创建一个新项目

2、选择spring initalizr

3、填写项目信息

4、选择初始化的组件(初学勾选 Web 即可)

5、填写项目路径

6、等待项目构建成功


通过上面步骤完成了基础项目的创建。就会自动生成以下文件:

1、程序的主启动类

2、一个 application.properties 配置文件

3、一个 测试类

4、一个 pom.xml

2、pom.xml


    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
	<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.21</version>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>

        <!-- mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.2.0</version>
        </dependency>
        <!-- 代码生成器 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.4.1</version>
        </dependency>
        <!-- Freemarker模板引擎 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- shiro -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
            <version>1.5.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.5.3</version>
        </dependency>

        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- swagger -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
                <configuration>
                    <!--跳过项目运行测试用例-->
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>
        </plugins>
    </build>

3、yaml配置注入


3.1 配置文件

SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的

  • application.properties
    • 语法结构:key=value
  • application.yml
    • 语法结构:key:空格 value

配置文件的作用 :修改SpringBoot自动配置的默认值。

spring:
  profiles:
    active: dev

# 开发环境
---
server:
  tomcat:
    uri-encoding: UTF-8
    max-threads: 1000
    min-spare-threads: 30
  port: 8080
  connection-timeout: 5000ms
  servlet:
    context-path: /

mybatis-plus:
  mapper-locations: classpath*:mybatis/mappers/*Mapper.xml
  # 配置日志
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#    map-underscore-to-camel-case: true
  # 配置逻辑删除
  global-config:
    db-config:
      logic-delete-field: deleted  # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

spring:
  jackson:
    time-zone: GMT+8
    date-format: yyyy-MM-dd HH:mm:ss
  profiles: dev
  aop:
    proxy-target-class: true

  # 文件上传大小
  servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 100MB
      enabled: true

  # 数据源
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/Mybatis_plus?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=round
      username: root
      password: 123456
      initial-size: 10
      max-active: 100
      min-idle: 10
      max-wait: 60000
      pool-prepared-statements: true
      max-pool-prepared-statement-per-connection-size: 20
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      stat-view-servlet:
        enabled: true
        url-pattern: /druid/*
      filter:
        # 监控统计
        stat:
          log-slow-sql: false
          slow-sql-millis: 1000
          merge-sql: false
        # 防御sql注入
        wall:
          config:
            multi-statement-allow: true
        # 日志
        log4j2:
          connection-log-enabled: false
          statement-log-enabled: false
          result-set-log-enabled: true
          statement-executable-sql-log-enable: true
          enabled: true

  # redis
  redis:
    host: 127.0.0.1
    port: 6379
    database: 0

swagger:
  enabled: true

3.2 JSR303数据校验(好像没什么用)

Springboot中可以用 @Validated 来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式;

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Validated //数据校验
    public class Person {
        @Email(message = "邮箱格式错误") //name必须是邮箱格式
        private String name;
        private Integer age;
        private Boolean happy;
        private Date birth;
        private Map<String,Object> maps;
        private List<Object> lists;
        private Dog dog;
    }

使用 @Validated 注解需导入依赖

    <dependency>
        <groupId>org.hibernate.validator</groupId>
        <artifactId>hibernate-validator</artifactId>
    </dependency>

表 1. Bean Validation 中内置的 constraint

Constraint详细信息
@Null被注释的元素必须为 null
@NotNull被注释的元素必须不为 null, 无法查检长度为0的字符串
@NotBlank检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格
@NotEmpty检查约束元素是否为NULL或者是EMPTY
@AssertTrue被注释的元素必须为 true
@AssertFalse被注释的元素必须为 false
@Min(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min)被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction)被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past被注释的元素必须是一个过去的日期
@Future被注释的元素必须是一个将来的日期
@Pattern(value)被注释的元素必须符合指定的正则表达式

表 2. Hibernate Validator 附加的 constraint

Constraint详细信息
@Email被注释的元素必须是电子邮箱地址
@Length被注释的字符串的大小必须在指定的范围内
@NotEmpty被注释的字符串的必须非空
@Range被注释的元素必须在合适的范围内

3.3 多环境切换

profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境。

yaml的多文档块

  通过—分隔各个环境

    server:
      port: 8081
    
    #选择要激活哪个环境快
    spring:
      profiles:
        active: prod
    
    ---
    server:
      port: 8082
    spring:
      profiles: dev #配置环境的名称
    
    ---
    server:
      port: 8083
    spring:
      profiles: prod #配置环境的名称

4、整合Druid


  1. 添加上 Druid 数据源依赖。
<!-- druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.21</version>
</dependency>

com.alibaba.druid.pool.DruidDataSource 基本配置参数如下:


  1. 切换数据源,通过 spring.datasource.type 指定数据源。
spring:
  # 数据源
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://121.196.39.205:3310/huashu_monitor?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=round
      username: root
      password: root123456
      initial-size: 10
      max-active: 100
      min-idle: 10
      max-wait: 60000
      pool-prepared-statements: true
      max-pool-prepared-statement-per-connection-size: 20
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      stat-view-servlet:
        enabled: true
        url-pattern: /druid/*
      filter:
        # 监控统计
        stat:
          log-slow-sql: false
          slow-sql-millis: 1000
          merge-sql: false
        # 防御sql注入
        wall:
          config:
            multi-statement-allow: true
        # 日志
        log4j2:
          connection-log-enabled: false
          statement-log-enabled: false
          result-set-log-enabled: true
          statement-executable-sql-log-enable: true
          enabled: true

5、整合Mybatis-plus


5.1 导入 MyBatis-plus 的相关依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.2.0</version>
</dependency>
<!-- 代码生成器 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.4.1</version>
</dependency>
<!-- Freemarker模板引擎 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

5.2 配置mybatis-plus

mybatis-plus:
  mapper-locations: classpath*:mybatis/mappers/*Mapper.xml
  # 配置日志
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  # 配置逻辑删除
  global-config:
    db-config:
      logic-delete-field: deleted  # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

5.3 代码自动生成器

AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。

package com.hdu.demo;

import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class CodeGenerator {

    /**
     * <p>
     * 读取控制台内容
     * </p>
     */
    public static String scanner(String tip) {
        Scanner scanner = new Scanner(System.in);
        StringBuilder help = new StringBuilder();
        help.append("请输入" + tip + ":");
        System.out.println(help.toString());
        if (scanner.hasNext()) {
            String ipt = scanner.next();
            if (StringUtils.isNotBlank(ipt)) {
                return ipt;
            }
        }
        throw new MybatisPlusException("请输入正确的" + tip + "!");
    }

    public static void main(String[] args) {
        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir(projectPath + "/src/main/java");
        gc.setAuthor("yh");
        gc.setOpen(false);
        gc.setServiceName("%sService"); // 去Service的I前缀
        // gc.setSwagger2(true); // 实体属性 Swagger2 注解
        mpg.setGlobalConfig(gc);

        // 数据源配置(要修改)
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://121.196.39.205:3310/huashu_monitor?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=round");
        // dsc.setSchemaName("public");
        dsc.setDriverName("com.mysql.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("root123456");
        mpg.setDataSource(dsc);

        // 包配置(要修改)
        PackageConfig pc = new PackageConfig();
        // pc.setModuleName(scanner("模块名"));
        pc.setParent("com.hdu.demo");
        pc.setEntity("pojo");
        pc.setMapper("dao");
        mpg.setPackageInfo(pc);

        // 自定义配置
        InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
                // to do nothing
            }
        };

        // 如果模板引擎是 freemarker
        String templatePath = "/templates/mapper.xml.ftl";
        // 如果模板引擎是 velocity
        // String templatePath = "/templates/mapper.xml.vm";

        // 自定义输出配置
        List<FileOutConfig> focList = new ArrayList<>();
        // 自定义配置会被优先输出
        focList.add(new FileOutConfig(templatePath) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
                return projectPath + "/src/main/resources/mybatis/mappers/" + pc.getModuleName()
                        + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
            }
        });
        /*
        cfg.setFileCreate(new IFileCreate() {
            @Override
            public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {
                // 判断自定义文件夹是否需要创建
                checkDir("调用默认方法创建的目录,自定义目录用");
                if (fileType == FileType.MAPPER) {
                    // 已经生成 mapper 文件判断存在,不想重新生成返回 false
                    return !new File(filePath).exists();
                }
                // 允许生成模板文件
                return true;
            }
        });
        */
        cfg.setFileOutConfigList(focList);
        mpg.setCfg(cfg);

        // 配置模板
        TemplateConfig templateConfig = new TemplateConfig();

        // 配置自定义输出模板
        //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
        // templateConfig.setEntity("templates/entity2.java");
        // templateConfig.setService();
        // templateConfig.setController();

        templateConfig.setXml(null);
        mpg.setTemplate(templateConfig);

        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        // strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
        strategy.setEntityLombokModel(true);
        strategy.setRestControllerStyle(true);
        // 公共父类
        // strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");
        // 写于父类中的公共字段
        strategy.setSuperEntityColumns("id");
        strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
        strategy.setControllerMappingHyphenStyle(true);
        // 配置表前缀,生成实体时去除表前缀
        strategy.setTablePrefix(pc.getModuleName() + "_");
        mpg.setStrategy(strategy);
        mpg.setTemplateEngine(new FreemarkerTemplateEngine());
        mpg.execute();
    }

}

5.4 配置 MapperScan 注解

@MapperScan("com.hdu.demo.dao")

可在config中MyBatisPlusConfig配置,也可在主启动类上配置。

5.5 Service CRUD 接口

Save

// 插入一条记录(选择字段,策略插入)
boolean save(T entity);
// 插入(批量)
boolean saveBatch(Collection<T> entityList);
// 插入(批量)
boolean saveBatch(Collection<T> entityList, int batchSize);

SaveOrUpdate

// TableId 注解存在更新记录,否插入一条记录
boolean saveOrUpdate(T entity);
// 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);

Remove

// 根据 entity 条件,删除记录
boolean remove(Wrapper<T> queryWrapper);
// 根据 ID 删除
boolean removeById(Serializable id);
// 根据 columnMap 条件,删除记录
boolean removeByMap(Map<String, Object> columnMap);
// 删除(根据ID 批量删除)
boolean removeByIds(Collection<? extends Serializable> idList);

Update

// 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
boolean update(Wrapper<T> updateWrapper);
// 根据 whereWrapper 条件,更新记录
boolean update(T updateEntity, Wrapper<T> whereWrapper);
// 根据 ID 选择修改
boolean updateById(T entity);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList, int batchSize);

使用update的重载方法 update(T entity, Wrapper updateWrapper)可解决自动填充不生效问题

@Test
public void test2(){
    UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
    updateWrapper
            .eq("age",29)
            .eq("name","Tom")
            .set("age",30);
    // 使用update的重载方法 update(T entity, Wrapper updateWrapper)可解决自动填充不生效问题
    userService.update(new User(),updateWrapper);  
}

Get

// 根据 ID 查询
T getById(Serializable id);
// 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
T getOne(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
T getOne(Wrapper<T> queryWrapper, boolean throwEx);
// 根据 Wrapper,查询一条记录
Map<String, Object> getMap(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);

List

// 查询所有
List<T> list();
// 查询列表
List<T> list(Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
Collection<T> listByIds(Collection<? extends Serializable> idList);
// 查询(根据 columnMap 条件)
Collection<T> listByMap(Map<String, Object> columnMap);
// 查询所有列表
List<Map<String, Object>> listMaps();
// 查询列表
List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
// 查询全部记录
List<Object> listObjs();
// 查询全部记录
<V> List<V> listObjs(Function<? super Object, V> mapper);
// 根据 Wrapper 条件,查询全部记录
List<Object> listObjs(Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录
<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);

Count

// 查询总记录数
int count();
// 根据 Wrapper 条件,查询总记录数
int count(Wrapper<T> queryWrapper);

5.6 Page分页

// 无条件分页查询
IPage<T> page(IPage<T> page);
// 条件分页查询
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
// 无条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page);
// 条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper);
  1. 新建config MybatisPlusConfig 配置分页插件
@Configuration
@MapperScan("com.baomidou.cloud.service.*.mapper*")
public class MybatisPlusConfig {
    // 最新版
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); 
        return interceptor;
    }
    
}
  1. 测试
@Test
public void page(){
    IPage<User> page = new Page<>(2,5);
    IPage<User> userIPage = userService.page(page);
    userIPage.getRecords().forEach(System.out::println);
}
  1. 基本操作
page.getCurrent(); // 获取当前页
page.getTotal(); // 获取总记录数
page.getSize(); // 获取每页的条数
page.getRecords(); // 获取每页数据的集合
page.getPages(); // 获取总页数
page.hasNext(); // 是否存在下一页
page.hasPrevious(); // 是否存在上一页

5.7 常用注解

@TableId
  • 描述:主键注解
属性类型必须指定默认值描述
valueString“”主键字段名
typeEnumIdType.NONE主键类型

IdType

描述
AUTO数据库ID自增
NONE无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
INPUTinsert前自行set主键值
ASSIGN_ID分配ID(主键类型为Number(Long和Integer)或String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法)
ASSIGN_UUID分配UUID,主键类型为String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(默认default方法)
@TableField
  • 描述:字段注解(非主键)
属性类型必须指定默认值描述
valueString“”数据库字段名
elString“”映射为原生 #{ ... } 逻辑,相当于写在 xml 里的 #{ ... } 部分
existbooleantrue是否为数据库表字段
conditionString“”字段 where 实体查询比较条件,有值设置则按设置的值为准,没有则为默认全局的 %s=#{%s},参考(opens new window)
updateString“”字段 update set 部分注入, 例如:update="%s+1":表示更新时会set version=version+1(该属性优先级高于 el 属性)
insertStrategyEnumNDEFAULT举例:NOT_NULL: insert into table_a(<if test="columnProperty != null">column</if>) values (<if test="columnProperty != null">#{columnProperty}</if>)
updateStrategyEnumNDEFAULT举例:IGNORED: update table_a set column=#{columnProperty}
whereStrategyEnumNDEFAULT举例:NOT_EMPTY: where <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if>
fillEnumFieldFill.DEFAULT字段自动填充策略
selectbooleantrue是否进行 select 查询
keepGlobalFormatbooleanfalse是否保持使用全局的 format 进行处理
jdbcTypeJdbcTypeJdbcType.UNDEFINEDJDBC类型 (该默认值不代表会按照该值生效)
typeHandlerClass<? extends TypeHandler>UnknownTypeHandler.class类型处理器 (该默认值不代表会按照该值生效)
numericScaleString“”指定小数点后保留的位数

FieldStrategy

描述
IGNORED忽略判断
NOT_NULL非NULL判断
NOT_EMPTY非空判断(只对字符串类型字段,其他类型字段依然为非NULL判断)
DEFAULT追随全局配置

FieldFill

描述
DEFAULT默认不处理
INSERT插入时填充字段
UPDATE更新时填充字段
INSERT_UPDATE插入和更新时填充字段

6、 乐观锁


当要更新一条记录的时候,希望这条记录没有被别人更新

乐观锁实现方式:

  • 取出记录时,获取当前version
  • 更新时,带上这个version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败

6.1 配置插件

在 MybatisPlusConfig 类中配置

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    return interceptor;
}

6.2 在实体类的字段上加上@Version注解

@Version
private Integer version;

7、逻辑删除


逻辑删除:数据库中没有真正被移除,而是通过一个变量来让他失效! deleted = 0 => deleted = 1

管理员可以查看被删除的记录!防止数据的丢失,类似于回收站!

7.1 在数据表中增加一个 deleted 字段

7.2 在yml中进行配置

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted  # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置注解)
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

7.3 实体类字段上加上@TableLogic注解

@TableLogic
private Integer deleted;

8、条件构造器

8.1 QueryWrapper

  • 设置查询字段

    select(String... sqlSelect)
    select(Predicate<TableFieldInfo> predicate)
    select(Class<T> entityClass, Predicate<TableFieldInfo> predicate)
    
  • 例: select("id", "name", "age")

  • 例: select(i -> i.getProperty().startsWith("test"))

8.2 UpdateWrapper

1. set
set(String column, Object val)
set(boolean condition, String column, Object val)
  • SQL SET 字段
  • 例: set("name", "老李头")
  • 例: set("name", "")—>数据库字段值变为空字符串
  • 例: set("name", null)—>数据库字段值变为null
2. setSql
setSql(String sql)
  • 设置 SET 部分 SQL
  • 例: setSql("name = '老李头'")
3. 通用条件
【比较大小: ( =, <>, >, >=, <, <= )eq(R column, Object val); // 等价于 =,例: eq("name", "老王") ---> name = '老王'
    ne(R column, Object val); // 等价于 <>,例: ne("name", "老王") ---> name <> '老王'
    gt(R column, Object val); // 等价于 >,例: gt("name", "老王") ---> name > '老王'
    ge(R column, Object val); // 等价于 >=,例: ge("name", "老王") ---> name >= '老王'
    lt(R column, Object val); // 等价于 <,例: lt("name", "老王") ---> name < '老王'
    le(R column, Object val); // 等价于 <=,例: le("name", "老王") ---> name <= '老王'
    
【范围:(between、not between、in、not in)】
   between(R column, Object val1, Object val2); // 等价于 between a and b, 例: between("age", 18, 30) ---> age between 18 and 30
   notBetween(R column, Object val1, Object val2); // 等价于 not between a and b, 例: notBetween("age", 18, 30) ---> age not between 18 and 30
   in(R column, Object... values); // 等价于 字段 IN (v0, v1, ...),例: in("age",{1,2,3}) ---> age in (1,2,3)
   notIn(R column, Object... values); // 等价于 字段 NOT IN (v0, v1, ...), 例: notIn("age",{1,2,3}) ---> age not in (1,2,3)
   inSql(R column, Object... values); // 等价于 字段 IN (sql 语句), 例: inSql("id", "select id from table where id < 3") ---> id in (select id from table where id < 3)
   notInSql(R column, Object... values); // 等价于 字段 NOT IN (sql 语句)
   
【模糊匹配:(like)】
    like(R column, Object val); // 等价于 LIKE '%值%',例: like("name", "王") ---> name like '%王%'
    notLike(R column, Object val); // 等价于 NOT LIKE '%值%',例: notLike("name", "王") ---> name not like '%王%'
    likeLeft(R column, Object val); // 等价于 LIKE '%值',例: likeLeft("name", "王") ---> name like '%王'
    likeRight(R column, Object val); // 等价于 LIKE '值%',例: likeRight("name", "王") ---> name like '王%'
    
【空值比较:(isNull、isNotNull)】
    isNull(R column); // 等价于 IS NULL,例: isNull("name") ---> name is null
    isNotNull(R column); // 等价于 IS NOT NULL,例: isNotNull("name") ---> name is not null

【分组、排序:(group、having、order)】
    groupBy(R... columns); // 等价于 GROUP BY 字段, ..., 例: groupBy("id", "name") ---> group by id,name
    orderByAsc(R... columns); // 等价于 ORDER BY 字段, ... ASC, 例: orderByAsc("id", "name") ---> order by id ASC,name ASC
    orderByDesc(R... columns); // 等价于 ORDER BY 字段, ... DESC, 例: orderByDesc("id", "name") ---> order by id DESC,name DESC
    having(String sqlHaving, Object... params); // 等价于 HAVING ( sql语句 ), 例: having("sum(age) > {0}", 11) ---> having sum(age) > 11

【拼接、嵌套 sql:(or、and、nested、apply)】
   or(); // 等价于 a or b, 例:eq("id",1).or().eq("name","老王") ---> id = 1 or name = '老王'
   or(Consumer<Param> consumer); // 等价于 or(a or/and b),or 嵌套。例: or(i -> i.eq("name", "李白").ne("status", "活着")) ---> or (name = '李白' and status <> '活着')
   and(Consumer<Param> consumer); // 等价于 and(a or/and b),and 嵌套。例: and(i -> i.eq("name", "李白").ne("status", "活着")) ---> and (name = '李白' and status <> '活着')
   nested(Consumer<Param> consumer); // 等价于 (a or/and b),普通嵌套。例: nested(i -> i.eq("name", "李白").ne("status", "活着")) ---> (name = '李白' and status <> '活着')
   apply(String applySql, Object... params); // 拼接sql(若不使用 params 参数,可能存在 sql 注入),例: apply("date_format(dateColumn,'%Y-%m-%d') = {0}", "2008-08-08") ---> date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
   last(String lastSql); // 无视优化规则直接拼接到 sql 的最后,可能存在 sql 注入。
   exists(String existsSql); // 拼接 exists 语句。例: exists("select id from table where age = 1") ---> exists (select id from table where age = 1)

9、自动填充功能


9.1 注解填充字段 @TableField(fill = FieldFill.INSERT) `…

/**
 * 创建时间
 */
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;

/**
 * 更新时间
 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
public enum FieldFill {
    /**
     * 默认不处理
     */
    DEFAULT,
    /**
     * 插入填充字段
     */
    INSERT,
    /**
     * 更新填充字段
     */
    UPDATE,
    /**
     * 插入和更新填充字段
     */
    INSERT_UPDATE
}

9.2 自定义实现类 MyMetaObjectHandler

@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
    // 插入时的填充策略
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert ......");
        this.setFieldValByName("createTime",LocalDateTime.now(),metaObject);
        this.setFieldValByName("updateTime",LocalDateTime.now(),metaObject);
    }
    // 更新时的填充策略
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update ......");
        this.setFieldValByName("updateTime",LocalDateTime.now(),metaObject);
    }
}

9.3 自动填充遇到的问题

1. 调用update方法时,自动填充字段不生效问题

实体更新时,直接使用update(Wrapper updateWrapper)的重载方法boolean update(T entity, Wrapper updateWrapper)

2. 逻辑删除时,自动填充字段不生效问题

使用 update 方法并: UpdateWrapper.set(column, value)

3. 举例(删除用户)
@GetMapping("/delete")
@RequiresRoles("admin")
public Object deleteUserByUserName(String username){
    try {
        // 逻辑删除改为update接口方法
        // 并且使用 update(T entity, Wrapper updateWrapper) 更新方法
        if (userService.update(new User(), new UpdateWrapper<User>().set("deleted","1").eq("username",username))) {
            return ServerResponse.createBySuccessMessage("删除用户成功");
        }
    }catch (Exception e){

    }
    return ServerResponse.createByErrorMessage("删除用户失败");
}

10、用原生的mybatis完成sql语句


利用mybatis-plus无法完成一些复杂的sql语句,例如多表查询。

10.1 整体步骤

  1. 修改配置文件application.yml

    在mybatis-plus下添加mapper文件的路径

    mybatis-plus:
      mapper-locations: classpath*:mybatis/mappers/*Mapper.xml
    
  2. 在pojo模块下新建一个VO 包路径用于提供页面展示所需的数据

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Accessors(chain = true)
    public class UserRoleVo {
        private Long id;
    
        private String username;
    
        private Integer roleId;
    
        private String roleName;
    
        private List<Role> roles;
    }
    
  3. 在dao层中定义方法

    public interface UserMapper extends BaseMapper<User> {
        UserRoleVo findRolesByUserName(String username);
    }
    
  4. 在UserMapper.xml中编写相应的sql语句

    <mapper namespace="com.hdu.demo.dao.UserMapper">
        <resultMap id="UserRoleMap" type="com.hdu.demo.vo.UserRoleVo">
            <id column="id" jdbcType="BIGINT" property="id"/>
            <result column="username" jdbcType="VARCHAR" property="username"/>
            <collection property="roles" javaType="list" ofType="com.hdu.demo.pojo.Role">
                <id column="role_id" jdbcType="INTEGER" property="roleId"/>
                <result column="role_name" jdbcType="VARCHAR" property="roleName"/>
            </collection>
        </resultMap>
    
        <select id="findRolesByUserName" resultMap="UserRoleMap" parameterType="string">
            SELECT USER.id,
                   USER.username,
                   role.role_id,
                   role.role_name
            FROM
                USER LEFT JOIN user_role ON USER.id = user_role.id
                     LEFT JOIN role ON user_role.role_id = role.role_id
            WHERE
                USER.username = #{username}
        </select>
    </mapper>
    
  5. 在service层调用dao层方法返回数据

    public interface UserService extends IService<User> {
        UserRoleVo findRolesByUserName(String username);
    }
    
    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
        @Autowired
        UserMapper userMapper;
        
        @Override
        public UserRoleVo findRolesByUserName(String username) {
            return userMapper.findRolesByUserName(username);
        }
    }
    
  6. 在controller调用service层返回前端数据

    @RestController
    @RequestMapping("/user")
    public class UserController {
    
        @Autowired
        UserService userService;
    
        @GetMapping("/get")
        public UserRoleVo getRolesByUserName(String username){
            return userService.findRolesByUserName(username);
        }
    }
    

10.2 XML映射文件

1. sql

这个元素可以用来定义可重用的 SQL 代码片段,以便在其它语句中使用。 参数可以静态地(在加载的时候)确定下来,并且可以在不同的 include 元素中定义不同的参数值。比如:

<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>

这个 SQL 片段可以在其它语句中使用,例如:

<select id="selectUsers" resultType="map">
  select
    <include refid="userColumns"><property name="alias" value="t1"/></include>,
    <include refid="userColumns"><property name="alias" value="t2"/></include>
  from some_table t1
    cross join some_table t2
</select>

也可以在 include 元素的 refid 属性或内部语句中使用属性值,例如:

<sql id="sometable">
  ${prefix}Table
</sql>

<sql id="someinclude">
  from
    <include refid="${include_target}"/>
</sql>

<select id="select" resultType="map">
  select
    field1, field2, field3
  <include refid="someinclude">
    <property name="prefix" value="Some"/>
    <property name="include_target" value="sometable"/>
  </include>
</select>
2. 集合collection

集合用于解决一对多的问题。举例:一个博客有很多文章(Post)。 在博客类中,这可以用下面的写法来表示:

private List<Post> posts;

要像上面这样,映射嵌套结果集合到一个 List 中,可以使用集合元素。

首先, 让我们看看对应的 SQL 语句:

<select id="selectBlog" resultMap="blogResult">
  select
  B.id as blog_id,
  B.title as blog_title,
  B.author_id as blog_author_id,
  P.id as post_id,
  P.subject as post_subject,
  P.body as post_body,
  from Blog B
  left outer join Post P on B.id = P.blog_id
  where B.id = #{id}
</select>

我们连接了博客表和文章表,并且为每一列都赋予了一个有意义的别名,以便映射保持简单。 要映射博客里面的文章集合,就这么简单:

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <collection property="posts" ofType="Post">
    <id property="id" column="post_id"/>
    <result property="subject" column="post_subject"/>
    <result property="body" column="post_body"/>
  </collection>
</resultMap>

“ofType” 属性用来将 JavaBean(或字段)属性的类型和集合存储的类型区分开来。所以可以按照下面这样来阅读映射:

<collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>

读作: “posts 是一个存储 Post 的 ArrayList 集合”。在一般情况下,MyBatis 可以推断 javaType 属性,因此并不需要填写。

如果你喜欢更详略的、可重用的结果映射,你可以使用下面的等价形式:

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <collection property="posts" ofType="Post" resultMap="blogPostResult" columnPrefix="post_"/>
</resultMap>

<resultMap id="blogPostResult" type="Post">
  <id property="id" column="id"/>
  <result property="subject" column="subject"/>
  <result property="body" column="body"/>
</resultMap>

10.3 动态sql

1. if

使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。比如:

<select id="findActiveBlogWithTitleLike" resultType="Blog">
  SELECT * FROM BLOG
  WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
</select>

这条语句提供了可选的查找文本功能。如果不传入 “title”,那么所有处于 “ACTIVE” 状态的 BLOG 都会返回;如果传入了 “title” 参数,那么就会对 “title” 一列进行模糊查找并返回对应的 BLOG 结果。

如果希望通过 “title” 和 “author” 两个参数进行可选搜索:

<select id="findActiveBlogLike" resultType="Blog">
  SELECT * FROM BLOG 
  WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>
2. choose、when、otherwise

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

还是上面的例子,但是策略变为:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回标记为 featured 的 BLOG。

<select id="findActiveBlogLike" resultType="Blog">
  SELECT * FROM BLOG
  WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>
3. trim、where、set
<trim prefix="" suffix="" suffixOverrides="" prefixOverrides=""></trim>

prefix:在trim标签内sql语句加上前缀。

suffix:在trim标签内sql语句加上后缀。

prefixOverrides:指定去除多余的前缀内容

suffixOverrides:指定去除多余的后缀内容,如:suffixOverrides=",",去除trim标签内sql语句多余的后缀","。


现在回到之前的 “if” 示例,这次我们将 “state = ‘ACTIVE’” 设置成动态条件,看看会发生什么。

<select id="findActiveBlogLike" resultType="Blog">
  SELECT * FROM BLOG
  WHERE
  <if test="state != null">
    state = #{state}
  </if>
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>

如果没有匹配的条件,最终这条 SQL 会变成这样:

SELECT * FROM BLOG
WHERE

这会导致查询失败。如果匹配的只是第二个条件又会怎样?这条 SQL 会是这样:

SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’

这个查询也会失败。

where

MyBatis 有一个简单且适合大多数场景的解决办法:添加<where></where>

<select id="findActiveBlogLike" resultType="Blog">
  SELECT * FROM BLOG
  <where>
    <if test="state != null">
         state = #{state}
    </if>
    <if test="title != null">
        AND title like #{title}
    </if>
    <if test="author != null and author.name != null">
        AND author_name like #{author.name}
    </if>
  </where>
</select>

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

如果 where 元素与你期望的不太一样,你也可以通过自定义 trim元素来定制 where 元素的功能。比如,将上面例子改为:

<select id="findActiveBlogLike" resultType="Blog">
    SELECT * FROM BLOG
    <trim prefix="WHERE" prefixOverrides="AND ">
        <if test="state != null">
            state = #{state}
        </if>
        <if test="title != null">
            AND title like #{title}
        </if>
        <if test="author != null and author.name != null">
            AND author_name like #{author.name}
        </if>
    </trim>
</select>    

prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。

set

用于动态更新语句的类似解决方案叫做 setset 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:

<update id="updateAuthorIfNecessary">
    update Author
    <set>
        <if test="username != null">username=#{username},</if>
        <if test="password != null">password=#{password},</if>
        <if test="email != null">email=#{email},</if>
        <if test="bio != null">bio=#{bio}</if>
    </set>
    where id=#{id}
</update>

set 元素等价的自定义 trim 元素:

<update id="updateAuthorIfNecessary">
    update Author
    <trim prefix="SET" suffixOverrides=",">
        <if test="username != null">username=#{username},</if>
        <if test="password != null">password=#{password},</if>
        <if test="email != null">email=#{email},</if>
        <if test="bio != null">bio=#{bio}</if>
    </trim>
    where id=#{id}
</update>

insert

通过自定义 trim 元素实现insert操作:

<insert id="insertSelective" parameterType="com.huashu.monitor.pojo.User" >
  insert into user
  <trim prefix="(" suffix=")" suffixOverrides="," >
    <if test="userId != null" >
      user_id,
    </if>
    <if test="username != null" >
      username,
    </if>
    <if test="realname != null" >
      realname,
    </if>
    <if test="password != null" >
      password
    </if>
  </trim>
  <trim prefix="values (" suffix=")" suffixOverrides="," >
    <if test="userId != null" >
      #{userId,jdbcType=INTEGER},
    </if>
    <if test="username != null" >
      #{username,jdbcType=VARCHAR},
    </if>
    <if test="realname != null" >
      #{realname,jdbcType=VARCHAR},
    </if>
    <if test="password != null" >
      #{password,jdbcType=VARCHAR},
    </if>
  </trim>
</insert>
4. foreach

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:

<select id="selectPostIn" resultType="domain.blog.Post">
    SELECT *
    FROM POST P
    WHERE ID in
    <foreach item="item" index="index" collection="list" open="(" separator="," close=")">
        #{item}
    </foreach>
</select>

foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头(open)与结尾(close)的字符串以及集合项迭代之间的分隔符(separator)。

提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

11、shiro


shiro的三大对象:

  • Subject : 主体,外部应用与subject进行交互,subject记录了当前的操作用户,将用户的概念理解为当前操作的主体。外部程序通过subject进行认证授权,而subject是通过SecurityManager安全管理器进行认证授权
  • SecurityMapper:安全管理器,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等
  • Realm:域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息

总结shiro的几个核心API:

SecurityManager securityManager = factory.getInstance();  // 得到SecurityManager实例
Subject currentUser = SecurityUtils.getSubject();         // 获取当前执行的用户
currentUser.isAuthenticated()                             // 判断用户是否已登录
currentUser.hasRole("schwartz")                           // 判断用户的角色         
currentUser.isPermitted("lightsaber:wield")               // 判断用户是否有权做什么
currentUser.logout();                                     // 注销

SpringBoot集成Shiro只需要两个类 ,Shiro的配置类ShiroConfig以及自定义的Realm类 CustomerRealm

ShiroConfig是对Shiro的一些配置,比如过滤的文件和权限,密码加密的算法等相关功能。

CustomerRealm是自定义的Realm类,继承了AuthorizingRealm,并且重写了doGetAuthorizationInfo(权限相关)和doGetAuthenticationInfo (身份认证)两个方法。

11.1 MD5、Salt的注册流程

1. 导入Spring整合Shiro依赖
    <!-- shiro整合spring的包 -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.5.3</version>
    </dependency>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-ehcache</artifactId>
        <version>1.5.3</version>
    </dependency>
2. UserController.java
@RestController
@RequestMapping("/user")
@Api(tags = "用户管理模块")
@Slf4j
public class UserController {

    @Autowired
    UserService userService;

    @GetMapping("/register")
    @ApiOperation(value = "用户注册", notes = "参数: ")
    public Object register(User user){
        //1.生成随机盐
        String salt = SaltUtils.getSalt(8);
        //2.将随机盐保存到数据
        user.setSalt(salt);
        //3.明文密码进行md5+salt+hash散列
        Md5Hash md5Hash = new Md5Hash(user.getPassword(), salt, 1024);
        user.setPassword(md5Hash.toHex());
        if (userService.save(user)){
            return ServerResponse.createBySuccessMessage("注册成功");
        }
        else{
            return ServerResponse.createByErrorMessage("注册失败");
        }
    }
}
3. SaltUtil.java

在utils包下创建 SaltUtil.java

public class SaltUtils {
    /**
     * 获取一个长度为n的盐
     * @param n 盐的长度
     * @return
     */
    public static String getSalt(int n) {
        // 从这些字符中随机选取n个作为盐
        char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()".toCharArray();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < n; i++) {
            char c = chars[new Random().nextInt(chars.length)];
            sb.append(c);
        }
        return sb.toString();
    }
}
4. 修改ShiroConfig.java
// 设置为公共资源 无需授权认证
filterMap.put("/user/register","anon");

11.2 MD5、Salt的认证流程

身份验证的三个流程:

  • 收集用户身份/凭证,即如用户名/密码
  • 调用 Subject.login 进行登录,如果失败将得到相应的 AuthenticationException 异常,根据异常提示用户错误信息;否则登录成功
  • 创建自定义的 Realm 类,继承AuthorizingRealm 类,实现doGetAuthenticationInfo() 方法
1. 自定义Realm方式 CustomerRealm

创建shiro包,在其下新建CustomerRealm.java

public class CustomerRealm extends AuthorizingRealm {
    @Autowired
    UserService userService;

    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 在token中获取用户名
        String principal = (String) authenticationToken.getPrincipal();
        User user = userService.getOne(new QueryWrapper<User>().eq("username",principal));
        // 模拟根据身份信息从数据库查询
        if(!ObjectUtils.isEmpty(user)){
            // 参数说明:用户名 | 密码 | credentialsSalt(盐) | 当前realm的名字
            return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), new CustomerByteSource(user.getSalt()), this.getName());
        }
        return null;
    }
}
3. CustomerByteSource.java

在utils下创建CustomerByteSource.java

//自定义salt实现  实现序列化接口
public class CustomerByteSource implements ByteSource, Serializable {

    private byte[] bytes;
    private String cachedHex;
    private String cachedBase64;

    public CustomerByteSource() {

    }

    public CustomerByteSource(byte[] bytes) {
        this.bytes = bytes;
    }

    public CustomerByteSource(char[] chars) {
        this.bytes = CodecSupport.toBytes(chars);
    }

    public CustomerByteSource(String string) {
        this.bytes = CodecSupport.toBytes(string);
    }

    public CustomerByteSource(ByteSource source) {
        this.bytes = source.getBytes();
    }

    public CustomerByteSource(File file) {
        this.bytes = (new CustomerByteSource.BytesHelper()).getBytes(file);
    }

    public CustomerByteSource(InputStream stream) {
        this.bytes = (new CustomerByteSource.BytesHelper()).getBytes(stream);
    }

    public static boolean isCompatible(Object o) {
        return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream;
    }

    public byte[] getBytes() {
        return this.bytes;
    }

    public boolean isEmpty() {
        return this.bytes == null || this.bytes.length == 0;
    }

    public String toHex() {
        if (this.cachedHex == null) {
            this.cachedHex = Hex.encodeToString(this.getBytes());
        }

        return this.cachedHex;
    }

    public String toBase64() {
        if (this.cachedBase64 == null) {
            this.cachedBase64 = Base64.encodeToString(this.getBytes());
        }

        return this.cachedBase64;
    }

    public String toString() {
        return this.toBase64();
    }

    public int hashCode() {
        return this.bytes != null && this.bytes.length != 0 ? Arrays.hashCode(this.bytes) : 0;
    }

    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (o instanceof ByteSource) {
            ByteSource bs = (ByteSource) o;
            return Arrays.equals(this.getBytes(), bs.getBytes());
        } else {
            return false;
        }
    }

    private static final class BytesHelper extends CodecSupport {
        private BytesHelper() {
        }

        public byte[] getBytes(File file) {
            return this.toBytes(file);
        }

        public byte[] getBytes(InputStream stream) {
            return this.toBytes(stream);
        }
    }
}
4. 配置Shiro ShiroConfig

ShiroConfig的三个方法,主要用来得到自定义的Realm对象,把得到的Realm对象注入给SecurityManager对象,再把SecurityManager对象给ShiroFilter。

ShiroFilter :是Shiro的过滤器,可以设置登录页面,权限不足跳转页面,具体某些页面的权限控制和身份认证。

建议从下往上来编写ShiroConfig,即:

获取Realm对象 => 获取securityManager对象 => 把Realm对象注入给securityManager对象 => 把securityManager对象注入给ShiroFilter

常用的内置Filter

  • anon :无需认证就可访问
  • authc :必须认证了才能访问
  • user :必须拥有[记住我]功能才能访问
  • perms :拥有对某个资源的权限才能访问
  • role :拥有某个角色权限才能访问
@Configuration
public class ShiroConfig {
    // 1.创建shiroFilter  负责拦截所有请求
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        //给filter设置安全管理器
        shiroFilter.setSecurityManager(defaultWebSecurityManager);

        //配置系统受限资源
        //配置系统公共资源
        Map<String,String> filterMap = new HashMap<>();
        // anon 匿名访问  authc 验证
        filterMap.put("/user/register","anon"); //authc 请求这个资源需要认证和授权

//        // 除了以上路径,其他都需要权限验证
//        filterMap.put("/**", "authc");
//        filterMap.put("/**", "anon");

        //默认认证界面路径
        shiroFilter.setFilterChainDefinitionMap(filterMap);
        //设置登陆的请求  没有得到认证的请求会跳转到登录页进行授权
        shiroFilter.setLoginUrl("/user/login");
        
        return shiroFilter;
    }

    // 2.创建安全管理器
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(CustomerRealm customerRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 关联realm
        securityManager.setRealm(customerRealm);
        return securityManager;
    }

    // 3.创建自定义realm对象
    @Bean
    public CustomerRealm getRealm(){
        CustomerRealm customerRealm = new CustomerRealm();
        // 修改凭证校验匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        // 设置加密算法为md5
        credentialsMatcher.setHashAlgorithmName("MD5");
        // 设置散列次数
        credentialsMatcher.setHashIterations(1024);
        customerRealm.setCredentialsMatcher(credentialsMatcher);
        return customerRealm;
    }
}
5. 设置登录、退出接口
@GetMapping("/login")
@ApiOperation(value = "用户登录", notes = "参数: ")
public Object login(@RequestParam String username,@RequestParam String password){
    // 通过安全工具类获取当前登录用户
    Subject subject = SecurityUtils.getSubject();
    // 创建token 执行登录操作
    subject.login(new UsernamePasswordToken(username, password));
    return ServerResponse.createBySuccess("登录成功", "data");
}

@GetMapping("/logout")
@ApiOperation(value = "用户登出", notes = "参数: ")
public Object logout(){
    SecurityUtils.getSubject().logout();
    return ServerResponse.createBySuccessMessage("退出成功");
}

11.3 Shiro请求授权

授权,也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)。

1. 授权方式

编程式:通过写 if/else 授权代码块完成:

Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
    //有权限
} else {
    //无权限
}

注解式:通过在执行的 Java 方法上放置相应的注解完成:

@RequiresRoles("admin")
public void hello() {
    //有权限
}

没有权限将抛出相应的异常;

JSP/GSP 标签:在 JSP/GSP 页面通过相应的标签完成:

<shiro:hasRole name="admin">
<!— 有权限 —>
</shiro:hasRole>
2. 授权
基于角色的访问控制(隐式角色)
  1. 数据库中表的情况

user表

role表

user_role表

  1. Realm中实现授权

    public class CustomerRealm extends AuthorizingRealm {
        @Autowired
        UserService userService;
    
        // 授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            // 获取主身份信息
            String principal = (String) principalCollection.getPrimaryPrincipal();
            // 根据主身份信息获取角色信息
            User user = userService.findRolesByUserName(principal);
            if(!CollectionUtils.isEmpty(user.getRoles())){
                SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
                user.getRoles().forEach(role -> {
                    simpleAuthorizationInfo.addRole(role.getRoleName());
                });
                return simpleAuthorizationInfo;
            }
            return null;
        }
    
        // 认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            return null;
        }
    }
    
  2. 在User实体类中添加需要获取其他表字段

    @TableField(exist = false) // 是否为数据库表字段
    private List<Role> roles;
    
    @TableField(exist = false)
    private Integer roleId;
    
    @TableField(exist = false)
    private String roleName;
    
  3. dao层(UserMapper)

    public interface UserMapper extends BaseMapper<User> {
        User findRolesByUserName(String username);
    }
    
  4. UserMapper.xml

    <resultMap id="UserRoleMap" type="com.hdu.demo.pojo.User">
        <id column="id" jdbcType="BIGINT" property="id"/>
        <result column="username" jdbcType="VARCHAR" property="username"/>
        <collection property="roles" javaType="list" ofType="com.hdu.demo.pojo.Role">
            <id column="role_id" jdbcType="INTEGER" property="roleId"/>
            <result column="role_name" jdbcType="VARCHAR" property="roleName"/>
        </collection>
    </resultMap>
    
    <select id="findRolesByUserName" resultMap="UserRoleMap" parameterType="string">
        SELECT USER.id,
               USER.username,
               role.role_id,
               role.role_name
        FROM
            USER LEFT JOIN user_role ON USER.id = user_role.id
                 LEFT JOIN role ON user_role.role_id = role.role_id
        WHERE
            USER.username = #{username}
    </select>
    
  5. service层(UserService)及其实现类(UserServiceImpl)

    public interface UserService extends IService<User> {
        User findRolesByUserName(String username);
    }
    
    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    
        @Autowired
        UserMapper userMapper;
    
        @Override
        public User findRolesByUserName(String username) {
            return userMapper.findRolesByUserName(username);
        }
    }
    
  6. 通过注解方式定义admin角色才能删除用户

    @GetMapping("/delete")
    @RequiresRoles("admin")
    public Object deleteUserByUserName(String username){
        try {
            if (userService.update(new User(), new UpdateWrapper<User>().set("deleted","1").eq("username",username))) {
                return ServerResponse.createBySuccessMessage("删除用户成功");
            }
        }catch (Exception e){
    
        }
        return ServerResponse.createByErrorMessage("删除用户失败");
    }
    

按照上述用户角色关系表,只有202050150能够删除用户。

基于角色的访问控制(即隐式角色)就完成了,这种方式的缺点就是如果很多地方进行了角色判断,但是有一天不需要了那么就需要修改相应代码把所有相关的地方进行删除,这就是粗粒度造成的问题。


基于资源的访问控制(显示角色)
  1. 数据库中表的情况

    role表

permission表

role_permission表

  1. Realm中实现授权

    @Autowired
    RoleService roleService;
    
    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 获取主身份信息
        String principal = (String) principalCollection.getPrimaryPrincipal();
        // 根据主身份信息获取角色信息
        User user = userService.findRolesByUserName(principal);
        if(!CollectionUtils.isEmpty(user.getRoles())){
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            user.getRoles().forEach(role -> {
                simpleAuthorizationInfo.addRole(role.getRoleName());
    
                Role roles = roleService.getPermissionsByRoleId(role.getRoleId());
                if(!CollectionUtils.isEmpty(roles.getPermissions())){
                    roles.getPermissions().forEach(permission -> {
                        simpleAuthorizationInfo.addStringPermission(permission.getPermissionName());
                    });
                }
            });
            return simpleAuthorizationInfo;
        }
        return null;
    }
    
  2. 在Role实体类中添加需要获取其他表字段

    @TableField(exist = false)
    private List<Permission> permissions;
    
    @TableField(exist = false)
    private int permissionId;
    
    @TableField(exist = false)
    private String permissionName;
    
  3. dao层(RoleMapper)

    public interface RoleMapper extends BaseMapper<Role> {
        Role getPermissionsByRoleId(int RoleId);
    }
    
  4. RoleMapper.xml

    <resultMap id="RolePermissionsMap" type="com.hdu.demo.pojo.Role">
        <id column="role_id" property="roleId"/>
        <result column="role_name" property="roleName"/>
        <collection property="permissions" ofType="com.hdu.demo.pojo.Permission">
            <id column="permission_id" property="permissionId"/>
            <result column="permission_name" property="permissionName"/>
        </collection>
    </resultMap>
    
    <select id="getPermissionsByRoleId" resultMap="RolePermissionsMap">
        SELECT
            role.role_id,
            role.role_name,
            permission.permission_id,
            permission.permission_name
        FROM
            role
                LEFT JOIN role_permission ON role.role_id = role_permission.role_id
                LEFT JOIN permission ON role_permission.permission_id = permission.permission_id
        WHERE
            role.role_id = #{RoleId}
    </select>
    
  5. service层(RoleService)及其实现类(RoleServiceImpl)

    public interface RoleService extends IService<Role> {
        Role getPermissionsByRoleId(int RoleId);
    }
    
    @Service
    public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
        @Autowired
        RoleMapper roleMapper;
    
        @Override
        public Role getPermissionsByRoleId(int RoleId) {
            return roleMapper.getPermissionsByRoleId(RoleId);
        }
    }
    
  6. 无需再通过注解定义角色控制,拥有user:delete的权限才能删除用户

    @GetMapping("/delete")
    // @RequiresRoles("admin")
    public Object deleteUserByUserName(String username){
        try {
            if (userService.update(new User(), new UpdateWrapper<User>().set("deleted","1").eq("username",username))) {
                return ServerResponse.createBySuccessMessage("删除用户成功");
            }
        }catch (Exception e){
    
        }
        return ServerResponse.createByErrorMessage("删除用户失败");
    }
    

Shiro 提供了 isPermitted 和 isPermittedAll 用于判断用户是否拥有某个权限或所有权限,也没有提供如 isPermittedAny 用于判断拥有某一个权限的接口。

但是失败的情况下会抛出 UnauthorizedException 异常。

到此基于资源的访问控制(显示角色)就完成了,也可以叫基于权限的访问控制,这种方式的一般规则是“资源标识符:操作”,即是资源级别的粒度;这种方式的好处就是如果要修改基本都是一个资源级别的修改,不会对其他模块代码产生影响,粒度小。但是实现起来可能稍微复杂点,需要维护“用户——角色,角色——权限(资源:操作)”之间的关系。

11.4 EhCache实现缓存

shiro提供了缓存管理器,这样在用户第一次认证授权后访问其受限资源的时候就不用每次查询数据库从而达到减轻数据压力的作用,使用shiro的缓存管理器也很简单

1. 修改pom.xml
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-ehcache</artifactId>
        <version>1.5.3</version>
    </dependency>
2. 修改ShiroConfig.java中getRealm
@Configuration
public class ShiroConfig {
    // 1.创建shiroFilter  负责拦截所有请求
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        //给filter设置安全管理器
        shiroFilter.setSecurityManager(defaultWebSecurityManager);

        //配置系统受限资源
        //配置系统公共资源
        Map<String,String> filterMap = new HashMap<>();
        // anon 匿名访问  authc 验证
        filterMap.put("/user/get","authc"); //authc 请求这个资源需要认证和授权

//        // 除了以上路径,其他都需要权限验证
//        filterMap.put("/**", "authc");
        filterMap.put("/**", "anon");

        //默认认证界面路径
        shiroFilter.setFilterChainDefinitionMap(filterMap);
        //设置登陆的请求  没有得到认证的请求会跳转到登录页进行授权
        shiroFilter.setLoginUrl("/user/login");

        return shiroFilter;
    }

    // 2.创建安全管理器
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(CustomerRealm customerRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 关联realm
        securityManager.setRealm(customerRealm);
        return securityManager;
    }

    // 3.创建自定义realm对象
    @Bean
    public CustomerRealm getRealm(){
        CustomerRealm customerRealm = new CustomerRealm();
        // 修改凭证校验匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        // 设置加密算法为md5
        credentialsMatcher.setHashAlgorithmName("MD5");
        // 设置散列次数
        credentialsMatcher.setHashIterations(1024);
        customerRealm.setCredentialsMatcher(credentialsMatcher);

        // 设置缓存管理器(EhCacheManager缓存在本地)
        customerRealm.setCacheManager(new EhCacheManager());
        // customerRealm.setCacheManager(new RedisCacheManager());
        // 开启全局缓存
        customerRealm.setCachingEnabled(true);
        // 开启认证缓存并指定缓存名称
        customerRealm.setAuthenticationCachingEnabled(true);
        customerRealm.setAuthenticationCacheName("authenticationCache");
        // 开启授权缓存并指定缓存名称
        customerRealm.setAuthorizationCachingEnabled(true);
        customerRealm.setAuthorizationCacheName("authorizationCache");

        return customerRealm;
    }
}

这样就将EhCache集成进来了,但是shiro的这个缓存是本地缓存,也就是说当程序宕机重启后仍然需要从数据库加载数据,不能实现分布式缓存的功能。下面我们集成redis来实现缓存

11.5 集成Redis实现Shiro缓存

1. 修改pom.xml
    <!-- redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
2. 修改application.yml
spring: 
  redis:
    host: 127.0.0.1
    port: 6379
    database: 0
3. 启动redis服务

4. RedisCacheManager.java与RedisCache.java

在shiro包下新建cache包,并在其下创建RedisCacheManager.java与RedisCache.java

RedisCacheManager

public class RedisCacheManager implements CacheManager{

    @Override
    public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {
        System.out.println("缓存名称: "+cacheName);
        return new RedisCache<K,V>(cacheName);
    }
}

RedisCache

/**
 * redis缓存用户登录信息
 */
public class RedisCache<k, v> implements Cache<k, v> {

    private String cacheName;

    public RedisCache() {}

    public RedisCache(String cacheName) {
        this.cacheName = cacheName;
    }

    @Override
    public v get(k k) throws CacheException {
        System.out.println("获取缓存:" + k);
        return (v) getRedisTemplate().opsForHash().get(this.cacheName, k.toString());
    }

    @Override
    public v put(k k, v v) throws CacheException {
        System.out.println("设置缓存key: " + k + " value:" + v);
        getRedisTemplate().opsForHash().put(this.cacheName, k.toString(), v);
        return null;
    }

    @Override
    public v remove(k k) throws CacheException {
        return (v) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString());
    }

    @Override
    public void clear() throws CacheException {
        getRedisTemplate().delete(this.cacheName);
    }

    @Override
    public int size() {
        return getRedisTemplate().opsForHash().size(this.cacheName).intValue();
    }

    @Override
    public Set<k> keys() {
        return getRedisTemplate().opsForHash().keys(this.cacheName);
    }

    @Override
    public Collection<v> values() {
        return getRedisTemplate().opsForHash().values(this.cacheName);
    }

    // 封装获取redisTemplate
    // shiro使用
    private RedisTemplate getRedisTemplate() {
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}
5. 修改ShiroConfig.java

这一步主要修改的就是**getRealm()**方法中设置缓存管理器的代码,由EnCacheManager()换成我们自定义的RedisCacheManager()其他的不用动

customerRealm.setCacheManager(new RedisCacheManager());

12、Spring Boot 集成 Swagger


  1. 导入相关依赖
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>2.9.2</version>
    </dependency>
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>2.9.2</version>
    </dependency>
  1. 要使用Swagger,我们需要编写一个配置类 SwaggerConfig来配置 Swagger
@EnableSwagger2
@Configuration
public class SwaggerConfig {
    // 是否开启swagger,正式环境一般是需要关闭的,可根据springboot的多环境配置进行设置
    @Value(value = "${swagger.enabled}")
    Boolean swaggerEnabled;
    
    /**
     * apiInfo() 用来创建该 Api 的基本信息(这些基本信息会展现在文档页面中)
     *
     */
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("利用swagger构建api文档")
                .description("杭州电子科技大学")
                .version("1.1.0")
                .contact(new Contact("yuhao", "https://www.baidu", "xxx@163"))
                .build();
    }
    /**
     * createRestApi 函数创建 Docket 的Bean
     *
     */
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                // 创建API的基本信息
                .apiInfo(apiInfo())     

                // 是否开启 select()函数返回一个ApiSelectorBuilder实例用来控制哪些接口暴露给Swagger来展现 构建选择器
                .enable(swaggerEnabled).select()
                .apis(RequestHandlerSelectors.basePackage("com.hdu.demo"))      // 指定扫描的包路径

                // Swagger会扫描该包下的所有Controller定义的API,并产生文档内容(除了被@ApiIgnore定义的请求)
                .paths(PathSelectors.any())
                .build();
    }
}
  1. 访问测试 :http://localhost:8080/swagger-ui.html
  2. 下面是Swagger常用注解的使用
// 常用:
@Api:修饰整个类,描述Controller的作用
@ApiOperation:描述一个类的一个方法,或者说一个接口

@ApiParam:单个参数描述
@ApiModel:用对象来接收参数
@ApiProperty:用对象接收参数时,描述对象的一个字段
@ApiResponse:HTTP响应其中1个描述
@ApiResponses:HTTP响应整体描述
@ApiIgnore:使用该注解忽略这个API
@ApiError :发生错误返回的信息
@ApiImplicitParam:描述一个请求参数,可以配置参数的中文含义,还可以给参数设置默认值
@ApiImplicitParams:描述由多个 @ApiImplicitParam 注解的参数组成的请求参数列表

例如:

@RestController
@RequestMapping("/user")
@Api(tags = "用户管理模块")
@Slf4j
public class UserController {

    @Autowired
    UserService userService;

    @GetMapping("/register")
    @ApiOperation(value = "用户注册", notes = "参数: ")
    public Object register(User user){
        //1.生成随机盐
        String salt = SaltUtils.getSalt(8);
        //2.将随机盐保存到数据
        user.setSalt(salt);
        //3.明文密码进行md5+salt+hash散列
        Md5Hash md5Hash = new Md5Hash(user.getPassword(), salt, 1024);
        user.setPassword(md5Hash.toHex());
        if (userService.save(user)){
            return ServerResponse.createBySuccessMessage("注册成功");
        }
        else{
            return ServerResponse.createByErrorMessage("注册失败");
        }
    }

}

参考: KuangStudy,以学为伴,一生相伴

更多推荐

Java学习笔记

本文发布于:2023-03-31 14:41:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/2bad9137465674f0cfcf5669e98b1da9.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:学习笔记   Java

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!