【mybatis-plus】Springboot+AOP+自定义注解实现多数据源操作(数据源信息存在数据库)

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
云数据库 RDS MySQL Serverless,价值2615元额度,1个月
简介: 【mybatis-plus】Springboot+AOP+自定义注解实现多数据源操作(数据源信息存在数据库)

背景

本文主要讲述的是如何实现动态切换数据源,数据源信息是存储在数据库表里,与在配置文件中写好数据库信息然后用@DS("XX")注解不同。


前言

本文是为了解决多数据源切换执行任务等,希望对大家有所帮助,有问题的可以在评论区留言,看到一定回复您!

一、准备工作

1.依赖

 <dependency>
     <groupId>com.baomidou</groupId>
     <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
     <version>3.4.0</version>
 </dependency>

2.数据库表(脚本)

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
 
-- ----------------------------
-- Table structure for sys_data_resource
-- ----------------------------
DROP TABLE IF EXISTS `sys_data_resource`;
CREATE TABLE `sys_data_resource`  (
  `uuid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主键',
  `data_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '数据源别名',
  `ip_address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '服务器地址(支持ip和域名)',
  `port` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '端口号',
  `data_resource_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '数据库名称',
  `user_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户名',
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '密码',
  `parent_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '父id,默认0,0:代表父级 用于树形结构',
  `data_resource_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'mysql' COMMENT '数据库类型,默认:mysql 用于区分不同数据库驱动',
  `create_user_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建用户id',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  `update_user_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '修改用户id',
  `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
  `url` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '数据库链接信息',
  `status` int(2) NULL DEFAULT 0 COMMENT '0:未删除,1:已删除',
  `data_desc` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '数据描述',
  PRIMARY KEY (`uuid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '动态数据源管理' ROW_FORMAT = Dynamic;
 
SET FOREIGN_KEY_CHECKS = 1;

3.配置文件

#配置数据源
spring:
  datasource:
    dynamic:
      primary: master #设置默认的数据源,默认值为master
      strict: false
      datasource:
        master: # 数据源1配置
          url: jdbc:log4jdbc:mysql://192.168.1.202:3306/xxxxxx?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
          username: xxxxx
          password: xxx123!
          driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
      druid:
        db-type: com.alibaba.druid.pool.DruidDataSource
        break-after-acquire-failure: true
        # 初始化配置
        initial-size: 3
        # 最小连接数
        min-idle: 3
        # 最大连接数
        max-active: 15
        # 获取连接超时时间
        max-wait: 5000
        # 连接有效性检测时间
        time-between-eviction-runs-millis: 90000
        # 最大空闲时间
        min-evictable-idle-time-millis: 1800000
        test-while-idle: true
        test-on-borrow: false
        test-on-return: false
 
        validation-query: select 1
        # 配置监控统计拦截的filters
        filters: stat
        stat-view-servlet:
          url-pattern: /druid/*
          reset-enable: false
        web-stat-filter:
          url-pattern: /*
          exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
  redis:
    #数据库索引
    database: 0
    host: 192.168.1.202
    port: 6379
    password: xxxx23!

url和driver-class-name 大家可以自行换成mysql的驱动就行。

4.自定义注解@ChangeDB

当方法上使用@ChangeDB时,代表切换数据源,不加时,默认走主数据源。

 
import java.lang.annotation.*;
 
/**
 * 自定义多数据源切换注解
 * <p>
 * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准
 * 如果传递value,则切换到value对应的数据源
 *
 * @author 曹震
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface ChangeDB {
    String value() default "";
}

5.定义AOP切面

 
import com.alibaba.fastjson.JSONObject;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import com.hvit.data_governance.dataComparison.dbhelp.DataSourceHelp;
import com.hvit.data_governance.dataComparison.entity.SysDataResource;
import com.hvit.data_governance.dataComparison.service.SysDataResourceService;
import com.hvit.data_governance.utils.DBUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
 
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Set;
 
/**
 * 动态数据源切面拦截
 * 请注意:这里order一定要小于tx:annotation-driven的order,即先执行DynamicDataSourceAspectAdvice切面,再执行事务切面,才能获取到最终的数据源
 *
 * @author 曹震
 * @version 1.0
 */
@Slf4j
@Aspect
@Component
@Order(1)
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DynamicDataSourceAspect {
    @Autowired
    private DataSource dataSource;
    @Resource
    private DefaultDataSourceCreator defaultDataSourceCreator;
    @Autowired
    private SysDataResourceService sysDataResourceService;
 
    @Pointcut("@annotation(com.hvit.data_governance.dataComparison.dbhelp.ChangeDB) || @within(com.hvit.data_governance.dataComparison.dbhelp.ChangeDB)")
    public void dsPointCut() {
 
    }
 
    @Around("dsPointCut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
        DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
        String dynamicDataSource = "";
        try {
            //获取参数,切换数据源参数固定都传poolName参数
            //get请求获取参数
            dynamicDataSource = request.getParameter(DBUtils.DATA_RESOURCE_NAME);
            if (StringUtils.isEmpty(dynamicDataSource)) {
                //如果get请求没获取到参数,就那用post请求body获取参数
                dynamicDataSource = getDataResourceName(request);
            }
            //如果get、post都没获取到,支持动态传参切换数据源
            if (StringUtils.isEmpty(dynamicDataSource)) {
                dynamicDataSource = (String) request.getAttribute("poolName");
            }
            if (StringUtils.isNotEmpty(dynamicDataSource)) {
                //先获取传入的数据源是不是存在数据池当中
                Set<String> sets = ds.getCurrentDataSources().keySet();
                boolean flag = false;
                for (String str : sets) {
                    //首先排除主数据源
                    if (!"master".equals(str)) {
                        if (str.equals(dynamicDataSource)) {
                            DynamicDataSourceContextHolder.push(dynamicDataSource);
                            flag = true;
                            break;
                        }
                    }
                }
                //如果数据池中没有找到当前数据源,那么开始进行新增数据源
                //poolName其实就是数据源id
                if (!flag) {
                    SysDataResource sysDataResource = sysDataResourceService.getById(dynamicDataSource);
                    if (sysDataResource != null) {
                        manualAddSource(sysDataResource);
                        DynamicDataSourceContextHolder.push(dynamicDataSource);
                    }
                }
                return joinPoint.proceed();
            }
            return joinPoint.proceed();
        } finally {
            //每次使用完成,清空数据源
            //DynamicDataSourceContextHolder.poll();
            if (StringUtils.isNotEmpty(dynamicDataSource)) {
                ds.removeDataSource(dynamicDataSource);
            }
        }
    }
 
    /***
     * post请求获取HttpServletRequest 内 poolName参数
     * @param request
     * @return
     */
    public String getDataResourceName(HttpServletRequest request) throws IOException {
        StringBuilder data = new StringBuilder();
        String line;
        BufferedReader reader;
        try {
            reader = request.getReader();
            while (null != (line = reader.readLine())) {
                data.append(line);
            }
        } catch (IOException e) {
            return null;
        }
        JSONObject jsonObject = JSONObject.parseObject(data.toString());
        if (jsonObject == null) {
            return "";
        }
        return jsonObject.getString(DBUtils.DATA_RESOURCE_NAME);
    }
 
    /***
     * 手动加入数据源池
     * @param sysDataResource
     */
    public void manualAddSource(SysDataResource sysDataResource) {
        DataSourceHelp dataSourceHelp = new DataSourceHelp();
        dataSourceHelp.setDriverClassName(DBUtils.getDriverClassName(sysDataResource.getDataResourceType()));
        dataSourceHelp.setPassword(sysDataResource.getPassword());
        dataSourceHelp.setPoolName(sysDataResource.getUuid());
        dataSourceHelp.setUsername(sysDataResource.getUserName());
        dataSourceHelp.setUrl(sysDataResource.getUrl());
        addSource(dataSourceHelp);
    }
 
    public Set<String> addSource(DataSourceHelp dataSourceHelp) {
        DataSourceProperty dataSourceProperty = new DataSourceProperty();
        BeanUtils.copyProperties(dataSourceHelp, dataSourceProperty);
        DynamicRoutingDataSource source = (DynamicRoutingDataSource) dataSource;
        DataSource dataSource = defaultDataSourceCreator.createDataSource(dataSourceProperty);
        source.addDataSource(dataSourceHelp.getPoolName(), dataSource);
        return source.getCurrentDataSources().keySet();
    }
}

这里解释下,我们会先做页面中保存数据库链接信息,然后在树结构中点击查询时,会固定传一个参数poolName,我们传的是记录数据库信息的id,作为poolName,用于我们后续切换数据源的时候的一个标识。

注意: 切面中,我们会在get请求里拿到poolName进行数据源切换,但是在日常考虑中,有post请求也需要切换数据源,所以代码中也加入了从body里取得poolName进行切换参数。为了进一步实现自定义切换参数,我们也可以在  request.getAttribute("poolName");取得参数进行切换!


6.DataSourceHelp类

import lombok.Data;
 
@Data
public class DataSourceHelp {
    private String poolName;
    private String driverClassName;
    private String url;
    private String username;
    private String password;
}

二、开发代码

1.controller层

 
import com.hvit.data_governance.dataComparison.request.*;
import com.hvit.data_governance.dataComparison.service.data.DBService;
import com.hvit.data_governance.dataComparison.service.data.SysDataResourceDataService;
import com.hvit.data_governance.utils.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.sql.SQLException;
 
/**
 * <p>
 * 动态数据源管理 前端控制器
 * </p>
 *
 * @author 曹震
 * @since 2022-12-06
 */
@Api(tags = "动态数据源管理")
@RestController
@RequestMapping("/hvit/dataResource/")
public class SysDataResourceController {
 
    @Autowired
    private SysDataResourceDataService sysDataResourceDataService;
    @Autowired
    private DBService dbService;
 
    @ApiOperation("获取数据集的字段信息")
    @GetMapping("/getDataTableColumns")
    public ResponseEntity getDataTableColumns(String tableName, String poolName) {
        return ResponseEntity.ok(sysDataResourceDataService.getDataTableColumns(tableName, poolName));
    }
}

2.service层

@Slf4j
@Service
public class SysDataResourceDataService {
    
    @Autowired
    private SysDataResourceService sysDataResourceService;
    @Autowired
    private DBService dbService;
 
    public R getDataTableColumns(String tableName, String poolName) {
        if (StringUtils.isAnyEmpty(tableName, poolName)) {
            return R.error("缺少参数!");
        }
        getDataTableFramework(tableName, poolName);
        QueryWrapper<SysTableStructure> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(SysTableStructure::getDataResourceUuid, poolName);
        queryWrapper.lambda().eq(SysTableStructure::getTableName, tableName);
        queryWrapper.lambda().orderByAsc(SysTableStructure::getSort);
        return R.ok().put("data", sysTableStructureService.list(queryWrapper));
    }
 
 /***
     * 注意点:当方法内调用其他需要数据源切换的方法时,需要将类本身注入进来,然后调用才能生效(数据源切换)
     * 处理字段入库
     * @param tableName
     * @param poolName
     */
    @Transactional
    public void getDataTableFramework(String tableName, String poolName) {
        //获取表字段结构信息
        List<TableFramework> tableFrameworks = dbService.getTableFramework(tableName);
        //获取不在表中的字段信息
        List<String> stringList = getDifferenceSet(tableFrameworks, tableName, poolName);
        //装备剔除没用的字段
        if (!CollectionUtils.isEmpty(stringList)) {
            QueryWrapper<SysTableStructure> queryWrapper = new QueryWrapper<>();
            queryWrapper.lambda().eq(SysTableStructure::getDataResourceUuid, poolName);
            queryWrapper.lambda().eq(SysTableStructure::getTableName, tableName);
            queryWrapper.lambda().in(SysTableStructure::getField, stringList);
            sysTableStructureService.remove(queryWrapper);
        }
        AtomicInteger i = new AtomicInteger();
        if (!CollectionUtils.isEmpty(tableFrameworks)) {
            tableFrameworks.forEach(x -> {
                QueryWrapper<SysTableStructure> wrapper = new QueryWrapper<>();
                wrapper.lambda().eq(SysTableStructure::getDataResourceUuid, poolName);
                wrapper.lambda().eq(SysTableStructure::getTableName, tableName);
                wrapper.lambda().eq(SysTableStructure::getField, x.getField());
                if (sysTableStructureService.count(wrapper) == 0) {
                    SysTableStructure sysTableStructure = new SysTableStructure();
                    sysTableStructure.setField(x.getField());
                    sysTableStructure.setTableName(tableName);
                    sysTableStructure.setHidden(1);
                    sysTableStructure.setType(x.getType());
                    sysTableStructure.setSort(i.get());
                    sysTableStructure.setLength(DBUtils.getContent(x.getType()));
                    sysTableStructure.setDataResourceUuid(poolName);
                    sysTableStructure.setPriKey(x.getKey());
                    sysTableStructureService.save(sysTableStructure);
                }
                i.getAndIncrement();
            });
        }
    }
 
/***
     * 智能匹配字段差集
     * 当表字段存在记录表中,而实际数据库表字段通过其他手动修改过时,智能剔除。
     * @param tableFrameworks
     * @param tableName
     * @return
     */
    public List<String> getDifferenceSet(List<TableFramework> tableFrameworks, String tableName, String poolName) {
        QueryWrapper<SysTableStructure> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(SysTableStructure::getTableName, tableName);
        queryWrapper.lambda().eq(SysTableStructure::getDataResourceUuid, poolName);
        List<SysTableStructure> structureList = sysTableStructureService.list(queryWrapper);
        List<String> retList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(structureList)) {
            List<String> strings = sysTableStructureService.list(queryWrapper).stream().map(SysTableStructure::getField).collect(Collectors.toList());
            List<String> stringList = tableFrameworks.stream().map(TableFramework::getField).collect(Collectors.toList());
            retList = strings.stream().filter(e -> {
                return !stringList.contains(e);
            }).collect(Collectors.toList());
        }
        return retList;
    }
}

3. dbService类

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.hvit.data_governance.dataComparison.dao.SysDataResourceMapper;
import com.hvit.data_governance.dataComparison.dbhelp.ChangeDB;
import com.hvit.data_governance.dataComparison.entity.SysDataResource;
import com.hvit.data_governance.dataComparison.entity.TableFramework;
import com.hvit.data_governance.dataComparison.request.HandleMianDataReq;
import com.hvit.data_governance.dataComparison.request.RowDataReq;
import com.hvit.data_governance.dataComparison.service.SysDataResourceService;
import com.hvit.data_governance.utils.DBUtils;
import com.hvit.data_governance.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
 
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
 
@Slf4j
@Service
public class DBService {
    @Resource
    private SysDataResourceMapper sysDataResourceMapper;
    @Autowired
    private SysDataResourceDataService sysDataResourceDataService;
 
   /***
     * 获取表结构信息
     * @param tableName
     * @return
     */
    @ChangeDB
    public List<TableFramework> getTableFramework(String tableName) {
        return sysDataResourceMapper.getTableFramework(tableName);
    }
}

4.sysDataResourceMapper类

import com.hvit.data_governance.dataComparison.entity.SysDataResource;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hvit.data_governance.dataComparison.entity.TableFramework;
import org.apache.ibatis.annotations.*;
 
import java.util.LinkedHashMap;
import java.util.List;
 
/**
 * <p>
 * 动态数据源管理 Mapper 接口
 * </p>
 *
 * @author 曹震
 * @since 2022-12-06
 */
@Mapper
public interface SysDataResourceMapper extends BaseMapper<SysDataResource> {
 
/***
     * 获取表结构信息
     * @param tableName
     * @return
     */
    @Select("DESC `${tableName}`")
    List<TableFramework> getTableFramework(@Param("tableName") String tableName);
 
 
}

5.看下调用过程

我们可以看到日志输出中,已经进行的数据源的切换,在调用完后也及时的清除了切换的数据源。

可以看到表的结构信息已经获取到了。同样postman中也有,给大家看下。

那么到此处就已经做好了动态数据源的切换了。

如果大家喜欢的话,点个关注,点个赞,谢谢。

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
9天前
|
运维 监控 安全
云HIS医疗管理系统源码——技术栈【SpringBoot+Angular+MySQL+MyBatis】
云HIS系统采用主流成熟技术,软件结构简洁、代码规范易阅读,SaaS应用,全浏览器访问前后端分离,多服务协同,服务可拆分,功能易扩展;支持多样化灵活配置,提取大量公共参数,无需修改代码即可满足不同客户需求;服务组织合理,功能高内聚,服务间通信简练。
26 4
|
6天前
|
缓存 Java Sentinel
Springboot 中使用 Redisson+AOP+自定义注解 实现访问限流与黑名单拦截
Springboot 中使用 Redisson+AOP+自定义注解 实现访问限流与黑名单拦截
|
1天前
|
SQL Java 数据库连接
Springboot框架整合Spring JDBC操作数据
JDBC是Java数据库连接API,用于执行SQL并访问多种关系数据库。它包括一系列Java类和接口,用于建立数据库连接、创建数据库操作对象、定义SQL语句、执行操作并处理结果集。直接使用JDBC涉及七个步骤,包括加载驱动、建立连接、创建对象、定义SQL、执行操作、处理结果和关闭资源。Spring Boot的`spring-boot-starter-jdbc`简化了这些步骤,提供了一个在Spring生态中更便捷使用JDBC的封装。集成Spring JDBC需要添加相关依赖,配置数据库连接信息,并通过JdbcTemplate进行数据库操作,如插入、更新、删除和查询。
|
1天前
|
XML Java 数据库连接
Springboot整合mybatisPlus操作数据库
MyBatis-Plus是MyBatis的增强工具,简化开发、提高效率。它提供官网文档,便于集成到SpringBoot项目中。集成步骤包括添加mybatis-plus-boot-starter和数据库驱动依赖,配置数据源,扫描Mapper类包。Mapper接口继承BaseMapper即可使用基本的CRUD操作。示例代码展示了Service层的增删改查实现。MyBatisPlus还支持逻辑删除、自动填充等功能,同时可与Mybatis XML配合使用,通过调整配置指定XML映射文件位置。
|
1天前
|
Java 关系型数据库 MySQL
springboot业务开发--springboot一键生成数据库文档
Screw是一个数据库文档生成工具,能自动化根据数据库表结构生成文档,减轻开发人员工作负担,支持MySQL、MariaDB、TiDB等多种数据库和HTML、Word、Markdown等格式。它依赖HikariCP数据库连接池和Freemarker模板引擎。通过在Spring Boot项目中添加相关依赖并配置,可以用代码或Maven插件方式生成文档。示例代码展示了如何在JUnit测试中使用Screw生成HTML文档。
|
1天前
|
安全
自定义注解,aop实现注解锁
自定义注解,aop实现注解锁
|
1天前
|
Java 测试技术 开发者
【亮剑】如何通过自定义注解来实现 Spring AOP,以便更加灵活地控制方法的拦截和增强?
【4月更文挑战第30天】通过自定义注解实现Spring AOP,可以更灵活地控制方法拦截和增强。首先定义自定义注解,如`@MyCustomAnnotation`,然后创建切面类`MyCustomAspect`,使用`@Pointcut`和`@Before/@After`定义切点及通知。配置AOP代理,添加`@EnableAspectJAutoProxy`到配置类。最后,在需拦截的方法上应用自定义注解。遵循保持注解职责单一、选择合适保留策略等最佳实践,提高代码可重用性和可维护性。记得测试AOP逻辑。
|
5天前
|
存储 Java 数据库
SpringBoot使用jasypt实现数据库配置加密
这样,你就成功地使用Jasypt实现了Spring Boot中的数据库配置加密,确保敏感信息在配置文件中以加密形式存储,并在应用启动时自动解密。
30 2
|
6天前
|
安全 Java API
SpringBoot 实现 elasticsearch 索引操作(RestHighLevelClient 的应用)
SpringBoot 实现 elasticsearch 索引操作(RestHighLevelClient 的应用)
12 1
|
6天前
|
自然语言处理 Java 索引
SpringBoot 实现 elasticsearch 查询操作(RestHighLevelClient 的案例实战)
SpringBoot 实现 elasticsearch 查询操作(RestHighLevelClient 的案例实战)
12 1