SuperAPI AI Directory

java-dev

Coding & development

View repository

Updated 10-15 18:26

About this skill

Java Development Standards

References: Google Java Style Guide, Alibaba Java Development Manual


Toolchain

# Maven
mvn clean compile                    # 编译
mvn test                             # 运行测试
mvn verify                           # 运行所有检查

# Gradle
./gradlew build                      # 构建
./gradlew test                       # 运行测试

Naming Conventions

Type Rule Example
Package name All lowercase, reverse domain name com.example.project
Class name UpperCamelCase, noun/noun phrase UserService, HttpClient
Method name lowerCamelCase, starting with a verb findById, isValid
Constants All uppercase with underscore separators MAX_RETRY_COUNT
Boolean return values is/has/can prefix isActive(), hasPermission()

Class Member Order

public class Example {
    // 1. 静态常量
    public static final String CONSTANT = "value";

    // 2. 静态变量
    private static Logger logger = LoggerFactory.getLogger(Example.class);

    // 3. 实例变量
    private Long id;

    // 4. 构造函数
    public Example() { }

    // 5. 静态方法
    public static Example create() { return new Example(); }

    // 6. 实例方法(公共 → 私有)
    public void doSomething() { }
    private void helperMethod() { }

    // 7. getter/setter(或使用 Lombok)
}

DTO/VO Class Standards

Rule Description
❌ Handwritten getter/setter prohibited DTO, VO, Request, and Response classes must all use Lombok
✅ Use @Data Regular DTO
✅ Use @Value Immutable DTO
✅ Use @Builder Use together when there are many fields
⚠️ Use with caution on Entity classes @Data JPA Entity equals/hashCode can affect Hibernate proxies
// ❌ 手写 getter/setter
public class UserDTO {
    private Long id;
    private String name;
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    // ... 大量样板代码
}

// ✅ 使用 Lombok
@Data
public class UserDTO {
    private Long id;
    private String name;
}

Batch Query Standards

Rule Description
❌ IN clauses with more than 500 parameters prohibited High SQL parsing overhead, unstable execution plan
✅ Query in batches when exceeded 500 per batch, merge results
✅ Encapsulate a common utility method Avoid handwriting batching logic everywhere
// ❌ 1700 个 ID 一次查询
List<User> users = userRepository.findByIdIn(allIds); // IN 子句过长

// ✅ 分批查询工具方法
public static <T, R> List<R> batchQuery(List<T> params, int batchSize,
                                         Function<List<T>, List<R>> queryFn) {
    List<R> result = new ArrayList<>();
    for (int i = 0; i < params.size(); i += batchSize) {
        List<T> batch = params.subList(i, Math.min(i + batchSize, params.size()));
        result.addAll(queryFn.apply(batch));
    }
    return result;
}

// 使用
List<User> users = batchQuery(allIds, 500, ids -> userRepository.findByIdIn(ids));

N+1 Query Prevention

Rule Description
❌ Calling Repository/Mapper inside a loop prohibited Each iteration in stream/forEach/for triggers a query
✅ Query in batches outside the loop, convert results to a Map Query count drops from N to 1 (or the number of distinct values)
// ❌ N+1:循环内逐行查询 count
records.forEach(record -> {
    long count = deviceRepo.countByDeviceId(record.getDeviceId()); // 每条触发一次查询
    record.setDeviceCount(count);
});

// ✅ 循环外批量查询 + Map 查找
List<String> deviceIds = records.stream()
    .map(Record::getDeviceId).distinct().collect(Collectors.toList());
Map<String, Long> countMap = deviceRepo.countByDeviceIdIn(deviceIds).stream()
    .collect(Collectors.toMap(CountDTO::getDeviceId, CountDTO::getCount));
records.forEach(r -> r.setDeviceCount(countMap.getOrDefault(r.getDeviceId(), 0L)));

Common N+1 scenarios and fix patterns:

Scenario Inside the loop (❌) Outside the loop (✅)
count repo.countByXxx(id) repo.countByXxxIn(ids) → Map<id, count>
findById repo.findById(id) repo.findByIdIn(ids) → Map<id, entity>
exists repo.existsByXxx(id) repo.findXxxIn(ids) → Set<id> + set.contains()

Concurrency Safety Standards

Rule Description
❌ read-modify-write prohibited Read the balance first and write it back; lost updates under concurrency
❌ check-then-act without a fallback prohibited Check first and then act; conditions become invalid under concurrency
✅ Use atomic update SQL UPDATE SET balance = balance + :delta WHERE id = :id
✅ Or use optimistic locking @Version field + retry mechanism
✅ Unique index as a fallback The last line of defense against duplicate inserts
// ❌ read-modify-write 竞态条件
PointsAccount account = accountRepo.findById(id);
account.setBalance(account.getBalance() + points); // 并发时丢失更新
accountRepo.save(account);

// ✅ 方案一:原子更新 SQL
@Modifying
@Query("UPDATE PointsAccount SET balance = balance + :points WHERE id = :id")
int addBalance(@Param("id") Long id, @Param("points") int points);

// ✅ 方案二:乐观锁
@Version
private Long version; // Entity 中添加版本字段
// ❌ check-then-act 无兜底(并发下可能重复结算)
if (!rewardRepo.existsByTenantIdAndPeriod(tenantId, period)) {
    rewardRepo.save(new RankingReward(...));
}

// ✅ 唯一索引兜底 + 异常捕获
// DDL: UNIQUE INDEX uk_tenant_period (tenant_id, ranking_type, period, rank_position)
try {
    rewardRepo.save(new RankingReward(...));
} catch (DataIntegrityViolationException e) {
    log.warn("重复结算已被唯一索引拦截: tenantId={}, period={}", tenantId, period);
}

Exception Handling

// ✅ 好:捕获具体异常,添加上下文
try {
    user = userRepository.findById(id);
} catch (DataAccessException e) {
    throw new ServiceException("Failed to find user: " + id, e);
}

// ✅ 好:资源自动关闭
try (InputStream is = new FileInputStream(file)) {
    // 使用资源
}

// ❌ 差:捕获过宽
catch (Exception e) { e.printStackTrace(); }

Null Handling

// ✅ 使用 Optional
public Optional<User> findById(Long id) {
    return userRepository.findById(id);
}

// ✅ 参数校验
public void updateUser(User user) {
    Objects.requireNonNull(user, "user must not be null");
}

// ✅ 安全的空值处理
String name = Optional.ofNullable(user)
    .map(User::getName)
    .orElse("Unknown");

Concurrent Programming

// ✅ 使用 ExecutorService
ExecutorService executor = Executors.newFixedThreadPool(10);
Future<Result> future = executor.submit(() -> doWork());

// ✅ 使用 CompletableFuture
CompletableFuture<User> future = CompletableFuture
    .supplyAsync(() -> findUser(id))
    .thenApply(user -> enrichUser(user));

// ❌ 差:直接创建线程
new Thread(() -> doWork()).start();

Testing Standards (JUnit 5)

class UserServiceTest {
    @Test
    @DisplayName("根据 ID 查找用户 - 用户存在时返回用户")
    void findById_whenUserExists_returnsUser() {
        // given
        when(userRepository.findById(1L)).thenReturn(Optional.of(expected));

        // when
        Optional<User> result = userService.findById(1L);

        // then
        assertThat(result).isPresent();
        assertThat(result.get().getName()).isEqualTo("test");
    }
}

Spring Boot Standards

// ✅ 构造函数注入
@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;
    private final EmailService emailService;
}

// ✅ REST Controller
@RestController
@RequestMapping("/api/users")
public class UserController {
    @GetMapping("/{id}")
    public ResponseEntity<UserDto> findById(@PathVariable Long id) {
        return userService.findById(id)
            .map(ResponseEntity::ok)
            .orElse(ResponseEntity.notFound().build());
    }
}

Auth Filter Degradation Principles

Rule Description
✅ optional-auth paths degrade to anonymous access when encountering invalid/expired/incomplete tokens Should not return 401/403
❌ Users with partial credentials must not have a worse experience than anonymous users E.g., a temporary token returns 403 on a public API

Circular Dependency Prevention (Spring Boot 3.x)

Spring Boot 3.x prohibits constructor circular dependencies by default. When splitting a large Service into sub-Services, the dependency direction must be checked.

Approach Priority Applicable Scenario
Extract common methods into a standalone utility class ✅ Preferred Pure utility methods (e.g., resolveTenantIds)
@Lazy field injection ⚠️ Emergency When bidirectional calls are truly needed
Function<> Callback ⚠️ Alternative Flexible but adds complexity
// ❌ 拆分后子服务回调父服务 → 循环依赖
@RequiredArgsConstructor
public class ReportInvoiceService {
    private final ReportService reportService; // 启动失败!
}

// ✅ 提取公共方法到独立类
@Component
public class TenantHelper {
    public List<Long> resolveTenantIds(Long tenantId) { ... }
}

See refactor-safety skill - Pitfall #5

Pagination Parameter Convention (Spring Data JPA)

Spring Data JPA pagination index starts at 0. When refactoring pagination parameters, the index base must be kept consistent between frontend and backend.

Rule Description
Full-stack unified 0-based Frontend, Controller, Service, and JPA all use 0-based indexes
Controller default value must be 0 @RequestParam(defaultValue = "0") int page
Service directly uses page PageRequest.of(page, size), do not page - 1
// ❌ 错误:前端传 page=0,Controller 默认值 1,Service 内部 -1
@GetMapping("/list")
public Page<Product> list(@RequestParam(defaultValue = "1") int page) {
    return service.list(page);  // Service 内部 PageRequest.of(page - 1, size)
}
// 问题:前端传 page=0 → Service 计算 page - 1 = -1 → 异常

// ✅ 正确:全栈统一 0-based
@GetMapping("/list")
public Page<Product> list(@RequestParam(defaultValue = "0") int page) {
    return service.list(page);  // Service 内部 PageRequest.of(page, size)
}

Refactoring Checklist:

  • Frontend calls pass page: 0(page 1)
  • Controller default value is 0
  • Service uses PageRequest.of(page, size)(do not subtract 1)
  • Test page=0 and page=1 both return data normally

Input Validation Convention

Rule Description
❌ Forbidden @RequestBody without @Valid All request bodies must be validated
✅ Add constraint annotations to DTO fields @NotBlank、@Size、@Pattern etc.
✅ Add range constraints to numeric fields @Min、@Max、@Positive etc.
✅ Add an upper limit to pagination parameters size must @Max(100) to prevent large queries
✅ Whitelist validation for enum/status fields Custom validator or @Pattern

Common DTO Field Validation Quick Reference:

Field Type Required Annotation Description
Quantity quantity @NotNull @Min(1) Prevent 0 or negative numbers (negatives can cause reverse operations)
Amount amount/price @NotNull @Positive or @DecimalMin("0.01")
Pagination size @Min(1) @Max(100) Prevent size=999999 from overwhelming the database
Pagination page @Min(1) Page numbers start at 1
Percentage rate @Min(0) @Max(100) Define range based on business needs
// ❌ 无校验,任意输入直接进入业务逻辑
@PostMapping("/ship")
public Result ship(@RequestBody ShippingRequest request) { ... }

// ✅ 完整校验
@PostMapping("/ship")
public Result ship(@RequestBody @Valid ShippingRequest request) { ... }

public record ShippingRequest(
    @NotNull Long orderId,
    @NotBlank @Size(max = 500) String shippingInfo,
    @Pattern(regexp = "pending|shipped|delivered") String giftStatus
) {}

// ❌ quantity 只有 @NotNull,负数会导致 Redis DECRBY 反向加库存
public record CreateOrderRequest(
    @NotNull Integer quantity  // 可提交 0 或负数
) {}

// ✅ 数量必须 >= 1
public record CreateOrderRequest(
    @NotNull @Min(1) Integer quantity
) {}

// ❌ 分页无上限,用户可传 size=999999
@GetMapping("/orders")
public Result list(@RequestParam int page, @RequestParam int size) { ... }

// ✅ 分页参数加约束
@GetMapping("/orders")
public Result list(@RequestParam @Min(1) int page,
                   @RequestParam @Min(1) @Max(100) int size) { ... }

Performance Optimization

Pitfall Solution
N+1 query See the "N+1 Query Prevention" section
String concatenation in a loop Use StringBuilder
Frequent boxing/unboxing Use primitive type streams
Collection initial capacity not specified new ArrayList<>(size)

Third-Party API HTTP Client Selection

Rule Description
❌ Avoid RestTemplate default client for calling domestic platform APIs Default HttpURLConnection POST requests have compatibility issues with CDNs such as WeChat/Alipay (known to trigger 412/403)
✅ Prefer java.net.http.HttpClient(JDK 11+) modern HTTP clients, no CDN compatibility issues
✅ Or configure HttpComponentsClientHttpRequestFactory to make RestTemplate use Apache HttpClient under the hood

Diagnostic characteristics: HTTP error + empty body + extremely minimal response headers (only Connection/Content-Length) = interception at the CDN layer, not a response from the API itself. When the same API's GET works normally but POST is abnormal, first suspect HTTP client compatibility.


Native SQL conventions

Aliases avoid MySQL reserved words

@Query(nativeQuery = true) If a column alias in is a MySQL reserved word, it will cause a syntax error.

Frequently encountered reserved words:year_month, order, status, key, value, name, type, date, time, rank, range, rows, column, user, role, group

Rule Description
✅ Use short aliases or abbreviations ym, ord_status, cnt
✅ Or escape with backticks `year_month`
❌ Do not use reserved words directly as aliases as year_month、as order、as rank
// ❌ year_month 是 MySQL 保留字(INTERVAL YEAR_MONTH)
@Query(value = """
    SELECT DATE_FORMAT(o.order_date, '%Y-%m') as year_month,
           COUNT(*) as cnt
    FROM orders o
    GROUP BY year_month
    """, nativeQuery = true)

// ✅ 改用非保留字别名
@Query(value = """
    SELECT DATE_FORMAT(o.order_date, '%Y-%m') as ym,
           COUNT(*) as cnt
    FROM orders o
    GROUP BY ym
    """, nativeQuery = true)

Logging conventions

// ✅ 参数化日志
log.debug("Finding user by id: {}", userId);
log.info("User {} logged in successfully", username);
log.error("Failed to process order {}", orderId, exception);

// ❌ 差:字符串拼接
log.debug("Finding user by id: " + userId);

Detailed reference

File Content
references/java-style.md Naming conventions, exception handling, Spring Boot, testing conventions
references/collections.md Immutable collections (Guava), string splitting
references/concurrency.md Thread pool configuration, CompletableFuture timeout
references/concurrency-db-patterns.md Get-Or-Create concurrency, N+1 prevention, atomic updates, Redis+DB consistency
references/code-patterns.md Guard clauses, enum optimization, strategy factory pattern
references/date-time.md Date addition and subtraction, accounting period calculation, prohibit month-end alignment
references/http-client.md Third-party API HTTP client selection, CDN compatibility issues

📋 This reply follows:java-dev - [specific section]