学习目标:
了解数据库配置加密方法,数据库连接池,mybatis-paginationInterceptor分页,pagehelper分页常用功能
学习内容:
1、mybatis plus配置加密
1.1、生成加密配置
package sccba.example;import com.baomidou.mybatisplus.core.toolkit.AES;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class BootDemoApplicationTests {@Testvoid contextLoads() {//生成 16 位随机 AES 密钥String randomKey = AES.generateRandomKey();System.out.println(randomKey);//13fe43969b1bd013String url = AES.encrypt("jdbc:mysql://7.**.**.**:3306/boot_demo?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false", randomKey);String username = AES.encrypt("root", randomKey);String password = AES.encrypt("123456", randomKey);System.out.println(url);System.out.println(username);System.out.println(password);}}
1.2在配置文件中配置加密信息
spring:mvc:pathmatch:matching-strategy: ant_path_matcher #Spring Boot 2.6及以后,swagger配置报错,需添加此配置datasource:#运行命令java -jar bootDemo-0.0.1-SNAPSHOT.jar --mpw.key=***** --spring.profiles.active=proddriverClassName: com.mysql.cj.jdbc.Driverurl: mpw:Y9zn0h//FsmBS7JJ0Frsl8qAJ9b7****mSMkoHS0QyPiooOq01ylU4s9Dcj1mqg=username: mpw:Zg+h***soXJc+fw==password: mpw:LO8p0****TY7GcQ==
1.3启动时添加参数–mpw.key
通过cmd命令运行项目
java -jar bootDemo-0.0.1-SNAPSHOT.jar --mpw.key=13fe43969b1bd013 --spring.profiles.active=prod
通过ideal运行,添加Program arguments配置
1.4原理
SpringBoot 提供修改Spring环境后置处理器【EnvironmentPostProcessor】,允许在应用程序之前操作环境属性值。mybatis plus的SafetyEncryptProcessor 类实现了EnvironmentPostProcessor ,此方法也可对配置文件中的其他参数进行加密
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package com.baomidou.mybatisplus.autoconfigure;import com.baomidou.mybatisplus.core.toolkit.AES;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import java.util.HashMap;
import java.util.Iterator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.SimpleCommandLinePropertySource;public class SafetyEncryptProcessor implements EnvironmentPostProcessor {public SafetyEncryptProcessor() {}public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {String mpwKey = null;Iterator var4 = environment.getPropertySources().iterator();while(var4.hasNext()) {PropertySource<?> ps = (PropertySource)var4.next();if (ps instanceof SimpleCommandLinePropertySource) {SimpleCommandLinePropertySource source = (SimpleCommandLinePropertySource)ps;mpwKey = source.getProperty("mpw.key");break;}}if (StringUtils.isNotBlank(mpwKey)) {HashMap<String, Object> map = new HashMap();Iterator var15 = environment.getPropertySources().iterator();while(true) {PropertySource ps;do {if (!var15.hasNext()) {if (CollectionUtils.isNotEmpty(map)) {environment.getPropertySources().addFirst(new MapPropertySource("custom-encrypt", map));}return;}ps = (PropertySource)var15.next();} while(!(ps instanceof OriginTrackedMapPropertySource));OriginTrackedMapPropertySource source = (OriginTrackedMapPropertySource)ps;String[] var8 = source.getPropertyNames();int var9 = var8.length;for(int var10 = 0; var10 < var9; ++var10) {String name = var8[var10];Object value = source.getProperty(name);if (value instanceof String) {String str = (String)value;if (str.startsWith("mpw:")) {map.put(name, AES.decrypt(str.substring(4), mpwKey));}}}}}}
}
2、数据库连接池
2.1 HikaraCP 默认连接池
1、在 Spring Boot 项目中,一个简单的 Spring DataSource 配置,通常只需要设置数据库连接、用户名和密码三个参数。
2、在 Spring Boot 项目中, Spring DataSource 会使用默认的配置启用 HikaraCP 数据库连接池。需要注意程序启动时并不会创建数据库连接,而是按需创建。项目启动后执行mysql查询命令show processlist 查看数据库连接。
spring:datasource:type: com.zaxxer.hikari.HikariDataSource #连接池类型,默认自动获取#spring hikari 数据库连接池配置hikari:auto-commit: true #事务自动提交,默认trueconnection-timeout: 30000 #连接超时时间(ms),默认值30秒,如果连接池满等待释放线程最大时间,超过则报错idle-timeout: 600000 #连接池中允许闲置的时间(ms),默认值10分钟,最小允许10smax-lifetime: 1800000 #连接生命时长(ms),超时而没被使用则释放,默认值30分钟,最小允许30s。如果执行时间大于max-lifetime则在sql执行完后idle-timeout释放,如果执行时间小于max-lifetime则在max-lifetime减去执行时间后释放maximum-pool-size: 20 #连接池中允许的最大连接数,包括使用和闲置的连接,默认10,超过将进入等待队列,直到有空闲连接minimum-idle: 10 #连接池中允许的最小空闲连接数,默认10validation-timeout: 5000 #连接测试活动的最长等待时间(ms),默认5s,最小值250mspool-name: HikaraPool-1 #连接池名称,默认自动生成
2.2 Druid 连接池
2.2.1添加依赖
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>${druid.version}</version></dependency>
2.2.2配置
需要指定spring.datasource.type,spring.datasource.druid参数可以不写,会按照默认参数处理
spring:datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql:///:3306/数据库名?useSSL=false&useUnicode=true&characterEncoding=UTF-8username:password:type: com.alibaba.druid.pool.DruidDataSourcedruid:initial-size: 10 # 初始化时建立物理连接的个数。min-idle: 10 # 最小连接池数量maxActive: 200 # 最大连接池数量maxWait: 60000 # 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置timeBetweenEvictionRunsMillis: 60000 # 关闭空闲连接的检测时间间隔.Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。minEvictableIdleTimeMillis: 300000 # 连接的最小生存时间.连接保持空闲而不被驱逐的最小时间validationQuery: SELECT 1 FROM DUAL # 验证数据库服务可用性的sql.用来检测连接是否有效的sql 因数据库方言而差, 例如 oracle 应该写成 SELECT 1 FROM DUALtestWhileIdle: true # 申请连接时检测空闲时间,根据空闲时间再检测连接是否有效.建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRuntestOnBorrow: false # 申请连接时直接检测连接是否有效.申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。testOnReturn: false # 归还连接时检测连接是否有效.归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。poolPreparedStatements: true # 开启PSCachemaxPoolPreparedStatementPerConnectionSize: 20 #设置PSCache值connectionErrorRetryAttempts: 3 # 连接出错后再尝试连接三次breakAfterAcquireFailure: true # 数据库服务宕机自动重连机制timeBetweenConnectErrorMillis: 300000 # 连接出错后重试时间间隔asyncInit: true # 异步初始化策略remove-abandoned: true # 是否自动回收超时连接remove-abandoned-timeout: 1800 # 超时时间(以秒数为单位)transaction-query-timeout: 6000 # 事务超时时间filters: stat,wall,log4j2useGlobalDataSourceStat: true #合并多个DruidDataSource的监控数据connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000 #通过connectProperties属性来打开mergeSql功能;慢SQL记录
2.2.3数据库密码加密
打开依赖jar所在位置
打开dos窗口
使用命令生成加密密码&密钥(两种命令二选一,推荐命令2方便后面复制使用)
命令1:java -cp druid-1.2.20.jar com.alibaba.druid.filter.config.ConfigTools 数据库密码
直接把密钥打印在dos窗口
命令2:java -cp druid-1.2.20.jar com.alibaba.druid.filter.config.ConfigTools 数据库密码 > pwd.txt
生成一个文件在当前目录下
生成的文件
修改数据库配置,password的值放在spring.datasource.druid.password。publicKey的值放在spring.datasource.druid.connection-properties: config.decrypt=true;config.decrypt.key=${publicKey}
spring:datasource:driverClassName: com.mysql.cj.jdbc.Driver#druid加密方式druid:url: jdbc:mysql://*.*.*.*:3306/boot_demo?useUnicode=true&characterEncoding=utf-8&useSSL=falseusername: password: PhFyCDrV********126LwHTvYiVKYX6La1fDlSC8g==connection-properties: config.decrypt=true;config.decrypt.key=MFwwDQYJK*******LwiyGH5rgAMCYgYToVKbvSf10dSP53pUekVMLyk0DwWEOZ8PYY565V1oQ275uRgP2kRMbX5Lcc+qmWzM+0CAwEAAQ==filter:config:enabled: true
3、分页
3.1 mybatis-plus分页插件PaginationInnerInterceptor
3.1.1 配置分页插件
在没有引入分页插件的情况下,Mybatis Plus 是不支持分页功能的,IService 和 BaseMapper 中的分页方法都无法正常生效。所以,我们必须配置分页插件,简单来说,就是创建一个配置类,然后注册一个 Bean 对象:
package sccba.example.config;import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @ClassName MybatisPlusConfig* @Description TODO* @Author c_see* @Date 2024/1/23 17:41* @Version 1.0**/
@Configuration
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;}
}
3.1.2 测试分页功能
编写通用分页查询类
/*** 通用分页查询类*/
@Data
@ApiModel(description = "分页查询实体类")
public class PageQuery {@ApiModelProperty("页码")private Integer pageNum;@ApiModelProperty("页面大小")private Integer pageSize;@ApiModelProperty("排序字段")private String sortBy;@ApiModelProperty("是否升序")private Boolean isAsc;
}
修改UserQuery类集成PageQuery
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserQuery extends PageQuery {@ApiModelProperty(value = "用户ID", example = "1")private Long userId;@ApiModelProperty(value = "部门ID", example = "1")private Long depId;
}
添加分页查询方法
UserController添加查询接口
/*** 使用Mybatis-plus自带分页操作 并对parms中的Key进行模糊查询*/@ApiOperation("分页获取用户")@GetMapping("/getList")public R findPageByMybatisPlus(UserQuery userQuery) {IPage page = userService.pageByMybatisPlus(userQuery);return R.ok().putData(page);}
UserService添加分页查询方法
public interface UserService {public UserDTO getById(Long userId);UserDTO getUsersDep(Long userId);void insert(UserVO userVo);IPage pageByMybatisPlus(UserQuery userQuery);
}
UserServiceImpl实现分页查询方法
package sccba.example.admin.service.impl;import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import sccba.example.admin.dao.UserMapper;
import sccba.example.admin.domain.dto.UserDTO;
import sccba.example.admin.domain.entity.UserDO;
import sccba.example.admin.domain.query.UserQuery;
import sccba.example.admin.domain.transfer.UserTransfer;
import sccba.example.admin.domain.vo.UserVO;
import sccba.example.admin.service.UserService;import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Function;/*** @ClassName UserServiceImpl* @Description TODO* @Author c_see* @Date 2024/1/12 15:27* @Version 1.0**/
@Service(value = "userService")
public class UserServiceImpl extends ServiceImpl<UserMapper, UserDO> implements UserService {@Autowired //根据类型注入private UserMapper userMapper;@Overridepublic UserDTO getById(Long userId) {UserDO userDO = userMapper.get(userId);
// UserDO userDO = userMapper.selectById(userId);
// // 创建查询条件对象
// QueryWrapper<UserDO> queryWrapper = new QueryWrapper<>();
//
// // 添加连表条件
// queryWrapper.lambda().eq(UserDO::getId, userId).ge(UserDO::getAge,20).like(UserDO::getName,"老");
// // 调用selectList方法执行查询并返回结果集合
// UserDO userDO = userMapper.selectOne(queryWrapper);//TODO 内部使用UserBO封装中间所需的逻辑对象,可以包括一个或多个其它的对象。常见操作DO转DTO,DO转BO、BO转DTOreturn UserTransfer.toDTO(userDO);}@Overridepublic UserDTO getUsersDep(Long userId) {UserDTO userDTO = userMapper.getUserWithDep(userId);//TODO 内部使用UserBO封装中间所需的逻辑对象,可以包括一个或多个其它的对象。常见操作DO转DTO,DO转BO、BO转DTOreturn userDTO;}@Overridepublic void insert(UserVO userVo) {UserDO userDO = UserTransfer.vo2DO(userVo);userDO.setDepId(1L);userDO.setPassword("12546");userMapper.insert(userDO);}@Overridepublic IPage pageByMybatisPlus(UserQuery userQuery) {QueryWrapper<UserDO> queryWrapper = new QueryWrapper<>();queryWrapper.lambda().eq(UserDO::getId, userQuery.getUserId()).or().eq(UserDO::getDepId, userQuery.getDepId());Page<UserDO> pageQ = new Page<UserDO>(userQuery.getPageNum(), userQuery.getPageSize());if (!StringUtils.isEmpty(userQuery.getSortBy())) {pageQ.addOrder(new OrderItem(userQuery.getSortBy(), userQuery.getIsAsc()));}IPage<UserDO> page = this.page(pageQ, queryWrapper);return UserTransfer.toDTOPage(page);}}
编写单元测试,执行成功
3.2 pagehelper分页插件
3.2.1引入依赖
<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1.4.5</version></dependency>
3.2.2使用PageHelper分页
controller
/*** 使用pagehelper分页操作 并对parms中的Key进行模糊查询*/@ApiOperation("分页获取用户PageHelper")@GetMapping("/getList2")public R findPageByPageHelper(UserQuery userQuery) {return R.ok().putData(userService.pageByPageHelper(userQuery));}
serviceImp
@Overridepublic PageInfo<UserDTO> pageByPageHelper(UserQuery userQuery) {//使用PageHelper以下三句之间不能插入其他语句PageHelper.startPage(userQuery.getPageNum(), userQuery.getPageSize(), true).setOrderBy(userQuery.getSortBy());List<UserDO> userDOList = userMapper.getList(userQuery);PageInfo<UserDO> page = new PageInfo<UserDO>(userDOList);return UserTransfer.toDTOPage(page);//错误写法
// List<UserDTO> userDTOList = UserTransfer.toDTOList(userDOList);
// return new PageInfo<UserDTO>(userDTOList);}
3.2.3 编写测试案例测试
3.2.4原理概述
PageHelper是MyBatis的一个插件,内部实现了一个PageInterceptor拦截器。Mybatis会加载这个拦截器到拦截器链中。在我们使用过程中先使用PageHelper.startPage这样的语句在当前线程上下文中设置一个ThreadLocal变量,再利用PageInterceptor这个分页拦截器拦截,从ThreadLocal中拿到分页的信息,如果有分页信息拼装分页SQL(limit语句等)进行分页查询,最后再把ThreadLocal中的东西清除掉。
PageHelper原理详情参考:https://blog.csdn.net/fedorafrog/article/details/104412140
ThreadLocal 详解参考:https://blog.csdn.net/u010445301/article/details/111322569