mcpField
J
java-expert

java-expert

github.com/oimiragieo開発4493 回インストール17 週間前に更新
リポジトリ

java-expert について

Java と Spring Boot の開発者向けコーディングスキルで、コードレビュー、リファクタリング、アーキテクチャの助言を行います。最新の Java 機能、JPA の実践、テスト方法、明確なアンチパターン規則を扱います。

主な機能

  1. 01コードレビューとベストプラクティスの改善提案
  2. 02ドメインパターンとアーキテクチャの助言
  3. 03Java 21+ の言語機能の解説
  4. 04Spring Boot と JPA のレイヤー構成
  5. 05JUnit 5 と Mockito のテスト例
  6. 06よくあるアンチパターンと代替策

活用シーン

  1. 01Java または Spring Boot のコードレビュー
  2. 02サービスレイヤーや REST API のリファクタリング
  3. 03JPA クエリと関連データ読み込みの改善
  4. 04ユニットテストと統合テストの計画

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/oimiragieo/agent-studio --skill java-expert

インストール後、Claude Code で/java-expertを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

commands/
java-expert.md
hooks/
post-execute.cjs
pre-execute.cjs
references/
research-requirements.md
rules/
java-expert.md
schemas/
input.schema.json
output.schema.json
scripts/
main.cjs
templates/
implementation-template.md

詳細

Java エキスパート

Java と Spring Boot に精通した Java エキスパートです。REST API、JPA、マイクロサービスにも詳しいです。 確立されたガイドラインとベストプラクティスを適用し、開発者がより良いコードを書けるよう支援します。 - ベストプラクティスへの準拠状況をコードレビューします - ドメインパターンに基づいて改善案を提案します - 特定のアプローチが推奨される理由を説明します - 標準を満たすようコードのリファクタリングを支援します - アーキテクチャに関するガイダンスを提供します ### Java 21 以降の最新機能(2026 年)

仮想スレッド(Project Loom)

  • I/O バウンドのアプリケーションのスケーラビリティを大幅に向上させる軽量スレッドです
  • スレッドプールには Executors.newVirtualThreadPerTaskExecutor() を使用します
  • 多数の同時接続を処理する Web アプリケーションに最適です
  • Spring Boot 3.2 以降では、設定によって仮想スレッドをサポートしています
// Enable virtual threads in Spring Boot 3.2+
// application.properties
spring.threads.virtual.enabled=true

// Or programmatically
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
    return protocolHandler -> {
        protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
    };
}

// Using virtual threads directly
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    executor.submit(() -> {
        // I/O-bound task
        Thread.sleep(1000);
        return "result";
    });
}

パターンマッチング

  • switch のパターンマッチング(Java 21)
  • レコードパターン
  • パターンマッチングによる分解
// Pattern matching for switch
String result = switch (obj) {
    case String s -> "String: " + s;
    case Integer i -> "Integer: " + i;
    case Long l -> "Long: " + l;
    case null -> "null";
    default -> "Unknown";
};

// Record patterns
record Point(int x, int y) {}

if (obj instanceof Point(int x, int y)) {
    System.out.println("x: " + x + ", y: " + y);
}

レコード

  • イミュータブルなデータキャリアです
  • コンストラクター、ゲッター、equals()、hashCode()、toString() を自動生成します
public record UserDTO(String name, String email, LocalDate birthDate) {
    // Compact constructor for validation
    public UserDTO {
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("Name cannot be blank");
        }
    }
}

シールドクラス

  • 拡張または実装できるクラスを制限します
  • 網羅的なパターンマッチングを可能にします
public sealed interface Result<T> permits Success, Failure {
    record Success<T>(T value) implements Result<T> {}
    record Failure<T>(String error) implements Result<T> {}
}
Spring Boot 3.x のベストプラクティス(2026 年)

フレームワークのセットアップ:

  • ベースラインとして Java 21 以降(仮想スレッド、パターンマッチング)
  • Spring Boot 3.2 以降(最新の安定版)
  • Spring Framework 6.x
  • Jakarta EE(javax.* ではありません)— 名前空間の変更

プロジェクト構成(レイヤードアーキテクチャ):

src/main/java/com/example/app/
├── controller/         # REST endpoints (RestController)
├── service/           # Business logic (Service)
│   └── impl/         # Service implementations
├── repository/        # Data access (Repository)
├── model/
│   ├── entity/       # JPA entities
│   └── dto/          # Data Transfer Objects
├── config/           # Configuration classes
├── exception/        # Custom exceptions and handlers
└── util/             # Utility classes

コントローラーレイヤー(RestController):

  • HTTP リクエスト/レスポンスのみを処理します
  • ビジネスロジックをサービスに委譲します
  • リクエスト/レスポンスボディには DTO を使用します
  • リポジトリを直接インジェクションしてはいけません
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;

    @GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
        UserDTO user = userService.findById(id);
        return ResponseEntity.ok(user);
    }

    @PostMapping
    public ResponseEntity<UserDTO> createUser(@Valid @RequestBody CreateUserDTO dto) {
        UserDTO created = userService.create(dto);
        return ResponseEntity.status(HttpStatus.CREATED).body(created);
    }
}

サービスレイヤー:

  • ビジネスロジックを含みます
  • データアクセスにはリポジトリを使用します
  • エンティティと DTO の間で変換します
  • @Service を付与します
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class UserServiceImpl implements UserService {
    private final UserRepository userRepository;
    private final ModelMapper modelMapper;

    @Override
    public UserDTO findById(Long id) {
        User user = userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException(id));
        return modelMapper.map(user, UserDTO.class);
    }

    @Override
    @Transactional
    public UserDTO create(CreateUserDTO dto) {
        User user = modelMapper.map(dto, User.class);
        User saved = userRepository.save(user);
        return modelMapper.map(saved, UserDTO.class);
    }
}

リポジトリレイヤー(Spring Data JPA):

  • JpaRepository<Entity, ID> を継承します
  • カスタムクエリメソッドを定義します
  • 複雑なクエリには @Query を使用します
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);

    @Query("SELECT u FROM User u WHERE u.createdAt > :date")
    List<User> findRecentUsers(@Param("date") LocalDateTime date);

    // Projection for performance
    @Query("SELECT new com.example.dto.UserSummaryDTO(u.id, u.name, u.email) FROM User u")
    List<UserSummaryDTO> findAllSummaries();
}
JPA/Hibernate のベストプラクティス

エンティティ設計:

  • @Entity と @Table アノテーションを使用します
  • 常に生成戦略を指定して @Id を定義します
  • 制約とマッピングには @Column を使用します
  • ビジネスキーに基づいて equals() と hashCode() を実装します
@Entity
@Table(name = "users")
@Getter @Setter
@NoArgsConstructor
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String email;

    @Column(nullable = false)
    private String name;

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Order> orders = new ArrayList<>();

    @CreatedDate
    @Column(nullable = false, updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;
}

パフォーマンスの最適化:

  • N+1 クエリを防ぐには @EntityGraph または JOIN FETCH を使用します
  • 関連はデフォルトで遅延読み込みにします
  • 大きな結果セットにはページネーションを使用します
  • データベースに適切なインデックスを定義します
@Query("SELECT u FROM User u JOIN FETCH u.orders WHERE u.id = :id")
Optional<User> findByIdWithOrders(@Param("id") Long id);

// Pagination
Page<User> findAll(Pageable pageable);
テスト(JUnit 5 + Mockito)

サービスのユニットテスト:

@ExtendWith(MockitoExtension.class)
class UserServiceImplTest {
    @Mock
    private UserRepository userRepository;

    @Mock
    private ModelMapper modelMapper;

    @InjectMocks
    private UserServiceImpl userService;

    @Test
    void findById_WhenUserExists_ReturnsUserDTO() {
        // Given
        Long userId = 1L;
        User user = new User();
        user.setId(userId);
        UserDTO expectedDTO = new UserDTO();

        when(userRepository.findById(userId)).thenReturn(Optional.of(user));
        when(modelMapper.map(user, UserDTO.class)).thenReturn(expectedDTO);

        // When
        UserDTO result = userService.findById(userId);

        // Then
        assertNotNull(result);
        verify(userRepository).findById(userId);
        verify(modelMapper).map(user, UserDTO.class);
    }
}

統合テスト(Spring Boot Test):

@SpringBootTest
@AutoConfigureMockMvc
@Transactional
class UserControllerIntegrationTest {
    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    void createUser_WithValidData_ReturnsCreated() throws Exception {
        CreateUserDTO dto = new CreateUserDTO("John Doe", "[email protected]");

        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(dto)))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.name").value("John Doe"));
    }
}
ビルドツール(Maven と Gradle)

Maven(pom.xml):

<properties>
    <java.version>21</java.version>
    <spring-boot.version>3.2.0</spring-boot.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
</dependencies>

Gradle(build.gradle):

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.0'
    id 'io.spring.dependency-management' version '1.1.4'
}

java {
    sourceCompatibility = JavaVersion.VERSION_21
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
例外処理

グローバル例外ハンドラー:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException ex) {
        ErrorResponse error = new ErrorResponse(
            "USER_NOT_FOUND",
            ex.getMessage(),
            LocalDateTime.now()
        );
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex) {
        Map<String, String> errors = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .collect(Collectors.toMap(
                FieldError::getField,
                FieldError::getDefaultMessage
            ));

        ErrorResponse error = new ErrorResponse(
            "VALIDATION_ERROR",
            "Invalid input",
            errors,
            LocalDateTime.now()
        );
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
    }
}
ロギングとモニタリング

ロギング(SLF4J + Logback):

@Slf4j
@Service
public class UserServiceImpl implements UserService {

    public UserDTO findById(Long id) {
        log.debug("Finding user with id: {}", id);
        try {
            User user = userRepository.findById(id)
                .orElseThrow(() -> new UserNotFoundException(id));
            log.info("User found: {}", user.getEmail());
            return modelMapper.map(user, UserDTO.class);
        } catch (UserNotFoundException ex) {
            log.error("User not found with id: {}", id, ex);
            throw ex;
        }
    }
}

モニタリング用 Actuator:

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      show-details: always
使用例: ``` User: "Review this code for java best practices" Agent: [Analyzes code against consolidated guidelines and provides specific feedback] ``` ## 厳守事項
  1. 必ず、フィールドインジェクションで @Autowired を使うのではなく、コンストラクターインジェクションを使ってください。フィールドインジェクションは依存関係を隠し、テストを難しくするうえ、コンテキストが完全に読み込まれていない場合に実行時エラーを起こす、初期化が不完全なオブジェクトを生みます。
  2. 絶対に、先行する isPresent() の確認、または orElse()/orElseThrow() なしで Optional.get() を使わないでください。無条件の get() は、空の Optional に対して NoSuchElementException をスローし、Optional の目的そのものを気付かれないまま損ないます。
  3. 必ず、@Transactional の境界を明示的に処理してください。同じクラス内からトランザクションメソッドを呼び出すとプロキシを迂回し、トランザクションなしで実行されるため、気付かれないままデータの不整合が発生します。
  4. 絶対に、設定済みの TaskExecutor なしで @Async を使わないでください。Spring のデフォルトの @Async エグゼキューターは単一スレッドのプールを使用するため、並行する非同期呼び出しがキューに入り、並列処理の効果が失われます。
  5. 必ず、エラー処理には具体的な例外型を指定した @ControllerAdvice を使ってください。グローバルに Exception を捕捉すると根本原因が隠れます。具体的な例外ハンドラーを使えば、正しい HTTP ステータスコードと意味のあるエラーレスポンスを返せます。

アンチパターン

アンチパターン問題点正しい方法
@Autowired によるフィールドインジェクション依存関係が隠れる。Spring コンテキストなしではテストできず、単体テストでは null になるコンストラクターインジェクションを使い、必要な依存関係をすべて final フィールドとして宣言する
チェックなしの Optional.get()実行時に NoSuchElementException が発生し、Optional の null 安全性に関する契約を損なうorElseThrow()、orElse()、または map()/flatMap() のチェーンを使う
同じクラス内のメソッド呼び出しでの @TransactionalSpring プロキシが迂回され、メソッドがトランザクション外で実行されるため、データの整合性が失われるトランザクションメソッドを別のサービス Bean に移し、外部からインジェクションして呼び出す
デフォルトの @Async スレッドプール単一スレッドのプールにすべてのタスクがキューイングされ、非同期呼び出しが逐次実行されるプールサイズ、キュー、拒否ポリシーを指定して ThreadPoolTaskExecutor を設定する
グローバルな @ExceptionHandler(Exception.class)具体的な例外が握りつぶされ、すべてのエラーで同じ汎用的な 500 レスポンスが返される具体的な例外型を HTTP ステータスコードに対応付け、@ResponseStatus アノテーションを使う

統合されたスキル

このエキスパートスキルは、以下の 1 つの個別スキルを統合しています。

  • java-expert

メモリプロトコル(必須)

開始前:

cat .claude/context/memory/learnings.md

完了後: 新たに発見したパターンや例外を記録してください。

中断を想定してください。コンテキストがリセットされることがあります。メモリに記録されていなければ、起きていないのと同じです。

コメント