Spring Boot 性能优化与最佳实践

一、应用层优化 (Spring Boot & Spring Framework)

1. 合理使用 Spring 核心特性

@Transactional 事务优化
  • 将事务范围控制在最小必要单元,避免在只读操作上开启事务。
  • 明确指定 readOnly = true 用于只读事务。
  • 避免在事务方法中执行耗时操作(如远程调用、复杂计算)。
java 复制代码
@Service
public class OrderService {
    
    private final OrderRepository orderRepository;
    
    // 构造函数注入
    public OrderService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }
    
    // 只读事务优化
    @Transactional(readOnly = true)
    public List<Order> findAllOrders() {
        return orderRepository.findAll();
    }
    
    // 常规事务 - 最小化事务范围
    @Transactional
    public Order createOrder(Order order) {
        // 仅在必要操作上开启事务
        Order savedOrder = orderRepository.save(order);
        // 非事务性操作应放在事务外执行
        return savedOrder;
    }
    
    // 明确指定传播行为和隔离级别(根据实际需求)
    @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
    public void updateOrderStatus(Long orderId, OrderStatus status) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new ResourceNotFoundException("Order not found"));
        order.setStatus(status);
        orderRepository.save(order);
    }
}
懒加载与Bean作用域优化
  • 对于启动时不立即需要的大对象或依赖,使用 @Lazy 延迟初始化,加速应用启动。
  • 正确使用作用域(singleton, prototype, request, session)。
java 复制代码
@Configuration
public class AppConfig {
    
    // 懒加载示例 - 仅在首次使用时初始化
    @Bean
    @Lazy
    public HeavyComponent heavyComponent() {
        return new HeavyComponent(); // 假设这是一个初始化耗时的组件
    }
    
    // 原型作用域Bean - 每次请求创建新实例
    @Bean
    @Scope("prototype")
    public RequestScopedComponent requestScopedComponent() {
        return new RequestScopedComponent();
    }
}
AOP切面优化
  • 精确指定切点表达式 (@Pointcut),避免拦截不必要的 JoinPoint。评估 AOP 带来的性能开销是否可接受。
java 复制代码
@Aspect
@Component
public class OptimizedAspect {
    
    // 精确的切点表达式,避免过度拦截
    @Pointcut("execution(* com.yourcompany.service.*Service.*(..)) && !execution(* com.yourcompany.service.*Service.get*(..))")
    public void serviceMethodPointcut() {}
    
    @Around("serviceMethodPointcut()")
    public Object optimizeServiceCalls(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        try {
            return joinPoint.proceed();
        } finally {
            long duration = System.currentTimeMillis() - startTime;
            // 仅记录慢操作
            if (duration > 500) { // 超过500ms视为慢操作
                log.warn("Slow method: {} took {}ms", joinPoint.getSignature(), duration);
            }
        }
    }
}

2. 组件扫描

  • 使用 @SpringBootApplication(scanBasePackages = "包路径")@ComponentScan 明确指定扫描的基础包路径,避免扫描整个类路径 (classpath:*),减少启动时间和内存占用。
java 复制代码
// 优化组件扫描 - 明确指定基础包而非扫描整个根包
@SpringBootApplication(scanBasePackages = {
    "com.yourcompany.service", 
    "com.yourcompany.controller.api"
})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

3. 启动优化配置

  • Spring Boot 2.2+ 支持。将所有 Bean 设置为懒加载,可以显著减少启动时间(尤其是有大量 Bean 时)。注意: 这可能导致首次请求延迟变高,需权衡。
  • 确保类路径干净,移除无用的 JAR 包。使用 Spring Boot 的 spring-boot-maven-plugin 打包为可执行 JAR(内嵌容器)或 WAR(部署到外部容器)时,依赖的组织方式会影响启动速度。
yaml 复制代码
# application.yml
spring:
  main:
    lazy-initialization: true  # 全局懒加载
    web-application-type: servlet  # 明确指定应用类型,避免自动检测耗时
    
# 仅在需要时激活相关自动配置
spring.autoconfigure.exclude:
  - org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration
  - org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration

# 启动时排除不需要的自动配置类

4.日志优化

  • 使用异步日志框架(如 Logback 的 AsyncAppender 或 Log4j2 的异步 Logger)减少 I/O 阻塞。
  • 合理设置日志级别,生产环境避免 DEBUG/TRACE
  • 控制日志输出量,避免过于冗长的日志格式或记录不必要的信息。

二、JVM 层优化

1. JVM参数优化示例

针对不同场景的JVM配置:

1. 常规Web应用(4核8G内存服务器):

bash 复制代码
java -jar app.jar \
  -Xms6g -Xmx6g \
  -XX:+UseG1GC \
  -XX:MaxGCPauseMillis=200 \
  -XX:InitiatingHeapOccupancyPercent=75 \
  -XX:+UseStringDeduplication \
  -XX:+UseContainerSupport \
  -XX:MaxRAMPercentage=75.0 \  # 容器环境中使用75%的可用内存
  -XX:+HeapDumpOnOutOfMemoryError \
  -XX:HeapDumpPath=/var/log/app/heapdump.hprof

2. 微服务应用(小内存环境):

bash 复制代码
java -jar app.jar \
  -Xms2g -Xmx2g \
  -XX:+UseSerialGC \  # 小内存环境下SerialGC可能更高效
  -XX:NewRatio=3 \    # 老年代与年轻代比例1:3
  -XX:SurvivorRatio=4 \ # Eden:Survivor = 4:1:1
  -XX:MaxTenuringThreshold=15 \ # 对象晋升老年代的年龄阈值
  -XX:+UseCompressedOops \ # 压缩对象指针
  -XX:+UseCompressedClassPointers

2. 虚拟线程配置(Java 21+)

Spring Boot 3.2+ 支持虚拟线程:

yaml 复制代码
# application.yml
server:
  tomcat:
    threads:
      virtual:
        enabled: true  # 启用虚拟线程

三、数据库层优化

1. HikariCP连接池配置

  • 选择合适的连接池: HikariCP (Spring Boot 默认,性能优异) 是首选。Druid 功能强大(监控、防御 SQL 注入等)也是好选择。
  • 关键参数:
    • maximum-pool-size:根据数据库处理能力和应用并发需求设置。不是越大越好! 过大会导致数据库连接耗尽或资源争抢。
    • minimum-idle:维持的最小空闲连接数。
    • connection-timeout:获取连接的超时时间,设置合理值避免长时间等待。
    • idle-timeout / max-lifetime:连接空闲超时和最大生命周期,防止连接泄漏或数据库端主动断开导致的问题。
    • 监控连接池指标(活跃连接、空闲连接、等待线程数) 是调优的关键。
yaml 复制代码
# application.yml
spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://${DB_HOST:localhost}:3306/mydb?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
    username: ${DB_USERNAME:root}
    password: ${DB_PASSWORD:secret}
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      # 核心配置
      maximum-pool-size: 15  # 最大连接数,根据CPU核心数和数据库性能调整
      minimum-idle: 5        # 最小空闲连接数
      idle-timeout: 300000   # 空闲超时时间(ms),默认600000(10分钟)
      connection-timeout: 20000  # 连接超时时间(ms)
      max-lifetime: 1800000  # 连接最大生命周期(ms),建议比数据库wait_timeout小30秒
      connection-test-query: SELECT 1  # 连接测试查询
      pool-name: MyAppHikariCP  # 线程池名称,便于监控
      auto-commit: false       # 禁用自动提交,在事务中手动控制
      # 高级配置
      leak-detection-threshold: 60000  # 连接泄漏检测阈值(ms)
      data-source-properties:
        cachePrepStmts: true           # 启用预处理语句缓存
        prepStmtCacheSize: 250         # 预处理语句缓存大小
        prepStmtCacheSqlLimit: 2048    # 预处理语句最大长度
        useServerPrepStmts: true       # 使用服务器端预处理

2. JPA/Hibernate优化配置

yaml 复制代码
# application.yml
spring:
  jpa:
    hibernate:
      ddl-auto: validate  # 生产环境禁用update/create/drop
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    properties:
      hibernate:
        jdbc.batch_size: 30               # 批量操作大小
        order_inserts: true               # 启用插入排序优化批量操作
        order_updates: true               # 启用更新排序优化批量操作
        jdbc.batch_versioned_data: true   # 批量操作时处理版本控制
        show_sql: false                   # 生产环境禁用SQL显示
        format_sql: false
        generate_statistics: false         # 生产环境禁用统计
        cache:
          use_second_level_cache: true    # 启用二级缓存
          region.factory_class: org.hibernate.cache.jcache.JCacheRegionFactory
          use_query_cache: true           # 启用查询缓存
    open-in-view: false  # 关闭Open Session In View反模式!解决N+1问题和连接泄漏风险

3. SQL优化示例

JPA查询优化(避免N+1问题):

  • N+1 查询问题: 这是 ORM 最常见性能问题。使用 JOIN FETCH (JPA), @Fetch(FetchMode.JOIN) (Hibernate), 或 @NamedEntityGraph 来一次性加载关联数据。避免在循环中触发懒加载。
java 复制代码
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
    
    // 正确:使用JOIN FETCH一次性加载关联
    @Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.id = :id")
    Optional<Order> findByIdWithItems(@Param("id") Long id);
    
    // 分页查询优化
    @Query("SELECT o FROM Order o WHERE o.status = :status ORDER BY o.createTime DESC")
    Page<Order> findByStatus(@Param("status") OrderStatus status, Pageable pageable);
    
    // 投影查询 - 只查询需要的字段
    @Query("SELECT new com.example.dto.OrderSummaryDTO(o.id, o.orderNo, o.createTime, o.status) " +
           "FROM Order o WHERE o.customerId = :customerId")
    List<OrderSummaryDTO> findOrderSummariesByCustomerId(@Param("customerId") Long customerId);
}

MyBatis批量操作示例:

  • MyBatis 优化: 合理使用一级/二级缓存;避免动态 SQL 过于复杂;使用 foreach 进行批量操作;注意 #{}${} 的区别(防注入)。
xml 复制代码
<!-- OrderMapper.xml -->
<insert id="batchInsertOrders">
    INSERT INTO orders (order_no, customer_id, status, create_time)
    VALUES
    <foreach collection="orders" item="order" separator=",">
        (#{order.orderNo}, #{order.customerId}, #{order.status}, #{order.createTime})
    </foreach>
</insert>

四、缓存优化

  • 本地缓存 (Caffeine, Ehcache): 速度快,适合缓存少量、不易变、对一致性要求不高的数据。注意单机缓存的一致性和内存限制。
  • 分布式缓存 (Redis, Memcached): 适合共享缓存、大量缓存数据、高可用场景。Redis 功能更丰富(数据结构、持久化等)。

1. Redis缓存集成与配置

java 复制代码
@Configuration
@EnableCaching
public class RedisCacheConfig {

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        // 默认配置
        RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30))  // 默认30分钟过期
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
                new GenericJackson2JsonRedisSerializer()))
            .disableCachingNullValues();  // 不缓存null值
            
        // 特定缓存配置
        Map<String, RedisCacheConfiguration> configs = new HashMap<>();
        
        // 产品缓存时间较长
        configs.put("products", defaultConfig.entryTtl(Duration.ofHours(24)));
        
        // 用户缓存时间中等
        configs.put("users", defaultConfig.entryTtl(Duration.ofHours(1)));
        
        // 热点数据缓存时间短,但使用随机过期避免缓存雪崩
        configs.put("hotData", defaultConfig.entryTtl(Duration.ofMinutes(10)));
        
        return RedisCacheManager.builder(connectionFactory)
            .cacheDefaults(defaultConfig)
            .withInitialCacheConfigurations(configs)
            .transactionAware()  // 支持事务缓存
            .build();
    }
}

2. 缓存使用示例

java 复制代码
@Service
public class ProductService {
    
    private final ProductRepository productRepository;
    
    // 构造函数注入...
    
    // 基本缓存示例
    @Cacheable(value = "products", key = "#id", unless = "#result == null")
    public Product findById(Long id) {
        return productRepository.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("Product not found"));
    }
    
    // 条件缓存示例
    @Cacheable(value = "products", key = "#code", condition = "#code.length() > 3")
    public Product findByCode(String code) {
        return productRepository.findByCode(code)
            .orElseThrow(() -> new ResourceNotFoundException("Product not found"));
    }
    
    // 缓存更新
    @CachePut(value = "products", key = "#product.id")
    public Product updateProduct(Product product) {
        return productRepository.save(product);
    }
    
    // 缓存删除
    @CacheEvict(value = "products", key = "#id")
    public void deleteProduct(Long id) {
        productRepository.deleteById(id);
    }
    
    // 清空缓存
    @CacheEvict(value = "products", allEntries = true)
    @Scheduled(fixedRate = 86400000)  // 每天凌晨清空一次缓存
    public void clearProductCache() {
        log.info("Clearing product cache");
    }
}

3. 缓存问题解决方案示例

缓存穿透防护(空值缓存+布隆过滤器):

java 复制代码
@Service
public class ProductServiceImpl implements ProductService {
    
    private final ProductRepository productRepository;
    private final BloomFilter<Long> productIdBloomFilter;
    
    // 构造函数注入...
    
    @Override
    @Cacheable(value = "products", key = "#id", unless = "#result == null")
    public Product findById(Long id) {
        // 先检查布隆过滤器,如果不存在直接返回null
        if (!productIdBloomFilter.mightContain(id)) {
            return null;
        }
        
        Product product = productRepository.findById(id).orElse(null);
        
        // 缓存空值防止缓存穿透
        if (product == null) {
            // 缓存空值5分钟
            redisTemplate.opsForValue().set("products::" + id, null, 5, TimeUnit.MINUTES);
        }
        
        return product;
    }
}

缓存雪崩防护(添加随机过期时间):

java 复制代码
@Configuration
public class CacheConfig {
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        // 默认配置
        RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30))
            // 其他配置...
            
        return RedisCacheManager.builder(connectionFactory)
            .cacheDefaults(defaultConfig)
            .withCacheConfiguration("products", 
                defaultConfig.entryTtl(Duration.ofMinutes(30 + new Random().nextInt(10)))) // 30-40分钟随机过期
            .build();
    }
}

五、异步与并发优化

1. @Async异步方法与线程池配置

  • 将耗时且非结果依赖的操作(如发邮件、记录日志、调用外部系统)异步化,释放请求线程。
  • 配置自定义线程池 (ThreadPoolTaskExecutor) ,避免使用默认的 SimpleAsyncTaskExecutor(为每个任务新建线程)。
  • 设置合理的核心线程数、最大线程数、队列容量、拒绝策略。
java 复制代码
@Configuration
@EnableAsync
public class AsyncConfig {
    
    @Bean(name = "taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数 = CPU核心数 + 1
        int corePoolSize = Runtime.getRuntime().availableProcessors() + 1;
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(corePoolSize * 2);  // 最大线程数
        executor.setQueueCapacity(100);              // 队列容量
        executor.setThreadNamePrefix("Async-");      // 线程名前缀
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 拒绝策略:调用者执行
        executor.setAwaitTerminationSeconds(60);     // 等待终止时间
        executor.setWaitForTasksToCompleteOnShutdown(true); // 关闭时等待任务完成
        executor.initialize();
        return executor;
    }
}

@Service
public class NotificationService {
    
    private final EmailClient emailClient;
    
    // 构造函数注入...
    
    @Async("taskExecutor")  // 指定线程池
    public CompletableFuture<Void> sendOrderConfirmationEmail(Order order) {
        try {
            // 模拟邮件发送耗时操作
            Thread.sleep(1000);
            emailClient.send(order.getCustomerEmail(), "Order Confirmation", 
                            "Thank you for your order #" + order.getId());
            return CompletableFuture.completedFuture(null);
        } catch (Exception e) {
            log.error("Failed to send email", e);
            return CompletableFuture.failedFuture(e);
        }
    }
}

2. 响应式编程示例(WebFlux)

  • 对于高并发、IO 密集型(如微服务间调用、数据库访问)应用,考虑使用 Spring WebFlux 和非阻塞式驱动(如 R2DBC, Reactive MongoDB)。
  • 利用少量线程处理大量并发连接,提高资源利用率。
  • 注意: 编程模型与 Servlet 不同,需要学习曲线。确保整个调用链都是非阻塞的。
java 复制代码
@RestController
@RequestMapping("/api/orders")
public class OrderController {

    private final OrderService orderService;
    
    // 构造函数注入...
    
    @GetMapping("/{id}")
    public Mono<ResponseEntity<OrderDTO>> getOrder(@PathVariable Long id) {
        return orderService.findById(id)
            .map(order -> ResponseEntity.ok(convertToDTO(order)))
            .defaultIfEmpty(ResponseEntity.notFound().build());
    }
    
    @GetMapping
    public Flux<OrderSummaryDTO> getOrdersByCustomer(@RequestParam Long customerId) {
        return orderService.findByCustomerId(customerId)
            .map(this::convertToSummaryDTO)
            .sort((o1, o2) -> o2.getCreateTime().compareTo(o1.getCreateTime()));
    }
}

@Service
public class OrderService {
    private final OrderRepository orderRepository;
    
    // 构造函数注入...
    
    public Mono<Order> findById(Long id) {
        return Mono.fromCallable(() -> orderRepository.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("Order not found")));
    }
    
    public Flux<Order> findByCustomerId(Long customerId) {
        return Flux.fromIterable(orderRepository.findByCustomerId(customerId));
    }
}

六、完整的性能优化实践案例

案例1:数据库查询优化前后对比

优化前(问题代码):

java 复制代码
// 问题:N+1查询和全表扫描
@GetMapping("/{orderId}")
public OrderDTO getOrder(@PathVariable Long orderId) {
    Order order = orderRepository.findById(orderId).orElseThrow();
    // 每次调用getItems()都会触发额外查询
    List<OrderItem> items = order.getItems(); 
    // ... 转换为DTO
}

优化后:

java 复制代码
// 优化:使用JOIN FETCH + 投影DTO
@GetMapping("/{orderId}")
public OrderSummaryDTO getOrderSummary(@PathVariable Long orderId) {
    return orderRepository.findOrderSummaryById(orderId)
        .orElseThrow(() -> new ResourceNotFoundException("Order not found"));
}

// Repository方法
@Query("SELECT new com.example.dto.OrderSummaryDTO(o.id, o.orderNo, o.createTime, " +
       "SUM(oi.quantity * oi.unitPrice) as totalAmount, " +
       "COUNT(oi) as itemCount) " +
       "FROM Order o JOIN o.items oi WHERE o.id = :id " +
       "GROUP BY o.id, o.orderNo, o.createTime")
Optional<OrderSummaryDTO> findOrderSummaryById(@Param("id") Long id);

案例2:缓存策略实现

多级缓存实现(Caffeine本地缓存 + Redis分布式缓存):

java 复制代码
@Configuration
@EnableCaching
public class MultiLevelCacheConfig {

    // Caffeine本地缓存配置
    @Bean
    public Cache<String, Object> caffeineCache() {
        return Caffeine.newBuilder()
            .maximumSize(10_000)          // 最大缓存项数
            .expireAfterWrite(5, TimeUnit.MINUTES)  // 写入后过期时间
            .recordStats()                // 开启统计
            .build();
    }
    
    // Redis分布式缓存配置
    @Bean
    public RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
        // 配置Redis缓存
        // ... 省略RedisCacheManager配置,同上一节
    }
    
    // 自定义缓存管理器,结合本地缓存和Redis缓存
    @Bean
    public CacheManager cacheManager(RedisCacheManager redisCacheManager, Cache<String, Object> caffeineCache) {
        return new MultiLevelCacheManager(redisCacheManager, caffeineCache);
    }
}

// 自定义多级缓存管理器实现
public class MultiLevelCacheManager implements CacheManager {
    private final RedisCacheManager redisCacheManager;
    private final Cache<String, Object> caffeineCache;
    
    // 构造函数注入...
    
    @Override
    public Cache getCache(String name) {
        return new MultiLevelCache(
            name, 
            caffeineCache, 
            redisCacheManager.getCache(name)
        );
    }
    
    // 其他方法实现...
}

// 多级缓存实现类
class MultiLevelCache implements Cache {
    private final String name;
    private final Cache<String, Object> localCache;
    private final Cache redisCache;
    
    // 实现get、put、evict等方法,先查本地缓存,再查Redis缓存...
}

七、监控与诊断

  1. 启用 Actuator: spring-boot-starter-actuator 提供丰富的生产就绪端点 (/actuator/metrics, /actuator/health, /actuator/prometheus 等)。
  2. 集成监控系统:
    • Prometheus + Grafana: 强大的指标采集和可视化组合。使用 micrometer-registry-prometheus
    • ELK Stack (Elasticsearch, Logstash, Kibana): 集中式日志管理和分析。
    • 分布式追踪: Zipkin, Jaeger。集成 Spring Cloud Sleuth 来追踪请求在微服务间的流转。
  3. Profiler 工具: 使用 VisualVM , YourKit , JProfiler , Arthas 等进行 CPU 采样、内存分析、线程分析,定位热点方法和内存泄漏。
  4. APM (应用性能监控): New Relic, Dynatrace, AppDynamics, SkyWalking, Pinpoint 等提供端到端的应用性能洞察。

总结

Spring Boot性能优化是一个系统性工程,需要从多个维度综合考虑:

  1. 应用层:合理使用Spring特性、优化Bean管理、AOP和事务
  2. JVM层:选择合适的JVM参数和垃圾收集器,根据应用特性调整
  3. 数据库层:优化连接池、SQL查询、ORM配置
  4. 缓存策略:多级缓存、缓存穿透/击穿/雪崩防护
  5. 并发处理:异步方法、响应式编程、线程池优化
  6. 监控与持续优化:建立完善的监控体系,持续收集数据并优化

关键建议:

  • 始终以数据为驱动进行优化决策
  • 优先解决最大的性能瓶颈("低垂的果实")
  • 避免过早优化和过度优化
  • 建立性能测试和基准测试体系
  • 持续监控生产环境性能指标
  • 关注Spring官方博客和文档,了解最新性能改进
相关推荐
paopaokaka_luck32 分钟前
基于SpringBoot+Vue的电影售票系统(协同过滤算法)
vue.js·spring boot·后端
陌殇殇4 小时前
SpringBoot整合SpringCache缓存
spring boot·redis·缓存
小林学习编程6 小时前
Springboot + vue + uni-app小程序web端全套家具商场
前端·vue.js·spring boot
ladymorgana6 小时前
【Spring boot】tomcat Jetty Undertow对比,以及应用场景
spring boot·tomcat·jetty
IT_10247 小时前
Spring Boot项目开发实战销售管理系统——系统设计!
大数据·spring boot·后端
DCTANT7 小时前
【原创】国产化适配-全量迁移MySQL数据到OpenGauss数据库
java·数据库·spring boot·mysql·opengauss
ai小鬼头8 小时前
AIStarter最新版怎么卸载AI项目?一键删除操作指南(附路径设置技巧)
前端·后端·github
Touper.8 小时前
SpringBoot -- 自动配置原理
java·spring boot·后端
一只叫煤球的猫8 小时前
普通程序员,从开发到管理岗,为什么我越升职越痛苦?
前端·后端·全栈
一只鹿鹿鹿8 小时前
信息化项目验收,软件工程评审和检查表单
大数据·人工智能·后端·智慧城市·软件工程