一些使用spring的小技巧

一、依赖注入与配置优化

注解 vs XML 配置的小技巧

  1. 使用 @Qualifier、@Primary 避免注入冲突。

Spring 支持多种方式注入 Bean,但容易出现冲突或者不明确的情况。

@Component

@Primary

public class SmsService implements MessageService {

    public void send(String msg) {

        System.out.println("SMS: " + msg);

    }

}

@Component

public class EmailService implements MessageService {

    public void send(String msg) {

        System.out.println("Email: " + msg);

    }

}

@Service

public class NotificationService {

    // 明确指定使用 EmailService

    private final MessageService messageService;

    public NotificationService(@Qualifier("emailService") MessageService messageService) {

        this.messageService = messageService;

    }

    public void notifyUser(String msg) {

        messageService.send(msg);

    }

}

说明

@Primary 用于标记默认 Bean。

@Qualifier 可指定注入特定 Bean,避免多实现类冲突。

  1. 自定义注解组合,例如自定义 @ServiceWithLogging。

自定义组合注解

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Service

@Transactional

public @interface ServiceWithTx {

}

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Service

public @interface ServiceWithLogging {

    String value() default "";

}

配置类 @Configuration 与 @Bean 的懒加载技巧。

@Configuration

public class AppConfig {

    @Bean

    public HeavyService heavyService() {

        return new HeavyService();

    }

}

默认行为为Spring启动时立即实例化,即使这个Bean可能很少用、仅在特定场景使用、初始化非常耗时。

  1. @Lazy 标在 Bean 上:启动阶段不创建,第一次注入或调用时才初始化。

@Configuration

public class AppConfig {

    @Bean

    @Lazy

    public HeavyService heavyService() {

        return new HeavyService();

    }

}

  1. @Lazy 标在注入点

@Service

public class OrderService {

    @Lazy

    @Autowired

    private HeavyService heavyService;

}

  1. 懒加载 + 条件装配组合

@Bean

@Lazy

@ConditionalOnProperty(name = "feature.heavy.enabled", havingValue = "true")

public HeavyService heavyService() {

    return new HeavyService();

}

说明

如果这个 @Configuration 本身被早期加载(如被 Import),所有 @Bean 方法 会被提前增强,懒加载可能失效。

  1. 延迟初始化原型 Bean 的隐性问题

原型 Bean 注入到单例 Bean 时,默认只会创建一次。使用 ObjectProvider 或 Provider 动态获取 Bean。

@Component

public class SingletonService {

    @Autowired

    private ObjectProvider<PrototypeService> prototypeProvider;

    public void doSomething() {

        PrototypeService p = prototypeProvider.getObject();

        p.action();

    }

}

  1. 动态 Bean 注册

生产环境中,有时候需要根据配置或外部条件动态注册 Bean。使用 BeanDefinitionRegistry 或 ConfigurableApplicationContext实现动态Bean注册。

GenericBeanDefinition bd = new GenericBeanDefinition();

bd.setBeanClass(CustomService.class);

context.registerBeanDefinition("customService", bd);

@Value 与 @ConfigurationProperties

使用 @ConfigurationProperties 绑定复杂配置。

  1. 基础使用

YAML 配置示例

app:

  payment:

    enabled: true

    timeout: 3000

    retry:

      max-attempts: 3

      interval: 500

    channels:

      - alipay

      - wechat

@Component

@ConfigurationProperties(prefix = "app.payment")

public class PaymentProperties {

    private boolean enabled;

    private int timeout;

    private Retry retry;

    private List<String> channels;

    public static class Retry {

        private int maxAttempts;

        private int interval;

        // getter/setter

    }

    // getter/setter

}

  1. Map / List / 多层嵌套绑定

app:

  datasource:

    masters:

      db1:

        url: jdbc:mysql://...

        username: root

      db2:

        url: jdbc:mysql://...

        username: admin

@ConfigurationProperties(prefix = "app.datasource")

public class DataSourceProperties {

    private Map<String, DbConfig> masters;

    public static class DbConfig {

        private String url;

        private String username;

    }

}

  1. 配合校验

@Component

@ConfigurationProperties(prefix = "app.payment")

@Validated

public class PaymentProperties {

    @NotNull

    private Boolean enabled;

    @Min(100)

    private int timeout;

}

用 @ConstructorBinding 实现不可变配置对象。

@ConfigurationProperties(prefix = "app.payment")

@ConstructorBinding

public class PaymentProperties {

    private final boolean enabled;

    private final int timeout;

    public PaymentProperties(boolean enabled, int timeout) {

        this.enabled = enabled;

        this.timeout = timeout;

    }

}

提示如何避免 null 或默认值问题。

  1. 使用包装类型 + @NotNull

@ConfigurationProperties(prefix = "app.payment")

@Validated

public class PaymentProperties {

    @NotNull

    private Boolean enabled;

    @NotNull

    private Integer timeout;

}

  1. 数值型配置必须限制范围

@Min(100)

@Max(10000)

private Integer timeout;

  1. String 必须明确非空 / 非空白

@NotBlank

private String endpoint;

  1. 集合类型:最容易被忽略的 null 源头

默认初始化

private List<String> channels = new ArrayList<>();

强制要求配置存在

@NotEmpty

private List<String> channels;

  1. 嵌套对象的 null 防御

public class PaymentProperties {

    private Retry retry;

}

提前初始化

private Retry retry = new Retry();

对子对象也做校验

@Valid

@NotNull

private Retry retry;

public static class Retry {

    @Min(1)

    private int maxAttempts;

}

条件装配

@ConditionalOnProperty、@Profile 的使用技巧。

@Profile指示当前运行环境是什么,@ConditionalOnProperty指示某个功能是否开启。

  1. Profile 用在“实现类”,不用在“业务逻辑”

不推荐

if (env.acceptsProfiles("dev")) {

    ...

}

推荐

@Profile("dev")

@Service

class MockPaymentService implements PaymentService {}

@Profile("prod")

@Service

class RealPaymentService implements PaymentService {}

  1. Profile 支持逻辑表达式

@Profile("dev | test")

@Profile("!prod")

  1. @ConditionalOnProperty基础用法

@ConditionalOnProperty(

    name = "feature.cache.enabled",

    havingValue = "true",

    matchIfMissing = false

)

@Bean

public CacheService cacheService() {

    return new CacheService();

}

feature:

  cache:

    enabled: true

  1. matchIfMissing 的正确理解

matchIfMissing = false

没配置 → 不加载

@ConditionalOnProperty(

    name = "feature.audit.enabled",

    havingValue = "true",

    matchIfMissing = true

)

默认开启,除非显式关闭

  1. 用在“策略选择”而不是 if/else

反例

if (config.isUseRedis()) {

    ...

} else {

    ...

}

正确做法

@ConditionalOnProperty(name = "storage.type", havingValue = "redis")

@Component

class RedisStorage implements Storage {}

@ConditionalOnProperty(name = "storage.type", havingValue = "db")

@Component

class DbStorage implements Storage {}

storage:

  type: redis

  1. 和 @Lazy 联用(启动优化)

@Bean

@Lazy

@ConditionalOnProperty(name = "feature.report.enabled", havingValue = "true")

public ReportService reportService() {

    return new ReportService();

}

没开启 → 永远不加载;开启 → 首次使用才创建;

  1. 自定义组合条件注解

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@ConditionalOnProperty(name = "feature.logging.enabled", havingValue = "true")

public @interface ConditionalLoggingEnabled {}

  1. 条件 Bean 被强依赖

@Autowired

private CacheService cacheService;

正确做法:

@Autowired

private ObjectProvider<CacheService> cacheServiceProvider;

cacheServiceProvider.ifAvailable(CacheService::clear);

如何动态控制 Bean 是否加载,提高应用灵活性。

  1. @ImportSelector(模块级动态装配)

public class StorageImportSelector implements ImportSelector {

    @Override

    public String[] selectImports(AnnotationMetadata metadata) {

        String type = System.getProperty("storage.type");

        if ("redis".equals(type)) {

            return new String[]{RedisConfig.class.getName()};

        }

        return new String[]{DbConfig.class.getName()};

    }

}

@Import(StorageImportSelector.class)

@Configuration

public class StorageAutoConfig { }

  1. BeanDefinitionRegistryPostProcessor

@Component

public class DynamicBeanRegistrar

        implements BeanDefinitionRegistryPostProcessor {

    @Override

    public void postProcessBeanDefinitionRegistry(

            BeanDefinitionRegistry registry) {

        if (!registry.containsBeanDefinition("auditService")) {

            RootBeanDefinition bd =

                new RootBeanDefinition(AuditService.class);

            registry.registerBeanDefinition("auditService", bd);

        }

    }

}

参数校验(Validation)

在 Spring Boot 中,使用 javax.validation / jakarta.validation 注解对请求参数进行校验。

@RestController

@RequestMapping("/api/user")

public class UserController {

    @PostMapping("/create")

    public ResponseEntity<String> createUser(@Valid @RequestBody UserDTO userDTO) {

        // 如果校验失败,会自动返回 400 错误

        return ResponseEntity.ok("User created");

    }

}

@Data

public class UserDTO {

    @NotBlank(message = "用户名不能为空")

    private String username;

    @Email(message = "邮箱格式不正确")

    private String email;

    @Min(value = 18, message = "年龄必须大于等于18岁")

    private Integer age;

}

Jackson 多视图配置(JsonView)

通过 @JsonView 实现同一实体不同场景的序列化视图。

public class Views {

    public static class Summary {}

    public static class Detail extends Summary {}

}

@Data

public class UserDTO {

    @JsonView(Views.Summary.class)

    private String username;

    @JsonView(Views.Detail.class)

    private String email;

    @JsonView(Views.Detail.class)

    private Integer age;

}

@RestController

@RequestMapping("/api/user")

public class UserController {

    @GetMapping("/summary")

    @JsonView(Views.Summary.class)

    public UserDTO getUserSummary() {

        return userService.getUser();

    }

    @GetMapping("/detail")

    @JsonView(Views.Detail.class)

    public UserDTO getUserDetail() {

        return userService.getUser();

    }

}

二、Spring 生命周期与初始化技巧

Bean 生命周期管理

实例化

  ↓

依赖注入

  ↓

Aware 回调

  ↓

@PostConstruct / afterPropertiesSet

  ↓

BeanPostProcessor

  ↓

可用状态

  ↓

容器关闭

  ↓

@PreDestroy / destroy

  1. 使用 @PostConstruct、@PreDestroy 或 InitializingBean、DisposableBean。

@Component

public class CacheManager {

    @PostConstruct

    public void init() {

        // 初始化连接、缓存、线程池

    }

    @PreDestroy

    public void shutdown() {

        // 释放资源

    }

}

@Component

public class JobScheduler

        implements InitializingBean, DisposableBean {

    @Override

    public void afterPropertiesSet() {

        // 初始化

    }

    @Override

    public void destroy() {

        // 清理

    }

}

  1. 初始化 = 校验 + 轻量准备

@PostConstruct

public void init() {

    Assert.notNull(config.getEndpoint(), "endpoint required");

}

  1. 真正“重”的初始化 → 延迟执行

@EventListener(ApplicationReadyEvent.class)

public void onReady() {

    loadBigData();

}

  1. 关闭资源要考虑“幂等”

@PreDestroy

public void shutdown() {

    if (closed.compareAndSet(false, true)) {

        pool.shutdown();

    }

}

  1. SmartInitializingSingleton

@Component

public class CacheWarmup

        implements SmartInitializingSingleton {

    @Override

    public void afterSingletonsInstantiated() {

        // 所有单例 Bean 初始化完成后

    }

}

懒加载 & 单例/原型模式

使用 @Lazy 控制启动速度。

  1. 用在 Bean 上

@Lazy

@Component

public class BigReportService {

    public BigReportService() {

        // 构造成本高

    }

}

  1. 用在 @Bean 方法上

@Bean

@Lazy

public HeavyClient heavyClient() {

    return new HeavyClient();

}

  1. 用在 @Configuration 类上

@Configuration

@Lazy

public class OptionalFeatureConfig {

    @Bean

    public OptionalService service() {

        return new OptionalService();

    }

}

  1. ObjectProvider + @Lazy

@Autowired

private ObjectProvider<BigReportService> reportServiceProvider;

public void generate() {

    BigReportService svc = reportServiceProvider.getIfAvailable();

}

  1. @Lazy + @PostConstruct 的陷阱

@Lazy

@Component

public class LazyBean {

    @PostConstruct

    public void init() {

        // 不会在启动期执行

    }

}

原型 Bean 与单例 Bean 的注入注意事项。

  1. 单例 Bean(默认)

@Component

@Scope("singleton")

public class A {}

  1. 原型 Bean

@Component

@Scope("prototype")

public class B {}

  1. 错误应用:原型 Bean 注入到单例 Bean

@Component

public class OrderService {

    @Autowired

    private TaskContext taskContext; // prototype

}

@Component

@Scope("prototype")

public class TaskContext {}

  1. 使用 ObjectProvider

@Component

public class OrderService {

    @Autowired

    private ObjectProvider<TaskContext> taskContextProvider;

    public void handle() {

        TaskContext ctx = taskContextProvider.getObject();

    }

}

  1. 使用 ApplicationContext

@Autowired

private ApplicationContext applicationContext;

public void handle() {

    TaskContext ctx = applicationContext.getBean(TaskContext.class);

}

  1. 使用 @Lookup

@Component

public abstract class OrderService {

    public void handle() {

        TaskContext ctx = taskContext();

    }

    @Lookup

    protected abstract TaskContext taskContext();

}

  1. 原型 Bean 不会触发 @PreDestroy

@PreDestroy

public void destroy() {

    // 不会被调用

}

正确做法,由调用方负责清理,或显式调用销毁逻辑。

TaskContext ctx = provider.getObject();

try {

    ...

} finally {

    ctx.close();

}

  1. 使用 Web Scope 而不是 Prototype

@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)

@Component

public class RequestContext {}

三、AOP & 拦截技巧

切面小技巧

用 @Around 获取方法执行时间。

  1. 基础示例

@Aspect

@Component

public class MethodTimeAspect {

    @Around("execution(* com.example.service..*(..))")

    public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {

        long start = System.currentTimeMillis();

        try {

            return pjp.proceed();

        } finally {

            long cost = System.currentTimeMillis() - start;

            log.info("Method {} cost {} ms",

                    pjp.getSignature().toShortString(), cost);

        }

    }

}

  1. 自定义注解 + 精准拦截

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface CostTime {

    long threshold() default 0; // 超过阈值才打印

}

@Around("@annotation(costTime)")

public Object around(ProceedingJoinPoint pjp, CostTime costTime) throws Throwable {

    long start = System.nanoTime();

    try {

        return pjp.proceed();

    } finally {

        long costMs = (System.nanoTime() - start) / 1_000_000;

        if (costMs >= costTime.threshold()) {

            log.warn("Method {} cost {} ms",

                    pjp.getSignature().toShortString(), costMs);

        }

    }

}

  1. 获取更丰富的上下文信息

方法名 / 类名

MethodSignature signature = (MethodSignature) pjp.getSignature();

Method method = signature.getMethod();

方法参数

Object[] args = pjp.getArgs();

日志切面、权限切面、异常处理切面实用示例。

  1. 日志切面(访问日志 + 执行耗时)

定义日志注解

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface AccessLog {

    boolean recordArgs() default false;

}

日志切面实现

@Aspect

@Component

@Order(10) // 顺序靠后

public class AccessLogAspect {

    @Around("@annotation(accessLog)")

    public Object log(ProceedingJoinPoint pjp, AccessLog accessLog) throws Throwable {

        long start = System.nanoTime();

        try {

            return pjp.proceed();

        } finally {

            long costMs = (System.nanoTime() - start) / 1_000_000;

            MethodSignature sig = (MethodSignature) pjp.getSignature();

            String method = sig.getDeclaringType().getSimpleName()

                    + "." + sig.getName();

            if (accessLog.recordArgs()) {

                log.info("method={} args={} cost={}ms",

                        method, safeArgs(pjp.getArgs()), costMs);

            } else {

                log.info("method={} cost={}ms", method, costMs);

            }

        }

    }

    private Object safeArgs(Object[] args) {

        // 生产中一定要做脱敏 / 限制

        return Arrays.toString(args);

    }

}

使用示例

@AccessLog(recordArgs = true)

public Order createOrder(CreateOrderCmd cmd) {

    ...

}

  1. 权限切面

权限注解设计

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface RequirePermission {

    String value();

}

权限切面实现

@Aspect

@Component

@Order(1) // 必须最早执行

public class PermissionAspect {

    @Before("@annotation(permission)")

    public void check(JoinPoint jp, RequirePermission permission) {

        String perm = permission.value();

        UserContext user = UserContextHolder.get();

        if (user == null || !user.hasPermission(perm)) {

            throw new AccessDeniedException("Permission denied: " + perm);

        }

    }

}

使用示例

@RequirePermission("order:create")

public Order createOrder(...) {

    ...

}

  1. 异常处理切面

异常切面实现

@Aspect

@Component

@Order(5)

public class ExceptionTranslateAspect {

    @Around("execution(* com.example.service..*(..))")

    public Object handle(ProceedingJoinPoint pjp) throws Throwable {

        try {

            return pjp.proceed();

        } catch (IllegalArgumentException e) {

            throw new BizException("PARAM_ERROR", e.getMessage(), e);

        } catch (Exception e) {

            throw new BizException("SYSTEM_ERROR", "Internal error", e);

        }

    }

}

业务异常定义

public class BizException extends RuntimeException {

    private final String code;

    public BizException(String code, String message, Throwable cause) {

        super(message, cause);

        this.code = code;

    }

    public String getCode() {

        return code;

    }

}

自定义注解结合 AOP

为特定方法添加注解控制限流、缓存、事务。

  1. 限流:自定义 @RateLimit

注解定义

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface RateLimit {

    String key();          // 限流维度(如 userId / ip)

    int permitsPerSecond();// 每秒允许次数

}

限流切面实现

@Aspect

@Component

@Order(1) // 非常靠前

public class RateLimitAspect {

    private final ConcurrentMap<String, RateLimiter> limiters = new ConcurrentHashMap<>();

    @Around("@annotation(rateLimit)")

    public Object limit(ProceedingJoinPoint pjp, RateLimit rateLimit) throws Throwable {

        String key = parseKey(pjp, rateLimit.key());

        RateLimiter limiter = limiters.computeIfAbsent(

                key,

                k -> RateLimiter.create(rateLimit.permitsPerSecond())

        );

        if (!limiter.tryAcquire()) {

            throw new BizException("RATE_LIMIT", "Too many requests");

        }

        return pjp.proceed();

    }

    private String parseKey(ProceedingJoinPoint pjp, String keyExpr) {

        // 实际生产中建议用 SpEL

        return keyExpr;

    }

}

使用示例

@RateLimit(key = "createOrder", permitsPerSecond = 10)

public Order createOrder(...) {

    ...

}

  1. 缓存:自定义 @MethodCache

注解定义

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface MethodCache {

    String key();      // 缓存 key

    long ttl() default 60; // 秒

}

缓存切面实现

@Aspect

@Component

@Order(5)

public class CacheAspect {

    @Around("@annotation(cache)")

    public Object cache(ProceedingJoinPoint pjp, MethodCache cache) throws Throwable {

        String key = buildKey(pjp, cache.key());

        Object value = redis.get(key);

        if (value != null) {

            return value;

        }

        Object result = pjp.proceed();

        if (result != null) {

            redis.set(key, result, cache.ttl());

        }

        return result;

    }

}

使用示例

@MethodCache(key = "user:{#id}", ttl = 300)

public User getUser(Long id) {

    ...

}

  1. 事务:自定义 @BizTransactional

注解定义

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface BizTransactional {

    boolean rollbackOnException() default true;

}

事务切面实现

@Aspect

@Component

@Order(10) // 在缓存、限流之后

public class TransactionAspect {

    @Autowired

    private PlatformTransactionManager txManager;

    @Around("@annotation(tx)")

    public Object tx(ProceedingJoinPoint pjp, BizTransactional tx) throws Throwable {

        TransactionStatus status = txManager.getTransaction(

                new DefaultTransactionDefinition()

        );

        try {

            Object result = pjp.proceed();

            txManager.commit(status);

            return result;

        } catch (Throwable e) {

            if (tx.rollbackOnException()) {

                txManager.rollback(status);

            }

            throw e;

        }

    }

}

使用示例

@BizTransactional

public void createOrder(...) {

    ...

}

四、事务与数据库优化

事务管理

  1. @Transactional 的传播行为、只读事务优化。
  1. 事务生效条件

只有“通过代理对象调用的方法”事务才会生效,同一个类内部的自调用 = 不经过代理 = 事务失效。

  1. 事务传播行为

REQUIRED(默认,90% 场景):有事务 → 加入;没事务 → 新建;

@Transactional(propagation = Propagation.REQUIRED)

REQUIRES_NEW(强制新事务):挂起外层事务,新开一个事务,自己提交 / 回滚。

@Transactional(propagation = Propagation.REQUIRES_NEW)

SUPPORTS(读多写少场景):有事务 → 加入;没事务 → 非事务执行;

@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)

NOT_SUPPORTED(显式“去事务”):挂起当前事务,非事务执行。

@Transactional(propagation = Propagation.NOT_SUPPORTED)

NEVER(强约束):如果存在事务 → 直接抛异常。

@Transactional(propagation = Propagation.NEVER)

  1. 正确理解只读事务

@Transactional(readOnly = true)

public User queryUser(...) { }

告诉数据库这是只读事务,允许数据库做优化。以 MySQL InnoDB为例,不生成 undo log、不加写锁、减少行级锁竞争、主从架构可配合读写分离路由

  1. 事务失效的“死亡陷阱”
  1. private 方法

@Transactional

private void save() { }

  1. 同类内部调用

public void createOrder() {

    saveOrder(); // 事务失效

}

@Transactional

public void saveOrder() { }

  1. 异常被吞掉

@Transactional

public void save() {

    try {

        ...

    } catch (Exception e) {

        log.error("error", e);

    }

}

  1. checked Exception 不回滚

throw new Exception("xxx");

默认行为,不会回滚。

正确配置

@Transactional(rollbackFor = Exception.class)

  1. @Transactional 写在接口上

JDK 代理模式下,实现类方法不生效,始终写在实现类上。

  1. 异步事务组合

异步方法默认不在事务范围内,如果希望事务提交后再执行异步任务,可以这样处理:

@Service

public class OrderService {

    @Autowired

    private AsyncService asyncService;

    @Transactional

    public void createOrder(Order order) {

        orderRepository.save(order);

        TransactionSynchronizationManager.registerSynchronization(

            new TransactionSynchronizationAdapter() {

                @Override

                public void afterCommit() {

                    asyncService.sendNotification(order);

                }

            });

    }

}

说明:

使用 TransactionSynchronizationManager 确保异步方法在事务提交后执行。

避免异步任务因事务回滚而产生不一致问题。

JdbcTemplate / MyBatis / JPA

批量操作优化技巧。

  1. JdbcTemplate

String sql = "INSERT INTO user(name, age) VALUES (?, ?)";

jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {

    @Override

    public void setValues(PreparedStatement ps, int i) throws SQLException {

        User u = users.get(i);

        ps.setString(1, u.getName());

        ps.setInt(2, u.getAge());

    }

    @Override

    public int getBatchSize() {

        return users.size();

    }

});

JDBC 参数优化:rewriteBatchedStatements=true

  1. MyBatis

错误用法(看似批量,实则 N 次)

<insert id="batchInsert">

    INSERT INTO user(name, age)

    VALUES

    <foreach collection="list" item="u" separator=",">

        (#{u.name}, #{u.age})

    </foreach>

</insert>

正确方式 1:ExecutorType.BATCH

SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);

UserMapper mapper = session.getMapper(UserMapper.class);

for (User u : users) {

    mapper.insert(u);

}

session.commit();

session.close();

  1. JPA

repository.saveAll(list);

实际一条一条insert,Persistence Context持有所有对象,OOM 风险极高。

使用 NamedParameterJdbcTemplate 提高 SQL 可读性。

延迟加载和 N+1 问题的优化方法。

数据库锁优化

  1. 对高并发操作,合理使用悲观锁或乐观锁

JPA 中的悲观锁@Lock(LockModeType.PESSIMISTIC_WRITE)

基本用法

@Lock(LockModeType.PESSIMISTIC_WRITE)

@Query("SELECT o FROM Order o WHERE o.id = :id")

Order findByIdForUpdate(@Param("id") Long id);

生成的 SQL

SELECT * FROM order WHERE id = ? FOR UPDATE;

必须配合事务(否则锁无效,没有事务或者过大事务都有可能造成锁失效)

@Transactional

public void updateOrder(Long id) {

    Order order = repo.findByIdForUpdate(id);

    order.setStatus("PAID");

}

设置锁超时(避免无限阻塞)

@QueryHints({

    @QueryHint(name = "javax.persistence.lock.timeout", value = "3000")

})

  1. 使用 @Version 乐观锁避免频繁死锁

实体定义版本号

@Entity

public class Product {

    @Id

    private Long id;

    @Version

    private Integer version;

}

并发更新行为

UPDATE product

SET stock = ?, version = version + 1

WHERE id = ? AND version = ?

影响行数 = 0 → 并发冲突,抛 OptimisticLockException。

正确的处理方式,乐观锁一定要有重试策略。

for (int i = 0; i < 3; i++) {

    try {

        updateStock();

        break;

    } catch (OptimisticLockException e) {

        // retry

    }

}

  1. 锁与隔离级别的关系

隔离级别

锁行为

READ COMMITTED

行锁

REPEATABLE READ(MySQL 默认)

行锁 + 间隙锁

SERIALIZABLE

表级锁

五、缓存与性能优化

Spring Cache

  1. @Cacheable、@CacheEvict 的小技巧。
  1. @Cacheable基本用法

@Cacheable(value = "userCache", key = "#id")

public User getUserById(Long id) {

    return userRepository.findById(id).orElse(null);

}

  1. 条件缓存(避免缓存无效数据condition决定是否缓存unless缓存后再决定是否存储

@Cacheable(value = "userCache", key = "#id", condition = "#id > 0")

public User getUser(Long id) { ... }

@Cacheable(value = "userCache", key = "#id", unless = "#result == null")

public User getUser(Long id) { ... }

  1. 动态 key 构建技巧

@Cacheable(value = "orderCache", key = "'order:' + #userId + ':' + #status")

public List<Order> listOrders(Long userId, String status) { ... }

  1. 避免 N+1 查询缓存失效

@Cacheable(value = "orders", key = "#userId")

public List<Order> listOrdersWithItems(Long userId) {

    return orderRepository.findAllWithItems(userId); // 一条 SQL JOIN FETCH

}

  1. @CacheEvict 基本用法

//方法执行成功后清除缓存

@CacheEvict(value = "userCache", key = "#id")

public void updateUser(Long id, UserDTO dto) { ... }

  1. 删除全部缓存

@CacheEvict(value = "userCache", allEntries = true)

public void reloadAllUsers() { ... }

  1. 延迟失效(缓存雪崩防护)

@Cacheable(value = "userCache", key = "#id")

public User getUser(Long id) {

    User user = repo.findById(id).orElse(null);

    // TTL 由底层缓存实现设置随机过期

    return user;

}

  1. 更新缓存与清理缓存组合

@CacheEvict(value = "userCache", key = "#id")

@CachePut(value = "userCache", key = "#id")

public User updateUser(Long id, UserDTO dto) { ... }

  1. SpEL 表达式在缓存 key 中的巧用。
  1. 使用方法参数构建复合 key

@Cacheable(value = "orderCache", key = "'order:' + #userId + ':' + #status")

public List<Order> listOrders(Long userId, String status) { ... }

  1. 利用对象属性

@Cacheable(value = "userCache", key = "#user.id")

public User getUser(User user) { ... }

  1. 多参数动态组合

@Cacheable(value = "reportCache", key = "'report:' + #year + ':' + #month + ':' + #region.id")

public Report generateReport(int year, int month, Region region) { ... }

  1. 方法调用 / 静态变量

@Cacheable(value = "configCache", key = "T(com.example.ConfigUtils).GLOBAL_KEY")

public Config getConfig() { ... }

  1. 条件缓存结合 SpEL

@Cacheable(value = "userCache", key = "#id", condition = "#id > 0", unless = "#result == null")

public User getUser(Long id) { ... }

  1. 缓存刷新策略

@Caching(evict = {

    @CacheEvict(value = "userCache", key = "#user.id"),

    @CacheEvict(value = "allUsersCache", allEntries = true)

})

public void updateUser(User user) {

    userRepository.save(user);

}

结合 定时任务刷新缓存,避免缓存穿透:

@Scheduled(fixedRate = 60000)

public void refreshCache() {

    List<User> all = userRepository.findAll();

    all.forEach(user -> cacheManager.getCache("userCache").put(user.getId(), user));

}

  1. 多级缓存策略

本地缓存 + 分布式缓存组合使用,提高读性能。

@Cacheable(value = "localUserCache", key = "#id", cacheManager = "localCacheManager")

@Cacheable(value = "redisUserCache", key = "#id", cacheManager = "redisCacheManager")

public User getUser(Long id) { return userRepository.findById(id).orElse(null); }

异步与线程池

@Async 配合自定义线程池提高性能。

CompletableFuture 与 Spring TaskExecutor 的结合。

、实用组合技巧

事务 + 异步组合

异步方法的事务边界问题。

在 Spring 中,@Transactional 默认只对当前线程有效。@Async 会把方法放到 线程池的新线程中执行。如果你在一个事务中调用 @Async 方法。

@Transactional

public void updateAndNotify() {

    deviceRepository.update(device);

    asyncNotify(device);  // @Async

}

这里当前事务还没提交,DB 更新还在挂起。asyncNotify 在 另一个线程执行,此时可能读取不到刚更新的数据,或者事务回滚了但异步已经执行了。异步线程无法感知原线程事务的提交或回滚状态。

解决方案:

  1. 事务提交后异步执行

利用 TransactionSynchronizationManager 或 Spring 提供的回调:

@Transactional

public void updateDevice(Device device) {

    deviceRepository.update(device);

    // 事务提交后再异步执行

    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {

        @Override

        public void afterCommit() {

            asyncNotify(device);

        }

    });

}

  1. 异步方法独立事务

@Async

@Transactional(propagation = Propagation.REQUIRES_NEW)

public void asyncNotify(Device device) {

    // 独立事务,可以提交自己的 DB 操作

}

  1. 消息队列解耦

@Transactional

public void updateDevice(Device device) {

    deviceRepository.update(device);

    messageQueue.send(new DeviceUpdatedEvent(device.getId()));

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值