一、依赖注入与配置优化
注解 vs XML 配置的小技巧
- 使用 @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,避免多实现类冲突。
- 自定义注解组合,例如自定义 @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可能很少用、仅在特定场景使用、初始化非常耗时。
- @Lazy 标在 Bean 上:启动阶段不创建,第一次注入或调用时才初始化。
@Configuration
public class AppConfig {
@Bean
@Lazy
public HeavyService heavyService() {
return new HeavyService();
}
}
- @Lazy 标在注入点
@Service
public class OrderService {
@Lazy
@Autowired
private HeavyService heavyService;
}
- 懒加载 + 条件装配组合
@Bean
@Lazy
@ConditionalOnProperty(name = "feature.heavy.enabled", havingValue = "true")
public HeavyService heavyService() {
return new HeavyService();
}
说明:
如果这个 @Configuration 本身被早期加载(如被 Import),所有 @Bean 方法 会被提前增强,懒加载可能失效。
- 延迟初始化原型 Bean 的隐性问题
原型 Bean 注入到单例 Bean 时,默认只会创建一次。使用 ObjectProvider 或 Provider 动态获取 Bean。
@Component
public class SingletonService {
@Autowired
private ObjectProvider<PrototypeService> prototypeProvider;
public void doSomething() {
PrototypeService p = prototypeProvider.getObject();
p.action();
}
}
- 动态 Bean 注册
生产环境中,有时候需要根据配置或外部条件动态注册 Bean。使用 BeanDefinitionRegistry 或 ConfigurableApplicationContext实现动态Bean注册。
GenericBeanDefinition bd = new GenericBeanDefinition();
bd.setBeanClass(CustomService.class);
context.registerBeanDefinition("customService", bd);
@Value 与 @ConfigurationProperties
使用 @ConfigurationProperties 绑定复杂配置。
- 基础使用
YAML 配置示例
app:
payment:
enabled: true
timeout: 3000
retry:
max-attempts: 3
interval: 500
channels:
- alipay
@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
}
- 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;
}
}
- 配合校验
@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 或默认值问题。
- 使用包装类型 + @NotNull
@ConfigurationProperties(prefix = "app.payment")
@Validated
public class PaymentProperties {
@NotNull
private Boolean enabled;
@NotNull
private Integer timeout;
}
- 数值型配置必须限制范围
@Min(100)
@Max(10000)
private Integer timeout;
- String 必须明确非空 / 非空白
@NotBlank
private String endpoint;
- 集合类型:最容易被忽略的 null 源头
默认初始化
private List<String> channels = new ArrayList<>();
强制要求配置存在
@NotEmpty
private List<String> channels;
- 嵌套对象的 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指示某个功能是否开启。
- Profile 用在“实现类”,不用在“业务逻辑”
不推荐
if (env.acceptsProfiles("dev")) {
...
}
推荐
@Profile("dev")
@Service
class MockPaymentService implements PaymentService {}
@Profile("prod")
@Service
class RealPaymentService implements PaymentService {}
- Profile 支持逻辑表达式
@Profile("dev | test")
@Profile("!prod")
- @ConditionalOnProperty基础用法
@ConditionalOnProperty(
name = "feature.cache.enabled",
havingValue = "true",
matchIfMissing = false
)
@Bean
public CacheService cacheService() {
return new CacheService();
}
feature:
cache:
enabled: true
- matchIfMissing 的正确理解
matchIfMissing = false
没配置 → 不加载
@ConditionalOnProperty(
name = "feature.audit.enabled",
havingValue = "true",
matchIfMissing = true
)
默认开启,除非显式关闭
- 用在“策略选择”而不是 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
- 和 @Lazy 联用(启动优化)
@Bean
@Lazy
@ConditionalOnProperty(name = "feature.report.enabled", havingValue = "true")
public ReportService reportService() {
return new ReportService();
}
没开启 → 永远不加载;开启 → 首次使用才创建;
- 自定义组合条件注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ConditionalOnProperty(name = "feature.logging.enabled", havingValue = "true")
public @interface ConditionalLoggingEnabled {}
- 条件 Bean 被强依赖
@Autowired
private CacheService cacheService;
正确做法:
@Autowired
private ObjectProvider<CacheService> cacheServiceProvider;
cacheServiceProvider.ifAvailable(CacheService::clear);
如何动态控制 Bean 是否加载,提高应用灵活性。
- @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 { }
- 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
- 使用 @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() {
// 清理
}
}
- 初始化 = 校验 + 轻量准备
@PostConstruct
public void init() {
Assert.notNull(config.getEndpoint(), "endpoint required");
}
- 真正“重”的初始化 → 延迟执行
@EventListener(ApplicationReadyEvent.class)
public void onReady() {
loadBigData();
}
- 关闭资源要考虑“幂等”
@PreDestroy
public void shutdown() {
if (closed.compareAndSet(false, true)) {
pool.shutdown();
}
}
- SmartInitializingSingleton
@Component
public class CacheWarmup
implements SmartInitializingSingleton {
@Override
public void afterSingletonsInstantiated() {
// 所有单例 Bean 初始化完成后
}
}
懒加载 & 单例/原型模式
使用 @Lazy 控制启动速度。
- 用在 Bean 上
@Lazy
@Component
public class BigReportService {
public BigReportService() {
// 构造成本高
}
}
- 用在 @Bean 方法上
@Bean
@Lazy
public HeavyClient heavyClient() {
return new HeavyClient();
}
- 用在 @Configuration 类上
@Configuration
@Lazy
public class OptionalFeatureConfig {
@Bean
public OptionalService service() {
return new OptionalService();
}
}
- ObjectProvider + @Lazy
@Autowired
private ObjectProvider<BigReportService> reportServiceProvider;
public void generate() {
BigReportService svc = reportServiceProvider.getIfAvailable();
}
- @Lazy + @PostConstruct 的陷阱
@Lazy
@Component
public class LazyBean {
@PostConstruct
public void init() {
// 不会在启动期执行
}
}
原型 Bean 与单例 Bean 的注入注意事项。
- 单例 Bean(默认)
@Component
@Scope("singleton")
public class A {}
- 原型 Bean
@Component
@Scope("prototype")
public class B {}
- 错误应用:原型 Bean 注入到单例 Bean
@Component
public class OrderService {
@Autowired
private TaskContext taskContext; // prototype
}
@Component
@Scope("prototype")
public class TaskContext {}
- 使用 ObjectProvider
@Component
public class OrderService {
@Autowired
private ObjectProvider<TaskContext> taskContextProvider;
public void handle() {
TaskContext ctx = taskContextProvider.getObject();
}
}
- 使用 ApplicationContext
@Autowired
private ApplicationContext applicationContext;
public void handle() {
TaskContext ctx = applicationContext.getBean(TaskContext.class);
}
- 使用 @Lookup
@Component
public abstract class OrderService {
public void handle() {
TaskContext ctx = taskContext();
}
@Lookup
protected abstract TaskContext taskContext();
}
- 原型 Bean 不会触发 @PreDestroy
@PreDestroy
public void destroy() {
// 不会被调用
}
正确做法,由调用方负责清理,或显式调用销毁逻辑。
TaskContext ctx = provider.getObject();
try {
...
} finally {
ctx.close();
}
- 使用 Web Scope 而不是 Prototype
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class RequestContext {}
三、AOP & 拦截技巧
切面小技巧
用 @Around 获取方法执行时间。
- 基础示例
@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);
}
}
}
- 自定义注解 + 精准拦截
@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);
}
}
}
- 获取更丰富的上下文信息
方法名 / 类名
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
方法参数
Object[] args = pjp.getArgs();
日志切面、权限切面、异常处理切面实用示例。
- 日志切面(访问日志 + 执行耗时)
定义日志注解
@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) {
...
}
- 权限切面
权限注解设计
@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(...) {
...
}
- 异常处理切面
异常切面实现
@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
为特定方法添加注解控制限流、缓存、事务。
- 限流:自定义 @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(...) {
...
}
- 缓存:自定义 @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) {
...
}
- 事务:自定义 @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(...) {
...
}
四、事务与数据库优化
事务管理
- @Transactional 的传播行为、只读事务优化。
- 事务生效条件
只有“通过代理对象调用的方法”事务才会生效,同一个类内部的自调用 = 不经过代理 = 事务失效。
- 事务传播行为
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)
- 正确理解只读事务
@Transactional(readOnly = true)
public User queryUser(...) { }
告诉数据库这是只读事务,允许数据库做优化。以 MySQL InnoDB为例,不生成 undo log、不加写锁、减少行级锁竞争、主从架构可配合读写分离路由
- 事务失效的“死亡陷阱”
- private 方法
@Transactional
private void save() { }
- 同类内部调用
public void createOrder() {
saveOrder(); // 事务失效
}
@Transactional
public void saveOrder() { }
- 异常被吞掉
@Transactional
public void save() {
try {
...
} catch (Exception e) {
log.error("error", e);
}
}
- checked Exception 不回滚
throw new Exception("xxx");
默认行为,不会回滚。
正确配置
@Transactional(rollbackFor = Exception.class)
- @Transactional 写在接口上
JDK 代理模式下,实现类方法不生效,始终写在实现类上。
- 异步事务组合
异步方法默认不在事务范围内,如果希望事务提交后再执行异步任务,可以这样处理:
@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
批量操作优化技巧。
- 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
- 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();
- JPA
repository.saveAll(list);
实际一条一条insert,Persistence Context持有所有对象,OOM 风险极高。
使用 NamedParameterJdbcTemplate 提高 SQL 可读性。
延迟加载和 N+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")
})
- 使用 @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
}
}
- 锁与隔离级别的关系
|
隔离级别 |
锁行为 |
|
READ COMMITTED |
行锁 |
|
REPEATABLE READ(MySQL 默认) |
行锁 + 间隙锁 |
|
SERIALIZABLE |
表级锁 |
五、缓存与性能优化
Spring Cache
- @Cacheable、@CacheEvict 的小技巧。
- @Cacheable基本用法
@Cacheable(value = "userCache", key = "#id")
public User getUserById(Long id) {
return userRepository.findById(id).orElse(null);
}
- 条件缓存(避免缓存无效数据,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) { ... }
- 动态 key 构建技巧
@Cacheable(value = "orderCache", key = "'order:' + #userId + ':' + #status")
public List<Order> listOrders(Long userId, String status) { ... }
- 避免 N+1 查询缓存失效
@Cacheable(value = "orders", key = "#userId")
public List<Order> listOrdersWithItems(Long userId) {
return orderRepository.findAllWithItems(userId); // 一条 SQL JOIN FETCH
}
- @CacheEvict 基本用法
//方法执行成功后清除缓存
@CacheEvict(value = "userCache", key = "#id")
public void updateUser(Long id, UserDTO dto) { ... }
- 删除全部缓存
@CacheEvict(value = "userCache", allEntries = true)
public void reloadAllUsers() { ... }
- 延迟失效(缓存雪崩防护)
@Cacheable(value = "userCache", key = "#id")
public User getUser(Long id) {
User user = repo.findById(id).orElse(null);
// TTL 由底层缓存实现设置随机过期
return user;
}
- 更新缓存与清理缓存组合
@CacheEvict(value = "userCache", key = "#id")
@CachePut(value = "userCache", key = "#id")
public User updateUser(Long id, UserDTO dto) { ... }
- SpEL 表达式在缓存 key 中的巧用。
- 使用方法参数构建复合 key
@Cacheable(value = "orderCache", key = "'order:' + #userId + ':' + #status")
public List<Order> listOrders(Long userId, String status) { ... }
- 利用对象属性
@Cacheable(value = "userCache", key = "#user.id")
public User getUser(User user) { ... }
- 多参数动态组合
@Cacheable(value = "reportCache", key = "'report:' + #year + ':' + #month + ':' + #region.id")
public Report generateReport(int year, int month, Region region) { ... }
- 方法调用 / 静态变量
@Cacheable(value = "configCache", key = "T(com.example.ConfigUtils).GLOBAL_KEY")
public Config getConfig() { ... }
- 条件缓存结合 SpEL
@Cacheable(value = "userCache", key = "#id", condition = "#id > 0", unless = "#result == null")
public User getUser(Long id) { ... }
- 缓存刷新策略
@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));
}
- 多级缓存策略
本地缓存 + 分布式缓存组合使用,提高读性能。
@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 在 另一个线程执行,此时可能读取不到刚更新的数据,或者事务回滚了但异步已经执行了。异步线程无法感知原线程事务的提交或回滚状态。
解决方案:
- 事务提交后异步执行
利用 TransactionSynchronizationManager 或 Spring 提供的回调:
@Transactional
public void updateDevice(Device device) {
deviceRepository.update(device);
// 事务提交后再异步执行
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
asyncNotify(device);
}
});
}
- 异步方法独立事务
@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void asyncNotify(Device device) {
// 独立事务,可以提交自己的 DB 操作
}
- 消息队列解耦
@Transactional
public void updateDevice(Device device) {
deviceRepository.update(device);
messageQueue.send(new DeviceUpdatedEvent(device.getId()));
}
2348

被折叠的 条评论
为什么被折叠?



