自定义注解@SyncNeo4j实现增删改导入时自动同步数据节点和关系到Neo4j

:::tip SpringBoot整合Neo4j,通过自定义注解@SyncNeo4j和AOP切面增强,以及对应的SyncStrategy实现接口增删改导入的时候自动落库关系型数据库和Neo4j库 :::

1. 创建项目安装依赖

  1. pom.xml
xml 复制代码
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.17</version>
  </parent>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-neo4j</artifactId>
    </dependency>
  </dependencies>
  1. application.yml
yaml 复制代码
server:
  port: 8888
spring:
  neo4j:
    uri: bolt://localhost:7687
    authentication:
      username: neo4j
      password: neo4j123
project:
  neo4j:
    sync:
      enabled: true
      sync-on-startup: true

2. 相关配置

1. 创建节点 @Node

java 复制代码
@Data
@Node(labels = "Person")
public class Person implements Serializable,Neo4jBaseNode {
    // Neo4j节点的主键
    @Id
    @GeneratedValue
    private Long id;
    // Neo4j节点的属性
    @Property
    private String name;
}

2.创建Repository

java 复制代码
import org.springframework.data.neo4j.repository.query.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

@Repository
public interface PersonRepository extends Neo4jRepository<Person, String> {
    @Query("MATCH (p:Person) DETACH DELETE p")
    void detachDeleteAll();
    
    @Query("MATCH (p: Person{id: $id}-[r]-() DELETE r")
    void clearRelations(@Param("id") String id);
    
    @Query("MATCH (c: Company{id: $companyId}),(p:Person:{id: $personId}) MERGE(c)-[:WORKS_AT]->(p)")
    void addCmpPersonRelation(@Param("companyId") String companyId, @Param("personId") String personId);  
}

3. 自定义注解实现同步

1. 自定义注解@SyncNeo4j

java 复制代码
import net.lesscoding.enums.Neo4jOperateType;
import net.lesscoding.strategy.neo4j.Neo4jSyncStrategy;

import java.lang.annotation.*;

/**
 *
 * @author eleven
 * @since 2026/7/8 15:19
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SyncNeo4j {
    /**
     * 操作类型
     * @return {@link Neo4jOperateType} 操作类型
     */
    Neo4jOperateType type();

    /**
     * 指定当前实体类对应的同步策略类
     * @return {@link Class}< ? extends {@link Neo4jSyncStrategy}>}
     */
    Class<? extends Neo4jSyncStrategy> strategy();

    /**
     * 参数索引
     * @return
     */
    int paramIndex() default 0;
}

2. Neo4j操作类型

java 复制代码
/**
 *
 * @author eleven
 * @since 2026/7/8 15:21
 */
public enum Neo4jOperateType {
    CREATE,
    UPDATE,
    DELETE,
    DELETE_BATCH,
    DETACH_DELETE,
    CREATE_BATCH,
    UPDATE_BATCH
    ;
}

3. AOP增强类

这个@SyncNeo4j可以添加到方法上,然后通过这个AOP切面增强自动同步到Neo4j数据库, 使用详情可以看后续的示例

java 复制代码
import net.lesscoding.annotation.neo4j.SyncNeo4j;
import net.lesscoding.config.ProjectConfig;
import net.lesscoding.strategy.neo4j.Neo4jStrategyContext;
import net.lesscoding.strategy.neo4j.Neo4jSyncStrategy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 *
 * @author eleven
 * @since 2026/7/8 15:35
 */
@Order(16)
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class SyncNeo4jAspect {
    private final Neo4jStrategyContext strategyContext;
    private final ProjectConfig projectConfig;

    @SuppressWarnings("unchecked")
    @Around("@annotation(syncNeo4j)")
    public Object around(ProceedingJoinPoint point, SyncNeo4j syncNeo4j) throws Throwable {
        Object proceed = point.proceed();
        if (projectConfig.getNeo4j().isEnable()) {
            Object[] args = point.getArgs();
            Class<? extends Neo4jSyncStrategy> strategyCls = syncNeo4j.strategy();
            Neo4jSyncStrategy strategy = strategyContext.getStrategy(strategyCls);
            log.info("执行Neo4j同步操作 类型: {} 参数:{}",syncNeo4j.type(), args[syncNeo4j.paramIndex()]);
            switch (syncNeo4j.type()) {
                case CREATE:
                    strategy.save(strategy.extractEntity(args[syncNeo4j.paramIndex()]));
                    break;
                case UPDATE:
                    strategy.update(strategy.extractEntity(args[syncNeo4j.paramIndex()]));
                    break;
                case DELETE:
                    strategy.delete(String.valueOf(args[syncNeo4j.paramIndex()]));
                    break;
                case DETACH_DELETE:
                    strategy.detachDelete(String.valueOf(args[syncNeo4j.paramIndex()]));
                    break;
                case CREATE_BATCH:
                    strategy.createBatch((List<?>) args[syncNeo4j.paramIndex()]);
                    break;
                case UPDATE_BATCH:
                    strategy.updateBatch((List<?>) args[syncNeo4j.paramIndex()]);
                    break;
                case DELETE_BATCH:
                    strategy.deleteBatch((List<?>) args[syncNeo4j.paramIndex()]);
                    break;
                default:
                    throw new IllegalArgumentException("Unsupported SyncNeo4j type: " + syncNeo4j.type());
            }
        }
        return proceed;
    }
}

4. 同步策略类

1. 同步策略接口

java 复制代码
import java.util.List;

/**
 * Neo4j 节点同步策略接口
 * <p>
 * 统一管理节点的增删改与关系重建。
 * 全量同步(Neo4jInitRunner)和增量同步(切面触发 update)均复用
 * {@link #clearRelations} / {@link #rebuildRelations},避免两处维护。
 * </p>
 *
 * @param <T> DB 实体类型
 * @param <N> Neo4j Node 类型
 * @author eleven
 * @since 2026/7/8 15:23
 */
public interface Neo4jSyncStrategy<T, N> {

    /**
     * 返回当前策略处理的 DB 实体类型,用于 {@link Neo4jStrategyContext} 索引。
     */
    Class<T> getEntityClass();

    N save(T dbEntity);

    /**
     * 增量更新:先保存节点属性,再清除旧关系,最后从 DB 查最新数据重建关系
     */
    N update(T dbEntity);

    Boolean delete(String dbId);

    Boolean detachDelete(String dbId);

    /**
     * 清空该类型的所有 Neo4j 节点及关系(全量同步专用)。
     */
    void detachDeleteAll();

    List<N> createBatch(List<T> dbEntities);

    List<N> updateBatch(List<T> dbEntities);

    /**
     * 批量删除 Neo4j 节点
     * <p>默认实现逐条调用 {@link #delete},对于能批量操作的 Repository 应重写此方法。</p>
     *
     * @param dbIds DB 实体 ID 列表
     * @return 删除是否成功
     */
    default Boolean deleteBatch(List<String> dbIds) {
        if (dbIds != null) {
            dbIds.forEach(this::delete);
        }
        return true;
    }

    N convertDbEntityToNeo4jNode(T dbEntity);

    /**
     * 清除该节点的所有关系(入边 + 出边)
     * <p>全量同步和增量更新均复用此方法</p>
     *
     * @param id Neo4j 节点 ID
     */
    void clearRelations(String id);

    /**
     * 从 DB 查最新关联数据,重建该节点的所有关系。
     * <p>
     * 全量同步和增量更新均复用此方法。
     * 各策略实现时应完全对齐 Neo4jInitRunner 中的对应关系逻辑。
     * </p>
     *
     * @param dbEntity DB 实体(含最新字段值)
     */
    void rebuildRelations(T dbEntity);

    /**
     * 从 DB 加载全量实体列表(全量同步专用)。
     */
    List<T> loadEntities();

    /**
     * 批量重建节点(全量同步专用)。
     * <p>将实体列表转换为 Neo4j Node 并批量保存。</p>
     *
     * @param dbEntities DB 实体列表
     */
    void rebuildNodes(List<T> dbEntities);

    /**
     * 批量重建关系(全量同步专用)。
     * <p>
     * 默认实现逐条调用 {@link #rebuildRelations}。
     * 对于需要查关联表的策略,应重写此方法:先全量加载关联数据,
     * 再批量 MERGE,避免 N+1 查询。
     * </p>
     *
     * @param dbEntities DB 实体列表
     */
    default void rebuildRelationsBatch(List<T> dbEntities) {
        if (dbEntities != null) {
            dbEntities.forEach(this::rebuildRelations);
        }
    }

    @SuppressWarnings("unchecked")
    default T extractEntity(Object args) {
        return (T) args;
    }
}

2. 策略注入类

这个类可以将Neo4jSyncStrategy所有的子类都注入进入,我们使用的时候只需要注入当前这个类,通过getStrategy(Class)方法就能够获取到对应的策略类了

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 策略上下文,按实体类型索引所有 {@link Neo4jSyncStrategy} 实现。
 * <p>
 * Spring 自动注入全部策略实例,Runner 和切面通过本类获取策略进行批量操作。
 * </p>
 *
 * @author eleven
 * @since 2026/7/18
 */
@Component
public class Neo4jStrategyContext {

    private final Map<Class<?>, Neo4jSyncStrategy<?, ?>> strategyCache;
    private final List<Neo4jSyncStrategy<?, ?>> allStrategies;

    @Autowired
    public Neo4jStrategyContext(List<Neo4jSyncStrategy<?, ?>> strategies) {
        this.allStrategies = Collections.unmodifiableList(strategies);
        this.strategyCache = strategies.stream()
                .collect(Collectors.toMap(Neo4jSyncStrategy::getClass, s -> s));
    }

    /**
     * 按 DB 实体类型获取对应策略。
     */
    @SuppressWarnings("unchecked")
    public <T> Neo4jSyncStrategy<T, ?> getStrategy(Class<T> entityClass) {
        return (Neo4jSyncStrategy<T, ?>) strategyCache.get(entityClass);
    }

    /**
     * 返回所有策略(按 Spring 注入顺序),用于全量同步的批量遍历。
     */
    public List<Neo4jSyncStrategy<?, ?>> getAllStrategies() {
        return allStrategies;
    }
}

3. 具体的策略实现类

java 复制代码
import net.lesscoding.domain.entity.DbPerson;
import net.lesscoding.mapper.DbPersonMapper;
import net.lesscoding.neo4j.entity.Neo4jPerson;
import net.lesscoding.neo4j.repository.Neo4jPersonRepository;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.stream.Collectors;

/**
 * 合同节点同步策略
 * @author eleven
 * @since 2026/7/17
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class Neo4jPersonSyncStrategy implements Neo4jSyncStrategy<DbPerson, Neo4jPerson> {

    private final Neo4jPersonRepository Neo4jPersonRepository;
    private final DbPersonMapper personMapper;
    @Override
    public Neo4jPerson save(DbPerson dbPerson) {
        return Neo4jPersonRepository.save(convertDbEntityToNeo4jNode(dbPerson));
    }

    @Override
    public Neo4jPerson update(DbPerson dbPerson) {
        Neo4jPerson node = save(dbPerson);
        clearRelations(dbPerson.getId());
        rebuildRelations(dbPerson);
        return node;
    }

    @Override
    public void clearRelations(String id) {
        Neo4jPersonRepository.clearRelations(id);
    }

    @Override
    public void rebuildRelations(DbPerson entity) {
        // todo 换成自己的节点对应的关系
        if (StrUtil.isNotBlank(entity.getCompanyId())) {
            Neo4jPersonRepository.addCompanyRelation(entity.getCompanyId(), entity.getId());
        }
    }

    @Override
    public Boolean delete(String dbId) {
        Neo4jPersonRepository.deleteById(dbId);
        return true;
    }

    @Override
    public Boolean detachDelete(String dbId) {
        Neo4jPersonRepository.detachDeleteAll();
        return true;
    }

    @Override
    public void detachDeleteAll() {
        Neo4jPersonRepository.detachDeleteAll();
    }

    @Override
    public List<Neo4jPerson> createBatch(List<DbPerson> dbEntities) {
        return Neo4jPersonRepository.saveAll(dbEntities.stream()
                .map(this::convertDbEntityToNeo4jNode)
                .collect(Collectors.toList()));
    }

    @Override
    public List<Neo4jPerson> updateBatch(List<DbPerson> dbEntities) {
        return createBatch(dbEntities);
    }

    @Override
    public Class<DbPerson> getEntityClass() {
        return DbPerson.class;
    }

    @Override
    public List<DbPerson> loadEntities() {
        return personMapper.selectList(
                new LambdaQueryWrapper<DbPerson>().eq(DbPerson::getDeleted, "0"));
    }

    @Override
    public void rebuildNodes(List<DbPerson> dbEntities) {
        Neo4jPersonRepository.saveAll(dbEntities.stream()
                .map(this::convertDbEntityToNeo4jNode)
                .collect(Collectors.toList()));
    }

    @Override
    public Neo4jPerson convertDbEntityToNeo4jNode(DbPerson dbPerson) {
        return BeanUtil.copyProperties(dbPerson, Neo4jPerson.class);
    }
}

5. Neo4j基础节点

所有的Neo4j节点都需要实现这个基础Node,这样从Neo4j查出来结果的时候,可以通过Neo4jBaseNode.form(Class a)转换成对应的实体Neo4jNode

java 复制代码
import org.neo4j.driver.types.Node;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AssignableTypeFilter;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Neo4j 节点实体基类接口
 * 提供 label → 实体类 的自动注册与 Neo4j 原生 Node 到实体类的转换能力
 *
 * @author eleven
 */
public interface Neo4jBaseNode {

    /** 实体类扫描包路径 */
    String SCAN_PACKAGE = "net.lesscoding.neo4j.entity";

    /** label → 实体类 注册表(DCL 懒加载,首次 from() 调用时自动填充) */
    Map<String, Class<? extends Neo4jBaseNode>> REGISTRY = new ConcurrentHashMap<>();

    /**
     * 根据 Neo4j 原生 Node 的第一个 label 查找对应实体类并实例化。
     * 首次调用时自动扫描 SCAN_PACKAGE 下所有 Neo4jBaseNode 实现类并注册。
     *
     * @param driverNode Neo4j Driver 原生 Node
     * @param <T>        实体类型
     * @return 转换后的实体对象,label 未注册时返回 null
     */
    @SuppressWarnings("unchecked")
    static <T extends Neo4jBaseNode> T from(Node driverNode) {
        if (REGISTRY.isEmpty()) {
            synchronized (REGISTRY) {
                if (REGISTRY.isEmpty()) {
                    scan(SCAN_PACKAGE);
                }
            }
        }
        // 遍历所有 label,优先匹配非 OsRelation 的具体子类(Database / Middleware 等)
        Class<? extends Neo4jBaseNode> clazz = null;
        for (String label : driverNode.labels()) {
            Class<? extends Neo4jBaseNode> candidate = REGISTRY.get(label);
            if (candidate != null) {
                clazz = candidate;
                if (candidate != Neo4jOsRelation.class) {
                    break;
                }
            }
        }
        if (clazz == null) {
            return null;
        }
        return (T) mapToEntity(driverNode, clazz);
    }

    /**
     * 手动扫描指定包路径,将 Neo4jBaseNode 实现类注册到 REGISTRY。
     * 一般在 from() 中自动调用,也可在应用启动时显式调用提前预热。
     */
    @SuppressWarnings("unchecked")
    static void scan(String basePackage) {
        ClassPathScanningCandidateComponentProvider scanner =
                new ClassPathScanningCandidateComponentProvider(false);
        scanner.addIncludeFilter(new AssignableTypeFilter(Neo4jBaseNode.class));
        for (BeanDefinition bd : scanner.findCandidateComponents(basePackage)) {
            try {
                Class<?> clazz = Class.forName(bd.getBeanClassName());
                if (!Neo4jBaseNode.class.isAssignableFrom(clazz) || clazz.isInterface()) {
                    continue;
                }
                org.springframework.data.neo4j.core.schema.Node nodeAnno =
                        clazz.getAnnotation(org.springframework.data.neo4j.core.schema.Node.class);
                if (nodeAnno != null && nodeAnno.labels().length > 0) {
                    REGISTRY.put(nodeAnno.labels()[0], (Class<? extends Neo4jBaseNode>) clazz);
                }
            } catch (ClassNotFoundException ignored) {
            }
        }
    }

    /**
     * 将 Neo4j 原生 Node 的属性映射到实体类字段。
     * 使用 BeanWrapperImpl 自动处理类型转换(String / BigDecimal / LocalDate 等)。
     */
    static <T extends Neo4jBaseNode> T mapToEntity(Node driverNode, Class<T> clazz) {
        try {
            T instance = clazz.getDeclaredConstructor().newInstance();
            BeanWrapperImpl wrapper = new BeanWrapperImpl(instance);
            Map<String, Object> props = driverNode.asMap();
            for (Map.Entry<String, Object> entry : props.entrySet()) {
                if (wrapper.isWritableProperty(entry.getKey())) {
                    wrapper.setPropertyValue(entry.getKey(), entry.getValue());
                }
            }
            return instance;
        } catch (Exception e) {
            throw new RuntimeException("Failed to map Node to " + clazz.getSimpleName(), e);
        }
    }
}

6. 启动重建所有节点和关系

通过实现CommandLineRunner或者ApplicationRunner接口,使用ExecutorService在项目启动后异步执行重建所有节点和关系的逻辑

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 启动任务专用线程池,避免 Runner 阻塞主线程启动
 *
 * @author eleven
 */
@Configuration
public class TaskThreadPool {

    @Bean(name = "taskRunnerExecutor", destroyMethod = "shutdown")
    public ExecutorService taskRunnerExecutor() {
        return new ThreadPoolExecutor(
                2, 4,
                60L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(),
                r -> new Thread(r, "task-runner-" + r.hashCode())
        );
    }
}
java 复制代码
import net.lesscoding.neo4j.entity.Neo4jBaseNode;
import net.lesscoding.neo4j.entity.Neo4jNetworkRelation;
import net.lesscoding.neo4j.entity.Neo4jOsRelation;
import net.lesscoding.neo4j.entity.Neo4jSystemRelation;
import net.lesscoding.neo4j.repository.Neo4jOsRelationRepository;
import net.lesscoding.strategy.neo4j.Neo4jStrategyContext;
import net.lesscoding.strategy.neo4j.Neo4jSyncStrategy;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.data.neo4j.core.Neo4jClient;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;

/**
 * Neo4j 全量同步 Runner
 * <p>
 * 启动时 DETACH DELETE 全量重建节点与关系。
 * 通过 {@link Neo4jStrategyContext} 遍历所有策略,统一调用
 * {@link Neo4jSyncStrategy#detachDeleteAll()} /
 * {@link Neo4jSyncStrategy#loadEntities()} /
 * {@link Neo4jSyncStrategy#rebuildNodes} /
 * {@link Neo4jSyncStrategy#rebuildRelationsBatch}。
 * </p>
 * <p>
 * Middleware / Database 的关系因 Node ID 不一致
 * (全量同步用 os_relation.id,增量同步用 entity.id)仍在 Runner 中特殊处理。
 * </p>
 *
 * @author eleven
 */
@Slf4j
@Order(1)
@Component
@RequiredArgsConstructor
public class Neo4jInitRunner implements CommandLineRunner {

    private final Neo4jStrategyContext context;
    private final Neo4jClient neo4jClient;
    private final Environment env;
    private final ExecutorService taskRunnerExecutor;

    @Value("${project.neo4j.sync-on-startup:true}")
    private Boolean syncOnStartup;

    private static final List<Class<? extends Neo4jBaseNode>> IGNORE_CLASS = Arrays.asList(Neo4jUser.class);

    @Override
    public void run(String... args) {
        if (Arrays.asList(env.getActiveProfiles()).contains("test")) {
            log.info("当前为测试环境,跳过Neo4j初始化");
            return;
        }
        if (Boolean.FALSE.equals(syncOnStartup)) {
            log.info("[Neo4jInitRunner] sync-on-startup=false, 跳过");
            return;
        }
        taskRunnerExecutor.execute(this::syncAll);
    }

    public void syncAll() {
        long start = System.currentTimeMillis();
        log.info("[Neo4jInitRunner] 开始全量同步 Neo4j ...");
        try {
            clearAll();
            syncNodes();
            syncRelations();
            log.info("[Neo4jInitRunner] 同步完成, 耗时 {} ms", System.currentTimeMillis() - start);
        } catch (Exception e) {
            log.error("[Neo4jInitRunner] 同步失败", e);
        }
    }

    // ==================== 清空 ====================

    private void clearAll() {
        log.info("[Neo4jInitRunner] DETACH DELETE 全部节点 ...");
        for (Neo4jSyncStrategy<?, ?> s : context.getAllStrategies()) {
            if (IGNORE_CLASS.contains(s.getClass())) {
                continue;
            }
            s.detachDeleteAll();
        }
    }

    // ==================== 节点同步 ====================

    @SuppressWarnings({"rawtypes", "unchecked"})
    private void syncNodes() {
        log.info("[Neo4jInitRunner] 批量重建节点 ...");
        for (Neo4jSyncStrategy s : context.getAllStrategies()) {
            if (IGNORE_CLASS.contains(s.getClass())) {
                continue;
            }
            s.rebuildNodes(s.loadEntities());
        }
    }

    // ==================== 关系同步 ====================

    @SuppressWarnings({"rawtypes", "unchecked"})
    private void syncRelations() {
        log.info("[Neo4jInitRunner] 批量重建关系 ...");

        for (Neo4jSyncStrategy s : context.getAllStrategies()) {
            if (IGNORE_CLASS.contains(s.getClass())) {
                continue;
            }
            s.rebuildRelationsBatch(s.loadEntities());
        }            
    }

    // ==================== 工具方法 ====================

    /**
     * 执行 Cypher 语句,绑定两个参数 osRelationId 和 osId
     *
     * @param cypher Cypher 语句,包含两个参数 $a 和 $b
     * @param valA   osRelationId 参数值
     * @param valB   osId 参数值
     * @deprecated 请使用 {@link Neo4jOsRelationRepository#saveOsRelation(String, String)}
     */
    @Deprecated
    private void runCypher(String cypher, String valA, String valB) {
        if (StrUtil.isBlank(valA) || StrUtil.isBlank(valB)) return;
        try {
            neo4jClient.query(cypher).bind(valA).to("a").bind(valB).to("b").run();
        } catch (Exception e) {
            log.warn("[Neo4jInitRunner] Cypher failed: {}  a={}  b={}", cypher, valA, valB);
        }
    }
}

7. 具体使用

ServiceImpl 的方法上添加 @SyncNeo4j 注解,指定操作类型和对应的同步策略类,即可在 DB 操作完成后自动同步到 Neo4j。

这里有一个问题,这个注解不能加载MybatisPlus的Mapper上,如果写了insertBatch或者updateBatch之类的需要建一个统一的service来实现,然后在这个service的方法上添加上注解,这样AOP就能够获取到里边的参数。

PersonServiceImpl 为例:

java 复制代码
package net.lesscoding.service.impl;

import net.lesscoding.annotation.neo4j.SyncNeo4j;
import net.lesscoding.domain.entity.Person;
import net.lesscoding.enums.Neo4jOperateType;
import net.lesscoding.mapper.PersonMapper;
import net.lesscoding.service.PersonService;
import net.lesscoding.strategy.neo4j.Neo4jPersonSyncStrategy;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * Person Service 实现类
 *
 * @author eleven
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class PersonServiceImpl extends ServiceImpl<PersonMapper, Person> implements PersonService {

    private final PersonMapper personMapper;

    @Override
    public Page<Person> page(PersonQueryDto queryDto) {
        Page<Person> page = queryDto.getPage();
        List<Person> list = personMapper.getPersonPage(page, queryDto);
        page.setRecords(list);
        return page;
    }

    @Override
    @Transactional
    @SyncNeo4j(type = Neo4jOperateType.CREATE, strategy = Neo4jPersonSyncStrategy.class)
    public Integer addPerson(Person entity) {
        entity.setId(IdUtil.simpleUUID());
        return personMapper.insert(entity);
    }

    @Override
    @SyncNeo4j(type = Neo4jOperateType.UPDATE, strategy = Neo4jPersonSyncStrategy.class)
    public Integer editPerson(Person entity) {
        return personMapper.updateById(entity);
    }

    @Override
    @SyncNeo4j(type = Neo4jOperateType.DELETE, strategy = Neo4jPersonSyncStrategy.class)
    public Boolean delById(String id) {
        return removeById(id);
    }

    @Override
    public List<Person> allPerson() {
        return personMapper.selectList(
                new LambdaQueryWrapper<Person>().eq(Person::getDeleted, "0"));
    }

    @SyncNeo4j(type = Neo4jOperateType.CREATE_BATCH, strategy = Neo4jPersonSyncStrategy.class)
    public int syncCreateBatch(List<Person> entities) {
        return personMapper.insertBatch(entities);
    }
}

关键说明:

注解参数 含义
type CREATE/UPDATE/DELETE 分别对应新增、修改、删除的单条同步;*_BATCH 对应批量操作
strategy 指向具体的同步策略实现类(如 Neo4jPersonSyncStrategy),AOP 切面会根据此参数从 Neo4jStrategyContext 中获取对应策略执行同步

AOP 切面 SyncNeo4jAspect 会在目标方法执行成功后(point.proceed() 返回),根据注解参数调用策略的 save / update / delete / createBatch 等方法,完成 Neo4j 增量同步。@Transactional@SyncNeo4j 可叠加使用,DB 事务内操作完成后才触发 Neo4j 同步。

相关推荐
沙湖遇雨1 小时前
3.客户端向服务端建立连接
后端
沙湖遇雨1 小时前
1.netty源码阅读-管理端Server启动
后端
玖石书1 小时前
ASP.NET Core迁移Spring系列:运行时与 GC 篇
后端·spring·asp.net
谁来拯救笨小孩1 小时前
Cursor 流式协议:为何不是 REST,HTTP/2 与双向流怎么选
后端
明月光8181 小时前
在数据库里做权限控制?重新理解 PostgreSQL 的 RLS(行级安全)
后端
用户298698530141 小时前
PDF 转图片?三种方案,覆盖全平台与自动化场景
人工智能·后端
沙湖遇雨1 小时前
2.启动客户端client
后端
程序员只因1 小时前
SpringBoot实战项目目录结构+高频注解详解(黑马点评适配版)
后端
Conan在掘金1 小时前
鸿蒙 7.0 小艺全面进化:超 200 项感知+全天候引擎+超强记忆——AI 私人助理根因
后端