Spring Boot + Spring Cloud Alibaba + MyBatis-Plus——这三个框架构成了90%的Java后端项目的骨架。面试官要听的不是"我会用",而是"我知道企业里怎么用"

1. Spring Boot — 企业级开发的基石

1.1 企业中Spring Boot的典型项目结构

一个标准的企业级多模块Maven项目结构:

project-name/
├── project-name-api/            # 对外暴露的DTO/接口
│   └── src/main/java/.../dto/   # 数据传输对象
├── project-name-common/         # 通用工具类与常量
│   └── src/main/java/.../util/  # 工具类
├── project-name-dao/            # 数据访问层
│   └── src/main/java/.../mapper/  # MyBatis Mapper
│   └── src/main/resources/mapper/ # XML映射文件
├── project-name-service/        # 业务逻辑层
│   └── src/main/java/.../service/
│   └── src/main/java/.../service/impl/
├── project-name-web/            # 控制层(启动入口)
│   └── src/main/java/.../controller/
│   └── src/main/java/.../config/   # 配置类
│   └── src/main/resources/
│       ├── application.yml
│       ├── application-dev.yml
│       ├── application-prod.yml
│       └── bootstrap.yml        # Nacos配置
└── pom.xml

企业实践:多模块的核心目的是职责分离和依赖控制——dao层只依赖common,service层依赖dao,web层依赖service,形成单向依赖环。

1.2 自动配置原理 (面试高频)

// Spring Boot 自动配置的核心注解
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

自动配置原理三步拆解:

@SpringBootApplication
├── @EnableAutoConfiguration         ← 开启自动配置
│   └── @Import(AutoConfigurationImportSelector.class)
│       └── SpringFactoriesLoader.loadFactoryNames()
│           └── META-INF/spring.factories
│               └── (读取所有xxxAutoConfiguration类)
├── @ComponentScan                   ← 组件扫描
└── @SpringBootConfiguration         ← 标记配置类

核心流程:

  1. AutoConfigurationImportSelector 从 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports (Spring Boot 2.7+) 或 spring.factories 中读取所有 AutoConfiguration 类名
  2. 逐类解析 @Conditional 条件注解链 —— 条件不满足就跳过
  3. 匹配条件的配置类被加载,其中的 @Bean 方法被解析并注入容器

关键条件注解:

注解作用示例场景
@ConditionalOnClass类路径存在某类时生效RedisAutoConfiguration 依赖 RedisOperations
@ConditionalOnMissingBean容器中没有某Bean时生效用户自定义 DataSource 则跳过默认配置
@ConditionalOnProperty配置项匹配时生效spring.redis.host 存在时配置Redis
@ConditionalOnWebApplicationWeb环境下生效WebMvcAutoConfiguration
@ConditionalOnExpressionSpEL表达式为true时生效复杂组合条件

面试深问:如果问"如何自定义Starter",回答要点——(1) 创建 xxx-spring-boot-starter 模块 (2) 编写 XxxAutoConfiguration (3) 配置 spring.factories (4) 提供 spring-configuration-metadata.json 做IDE提示。

1.3 常用Starter清单与选型建议

分类Starter用途企业使用率
Webspring-boot-starter-web传统Servlet Web (Tomcat)★★★★★
响应式spring-boot-starter-webflux响应式Web (Netty)★★
数据spring-boot-starter-data-redisRedis操作 (Lettuce)★★★★★
数据mybatis-spring-boot-starterMyBatis整合★★★★★
数据spring-boot-starter-data-jpaJPA整合★★★
数据shardingsphere-jdbc-core-spring-boot-starter分库分表★★★
安全spring-boot-starter-security认证授权★★★★
消息spring-boot-starter-amqpRabbitMQ★★★
消息rocketmq-spring-boot-starterRocketMQ★★★★
监控spring-boot-starter-actuator端点监控★★★★★
测试spring-boot-starter-test单元测试★★★★★
云spring-cloud-starter-alibaba-nacos-discoveryNacos注册★★★★★
云spring-cloud-starter-alibaba-sentinel流量控制★★★★★
云spring-cloud-starter-gateway网关★★★★
云spring-cloud-starter-openfeignHTTP客户端★★★★★
无状态spring-boot-starter-oauth2-resource-serverJWT/OAuth★★★★

选型建议:

  • Starter版本必须跟随Spring Boot版本,使用 spring-boot-starter-parent 或 spring-boot-dependencies BOM统一管理
  • 避免引入不必要的Starter,降低启动时间和依赖冲突风险
  • 企业内部可以封装 company-spring-boot-starter 统一日志、监控、异常处理

1.4 企业级配置管理

多环境Profile

# application.yml — 公共配置
spring:
  profiles:
    active: @profile.active@      # Maven打包时动态替换
  application:
    name: order-service

server:
  tomcat:
    max-threads: 200              # 公共默认值
    min-spare-threads: 20

---
# application-dev.yml (开发环境)
spring:
  config:
    activate:
      on-profile: dev
  datasource:
    url: jdbc:mysql://localhost:3306/order?useSSL=false
  redis:
    host: localhost

---
# application-prod.yml (生产环境)
spring:
  config:
    activate:
      on-profile: prod
  datasource:
    url: jdbc:mysql://prod-master:3306/order?useSSL=true
    hikari:
      maximum-pool-size: 20       # 生产环境连接池调大
  redis:
    host: redis-prod-cluster
    timeout: 3000ms

企业实践:敏感配置(数据库密码、密钥)不放在Git仓库中,而是使用Nacos配置中心或Vault管理,本地配置只保留非敏感项。

Nacos配置中心

# bootstrap.yml — 优先于application.yml加载
spring:
  cloud:
    nacos:
      config:
        server-addr: 192.168.1.100:8848
        namespace: prod           # 命名空间隔离环境
        group: DEFAULT_GROUP
        file-extension: yaml
        refresh-enabled: true     # 配置热更新
      discovery:
        server-addr: 192.168.1.100:8848
// 配置热更新:两种方式
@RefreshScope  // 方式一:注解标记,配置变化时Bean刷新
public class DynamicConfig {
    @Value("${order.timeout:5000}")
    private Integer orderTimeout;
}

// 方式二:@ConfigurationProperties 自动刷新(推荐)
@ConfigurationProperties(prefix = "order")
public class OrderProperties {
    private Integer timeout;
    private Integer retryCount;
}

1.5 内嵌Tomcat调优

# application.yml — Tomcat调优配置
server:
  tomcat:
    # 连接器线程池 —— 处理请求的工作线程
    max-threads: 200              # 默认200,按CPU核心数*2~4调整
    min-spare-threads: 20         # 最小空闲线程数
    max-connections: 10000        # 最大连接数,默认10000
    accept-count: 1000            # 等待队列长度
    connection-timeout: 5000      # 连接超时ms

    # HTTP压缩
    compression: on
    compression-min-response-size: 1024  # 超过1KB启用压缩

  # 优雅关闭 —— 生产环境必配
  shutdown: graceful

spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s
// 代码方式配置
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCustomizer() {
    return factory -> {
        factory.addConnectorCustomizers(connector -> {
            connector.setProperty("maxSwallowSize", "-1");      // 不限制吞数据大小
            connector.setProperty("relaxedQueryChars", "[]{}"); // URL特殊字符
        });
        factory.addContextCustomizers(context -> {
            context.setSessionTimeout(30, TimeUnit.MINUTES);
        });
    };
}

面试点:调优的核心是观测 -> 调整 -> 再观测,不要盲目改参数。启动时观察 /actuator/metrics/tomcat.threads.busy 线程忙碌程度,找到水位线再调整。

2. Spring Cloud Alibaba — 微服务全家桶

2.1 企业微服务全景架构图

                           ┌─────────────────────┐
                           │      Nacos           │
                           │  注册中心+配置中心    │
                           └──────────┬──────────┘
                                      │
              ┌───────────────────────┼───────────────────────┐
              │  注册/发现             │  配置管理             │
              ▼                       ▼                       ▼
┌──────────────────────────────────────────────────────────────────────┐
│                        Spring Cloud Gateway                          │
│   路由转发  │  鉴权(Sentinel)  │  限流  │  跨域  │  请求日志         │
└──────┬───────────────────────────────────────────────────────────────┘
       │
       │  OpenFeign 调用
       ▼
┌──────────────────────────────────────────────────────────────────────┐
│                     微服务集群 (Service Cluster)                      │
│                                                                      │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐ ┌─────────┐   │
│  │ 用户服务│◄─┤ 订单服务│◄─┤ 库存服务│◄─┤ 支付服务│ │ 通知服务│   │
│  └────┬────┘  └────┬────┘  └────┬────┘  └────┬────┘ └────┬────┘   │
│       │            │            │            │          │          │
│       │            │  Seata AT  │            │          │          │
│       │            │ (分布式事务)│            │          │          │
│       └────────────┴────────────┴────────────┴──────────┘          │
│                                                                      │
│  同步调用: OpenFeign + Sentinel 熔断降级                              │
│  异步通信: RocketMQ (事务消息)                                       │
└──────────────────────────────────────────────────────────────────────┘

2.2 Nacos — 注册中心与配置中心二合一

Nacos 是阿里巴巴开源的一站式服务发现与配置管理平台,企业中最常用的功能点:

注册中心能力

# application.yml 服务注册配置
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.1.100:8848
        namespace: prod              # 环境隔离
        group: ORDER_GROUP           # 服务分组
        cluster-name: HZ             # 集群名(同城机房)
        weight: 1.0                  # 负载均衡权重
        ephemeral: true              # 临时实例(默认true)
        heart-beat-interval: 5000    # 心跳间隔ms

AP vs CP模式选择:

模式一致性模型特点适用场景
AP (临时实例)最终一致性心跳检测,不可用时直接剔除大部分业务场景(默认),可用性优先
CP (永久实例)强一致性使用Raft协议,需半数以上节点确认银行、交易核心,一致性优先

选型建议:90%的企业选择AP模式。CP模式在集群半数节点宕机时不可用,对生产环境风险较大。

配置中心实战

# Nacos中推送的配置 Data ID: order-service-prod.yaml
order:
  timeout: 3000
  auto-cancel-minutes: 30
  max-retry-count: 3

spring:
  datasource:
    url: jdbc:mysql://prod-db:3306/order?rewriteBatchedStatements=true
// 动态刷新配置
@Component
@RefreshScope
@ConfigurationProperties(prefix = "order")
public class OrderConfig {
    private Integer timeout;
    private Integer autoCancelMinutes;
    private Integer maxRetryCount;

    // getter/setter... 配置变更时自动注入新值
}

企业级配置最佳实践:

  1. 命名空间隔离:dev / test / prod 三个命名空间,配置完全隔离
  2. 配置分组管理:按业务域分组(ORDER_GROUP / USER_GROUP / PAY_GROUP)
  3. 灰度配置:先发布到灰度机器验证,再全量推送
  4. 配置变更审计:记录每次配置变更的操作人和变更内容

2.3 Sentinel — 流量控制框架

Sentinel 是阿里巴巴开源的流量防卫兵,企业中使用频率极高:

核心功能速查

功能说明配置方式
流控 (FlowRule)QPS/线程数限流直接/关联/链路模式
熔断 (DegradeRule)慢调用/异常比例/异常数熔断熔断后降级方法
热点 (ParamFlowRule)热点参数限流针对特定参数值限流
系统规则 (SystemRule)系统负载/CPU保护Load/RT/入口QPS
授权规则 (AuthorityRule)黑白名单根据调用来源控制
// 企业中最常用的Sentinel配置方式:注解 + Nacos持久化

// 1. 资源定义与流控
@Service
public class OrderService {

    @SentinelResource(
        value = "createOrder",
        blockHandler = "createOrderBlockHandler",    // 触发了流控 -> 走这里
        fallback = "createOrderFallback"              // 触发了熔断 -> 走这里
    )
    public OrderVO createOrder(OrderDTO orderDTO) {
        // 业务逻辑
        if (someCondition) {
            throw new RuntimeException("业务异常");
        }
        return doCreate(orderDTO);
    }

    // 流控降级 —— 返回友好的提示信息
    public OrderVO createOrderBlockHandler(OrderDTO orderDTO, BlockException ex) {
        log.warn("createOrder 被限流: {}", ex.getMessage());
        return OrderVO.failed("系统繁忙,请稍后重试");
    }

    // 熔断降级 —— 返回兜底数据
    public OrderVO createOrderFallback(OrderDTO orderDTO, Throwable t) {
        log.error("createOrder 熔断降级: {}", t.getMessage());
        return OrderVO.failed("服务异常,请稍后重试");
    }
}
// 2. 规则持久化到Nacos(避免重启丢失规则)
@Configuration
public class SentinelConfig {

    @PostConstruct
    public void initSentinelRules() {
        // 流控规则持久化
        ReadableDataSource<String, List<FlowRule>> flowRuleDataSource =
            new NacosDataSource<>(
                Arrays.asList("192.168.1.100:8848"),
                "DEFAULT_GROUP",
                "sentinel-flow-rules",
                source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {})
            );
        FlowRuleManager.register2Property(flowRuleDataSource.getProperty());

        // 熔断规则持久化
        ReadableDataSource<String, List<DegradeRule>> degradeRuleDataSource =
            new NacosDataSource<>(
                Arrays.asList("192.168.1.100:8848"),
                "DEFAULT_GROUP",
                "sentinel-degrade-rules",
                source -> JSON.parseObject(source, new TypeReference<List<DegradeRule>>() {})
            );
        DegradeRuleManager.register2Property(degradeRuleDataSource.getProperty());
    }
}

企业常问的Sentinel场景题:

场景解决方案
秒杀流量瞬间暴涨10倍QPS流控 + 排队等待模式,削峰填谷
第三方支付接口变慢熔断降级(慢调用比例>50%则熔断),返回"支付处理中"
热点商品频繁被刷热点参数限流,针对商品ID的QPS设为100
数据库连接池打满线程数限流 + 系统Load保护

面试重点:Sentinel与Hystrix的核心区别——(1) Sentinel支持实时监控和动态规则 (2) 支持热点限流 (3) 支持规则持久化 (4) 线程隔离是信号量而非线程池 (5) 微服务零侵入

2.4 Gateway — 路由/鉴权/限流

# application.yml — 企业级Gateway配置
spring:
  cloud:
    gateway:
      routes:
        - id: order-service
          uri: lb://order-service           # 负载均衡
          predicates:
            - Path=/api/order/**            # 路径匹配
            - Header=X-Request-Source, mobile  # Header匹配
          filters:
            - StripPrefix=1                 # 去掉/api前缀
            - name: RequestRateLimiter      # 限流
              args:
                key-resolver: "#{@userKeyResolver}"
                redis-rate-limiter:
                  replenishRate: 100        # 每秒令牌数
                  burstCapacity: 200        # 令牌桶容量
            - name: Retry                   # 重试
              args:
                retries: 3
                statuses: BAD_GATEWAY
                methods: GET

        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/user/**

      # 全局过滤器配置
      default-filters:
        - AddResponseHeader=X-Response-Time, localtime
        - DedupeResponseHeader=Access-Control-Allow-Origin
// 全局鉴权过滤器 —— 企业必配
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();

        // 白名单路径跳过鉴权
        List<String> whitelist = Arrays.asList("/api/user/login", "/api/user/register");
        if (whitelist.contains(path)) {
            return chain.filter(exchange);
        }

        // 从Header中提取Token
        String token = request.getHeaders().getFirst("Authorization");
        if (StringUtils.isBlank(token) || !token.startsWith("Bearer ")) {
            return unauthorized(exchange, "Missing token");
        }

        // 校验JWT(缓存校验结果减少重复解析)
        try {
            Claims claims = JwtUtil.parseToken(token.substring(7));
            // 将用户信息放入请求头,传递给下游服务
            ServerHttpRequest mutatedRequest = request.mutate()
                .header("X-User-Id", claims.get("userId").toString())
                .header("X-User-Name", URLEncoder.encode(claims.get("userName").toString(), "UTF-8"))
                .build();
            return chain.filter(exchange.mutate().request(mutatedRequest).build());
        } catch (Exception e) {
            return unauthorized(exchange, "Invalid token");
        }
    }

    @Override
    public int getOrder() {
        return -100;  // 高优先级
    }

    private Mono<Void> unauthorized(ServerWebExchange exchange, String msg) {
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        return exchange.getResponse().writeWith(
            Mono.just(exchange.getResponse().bufferFactory()
                .wrap(("{\"code\":401,\"msg\":\"" + msg + "\"}").getBytes()))
        );
    }
}

面试关键:Gateway的核心组件——Route(路由)、Predicate(匹配条件)、Filter(过滤处理)。Gateway是基于WebFlux响应式编程模型,使用Netty而非Tomcat,因此不能直接获取HttpServletRequest。

2.5 OpenFeign — 声明式HTTP调用

// 企业级OpenFeign配置

// 1. FeignClient声明
@FeignClient(
    name = "inventory-service",
    path = "/api/inventory",
    configuration = InventoryFeignConfig.class,
    fallbackFactory = InventoryFallbackFactory.class   // 降级工厂
)
public interface InventoryFeignClient {

    @PostMapping("/deduct")
    Result<Void> deductStock(@Valid @RequestBody StockDeductDTO dto);

    @GetMapping("/query/{skuId}")
    Result<StockVO> queryStock(@PathVariable("skuId") Long skuId);
}

// 2. 统一降级处理 —— 返回兜底数据 + 记录日志
@Component
public class InventoryFallbackFactory implements FallbackFactory<InventoryFeignClient> {

    @Override
    public InventoryFeignClient create(Throwable cause) {
        log.error("inventory-service 调用失败: {}", cause.getMessage());
        return new InventoryFeignClient() {
            @Override
            public Result<Void> deductStock(StockDeductDTO dto) {
                return Result.error("库存服务不可用");
            }

            @Override
            public Result<StockVO> queryStock(Long skuId) {
                return Result.success(new StockVO(skuId, 0));  // 返回安全值
            }
        };
    }
}

// 3. 配置类 —— 超时/拦截器/重试
@Configuration
public class InventoryFeignConfig {

    @Bean
    public RequestInterceptor requestInterceptor() {
        return requestTemplate -> {
            // 传递TraceId用于链路追踪
            requestTemplate.header("X-Trace-Id", MDC.get("traceId"));
            // 传递认证信息
            requestTemplate.header("X-User-Id", UserContext.getUserId());
        };
    }

    @Bean
    public Retryer retryer() {
        return new Retryer.Default(100, 1000, 3);  // 100ms间隔, 最多3次
    }
}
# application.yml — Feign全局配置
spring:
  cloud:
    openfeign:
      client:
        config:
          default:
            connect-timeout: 5000       # 连接超时
            read-timeout: 10000         # 读取超时
            logger-level: BASIC         # BASIC/HEADERS/FULL
      compression:
        request:
          enabled: true
          mime-types: application/json
          min-request-size: 2048
        response:
          enabled: true

性能警告:Feign底层是JDK HttpURLConnection或Apache HttpClient,生产环境建议替换为 OkHttp 或 Apache HttpClient,并设置连接池——JDK默认实现不支持连接池,高并发下性能极差。

2.6 Seata — 分布式事务

// AT模式 —— 最常用,对业务侵入最小
@GlobalTransactional(name = "create-order-tx", rollbackFor = Exception.class)
public OrderVO createOrder(OrderDTO orderDTO) {
    // 1. 创建订单(本地事务)
    orderMapper.insert(order);

    // 2. 扣减库存(远程调用:inventory-service)
    inventoryFeign.deductStock(new StockDeductDTO(order.getSkuId(), order.getCount()));

    // 3. 扣减账户余额(远程调用:account-service)
    accountFeign.deductBalance(new BalanceDeductDTO(order.getUserId(), order.getAmount()));

    // 任意一步失败,Seata自动回滚所有参与者
    return OrderVO.success(order);
}

三种模式对比:

模式性能一致性侵入性适用场景
AT较高最终一致低(自动回滚SQL)大部分业务场景(推荐)
TCC高最终一致高(需实现Try/Confirm/Cancel)高性能要求的核心链路
Saga高最终一致中(需实现补偿逻辑)长事务、异步场景

Seata AT模式原理:

AT模式执行流程
┌─────────┐     ┌─────────┐     ┌─────────┐
│  TM     │     │  TC     │     │  RM     │
│ (事务发起)│────▶│ (协调器)│────▶│ (资源管理)│
└────┬────┘     └─────────┘     └────┬────┘
     │                                │
     │ 1. 开启全局事务                  │
     │ 2. 注册分支事务                  │
     │ 3. 执行业务SQL                  │
     │    + 解析SQL生成UNDO_LOG        │
     │ 4. 提交/回滚                    │
     └────────────────────────────────┘

关键机制:
- 前置镜像: 在执行业务SQL前,记录修改前的数据快照
- UNDO_LOG表: 保存前后镜像,用于失败回滚
- 全局锁: 防止脏读脏写,事务结束后释放

2.7 Dubbo vs Spring Cloud 选型

很多企业内部会同时使用两者,了解它们的异同是面试加分项:

维度DubboSpring Cloud两者结合使用
通信协议Dubbo协议 (Netty, TCP长连接)HTTP (RestTemplate/Feign)内部RPC用Dubbo,对外开放用Spring Cloud
性能高(二进制协议,序列化快)较低(HTTP+JSON)Dubbo处理内部高并发调用
治理自带服务治理(路由、限流、降级)需整合Sentinel/HystrixDubbo + Sentinel 完美搭配
生态主要用于服务间RPC完整微服务体系互补使用,取长补短
学习曲线较低,配置简单组件较多,学习门槛稍高团队根据能力选择
互联网大厂阿里/京东/美团等大量使用中小型企业为主大厂内部Dubbo为主 + 网关层Spring Cloud

企业真实场景:很多企业用 Dubbo做内部服务间RPC调用(高性能),用 Spring Cloud Gateway做API网关(生态丰富),服务注册全部走 Nacos,流量控制统一用 Sentinel——这就是"Dubbo + Spring Cloud Alibaba 混合架构"。

3. 持久层框架 — MyBatis-Plus & JPA

3.1 MyBatis-Plus在企业中的实际用法

MyBatis-Plus(简称MP)是MyBatis的增强工具,国内企业渗透率极高:

BaseMapper 通用CRUD

// 1. 定义Mapper —— 继承BaseMapper即获得基础CRUD
@Mapper
public interface OrderMapper extends BaseMapper<Order> {
    // 无需写任何代码,自动拥有 insert/deleteById/updateById/selectById 等方法
}

// 2. 使用示例
@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderMapper orderMapper;

    public Order getById(Long id) {
        return orderMapper.selectById(id);   // 单表查询
    }

    public Page<Order> pageQuery(int page, int size) {
        Page<Order> pageObj = new Page<>(page, size);
        return orderMapper.selectPage(pageObj, null);
    }

    public boolean save(Order order) {
        // 自动填充 createTime, updateTime (配合 @TableField)
        return orderMapper.insert(order) > 0;
    }
}

条件构造器 (Wrapper)

// QueryWrapper —— 最常用,支持链式条件构建
public List<Order> queryOrders(Long userId, Integer status, String keyword) {
    LambdaQueryWrapper<Order> wrapper = Wrappers.<Order>lambdaQuery()
        .eq(Order::getUserId, userId)
        .eq(status != null, Order::getStatus, status)        // 条件判断
        .like(StringUtils.isNotBlank(keyword), Order::getOrderNo, keyword)  // 模糊查询
        .orderByDesc(Order::getCreateTime)
        .last("limit 10");

    return orderMapper.selectList(wrapper);
}

// UpdateWrapper —— 更新特定字段(避免全字段更新)
public void updateStatus(String orderNo, Integer status) {
    LambdaUpdateWrapper<Order> wrapper = Wrappers.<Order>lambdaUpdate()
        .set(Order::getStatus, status)
        .set(Order::getUpdateTime, new Date())
        .eq(Order::getOrderNo, orderNo);

    orderMapper.update(null, wrapper);
}

分页插件

@Configuration
@MapperScan("com.company.*.mapper")
public class MyBatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

        // 分页插件(必配)
        PaginationInnerInterceptor pagination = new PaginationInnerInterceptor(DbType.MYSQL);
        pagination.setMaxLimit(1000L);            // 单页最大条数保护
        pagination.setOverflow(true);             // 溢出后自动回到第一页
        interceptor.addInnerInterceptor(pagination);

        // 乐观锁插件(写操作需要用)
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());

        return interceptor;
    }
}

逻辑删除与自动填充

// 1. 逻辑删除 —— 企业标配,避免物理删除
@TableName("order")
public class Order {
    @TableId(type = IdType.ASSIGN_ID)          // 分布式ID
    private Long id;

    @TableLogic(value = "0", delval = "1")     // 逻辑删除
    private Integer deleted;

    @TableField(fill = FieldFill.INSERT)       // 自动填充
    private Date createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
}

// 2. 自动填充处理器
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
        this.strictInsertFill(metaObject, "updateTime", Date.class, new Date());
        this.strictInsertFill(metaObject, "deleted", Integer.class, 0);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
    }
}

3.2 MyBatis vs JPA 选型

维度MyBatis / MyBatis-PlusSpring Data JPA (Hibernate)
SQL控制完全手动编写SQL,精准控制自动生成SQL,控制力弱
学习曲线较低(会SQL就会用)较高(需要理解ORM映射机制)
复杂查询动态SQL灵活强大JPQL/Criteria API较复杂
性能高,SQL可控容易产生N+1问题,需要调优
迁移成本SQL与具体数据库耦合数据库无关性好
国内企业80%以上使用10%-15%使用
面试热度★★★★★★★★

国内企业为什么偏好MyBatis-Plus?

  1. SQL可控:DBA可以直接审查和优化SQL,不像JPA自动生成难掌控
  2. 复杂业务场景:国内企业业务逻辑复杂,多表关联、子查询频繁,MyBatis的动态SQL更灵活
  3. 团队习惯:Java开发者基本都熟悉SQL,JPA的学习曲线相对陡峭
  4. 与ShardingSphere整合:分库分表场景下MyBatis-Plus + ShardingSphere-JDBC是国内标准搭配

3.3 复杂SQL管理

// XML映射文件 —— 复杂SQL建议写在XML中而非注解
@Mapper
public interface OrderExtMapper {
    // 统计某时间段内的销售排行
    List<SalesRankVO> getSalesRanking(@Param("startDate") String startDate,
                                       @Param("endDate") String endDate,
                                       @Param("limit") Integer limit);
}
<!-- OrderExtMapper.xml —— 企业级复杂SQL示例 -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.company.order.mapper.OrderExtMapper">

    <!-- 基础结果映射 -->
    <resultMap id="OrderDetailMap" type="com.company.order.entity.OrderDetail">
        <id property="id" column="id"/>
        <result property="orderNo" column="order_no"/>
        <result property="totalAmount" column="total_amount"/>
        <association property="user" javaType="com.company.user.entity.User"
                     select="com.company.user.mapper.UserMapper.selectById"
                     column="user_id"/>
        <collection property="items" ofType="com.company.order.entity.OrderItem"
                    select="com.company.order.mapper.OrderItemMapper.selectByOrderId"
                    column="id"/>
    </resultMap>

    <!-- 动态SQL + 时间窗口统计 -->
    <select id="getSalesRanking" resultType="com.company.order.vo.SalesRankVO">
        SELECT
            oi.sku_id,
            sku_name,
            SUM(oi.quantity) AS total_sales,
            SUM(oi.amount) AS total_amount
        FROM order_item oi
        INNER JOIN `order` o ON oi.order_id = o.id
        <where>
            o.`status` = 2  <!-- 已支付状态 -->
            <if test="startDate != null and startDate != ''">
                AND o.pay_time >= #{startDate}
            </if>
            <if test="endDate != null and endDate != ''">
                AND o.pay_time <![CDATA[ <= ]]> #{endDate}
            </if>
            AND o.deleted = 0
        </where>
        GROUP BY oi.sku_id, sku_name
        ORDER BY total_sales DESC
        <if test="limit != null">
            LIMIT #{limit}
        </if>
    </select>

</mapper>

3.4 与ShardingSphere整合

# application.yml —— ShardingSphere + MyBatis-Plus 分库分表配置
spring:
  shardingsphere:
    datasource:
      names: ds0, ds1
      ds0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://192.168.1.10:3306/order_0?useSSL=false
        username: root
        password: ${DB_PASSWORD}
      ds1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://192.168.1.11:3306/order_1?useSSL=false
        username: root
        password: ${DB_PASSWORD}

    rules:
      sharding:
        tables:
          order:
            actual-data-nodes: ds$->{0..1}.order_$->{0..3}
            database-strategy:
              standard:
                sharding-column: user_id
                sharding-algorithm-name: db_hash  # 按user_id分库
            table-strategy:
              standard:
                sharding-column: id
                sharding-algorithm-name: table_mod  # 按id取模分表
            key-generate-strategy:
              column: id
              key-generator-name: snowflake  # 雪花算法分布式ID

        sharding-algorithms:
          db_hash:
            type: HASH_MOD
            props:
              sharding-count: 2   # 2个库
          table_mod:
            type: MOD
            props:
              sharding-count: 4   # 每库4张表

        key-generators:
          snowflake:
            type: SNOWFLAKE  # 雪花算法


    props:
      sql-show: true           # 打印SQL(开发环境启用)
      check-table-metadata-enabled: true  # 检查表元数据一致性

企业实践:ShardingSphere + MyBatis-Plus 是国内分库分表的标准组合。核心原则——(1) 分片键选择要均衡,避免热点 (2) 尽量避免跨分片查询 (3) 不适合做跨分片Join和事务,需要应用层处理。

4. 网关与认证授权

4.1 Spring Cloud Gateway 企业级配置详解

# Gateway核心路由配置(已在2.4节展示基础配置)
# 这里补充企业常用的高级特性

spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/user/**
            - Method=GET,POST,PUT,DELETE  # 允许的HTTP方法
            - Header=X-Request-ID, \d+    # Header匹配正则
            - Query=source, app.          # 查询参数匹配
            - RemoteAddr=192.168.1.0/24   # IP白名单
            - Weight=group1, 80           # 灰度权重
          filters:
            - name: CircuitBreaker        # 断路器
              args:
                name: userServiceCB
                fallbackUri: forward:/fallback/user
            - AddRequestHeader=X-Api-Version, v2
            - PrefixPath=/api/v2
            - name: SecureHeaders         # 安全头
              args:
                disable: x-frame-options

4.2 JWT + OAuth2.0 + RBAC 权限模型

企业中典型的权限模型是一个三层结构:

三层权限模型
┌─────────────────────────────────────────────────────┐
│                    用户 (User)                        │
│   ┌──────┴──────┐                                    │
│   │  admin      │  user1    user2    user3           │
│   └──────┬──────┘                                    │
│          │  多对多                                     │
│   ┌──────▼──────┐                                    │
│   │   角色 (Role)│                                    │
│   │  管理员 │ 运营 │ 客服 │ 财务                       │
│   └──────┬──────┘                                    │
│          │  多对多                                     │
│   ┌──────▼──────┐                                    │
│   │  权限 (Permission)                               │
│   │ 订单:查询 │ 订单:创建 │ 订单:退款 │ 用户:管理       │
│   └─────────────┘                                    │
└─────────────────────────────────────────────────────┘

JWT Token结构:

# JWT = Header.Payload.Signature

# Header
{
  "alg": "RS256",           // 非对称加密
  "typ": "JWT"
}

# Payload
{
  "sub": "user123",         // 用户ID
  "userName": "张三",
  "roles": ["admin", "operator"],
  "permissions": ["order:create", "order:query", "order:refund"],
  "exp": 1750000000,        // 过期时间
  "iat": 1700000000,        // 签发时间
  "jti": "<uuid>"        // Token唯一ID(用于黑名单)
}

# Signature
RSASHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), privateKey)

4.3 Spring Security 企业配置

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)   // 开启方法级别权限控制
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // 1. 关闭CSRF(前后端分离)
            .csrf().disable()

            // 2. 无状态Session
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

            // 3. 请求鉴权
            .authorizeRequests()
                .antMatchers("/api/user/login", "/api/user/register").permitAll()
                .antMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()

            // 4. 异常处理
            .exceptionHandling()
                .authenticationEntryPoint((request, response, authException) -> {
                    response.setContentType("application/json;charset=UTF-8");
                    response.getWriter().write("{\"code\":401,\"msg\":\"未登录\"}");
                })
                .accessDeniedHandler((request, response, accessDeniedException) -> {
                    response.setContentType("application/json;charset=UTF-8");
                    response.getWriter().write("{\"code\":403,\"msg\":\"无权限\"}");
                })

            // 5. JWT过滤器
            .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        return new JwtAuthenticationFilter();
    }
}

4.4 Sa-Token 轻量级权限框架

Sa-Token 在中小企业中使用广泛,以其简洁易用著称:

// Sa-Token 极简API
@RestController
@RequestMapping("/api")
public class UserController {

    // 登录 —— 一行代码
    @PostMapping("/login")
    public Result<String> login(@RequestBody LoginDTO dto) {
        User user = userService.login(dto.getUsername(), dto.getPassword());
        StpUtil.login(user.getId());                // 登录,生成Token
        StpUtil.getSession().set("userInfo", user);  // Session中存用户信息
        return Result.success(StpUtil.getTokenValue());  // 返回Token
    }

    // 鉴权 —— 注解方式
    @SaCheckLogin                          // 必须登录
    @SaCheckRole("admin")                  // 必须admin角色
    @SaCheckPermission("order:delete")     // 必须有指定权限
    @PostMapping("/order/delete")
    public Result<Void> deleteOrder(Long orderId) {
        orderService.delete(orderId);
        return Result.success();
    }
}

Sa-Token vs Spring Security 对比:

维度Sa-TokenSpring Security
上手难度低(15分钟上手)高(配置复杂)
功能覆盖核心认证授权完整安全框架
OAuth2支持需插件原生支持
社区活跃度国内活跃全球主流
企业使用中小企业为主中大型企业为主

选型建议:如果没有复杂的OAuth2 SSO需求,Sa-Token是个很好的轻量选择。大厂通常用Spring Security + 自研认证中心。

5. 定时任务与异步处理

5.1 XXL-JOB — 分布式任务调度

XXL-JOB是目前国内企业使用最广泛的分布式调度平台:

XXL-JOB核心特性:

特性说明企业使用场景
分片广播任务在每台机器上各执行一次数据对账、批量处理
动态分片按分片参数执行不同数据海量数据并行处理
故障转移调度失败后自动转移到其他机器核心定时任务高可用
失败重试支持配置重试次数补偿任务
任务依赖父子任务顺序执行数据ETL流程
日志查看在线查看执行日志调试线上问题
// XXL-JOB 任务示例 —— 分片广播处理海量数据
@Component
public class BatchOrderHandler extends IJobHandler {

    @Override
    public ReturnT<String> execute(String param) throws Exception {
        // 获取分片信息
        int shardIndex = XxlJobHelper.getShardIndex();    // 当前分片序号 (0,1,2...)
        int shardTotal = XxlJobHelper.getShardTotal();    // 总分片数

        // 按分片查询待处理订单:当前节点只处理自己分片的数据
        List<Order> orders = orderMapper.selectPendingOrders(
            shardIndex, shardTotal, 1000
        );

        XxlJobHelper.log("当前节点: {}/{},待处理订单: {}", shardIndex, shardTotal, orders.size());

        for (Order order : orders) {
            try {
                processOrder(order);
            } catch (Exception e) {
                XxlJobHelper.log("处理订单 {} 失败: {}", order.getOrderNo(), e.getMessage());
                // 记录失败,后续通过补偿任务处理
                orderMapper.insertFailedLog(order.getId(), e.getMessage());
            }
        }

        return ReturnT.SUCCESS;
    }
}

XXL-JOB调度中心配置:

# xxl-job 执行器配置
xxl:
  job:
    admin:
      addresses: http://192.168.1.100:8080/xxl-job-admin
    executor:
      appname: order-job-executor
      ip:
      port: 9999
      logpath: /data/applogs/xxl-job/jobhandler
      logretentiondays: 30
    accessToken: ${ACCESS_TOKEN}

5.2 Spring @Async + 线程池配置

@Configuration
@EnableAsync    // 开启异步支持
public class AsyncConfig implements AsyncConfigurer {

    @Override
    @Bean("taskExecutor")
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

        // 核心参数 —— 需根据业务类型调整
        executor.setCorePoolSize(10);                // 核心线程数
        executor.setMaxPoolSize(50);                 // 最大线程数
        executor.setQueueCapacity(200);              // 等待队列
        executor.setKeepAliveSeconds(60);            // 空闲线程存活时间
        executor.setThreadNamePrefix("async-biz-");  // 线程名前缀

        // 拒绝策略 —— 企业推荐CallerRunsPolicy(由调用线程执行)
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

        // 优雅关闭
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(30);

        executor.initialize();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (ex, method, params) -> {
            log.error("异步任务执行异常: {}#{}", method.getDeclaringClass().getSimpleName(),
                      method.getName(), ex);
            // 发送告警通知
            alertService.sendAlert("异步任务失败", method.getName(), ex.getMessage());
        };
    }
}

// 使用示例
@Service
public class OrderServiceImpl implements OrderService {

    @Async("taskExecutor")  // 指定线程池
    @Override
    public CompletableFuture<Void> asyncProcessAfterPay(Order order) {
        // 异步执行:发送短信、更新统计、推送消息......失败不影响主流程
        smsService.sendPaySuccessSms(order.getPhone(), order.getOrderNo());
        statisticsService.updateSalesStatistic(order);
        pushService.sendPaySuccessPush(order.getUserId(), order.getOrderNo());
        return CompletableFuture.completedFuture(null);
    }
}

面试高频坑点:@Async 的两个陷阱 —— (1) 同一个类中的方法互相调用时 @Async 不生效(走的是代理对象,this调用跳过代理)(2) 默认使用 SimpleAsyncTaskExecutor(每次创建新线程),必须自定义线程池。

5.3 Quartz vs Elastic-Job vs XXL-JOB 对比

维度QuartzElastic-JobXXL-JOB
定位单机/集群定时任务库分布式调度框架分布式调度平台
分布式支持集群(数据库锁)原生分布式(Zookeeper协调)原生分布式(数据库协调)
管理界面无(需自研)简易管理界面功能完善的管理界面
分片不支持支持(任务分片)支持(分片广播+动态分片)
动态配置不支持支持支持(支持暂停/触发/修改)
运维友好度低中高(有完善的日志和监控)
国内企业逐渐淘汰部分在用主流选择

企业真实情况:90%的新项目直接用XXL-JOB,老的Quartz任务也在分批迁移到XXL-JOB。原因很简单——XXL-JOB有完整的Web管理界面、支持在线日志查看、支持动态修改执行计划、调度中心宕机不影响已启动的任务。

6. 面试高频追问

这些是面试官在聊完技术细节后,会追着问的"为什么"——回答质量决定了是PASS还是Strong Hire:

Q1:为什么选择Spring Cloud Alibaba而不是Spring Cloud Netflix?

回答要点:

  1. Netflix组件进入维护期:Eureka 2.0已停止开发,Hystrix进入维护模式,Zuul社区活跃度持续下降
  2. 功能密度更高:Nacos = Eureka + Spring Cloud Config + Bus 三个组件,减少运维复杂度
  3. Sentinel vs Hystrix:Sentinel支持实时监控、热点限流、规则持久化、微服务零侵入,Hystrix不支持
  4. 社区活跃度:Alibaba组件在国内更新频繁,中文文档友好,遇到问题容易找到解决方案

Q2:你们为什么选MyBatis-Plus而不是JPA?

回答要点:

  1. 团队惯性:现有团队成员都熟悉SQL,MyBatis上手更快,JPA的HQL和Criteria API学习成本较高
  2. SQL可控:DBA可以直接review和优化MyBatis的SQL语句,JPA自动生成的SQL复杂场景下难以掌控
  3. 与ShardingSphere的兼容性:分库分表场景下,MyBatis-Plus + ShardingSphere-JDBC是经过验证的标准方案
  4. 动态SQL能力:MyBatis的动态SQL(if/where/foreach)在处理复杂查询时比JPA更直观灵活

Q3:你们用的哪种分布式事务方案?为什么选Seata AT?

回答要点:

  1. 业务场景匹配:我们的核心链路(下单 -> 扣库存 -> 扣余额)需要强一致性,AT模式正好满足
  2. 侵入性最低:AT模式对业务代码零侵入,只需加一个@GlobalTransactional注解,相比TCC需要手写Try/Confirm/Cancel,开发效率更高
  3. 性能可接受:AT模式的性能损失主要在前后镜像记录和全局锁上,经过压测单次AT事务耗时<50ms,满足业务要求
  4. 不做TCC的原因:我们的业务场景没有高性能强要求,TCC的Confirm/Cancel逻辑需要大量额外的业务补偿代码,维护成本高

Q4:Redis缓存常见三大问题,你们怎么解决的?

回答要点:

  1. 缓存穿透:布隆过滤器 + 空值缓存(过期时间5分钟),两种方案并行,布隆过滤器拦截绝大部分不合理请求
  2. 缓存击穿:互斥锁(Redisson分布式锁) + 热点数据不过期,热点数据采用后台定时刷新而不是等到过期再加载
  3. 缓存雪崩:缓存过期时间加随机值(基础值 +/- 随机1-10分钟) + 多级缓存(本地Caffeine + Redis) + 限流降级

Q5:如果让你设计一个微服务网关,你会怎么设计?

回答要点:

  1. 路由转发:配置中心(Nacos)+ 动态路由刷新,支持灰度发布
  2. 鉴权体系:JWT Token校验(缓存公钥,减少RSA解密次数)+ 白名单机制
  3. 限流熔断:集成Sentinel做网关流控,支持按API/用户/来源IP分组限流
  4. 统一异常处理:全局异常处理 + 统一返回格式
  5. 可观测性:全链路TraceId透传 + 请求响应日志(ES存储) + 请求耗时监控
  6. 高可用:集群部署 + 健康检查 + 与Sentinel Dashboard集成监控

Q6:微服务拆分的原则是什么?

回答要点:

  1. 业务域划分:按DDD限界上下文划分,用户域、订单域、支付域、库存域各自独立
  2. 数据独立:每个服务拥有自己的数据库,不共享库
  3. 通信原则:同步调用(OpenFeign)解决查询类场景,异步消息(MQ)解决最终一致性场景
  4. 拆分粒度控制:一开始不要拆太细,建议3-5个服务起步,随着业务复杂度逐步拆分
  5. 避免拆分的场景:强关联的业务(同一个事务中更新多个表)、频繁交互的模块(网络开销超过收益)

Q7:Gateway和Nginx的区别,能替代吗?

维度NginxSpring Cloud Gateway
定位Web服务器/反向代理/负载均衡API网关
协议HTTP/HTTPS/TCP/UDPHTTP/HTTPS/WebSocket
动态路由配置换需要reload支持动态刷新(集成Nacos)
鉴权集成需要lua脚本原生支持Spring Security
限流ngx_http_limit_req_module集成Sentinel/Redis RateLimiter
服务发现需配合consul/upstream动态更新原生支持Nacos/Eureka

企业实践:Nginx和Gateway并非替代关系,而是分层使用——Nginx负责接入层(SSL卸载、CDN、静态资源),Gateway负责业务层(路由、鉴权、限流、灰度)。

Q8:Sentinel@SentinelResource注解的BlockHandler和Fallback有什么区别?

维度blockHandlerfallback
触发条件流量控制 / 限流触发熔断降级 / 业务异常
异常类型BlockException (系统级)Throwable (业务异常)
返回值返回限流提示返回兜底数据
典型场景"系统繁忙,请稍后重试""服务异常,展示缓存数据"

Q9:你们如何保证缓存和数据库的一致?

企业常用方案(Cache Aside Pattern + Canal最终一致性):

读流程:
1. 查询缓存 -> 命中直接返回
2. 未命中 -> 查询数据库 -> 写入缓存 -> 返回

写流程(延迟双删):
1. 删除缓存
2. 更新数据库
3. 延迟500ms再次删除缓存(应对并发读写)

最终一致性(Canal方案):
1. Canal监听MySQL binlog变更
2. 解析变更数据
3. 发送MQ消息
4. 消费者更新Redis缓存

面试注意:强一致性方案(分布式锁+双写)会导致性能大幅下降,绝大多数企业接受最终一致性。真正要求强一致性的数据就不应该放缓存。

Q10:讲一下XxlJob调度器和执行器的通信机制

XXL-JOB通信模型 (调度中心 => 执行器)

调度中心 (xxl-job-admin)
    │
    │  触发调度 ├── 注册回调接口 (Post请求)
    │
    ▼
执行器 (xxl-job-executor)
    │
    │  接收调度 ├── 启动任务线程
    │  执行日志 ├── 写入日志文件
    │  回调结果 ├── Post请求通知调度中心
    │
    ▼
调度中心更新任务状态

核心机制:
- 调度中心和执行器解耦,调度中心不持有执行器连接
- 回调机制是推模式(执行器主动回调),避免调度中心轮询
- 内置分布式锁防止同一任务被多次调度
- 调度失败会触发故障转移,选择另一台执行器重试

核心要点:面试官问"为什么"比问"是什么"更能看出技术深度。每一次选型背后都应该有一个明确的理由——可能是性能、可能是团队、可能是生态、可能是维护成本。没有理由的选型 = 没有思考。