Skip to content

1.springboot树形结构 支持模糊查询,返回匹配节点和父节点,其他节点不返回

java
package com.me.meterdemo.ds;
import java.util.ArrayList;
import java.util.List;
 
public class TreeNode {
    private Long id;
    private String name;
    private Long parentId;
    private List<TreeNode> children = new ArrayList<>();
 
    // 构造方法
    public TreeNode(Long id, String name, Long parentId) {
        this.id = id;
        this.name = name;
        this.parentId = parentId;
    }
 
    // Getters and Setters
    public Long getId() { return id; }
    public String getName() { return name; }
    public Long getParentId() { return parentId; }
    public List<TreeNode> getChildren() { return children; }
 
    public void addChild(TreeNode child) {
        // 防止重复添加
        if (children.stream().noneMatch(c -> c.id.equals(child.id))) {
            children.add(child);
        }
    }
}
java
package com.me.meterdemo.ds;
 
import org.springframework.stereotype.Service;
 
import javax.annotation.PostConstruct;
import java.util.*;
import java.util.stream.Collectors;
 
 
 
@Service
public class TreeMemoryService {
    // 内存数据存储
    private final Map<Long, TreeNode> nodeMap = new HashMap<>();
 
    // 初始化测试数据
    @PostConstruct
    private void initData() {
        clearAndInitData();
    }
 
    private void clearAndInitData() {
        nodeMap.clear();
        addNode(1L, "集团总部", null);
        addNode(2L, "华东公司", 1L);
        addNode(3L, "技术部", 2L);
        addNode(4L, "开发组", 3L);
        addNode(5L, "测试组", 3L);
        addNode(6L, "华南公司", 1L);
    }
 
    private void addNode(Long id, String name, Long parentId) {
        TreeNode node = new TreeNode(id, name, parentId);
        nodeMap.put(id, node);
    }
 
    /**
     * 执行树结构搜索
     * @param keyword 查询关键词(可为空)
     * @return 符合条件节点及其父节点组成的树结构
     */
    public List<TreeNode> search(String keyword) {
        // 空关键词返回空结果
        if (keyword != null && keyword.trim().isEmpty()) {
            return Collections.emptyList();
        }
 
        // 获取所有匹配节点
        List<TreeNode> matches = nodeMap.values().stream()
                .filter(node -> matchesKeyword(node, keyword))
                .collect(Collectors.toList());
 
        // 收集需要展示的节点ID(匹配节点+父链)
        Set<Long> visibleIds = new LinkedHashSet<>();
        matches.forEach(node -> collectParentChain(node, visibleIds));
 
        // 构建精简树结构
        return buildPrunedTree(visibleIds);
    }
 
    // 判断节点是否匹配关键词
    private boolean matchesKeyword(TreeNode node, String keyword) {
        return keyword == null ||
                node.getName().contains(keyword);
    }
 
    // 收集节点及其所有父节点
    private void collectParentChain(TreeNode node, Set<Long> ids) {
        Long currentId = node.getId();
        while (currentId != null) {
            if (!ids.add(currentId)) break; // 防止循环
            TreeNode current = nodeMap.get(currentId);
            currentId = current.getParentId();
        }
    }
 
    // 构建修剪后的树结构
    private List<TreeNode> buildPrunedTree(Set<Long> visibleIds) {
        // 创建节点副本防止污染原始数据
        Map<Long, TreeNode> visibleNodes = createNodeCopies(visibleIds);
 
        // 构建层级关系
        List<TreeNode> roots = new ArrayList<>();
        visibleNodes.values().forEach(node -> {
            Long parentId = node.getParentId();
            if (parentId == null) {
                roots.add(node);
            } else {
                TreeNode parent = visibleNodes.get(parentId);
                if (parent != null) {
                    parent.addChild(node);
                }
            }
        });
 
        return roots;
    }
 
    // 创建节点副本(清空原有子节点)
    private Map<Long, TreeNode> createNodeCopies(Set<Long> visibleIds) {
        return visibleIds.stream()
                .map(id -> nodeMap.get(id))
                .filter(Objects::nonNull)
                .collect(Collectors.toMap(
                        TreeNode::getId,
                        orig -> new TreeNode(orig.getId(),
                                orig.getName(),
                                orig.getParentId())
                ));
    }
 
    // 重置测试数据(可选)
    public void resetData() {
        clearAndInitData();
    }
}
java
package com.me.meterdemo.ds;
 
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.List;
 
@RestController
public class TreeDSController {
    private final TreeMemoryService treeMemoryService;
 
    public TreeDSController(TreeMemoryService treeService) {
        this.treeMemoryService = treeService;
    }
 
    @GetMapping("/search")
    public List<TreeNode> searchTree(@RequestParam(required = false) String keyword) {
        return treeMemoryService.search(keyword);
    }
}

2.springboot 返回树形结构 支持模糊匹配

java
package com.me.meterdemo.tree;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import java.util.List;

@Data
public class TreeNode {
    private Long id;
    private String name;
    private Long parentId;
    private List<TreeNode> children;


    @JsonIgnore
    private  boolean matched; // 标记是否匹配搜索条件
}
java
package com.me.meterdemo.tree;

import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class TreeService {
    
    // 模拟数据库数据
    private List<TreeNode> getAllNodes() {
        List<TreeNode> nodes = new ArrayList<>();
        // 添加一些测试数据
        TreeNode root = new TreeNode();
        root.setId(1L);
        root.setName("目录one");
        root.setParentId(0L);
        
        TreeNode child1 = new TreeNode();
        child1.setId(2L);
        child1.setName("测试函数1");
        child1.setParentId(1L);
        
        TreeNode child2 = new TreeNode();
        child2.setId(3L);
        child2.setName("测试时间函数");
        child2.setParentId(1L);
        
        TreeNode grandChild1 = new TreeNode();
        grandChild1.setId(4L);
        grandChild1.setName("测试date函数");
        grandChild1.setParentId(2L);
        
        nodes.add(root);
        nodes.add(child1);
        nodes.add(child2);
        nodes.add(grandChild1);
        
        return nodes;
    }

    public List<TreeNode> searchTree(String keyword) {
        List<TreeNode> allNodes = getAllNodes();
        Map<Long, TreeNode> nodeMap = allNodes.stream()
                .collect(Collectors.toMap(TreeNode::getId, node -> node));

        // 构建树形结构
        List<TreeNode> rootNodes = allNodes.stream()
                .filter(node -> node.getParentId() == 0)
                .collect(Collectors.toList());

        // 递归构建树
        rootNodes.forEach(root -> buildTree(root, nodeMap));

        // 如果没有关键字,返回完整的树形结构
        if (keyword == null || keyword.trim().isEmpty()) {
            return rootNodes;
        }

        // 搜索并过滤不匹配的节点
        List<TreeNode> filteredRoots = new ArrayList<>();
        for (TreeNode root : rootNodes) {
            TreeNode filteredRoot = filterTree(root, keyword);
            if (filteredRoot != null) {
                filteredRoots.add(filteredRoot);
            }
        }

        return filteredRoots;
    }

    private void buildTree(TreeNode node, Map<Long, TreeNode> nodeMap) {
        List<TreeNode> children = nodeMap.values().stream()
                .filter(n -> n.getParentId().equals(node.getId()))
                .collect(Collectors.toList());

        node.setChildren(children);
        children.forEach(child -> buildTree(child, nodeMap));
    }

    private TreeNode filterTree(TreeNode node, String keyword) {
        // 如果当前节点匹配,保留该节点
        boolean isMatched = node.getName().contains(keyword);
        node.setMatched(isMatched);

        // 处理子节点
        if (node.getChildren() != null && !node.getChildren().isEmpty()) {
            List<TreeNode> filteredChildren = new ArrayList<>();
            for (TreeNode child : node.getChildren()) {
                TreeNode filteredChild = filterTree(child, keyword);
                if (filteredChild != null) {
                    filteredChildren.add(filteredChild);
                }
            }

            // 如果当前节点匹配或者有匹配的子节点,保留该节点
            if (isMatched || !filteredChildren.isEmpty()) {
                node.setChildren(filteredChildren);
                return node;
            }
        } else {
            // 如果是叶子节点,只有匹配时才保留
            return isMatched ? node : null;
        }

        return null;
    }
}
java
package com.me.meterdemo.demos.web;

import com.me.meterdemo.ds.TreeNodeDTO;
import com.me.meterdemo.tree.TreeNode;
import com.me.meterdemo.tree.TreeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;

@RestController
@RequestMapping("/api/tree")
public class TreeController {
    
    @Autowired
    private TreeService treeService;
    
        @GetMapping("/search")
    public List<TreeNode> searchTree(@RequestParam(required = false) String keyword) {
        return treeService.searchTree(keyword);
    }

}

3.springboot 返回树形结构 从任意节点开始生成,不一定有根节点

java
package com.me.meterdemo.treeList;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;

import java.util.ArrayList;
import java.util.List;
@Data
public class TreeNode {
    private Long id;
    private String name;
    private Long parentId;

    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    private List<TreeNode> children = new ArrayList<>();

    // 构造方法、getter、setter
    public TreeNode(Long id, String name, Long parentId) {
        this.id = id;
        this.name = name;
        this.parentId = parentId;
    }

    // 省略getter和setter...
}
java
package com.me.meterdemo.treeList;

import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


@Service
public class TreeListService {
    public List<TreeNode> buildTree() {
        List<TreeNode> allNodes = findAll();
        Map<Long, TreeNode> nodeMap = new HashMap<>();
        List<TreeNode> topNodes = new ArrayList<>();

        // 将所有节点存入Map,并初始化children
        for (TreeNode node : allNodes) {
            node.setChildren(new ArrayList<>());
            nodeMap.put(node.getId(), node);
        }

        // 构建树结构
        for (TreeNode node : allNodes) {
            Long parentId = node.getParentId();
            if (parentId != null) {
                TreeNode parent = nodeMap.get(parentId);
                if (parent != null) {
                    parent.getChildren().add(node);
                } else {
                    // 父节点不存在,当前节点作为顶级节点
                    topNodes.add(node);
                }
            } else {
                // parentId为null,直接作为顶级节点
                topNodes.add(node);
            }
        }

        return topNodes;
    }

    private List<TreeNode> findAll() {
        List<TreeNode> nodes = new ArrayList<>();
//        nodes.add(new TreeNode(1L, "集团总部", null));
//        nodes.add(new TreeNode(2L, "华东公司", 1L));
        nodes.add(new TreeNode(3L, "技术部", 2L));
        nodes.add(new TreeNode(4L, "开发组", 3L));
        nodes.add(new TreeNode(5L, "测试组", 3L));
        nodes.add(new TreeNode(6L, "华南公司", 1L));
        nodes.add(new TreeNode(7L, "中国烟草", null));




        return nodes;
    }

}
java
package com.me.meterdemo.treeList;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class TreeListController {

    @Autowired
    TreeListService treeListService;

    @GetMapping("/tree/1")
    public ResponseEntity<List<TreeNode>> getTree() {
        List<TreeNode> tree = treeListService.buildTree();
        return ResponseEntity.ok(tree);
    }
}

4 springboot naos seata 集成

4.1 maven 依赖 nacos 1.4.2 seata 1.4.2

客户端的每个数据库 度需要创建一个 undo_log 的表

xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.me</groupId>
    <artifactId>order-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>order-service</name>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.12.RELEASE</spring-boot.version>
        <spring-cloud.version>Hoxton.SR12</spring-cloud.version>
        <spring-cloud-alibaba.version>2.2.6.RELEASE</spring-cloud-alibaba.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <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>

        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcpkix-jdk18on</artifactId>
            <version>1.78.1</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.28</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-spring-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.4.2</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version> <!-- 添加这一行 -->
                <configuration>
                    <mainClass>com.me.OrderApp</mainClass>
                    <executable>true</executable>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

4.2 bootstrap .yml 配置

yml
server:
  port: 8080
spring:
  application:
    name: order-service
  cloud:

    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        group: DEFAULT_GROUP

      config:
        server-addr: 127.0.0.1:8848
        file-extension: yml
        auto-refresh: true
        data-id: ${spring.application.name}
##  seata  配置
seata:
## 事务组配置  需要和 seata 的registry.conf 配置文件相同   
  tx-service-group:  my_test_tx_group
  service:
    vgroup-mapping.my_test_tx_group: default
  enabled: true
  application-id: ${spring.application.name}

  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      namespace: ''
      ## 注意这里有坑   源码中默认的是 SEATA_GROUP 如果不指定 就是这个默认值 会导致无法找到可用的服务
      group: 'DEFAULT_GROUP'
      username: "nacos"
      password: "nacos"
      # 事务模式  AT  XA 
  data-source-proxy-mode: AT

4.3 NACOS 配置文件修改 执行SQL脚本 application.properties

properties
spring.datasource.platform=mysql
### Count of DB:
db.num=1

### Connect URL of DB:
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos23?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=root

然后执行sql 脚本 创建 数据库 和表结构

4.4 seat 服务端配置 1.4.2

seata 的数据库需要执行脚本创建三个表 file.conf 修改为数据库存储

## transaction log store, only used in seata-server
store {
 ## store mode: file、db、redis
 mode = "db"
 ## rsa decryption public key
 publicKey = ""


 ## database store property
 db {
   ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
   datasource = "druid"
   ## mysql/oracle/postgresql/h2/oceanbase etc.
   dbType = "mysql"
   driverClassName = "com.mysql.jdbc.Driver"
   ## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param
   url = "jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true"
   user = "root"
   password = "root"
   minConn = 5
   maxConn = 100
   globalTable = "global_table"
   branchTable = "branch_table"
   lockTable = "lock_table"
   queryLimit = 100
   maxWait = 5000
 }


}

registry.conf 修改为 nacos 注册中心 和配置中心

registry {
 # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
 type = "nacos"

 nacos {
   application = "seata-server"
   serverAddr = "127.0.0.1:8848"
   group = "DEFAULT_GROUP"
   namespace = ""
   cluster = "default"
   username = "nacos"
   password = "nacos"
   
 }

}

config {
 # file、nacos 、apollo、zk、consul、etcd3、springCloudConfig
 type = "nacos"

 nacos {
  serverAddr = "127.0.0.1:8848"
   namespace = ""
   group = "DEFAULT_GROUP"
   username = "nacos"
   password = "nacos"
   dataId = "seataServer.properties"
   
 }
 
}

4.5 nacos 创建配置

在nacos 中创建吗一个 dataId=seataServer.properties 的 配置文件 修改 数据库的配置 和事务组的配置

service.vgroupMapping.my_test_tx_group=default

properties
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableClientBatchSendRequest=false
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
## 这里配置的事务组  值就是  seata cluster=default  在多机房切换用到
service.vgroupMapping.my_test_tx_group=default
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=false
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
##  配置 存储为db 并配置响应的用户名和密码 等数据库信息
store.mode=db
store.publicKey=
store.file.dir=file_store/data
store.file.maxBranchSessionSize=16384
store.file.maxGlobalSessionSize=512
store.file.fileWriteBufferCacheSize=16384
store.file.flushDiskMode=async
store.file.sessionReloadReadSize=100
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=root
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
store.redis.mode=single
store.redis.single.host=127.0.0.1
store.redis.single.port=6379
store.redis.sentinel.masterName=
store.redis.sentinel.sentinelHosts=
store.redis.maxConn=10
store.redis.minConn=1
store.redis.maxTotal=100
store.redis.database=0
store.redis.password=
store.redis.queryLimit=100
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
log.exceptionRate=100
transport.serialization=seata
transport.compressor=none
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
···
然后客户端便可以使用分布式事务进行处理

5. springboot aop 解决幂等 基于数据库表的修改时间字段, 时间戳机制

场景描述 一个表的数据 由于权限控制 导致 数据能够被很多人看到,这些人分布在全国各地,有同一时刻修改同一条数据的可能, 需要控制只能有一个修改成功

接口如下

java
package com.me.demo2.controller;

import com.me.demo2.domain.Emp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@RestController
public class JDBCController {

    @Autowired
    JdbcTemplate jdbcTemplate;


    /**
     *  获取详情接口
     * @param id
     * @return
     */
    @GetMapping("/get/{id}")
    public Map<String,Object> get(@PathVariable("id") Integer id){
        Map<String,Object> res = jdbcTemplate.queryForMap("select * from tu_emp where id=?",new Object[]{id});
        return res;
    }
    /**
     *
     *   修改的前提是必须先调用 获取详情接口 查询信息
     *  修改接口
     * */
    @PostMapping("/update")
    public Map<String,Object> update(@RequestBody Emp emp){


        int update = jdbcTemplate.update("update tu_emp set name=? ,update_time=now() where id=?",emp.getName(),emp.getId() );
        Map<String,Object> res = jdbcTemplate.queryForMap("select * from tu_emp where id=?",emp.getId());

       return res;
    }
}

解决这个问题 需要对 update这个接口进行切面处理 保证只有有一个人修改成功

java
package com.me.demo2.aspects;

import com.me.demo2.domain.Emp;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Component;

import java.sql.ResultSet;
import java.sql.SQLException;

@Aspect
@Component
public class RepeatAspect {


    @Autowired
    JdbcTemplate jdbcTemplate;
	
    /**
      获取方法参数
      从数据库查询修改时间的这个字段  进行比较 如果不一致则为重复交易 则终止请求
    */
    @Around("execution(public * com.me.demo2.controller.JDBCController.update(..))")
    public Object repeatHandler(ProceedingJoinPoint joinPoint){
       Object[] args = joinPoint.getArgs();
        Emp dto = (Emp) args[0];
        Emp empDO = jdbcTemplate.queryForObject("select * from tu_emp where id=?", new RowMapper<Emp>() {
            @Override
            public Emp mapRow(ResultSet rs, int rowNum) throws SQLException {
                Emp emp1 = new Emp();
                emp1.setCreate_time(rs.getTimestamp("create_time"));
                emp1.setId(rs.getInt("id"));
                emp1.setName(rs.getString("name"));
                emp1.setUpdate_time(rs.getTimestamp("update_time"));
                return emp1;
            }
        }, dto.getId());
        if (!dto.getUpdate_time().equals(empDO.getUpdate_time())){
            throw new RuntimeException("重复交易 稍后重试");
        }else {
            try {
              return    joinPoint.proceed(args);
            }catch (Throwable e){
                e.printStackTrace();
            }
        }

        return  null;
    }
}