Skip to content

Javaのアノテーションの書き方

Spring Boot開発で頻繁に使用されるアノテーションを、頻出度とユースケース別に詳しく解説します。

アノテーションは、Javaコードにメタデータを追加するための機能です。Spring Bootでは、アノテーションを使用して設定を簡潔に記述し、フレームワークの機能を活用できます。

  • ★★★★★: ほぼすべてのプロジェクトで使用(必須レベル)
  • ★★★★☆: 多くのプロジェクトで使用(推奨レベル)
  • ★★★☆☆: 特定の機能で使用(中頻度)
  • ★★☆☆☆: 特殊なケースで使用(低頻度)
  • ★☆☆☆☆: まれに使用(参考レベル)

Spring Framework基本アノテーション

Section titled “Spring Framework基本アノテーション”

用途: SpringコンテナにBeanとして登録する汎用的なアノテーション

ユースケース:

  • カスタムユーティリティクラス
  • 設定クラス
  • 汎用的なサービスコンポーネント

実践例:

@Component
public class EmailValidator {
public boolean isValid(String email) {
return email != null && email.contains("@");
}
}
// 使用例
@Service
public class UserService {
@Autowired
private EmailValidator emailValidator; // 自動注入される
public void registerUser(String email) {
if (emailValidator.isValid(email)) {
// ユーザー登録処理
}
}
}

注意点:

  • @Service@Repository@Controller@Componentの特殊版
  • より具体的なアノテーションがある場合は、そちらを使用する

用途: ビジネスロジックを実装するサービス層のクラスに使用

ユースケース:

  • ビジネスロジックの実装
  • 複数のRepositoryを組み合わせた処理
  • トランザクション管理が必要な処理

実践例:

@Service
@Transactional
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private EmailService emailService;
public User createUser(UserCreateRequest request) {
// ビジネスロジック: ユーザー作成
User user = new User();
user.setName(request.getName());
user.setEmail(request.getEmail());
User savedUser = userRepository.save(user);
// メール送信
emailService.sendWelcomeEmail(savedUser.getEmail());
return savedUser;
}
public User findById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found: " + id));
}
}

よくある使い方:

  • @Transactionalと組み合わせてトランザクション管理
  • 複数のRepositoryを呼び出して複雑なビジネスロジックを実装

用途: データアクセス層のクラスに使用。JPAの例外をSpringの例外に変換

ユースケース:

  • JPAリポジトリのインターフェース
  • カスタムデータアクセス実装

実践例:

// インターフェース(Spring Data JPA)
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// メソッド名からクエリを自動生成
List<User> findByName(String name);
// カスタムクエリ
@Query("SELECT u FROM User u WHERE u.email = :email")
Optional<User> findByEmail(@Param("email") String email);
}
// カスタム実装クラス
@Repository
public class CustomUserRepositoryImpl implements CustomUserRepository {
@PersistenceContext
private EntityManager entityManager;
@Override
public List<User> findUsersWithCustomLogic() {
// カスタム実装
return entityManager.createQuery(
"SELECT u FROM User u", User.class
).getResultList();
}
}

注意点:

  • Spring Data JPAを使用する場合、インターフェースに@Repositoryは省略可能(自動的に認識される)
  • カスタム実装クラスには明示的に@Repositoryを付与

用途: MVCパターンのコントローラー層。主にHTMLビューを返す場合に使用

ユースケース:

  • サーバーサイドレンダリング(Thymeleaf、JSPなど)
  • フォーム処理
  • リダイレクト処理

実践例:

@Controller
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
// GET /users - ユーザー一覧ページ
@GetMapping
public String listUsers(Model model) {
List<User> users = userService.findAll();
model.addAttribute("users", users);
return "users/list"; // templates/users/list.html
}
// GET /users/{id} - ユーザー詳細ページ
@GetMapping("/{id}")
public String showUser(@PathVariable Long id, Model model) {
User user = userService.findById(id);
model.addAttribute("user", user);
return "users/detail";
}
// GET /users/new - ユーザー作成フォーム
@GetMapping("/new")
public String newUserForm(Model model) {
model.addAttribute("user", new User());
return "users/form";
}
// POST /users - ユーザー作成処理
@PostMapping
public String createUser(@ModelAttribute User user) {
userService.createUser(user);
return "redirect:/users"; // リダイレクト
}
}

@RestControllerとの違い:

  • @Controller: HTMLビューを返す
  • @RestController: JSON/XMLなどのデータを返す(REST API)

用途: REST APIのエンドポイントを提供するコントローラー

ユースケース:

  • RESTful APIの実装
  • JSON/XMLレスポンスの返却
  • フロントエンド(React、Vue.jsなど)との連携

実践例:

@RestController
@RequestMapping("/api/users")
public class UserRestController {
@Autowired
private UserService userService;
// GET /api/users - ユーザー一覧取得
@GetMapping
public ResponseEntity<List<UserDTO>> getUsers() {
List<UserDTO> users = userService.findAll()
.stream()
.map(UserDTO::fromEntity)
.collect(Collectors.toList());
return ResponseEntity.ok(users);
}
// GET /api/users/{id} - ユーザー詳細取得
@GetMapping("/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
User user = userService.findById(id);
return ResponseEntity.ok(UserDTO.fromEntity(user));
}
// POST /api/users - ユーザー作成
@PostMapping
public ResponseEntity<UserDTO> createUser(
@Valid @RequestBody UserCreateRequest request) {
User user = userService.createUser(request);
return ResponseEntity.status(HttpStatus.CREATED)
.body(UserDTO.fromEntity(user));
}
// PUT /api/users/{id} - ユーザー更新
@PutMapping("/{id}")
public ResponseEntity<UserDTO> updateUser(
@PathVariable Long id,
@Valid @RequestBody UserUpdateRequest request) {
User user = userService.updateUser(id, request);
return ResponseEntity.ok(UserDTO.fromEntity(user));
}
// DELETE /api/users/{id} - ユーザー削除
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return ResponseEntity.noContent().build();
}
}

よくある使い方:

  • @RequestMappingでベースパスを指定
  • @GetMapping@PostMappingなどでHTTPメソッドを指定
  • @RequestBodyでJSONリクエストを受け取る
  • ResponseEntityでHTTPステータスコードを制御

用途: 依存性注入(DI)。SpringコンテナからBeanを自動的に注入

ユースケース:

  • サービス層への依存注入
  • リポジトリへの依存注入
  • 設定クラスへの依存注入

実践例:

@Service
public class UserService {
// フィールドインジェクション(非推奨)
@Autowired
private UserRepository userRepository;
// コンストラクタインジェクション(推奨)
private final EmailService emailService;
@Autowired
public UserService(EmailService emailService) {
this.emailService = emailService;
}
// セッターインジェクション
private NotificationService notificationService;
@Autowired
public void setNotificationService(NotificationService notificationService) {
this.notificationService = notificationService;
}
}
// コンストラクタインジェクション(@Autowired省略可能 - Spring 4.3以降)
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final PaymentService paymentService;
// @Autowiredは省略可能
public OrderService(OrderRepository orderRepository,
PaymentService paymentService) {
this.orderRepository = orderRepository;
this.paymentService = paymentService;
}
}

推奨される使い方:

  • コンストラクタインジェクションを推奨(不変性、テスト容易性、必須依存関係の明確化)
  • フィールドインジェクションは避ける(テストが困難、不変性が保証されない)

注意点:

  • 複数のBean候補がある場合は@Qualifierを使用
  • オプショナルな依存関係には@Autowired(required = false)を使用

用途: プロパティファイルや環境変数から値を注入

ユースケース:

  • アプリケーション設定値の注入
  • 環境変数の読み込み
  • デフォルト値の設定

実践例:

@Service
public class EmailService {
// application.propertiesから値を読み込み
@Value("${email.smtp.host}")
private String smtpHost;
@Value("${email.smtp.port:587}") // デフォルト値587
private int smtpPort;
@Value("${email.from:noreply@example.com}") // デフォルト値
private String fromAddress;
// SpEL(Spring Expression Language)を使用
@Value("#{systemProperties['user.name']}")
private String systemUser;
@Value("#{T(java.lang.Math).random() * 100}")
private double randomValue;
// リストやマップの注入
@Value("${app.allowed.ips:127.0.0.1,localhost}")
private List<String> allowedIps;
}
// application.properties
// email.smtp.host=smtp.gmail.com
// email.smtp.port=587
// email.from=admin@example.com
// app.allowed.ips=192.168.1.1,192.168.1.2

よくある使い方:

  • 設定値の外部化
  • 環境別の設定(dev、staging、prod)
  • デフォルト値の指定

用途: コントローラークラスやメソッドにURLマッピングを定義

ユースケース:

  • ベースパスの定義
  • 複数のHTTPメソッドに対応
  • 共通のパス設定

実践例:

@RestController
@RequestMapping("/api/v1/users") // ベースパス
public class UserController {
// GET /api/v1/users
@RequestMapping(method = RequestMethod.GET)
public List<User> getUsers() {
return userService.findAll();
}
// GET /api/v1/users/{id}
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
// POST /api/v1/users
@RequestMapping(method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public User createUser(@RequestBody User user) {
return userService.createUser(user);
}
}

よくある使い方:

  • クラスレベルでベースパスを定義
  • メソッドレベルで具体的なパスとHTTPメソッドを指定
  • @GetMapping@PostMappingなどのショートカットアノテーションを使用

用途: GETリクエストのマッピング(@RequestMapping(method = RequestMethod.GET)のショートカット)

ユースケース:

  • リソースの取得
  • 一覧取得
  • 詳細取得

実践例:

@RestController
@RequestMapping("/api/users")
public class UserController {
// GET /api/users
@GetMapping
public List<UserDTO> getUsers(
@RequestParam(required = false) String name,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return userService.findUsers(name, page, size);
}
// GET /api/users/{id}
@GetMapping("/{id}")
public UserDTO getUser(@PathVariable Long id) {
return userService.findById(id);
}
// GET /api/users/{id}/orders
@GetMapping("/{id}/orders")
public List<OrderDTO> getUserOrders(@PathVariable Long id) {
return orderService.findByUserId(id);
}
// 複数のパスパターン
@GetMapping({"/list", "/all"})
public List<UserDTO> getAllUsers() {
return userService.findAll();
}
}

用途: POSTリクエストのマッピング

ユースケース:

  • リソースの作成
  • フォーム送信
  • ファイルアップロード

実践例:

@RestController
@RequestMapping("/api/users")
public class UserController {
// POST /api/users
@PostMapping
public ResponseEntity<UserDTO> createUser(
@Valid @RequestBody UserCreateRequest request) {
UserDTO user = userService.createUser(request);
return ResponseEntity.status(HttpStatus.CREATED)
.header("Location", "/api/users/" + user.getId())
.body(user);
}
// POST /api/users/{id}/activate
@PostMapping("/{id}/activate")
public ResponseEntity<Void> activateUser(@PathVariable Long id) {
userService.activateUser(id);
return ResponseEntity.ok().build();
}
// ファイルアップロード
@PostMapping(value = "/{id}/avatar", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<Void> uploadAvatar(
@PathVariable Long id,
@RequestParam("file") MultipartFile file) {
userService.uploadAvatar(id, file);
return ResponseEntity.ok().build();
}
}

用途: PUTリクエストのマッピング(リソースの完全置換)

ユースケース:

  • リソースの更新
  • リソースの置換

実践例:

@RestController
@RequestMapping("/api/users")
public class UserController {
// PUT /api/users/{id}
@PutMapping("/{id}")
public ResponseEntity<UserDTO> updateUser(
@PathVariable Long id,
@Valid @RequestBody UserUpdateRequest request) {
UserDTO user = userService.updateUser(id, request);
return ResponseEntity.ok(user);
}
}

PUTとPATCHの違い:

  • PUT: リソースの完全置換
  • PATCH: リソースの部分更新(@PatchMappingを使用)

用途: DELETEリクエストのマッピング

ユースケース:

  • リソースの削除
  • 論理削除のトリガー

実践例:

@RestController
@RequestMapping("/api/users")
public class UserController {
// DELETE /api/users/{id}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return ResponseEntity.noContent().build();
}
// DELETE /api/users/{id}/soft
@DeleteMapping("/{id}/soft")
public ResponseEntity<Void> softDeleteUser(@PathVariable Long id) {
userService.softDeleteUser(id);
return ResponseEntity.ok().build();
}
}

用途: URLパスから変数を取得

ユースケース:

  • RESTful APIのリソースID取得
  • 階層的なリソースの取得

実践例:

@RestController
@RequestMapping("/api/users")
public class UserController {
// GET /api/users/123
@GetMapping("/{id}")
public UserDTO getUser(@PathVariable Long id) {
return userService.findById(id);
}
// パス変数名を明示的に指定
@GetMapping("/{userId}/orders/{orderId}")
public OrderDTO getOrder(
@PathVariable("userId") Long userId,
@PathVariable("orderId") Long orderId) {
return orderService.findByUserIdAndOrderId(userId, orderId);
}
// 正規表現でバリデーション
@GetMapping("/{id:[0-9]+}")
public UserDTO getUserWithValidation(@PathVariable Long id) {
return userService.findById(id);
}
// オプショナルなパス変数
@GetMapping({"/{id}", ""})
public ResponseEntity<?> getUserOrList(@PathVariable(required = false) Long id) {
if (id != null) {
return ResponseEntity.ok(userService.findById(id));
} else {
return ResponseEntity.ok(userService.findAll());
}
}
}

用途: クエリパラメータやフォームデータから値を取得

ユースケース:

  • 検索条件の取得
  • ページネーション
  • フィルタリング

実践例:

@RestController
@RequestMapping("/api/users")
public class UserController {
// GET /api/users?name=John&age=30
@GetMapping
public List<UserDTO> searchUsers(
@RequestParam(required = false) String name,
@RequestParam(required = false) Integer age,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return userService.searchUsers(name, age, page, size);
}
// パラメータ名を明示的に指定
@GetMapping("/search")
public List<UserDTO> search(
@RequestParam("q") String query,
@RequestParam(value = "sort", defaultValue = "name") String sortBy) {
return userService.search(query, sortBy);
}
// リストや配列の取得
@GetMapping("/filter")
public List<UserDTO> filterUsers(
@RequestParam("ids") List<Long> ids) {
return userService.findByIds(ids);
}
// 必須パラメータ
@GetMapping("/required")
public UserDTO getUserRequired(
@RequestParam(required = true) Long id) {
return userService.findById(id);
}
}

用途: HTTPリクエストボディ(JSON、XMLなど)をJavaオブジェクトに変換

ユースケース:

  • REST APIでのJSONリクエストの受け取り
  • 複雑なオブジェクトの受け取り

実践例:

@RestController
@RequestMapping("/api/users")
public class UserController {
// POST /api/users
// Request Body: {"name": "John", "email": "john@example.com"}
@PostMapping
public ResponseEntity<UserDTO> createUser(
@Valid @RequestBody UserCreateRequest request) {
UserDTO user = userService.createUser(request);
return ResponseEntity.status(HttpStatus.CREATED).body(user);
}
// PUT /api/users/{id}
@PutMapping("/{id}")
public ResponseEntity<UserDTO> updateUser(
@PathVariable Long id,
@Valid @RequestBody UserUpdateRequest request) {
UserDTO user = userService.updateUser(id, request);
return ResponseEntity.ok(user);
}
}
// DTOクラス
public class UserCreateRequest {
@NotBlank(message = "名前は必須です")
@Size(max = 100, message = "名前は100文字以内で入力してください")
private String name;
@NotBlank(message = "メールアドレスは必須です")
@Email(message = "有効なメールアドレスを入力してください")
private String email;
// getters and setters
}

よくある使い方:

  • @Validと組み合わせてバリデーション
  • DTOクラスでリクエストデータを受け取る
  • @JsonIgnoreで不要なフィールドを無視

用途: JPAエンティティクラスであることを示す

ユースケース:

  • データベーステーブルに対応するエンティティクラス
  • ORMマッピング

実践例:

@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "user_name", nullable = false, length = 100)
private String name;
@Column(unique = true, nullable = false)
private String email;
@Column(name = "created_at")
@CreationTimestamp
private LocalDateTime createdAt;
@Column(name = "updated_at")
@UpdateTimestamp
private LocalDateTime updatedAt;
// getters and setters
}

用途: エンティティがマッピングされるテーブル名を指定

ユースケース:

  • テーブル名がクラス名と異なる場合
  • スキーマ名の指定
  • インデックスの定義

実践例:

@Entity
@Table(
name = "user_accounts",
schema = "public",
uniqueConstraints = {
@UniqueConstraint(columnNames = {"email"}),
@UniqueConstraint(columnNames = {"username"})
},
indexes = {
@Index(name = "idx_email", columnList = "email"),
@Index(name = "idx_created_at", columnList = "created_at")
}
)
public class User {
// ...
}

用途: プライマリキーを指定

実践例:

@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// ...
}

用途: プライマリキーの生成戦略を指定

実践例:

@Entity
public class User {
// AUTO: データベースに依存(推奨)
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// IDENTITY: データベースの自動増分(MySQL、PostgreSQL)
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// SEQUENCE: シーケンスを使用(Oracle、PostgreSQL)
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_seq")
@SequenceGenerator(name = "user_seq", sequenceName = "user_sequence", allocationSize = 1)
private Long id;
// TABLE: テーブルを使用(非推奨)
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "user_gen")
@TableGenerator(name = "user_gen", table = "id_generator", pkColumnName = "gen_name", valueColumnName = "gen_value")
private Long id;
}

用途: カラムの詳細設定

実践例:

@Entity
public class User {
@Column(
name = "user_name", // カラム名
nullable = false, // NOT NULL制約
unique = true, // ユニーク制約
length = 100, // 文字列の長さ
precision = 10, // 数値の精度(小数点を含む)
scale = 2, // 小数点以下の桁数
columnDefinition = "VARCHAR(100) DEFAULT 'Unknown'" // カラム定義
)
private String name;
@Column(updatable = false) // 更新不可
private LocalDateTime createdAt;
@Column(insertable = false) // 挿入不可
private LocalDateTime updatedAt;
}

用途: オブジェクトのバリデーションを有効化

ユースケース:

  • リクエストデータの検証
  • ネストされたオブジェクトの検証

実践例:

@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping
public ResponseEntity<UserDTO> createUser(
@Valid @RequestBody UserCreateRequest request) {
// バリデーションエラーがある場合は自動的に400 Bad Requestが返される
UserDTO user = userService.createUser(request);
return ResponseEntity.status(HttpStatus.CREATED).body(user);
}
}
// DTOクラス
public class UserCreateRequest {
@NotBlank(message = "名前は必須です")
@Size(min = 1, max = 100, message = "名前は1文字以上100文字以内で入力してください")
private String name;
@NotBlank(message = "メールアドレスは必須です")
@Email(message = "有効なメールアドレスを入力してください")
private String email;
@Min(value = 0, message = "年齢は0以上である必要があります")
@Max(value = 150, message = "年齢は150以下である必要があります")
private Integer age;
@Valid // ネストされたオブジェクトのバリデーション
@NotNull
private AddressDTO address;
}

用途: nullでないことを検証

実践例:

public class UserCreateRequest {
@NotNull(message = "IDは必須です")
private Long id;
@NotNull(message = "年齢は必須です")
@Min(0)
@Max(150)
private Integer age;
}

用途: 文字列がnull、空文字、空白のみでないことを検証

実践例:

public class UserCreateRequest {
@NotBlank(message = "名前は必須です")
private String name;
@NotBlank(message = "メールアドレスは必須です")
@Email
private String email;
}

用途: 文字列やコレクションのサイズを検証

実践例:

public class UserCreateRequest {
@Size(min = 1, max = 100, message = "名前は1文字以上100文字以内で入力してください")
private String name;
@Size(min = 1, max = 10, message = "タグは1個以上10個以内で入力してください")
private List<String> tags;
}

用途: メールアドレスの形式を検証

実践例:

public class UserCreateRequest {
@NotBlank(message = "メールアドレスは必須です")
@Email(message = "有効なメールアドレスを入力してください")
private String email;
}

用途: メソッドやクラスにトランザクション管理を適用

ユースケース:

  • データベース操作のトランザクション管理
  • 複数のデータベース操作の原子性保証
  • ロールバック制御

実践例:

@Service
@Transactional // クラス全体に適用
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private OrderRepository orderRepository;
// デフォルト設定(読み取り専用でない、REQUIRED伝播)
public User createUser(UserCreateRequest request) {
User user = new User();
user.setName(request.getName());
return userRepository.save(user);
}
// 読み取り専用トランザクション
@Transactional(readOnly = true)
public User findById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found: " + id));
}
// 新しいトランザクションを開始
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logUserActivity(Long userId, String activity) {
// ログ記録処理(独立したトランザクション)
}
// 特定の例外でロールバックしない
@Transactional(noRollbackFor = {IllegalArgumentException.class})
public void updateUserWithValidation(Long id, UserUpdateRequest request) {
// バリデーションエラーでもロールバックしない
}
// トランザクションのタイムアウト設定(秒)
@Transactional(timeout = 30)
public void processLargeData() {
// 30秒でタイムアウト
}
}

よくある使い方:

  • サービス層のメソッドに適用
  • readOnly = trueで読み取り専用トランザクション
  • 例外発生時のロールバック制御

Lombokは、Javaコードのボイラープレート(定型的なコード)を削減するためのライブラリです。アノテーションを使用して、コンパイル時にgetter、setter、toString、equals、hashCodeなどのメソッドを自動生成します。

  • ボイラープレートコードの削減: 繰り返し書く必要があるコード(getter/setterなど)を自動生成
  • コンパイル時処理: アノテーション処理により、コンパイル時にコードを生成
  • IDEサポート: IntelliJ IDEA、Eclipseなどの主要IDEでサポート
  • コードの可読性向上: 冗長なコードを減らし、ビジネスロジックに集中できる

Lombokは、Javaの**アノテーション処理(Annotation Processing)**機能を使用して、コンパイル時にコードを生成します。

1. ソースコードにLombokアノテーションを記述
2. コンパイル時にアノテーションプロセッサが実行
3. 必要なメソッド(getter/setterなど)を自動生成
4. 生成されたコードがコンパイルされる

Lombokなし(従来のコード):

@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// 以下、手動で記述する必要がある(ボイラープレートコード)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(id, user.id) &&
Objects.equals(name, user.name) &&
Objects.equals(email, user.email);
}
@Override
public int hashCode() {
return Objects.hash(id, name, email);
}
}

Lombokあり(簡潔なコード):

@Data
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// @Dataアノテーションにより、以下が自動生成される:
// - getter/setter
// - toString()
// - equals()/hashCode()
// - コンストラクタ(finalフィールドのみ)
}
  • @Data: getter、setter、toString、equals、hashCodeをまとめて生成
  • @Getter/@Setter: getter/setterメソッドを生成
  • @ToString: toStringメソッドを生成
  • @EqualsAndHashCode: equals/hashCodeメソッドを生成
  • @NoArgsConstructor: 引数なしコンストラクタを生成
  • @AllArgsConstructor: 全フィールドを引数に持つコンストラクタを生成
  • @RequiredArgsConstructor: finalフィールドのみを引数に持つコンストラクタを生成
  • @Builder: Builderパターンを実装
  • @Slf4j: SLF4Jロガーを生成(log変数が使用可能)

用途: @Getter@Setter@ToString@EqualsAndHashCode@RequiredArgsConstructorをまとめて適用

実践例:

@Data
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// 以下のコードが自動生成される:
// - getter/setter
// - toString()
// - equals()/hashCode()
// - 全フィールドを引数に持つコンストラクタ(finalフィールドのみ)
}

⚠️ 重要な注意点: 双方向の関連がある場合の@Exclude

もしあなたが@Data(または@ToString@EqualsAndHashCode)を使い、かつ「双方向(親子両方から参照できる)」の関連を作るなら、手動で@Excludeを入れるのは必須の儀式だと思ってください。

問題のある例(無限ループが発生):

// Userエンティティ
@Entity
@Table(name = "users")
@Data // ⚠️ 問題: 双方向の関連がある場合、無限ループが発生する可能性
@NoArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Order> orders = new ArrayList<>(); // ⚠️ OrderからもUserを参照
}
// Orderエンティティ
@Entity
@Table(name = "orders")
@Data // ⚠️ 問題: 双方向の関連がある場合、無限ループが発生する可能性
@NoArgsConstructor
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "user_id")
private User user; // ⚠️ UserからもOrderを参照(双方向)
private BigDecimal amount;
}
// 問題: toString()やequals()/hashCode()が無限ループを引き起こす
// User.toString() → Order.toString() → User.toString() → ...

解決方法: @Excludeを使用

// Userエンティティ
@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
@ToString(exclude = "orders") // ✅ ordersフィールドをtoStringから除外
@EqualsAndHashCode(exclude = "orders") // ✅ ordersフィールドをequals/hashCodeから除外
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Order> orders = new ArrayList<>();
}
// Orderエンティティ
@Entity
@Table(name = "orders")
@Data
@NoArgsConstructor
@ToString(exclude = "user") // ✅ userフィールドをtoStringから除外
@EqualsAndHashCode(exclude = "user") // ✅ userフィールドをequals/hashCodeから除外
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
private BigDecimal amount;
}

なぜ@Excludeが必須なのか:

  1. 無限ループの防止: @ToStringが双方向の関連フィールドを含むと、循環参照によりスタックオーバーフローが発生
  2. パフォーマンスの問題: @EqualsAndHashCodeが双方向の関連フィールドを含むと、計算コストが膨大になる
  3. 予期しない動作: 双方向の関連を含むequals()/hashCode()は、エンティティの管理状態によって予期しない動作を引き起こす可能性がある

ベストプラクティス:

  • 双方向の関連フィールドは常に@Excludeで除外する
  • IDフィールドのみを使用してequals()/hashCode()を計算する(推奨)
// より安全な実装例
@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
@ToString(exclude = "orders")
@EqualsAndHashCode(of = "id") // ✅ IDフィールドのみを使用
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Order> orders = new ArrayList<>();
}

用途: getter/setterメソッドを自動生成

実践例:

@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Getter // getterのみ生成
private Long id;
@Getter
@Setter
private String name;
@Getter
@Setter
private String email;
}

用途: ビルダーパターンを自動生成

実践例:

@Builder
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// 使用例
// User user = User.builder()
// .name("John")
// .email("john@example.com")
// .build();
}

@NoArgsConstructor / @AllArgsConstructor ★★★★☆

Section titled “@NoArgsConstructor / @AllArgsConstructor ★★★★☆”

用途: コンストラクタを自動生成

実践例:

@Entity
@NoArgsConstructor // 引数なしコンストラクタ
@AllArgsConstructor // 全フィールドを引数に持つコンストラクタ
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
}

用途: ロガー(log)を自動生成

実践例:

@Service
@Slf4j
public class UserService {
public void createUser(UserCreateRequest request) {
log.info("Creating user: {}", request.getName());
try {
// ユーザー作成処理
log.debug("User created successfully");
} catch (Exception e) {
log.error("Failed to create user", e);
throw e;
}
}
}

その他の重要なアノテーション

Section titled “その他の重要なアノテーション”

用途: メソッドがスーパークラスまたはインターフェースのメソッドをオーバーライドしていることを明示

@Overrideアノテーションを使用することで、以下のメリットがあります:

  • コンパイル時の検証: オーバーライドの誤りをコンパイル時に検出できる
  • 可読性の向上: コードを読む人が、このメソッドがオーバーライドであることをすぐに理解できる
  • リファクタリングの安全性: 親クラスのメソッド名を変更した際に、エラーとして検出できる
  • タイポの防止: メソッド名のタイポをコンパイル時に検出できる
// インターフェースの定義
public interface UserServiceInterface {
User findById(Long id);
void save(User user);
}
// 実装クラス
public class UserService implements UserServiceInterface {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
// @Overrideを付けることで、インターフェースのメソッドを実装していることを明示
@Override
public User findById(Long id) {
return userRepository.findById(id).orElse(null);
}
@Override
public void save(User user) {
userRepository.save(user);
}
}
// 継承の場合
public class Animal {
public void makeSound() {
System.out.println("Some sound");
}
}
public class Dog extends Animal {
// @Overrideを付けることで、親クラスのメソッドをオーバーライドしていることを明示
@Override
public void makeSound() {
System.out.println("Woof! Woof!");
}
}

1. オーバーライドするメソッドには必ず@Overrideを付ける

// ✅ 良い例: @Overrideを付けて明示的にオーバーライドを表明
public class UserService implements UserServiceInterface {
@Override
public User findById(Long id) {
return userRepository.findById(id).orElse(null);
}
}
// ❌ 悪い例: @Overrideがないと、タイポに気づけない
public class UserService implements UserServiceInterface {
// findByIdのタイポだが、コンパイルエラーにならない
public User findByIdd(Long id) { // 新しいメソッドとして扱われる
return userRepository.findById(id).orElse(null);
}
}

2. オーバーライドの条件を満たしているか確認する

public class Animal {
public void makeSound() { }
protected String getName() { return ""; }
}
public class Dog extends Animal {
// ✅ 正しい: メソッド名、引数、戻り値が一致
@Override
public void makeSound() {
System.out.println("Woof");
}
// ✅ 正しい: アクセス修飾子をpublicに変更可能
@Override
public String getName() {
return "Dog";
}
// ❌ エラー: 引数が異なる(これはオーバーロード)
// @Override
// public void makeSound(String volume) { }
}

3. 親クラスのメソッドを呼び出す場合はsuperを使用

public class Animal {
public void display() {
System.out.println("Animal");
}
}
public class Dog extends Animal {
@Override
public void display() {
super.display(); // 親クラスのメソッドを先に呼び出す
System.out.println("Dog");
}
}

1. @Overrideを付けずにオーバーライドする

// ❌ アンチパターン: @Overrideがないと、タイポに気づけない
public class UserService implements UserServiceInterface {
// findByIdのタイポだが、コンパイルエラーにならない
public User findByIdd(Long id) { // 新しいメソッドとして扱われる
return userRepository.findById(id).orElse(null);
}
}
// ✅ 正しい: @Overrideを付けると、タイポがコンパイルエラーになる
public class UserService implements UserServiceInterface {
@Override
public User findById(Long id) { // インターフェースのメソッドを正しく実装
return userRepository.findById(id).orElse(null);
}
}

2. オーバーロードとオーバーライドを混同する

// ❌ アンチパターン: オーバーロードに@Overrideを付けようとする
public class Animal {
public void makeSound() { }
}
public class Dog extends Animal {
@Override
public void makeSound() { } // ✅ これは正しい
// ❌ エラー: 引数が異なるため、これはオーバーロード(オーバーライドではない)
// @Override
// public void makeSound(String volume) { }
}
// ✅ 正しい: オーバーロードには@Overrideを付けない
public class Dog extends Animal {
@Override
public void makeSound() { } // オーバーライド
public void makeSound(String volume) { } // オーバーロード(@Override不要)
}

3. オーバーライドの条件を満たしていないメソッドに@Overrideを付ける

// ❌ アンチパターン: 戻り値の型が異なる
public class Animal {
public String getName() { return ""; }
}
public class Dog extends Animal {
// ❌ エラー: 戻り値の型が異なるため、オーバーライドではない
// @Override
// public int getName() { return 0; }
}
// ✅ 正しい: 戻り値の型が一致している
public class Dog extends Animal {
@Override
public String getName() { return "Dog"; }
}

用途: 非推奨のメソッドやクラスにマーク

@Deprecatedアノテーションを使用することで、以下のメリットがあります:

  • 段階的な移行: 既存のコードを壊さずに、新しいAPIへの移行を促せる
  • 警告の表示: IDEやコンパイラが非推奨の使用に対して警告を表示する
  • ドキュメント化: どのメソッドが非推奨で、代替手段が何かを明確にできる
  • 後方互換性の維持: 既存のコードを壊さずに、APIを改善できる
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
// @Deprecated: このメソッドは非推奨。findById()を使用してください
@Deprecated
public User findUserById(Long id) {
// 既存のコードとの互換性のために残しているが、新しいコードでは使用しない
return findById(id);
}
// 新しい推奨メソッド
public User findById(Long id) {
return userRepository.findById(id).orElse(null);
}
// @Deprecated with since and forRemoval
// since: 非推奨になったバージョン
// forRemoval: 将来削除予定かどうか
@Deprecated(since = "2.0", forRemoval = true)
public void oldMethod() {
// このメソッドは将来削除される予定
}
}
// クラス全体を非推奨にする場合
@Deprecated
public class OldUserService {
// このクラス全体が非推奨
}

1. @Deprecatedと一緒にJavaDocコメントを追加する

@Service
public class UserService {
/**
* ユーザーをIDで検索します。
*
* @deprecated このメソッドは非推奨です。{@link #findById(Long)}を使用してください。
* @param id ユーザーID
* @return ユーザー
*/
@Deprecated
public User findUserById(Long id) {
return findById(id);
}
/**
* ユーザーをIDで検索します(推奨)。
*
* @param id ユーザーID
* @return ユーザー
*/
public User findById(Long id) {
return userRepository.findById(id).orElse(null);
}
}

2. 代替手段を明確に示す

@Service
public class UserService {
// ❌ 悪い例: 代替手段が不明確
@Deprecated
public User findUserById(Long id) {
return findById(id);
}
// ✅ 良い例: JavaDocで代替手段を明確に示す
/**
* ユーザーをIDで検索します。
*
* @deprecated このメソッドは非推奨です。代わりに {@link #findById(Long)} を使用してください。
* 移行例: findUserById(id) → findById(id)
*
* @param id ユーザーID
* @return ユーザー
*/
@Deprecated
public User findUserById(Long id) {
return findById(id);
}
public User findById(Long id) {
return userRepository.findById(id).orElse(null);
}
}

3. sinceとforRemovalを適切に使用する

@Service
public class UserService {
// 段階的な非推奨化
// since: 非推奨になったバージョン
// forRemoval: 将来削除予定かどうか
@Deprecated(since = "2.0", forRemoval = true)
public void oldMethod() {
// このメソッドはバージョン2.0で非推奨になり、将来削除される予定
}
@Deprecated(since = "2.1")
public void deprecatedButNotRemoved() {
// このメソッドは非推奨だが、削除予定はない
}
}

4. 非推奨メソッドの実装を新しいメソッドに委譲する

@Service
public class UserService {
// ✅ 良い例: 非推奨メソッドは新しいメソッドに委譲
@Deprecated
public User findUserById(Long id) {
return findById(id); // 新しいメソッドに委譲
}
public User findById(Long id) {
return userRepository.findById(id).orElse(null);
}
}

1. @Deprecatedを付けただけで、代替手段を示さない

// ❌ アンチパターン: 代替手段が不明確
@Service
public class UserService {
@Deprecated
public User findUserById(Long id) {
// 何を使えばいいのかわからない
return userRepository.findById(id).orElse(null);
}
}
// ✅ 正しい: JavaDocで代替手段を明確に示す
@Service
public class UserService {
/**
* @deprecated このメソッドは非推奨です。{@link #findById(Long)}を使用してください。
*/
@Deprecated
public User findUserById(Long id) {
return findById(id);
}
public User findById(Long id) {
return userRepository.findById(id).orElse(null);
}
}

2. 非推奨メソッドを削除せずに放置する

// ❌ アンチパターン: 非推奨メソッドを長期間放置
@Service
public class UserService {
@Deprecated(since = "1.0", forRemoval = true) // 削除予定だが、何年も放置
public User findUserById(Long id) {
return findById(id);
}
}
// ✅ 正しい: 適切なタイミングで削除する
// 1. 非推奨をマーク
// 2. 十分な移行期間を設ける(例: 1-2バージョン)
// 3. 削除予定を通知
// 4. 削除

3. 非推奨メソッドの実装を変更する

// ❌ アンチパターン: 非推奨メソッドの実装を変更
@Service
public class UserService {
@Deprecated
public User findUserById(Long id) {
// 非推奨メソッドの実装を変更してしまうと、既存のコードが壊れる可能性がある
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException()); // 動作が変わってしまう
}
}
// ✅ 正しい: 非推奨メソッドは新しいメソッドに委譲し、実装を変更しない
@Service
public class UserService {
@Deprecated
public User findUserById(Long id) {
return findById(id); // 常に新しいメソッドに委譲
}
public User findById(Long id) {
return userRepository.findById(id).orElse(null);
}
}

4. 新しいコードで非推奨メソッドを使用する

// ❌ アンチパターン: 新しいコードで非推奨メソッドを使用
@Service
public class OrderService {
private final UserService userService;
public OrderService(UserService userService) {
this.userService = userService;
}
public Order createOrder(Long userId) {
// 警告: 非推奨メソッドを使用している
User user = userService.findUserById(userId); // ❌ 非推奨メソッド
// ...
}
}
// ✅ 正しい: 新しいコードでは推奨メソッドを使用
@Service
public class OrderService {
private final UserService userService;
public OrderService(UserService userService) {
this.userService = userService;
}
public Order createOrder(Long userId) {
User user = userService.findById(userId); // ✅ 推奨メソッド
// ...
}
}

用途: 設定クラスであることを示す

@Configurationアノテーションを使用することで、以下のメリットがあります:

  • Bean定義の集約: 複数のBean定義を1つのクラスに集約できる
  • 設定の一元管理: アプリケーションの設定を1箇所で管理できる
  • 条件付きBean定義: @ConditionalOnPropertyなどと組み合わせて、条件付きでBeanを定義できる
  • 依存関係の明確化: Bean間の依存関係を明確に定義できる
  • テストの容易性: テスト用の設定クラスを簡単に作成できる
@Configuration
public class AppConfig {
// RestTemplateのBean定義
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
return restTemplate;
}
// ObjectMapperのBean定義
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
return mapper;
}
// 条件付きBean定義
@Bean
@ConditionalOnProperty(name = "app.feature.enabled", havingValue = "true")
public FeatureService featureService() {
return new FeatureService();
}
// 他のBeanに依存するBean定義
@Bean
public ApiClient apiClient(RestTemplate restTemplate, ObjectMapper objectMapper) {
return new ApiClient(restTemplate, objectMapper);
}
}

1. 設定クラスは機能ごとに分離する

// ✅ 良い例: 機能ごとに設定クラスを分離
@Configuration
public class WebConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@Configuration
public class DatabaseConfig {
@Bean
public DataSource dataSource() {
// データソースの設定
return new HikariDataSource();
}
}
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager();
}
}
// ❌ 悪い例: すべての設定を1つのクラスに詰め込む
@Configuration
public class AllConfig {
@Bean
public RestTemplate restTemplate() { }
@Bean
public DataSource dataSource() { }
@Bean
public CacheManager cacheManager() { }
// ... 100個以上のBean定義
}

2. @ConfigurationPropertiesと組み合わせて使用する

// 設定プロパティクラス
@ConfigurationProperties(prefix = "app")
@Data
public class AppProperties {
private String apiUrl;
private int timeout;
private boolean enabled;
}
// 設定クラス
@Configuration
@EnableConfigurationProperties(AppProperties.class)
public class AppConfig {
@Bean
public ApiClient apiClient(AppProperties appProperties) {
return new ApiClient(appProperties.getApiUrl(), appProperties.getTimeout());
}
}

3. プロファイル別の設定クラスを作成する

// 開発環境用の設定
@Configuration
@Profile("dev")
public class DevConfig {
@Bean
public DataSource dataSource() {
// 開発環境用のデータソース
return new HikariDataSource();
}
}
// 本番環境用の設定
@Configuration
@Profile("prod")
public class ProdConfig {
@Bean
public DataSource dataSource() {
// 本番環境用のデータソース
return new HikariDataSource();
}
}

4. @Conditionalアノテーションで条件付きBean定義を行う

@Configuration
public class ConditionalConfig {
// プロパティが有効な場合のみBeanを作成
@Bean
@ConditionalOnProperty(name = "app.feature.enabled", havingValue = "true")
public FeatureService featureService() {
return new FeatureService();
}
// クラスが存在する場合のみBeanを作成
@Bean
@ConditionalOnClass(name = "com.example.ExternalLibrary")
public ExternalService externalService() {
return new ExternalService();
}
}

1. @Configurationを付けずに@Beanメソッドを定義する

// ❌ アンチパターン: @Configurationがないと、Bean間の依存関係が正しく解決されない
public class AppConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public ApiClient apiClient() {
// 問題: restTemplate()が毎回新しいインスタンスを作成してしまう
return new ApiClient(restTemplate()); // シングルトンにならない
}
}
// ✅ 正しい: @Configurationを付けると、Bean間の依存関係が正しく解決される
@Configuration
public class AppConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public ApiClient apiClient(RestTemplate restTemplate) {
// 正しく: restTemplate()はシングルトンとして注入される
return new ApiClient(restTemplate);
}
}

2. 設定クラスにビジネスロジックを含める

// ❌ アンチパターン: 設定クラスにビジネスロジックを含める
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
UserService service = new UserService();
// 問題: ビジネスロジックを設定クラスに書くべきではない
service.processUsers(); // ビジネスロジック
return service;
}
}
// ✅ 正しい: 設定クラスはBean定義のみを行う
@Configuration
public class AppConfig {
@Bean
public UserService userService(UserRepository userRepository) {
// Bean定義のみ
return new UserService(userRepository);
}
}
// ビジネスロジックはサービスクラスに
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void processUsers() {
// ビジネスロジック
}
}

3. すべての設定を1つのクラスに詰め込む

// ❌ アンチパターン: すべての設定を1つのクラスに詰め込む
@Configuration
public class AllConfig {
@Bean
public RestTemplate restTemplate() { }
@Bean
public DataSource dataSource() { }
@Bean
public CacheManager cacheManager() { }
@Bean
public EmailService emailService() { }
@Bean
public PaymentService paymentService() { }
// ... 100個以上のBean定義
// 問題: 保守性が低く、テストが困難
}
// ✅ 正しい: 機能ごとに設定クラスを分離
@Configuration
public class WebConfig {
@Bean
public RestTemplate restTemplate() { }
}
@Configuration
public class DatabaseConfig {
@Bean
public DataSource dataSource() { }
}

4. @Beanメソッド内でnewを直接使用する(依存性注入を使わない)

// ❌ アンチパターン: 依存関係を直接newで解決
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
// 問題: 依存関係を直接newで解決している
UserRepository repository = new UserRepositoryImpl();
return new UserService(repository);
}
}
// ✅ 正しい: 依存性注入を使用
@Configuration
public class AppConfig {
@Bean
public UserService userService(UserRepository userRepository) {
// 依存関係をSpringに委譲
return new UserService(userRepository);
}
}

用途: メソッドの戻り値をSpring Beanとして登録

@Beanアノテーションを使用することで、以下のメリットがあります:

  • サードパーティライブラリの統合: Springが管理していないクラスをBeanとして登録できる
  • カスタム設定: Beanの作成時にカスタム設定を適用できる
  • 条件付きBean定義: 条件に応じてBeanを作成できる
  • 複数の実装: 同じ型の複数のBeanを定義できる(名前で区別)
  • ライフサイクル管理: Springのライフサイクル(初期化、破棄)を利用できる
@Configuration
public class AppConfig {
// 基本的なBean定義
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
return restTemplate;
}
// 名前を指定したBean定義
@Bean(name = "customRestTemplate")
public RestTemplate customRestTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setConnectTimeout(Duration.ofSeconds(10));
restTemplate.setReadTimeout(Duration.ofSeconds(30));
return restTemplate;
}
// @Primaryで優先Beanを指定
@Bean
@Primary
public RestTemplate primaryRestTemplate() {
return new RestTemplate();
}
// 他のBeanに依存するBean定義
@Bean
public ApiClient apiClient(RestTemplate restTemplate) {
return new ApiClient(restTemplate);
}
// 初期化・破棄メソッドを指定
@Bean(initMethod = "init", destroyMethod = "cleanup")
public ResourceManager resourceManager() {
return new ResourceManager();
}
// 条件付きBean定義
@Bean
@ConditionalOnProperty(name = "app.cache.enabled", havingValue = "true")
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager();
}
}

1. Bean名は明確で一貫性のあるものにする

@Configuration
public class AppConfig {
// ✅ 良い例: 明確なBean名
@Bean(name = "httpRestTemplate")
public RestTemplate httpRestTemplate() {
return new RestTemplate();
}
@Bean(name = "httpsRestTemplate")
public RestTemplate httpsRestTemplate() {
return new RestTemplate();
}
// ❌ 悪い例: 曖昧なBean名
@Bean(name = "restTemplate1")
public RestTemplate restTemplate1() {
return new RestTemplate();
}
}

2. 依存関係はメソッド引数で注入する

@Configuration
public class AppConfig {
// ✅ 良い例: 依存関係をメソッド引数で注入
@Bean
public ApiClient apiClient(RestTemplate restTemplate, ObjectMapper objectMapper) {
return new ApiClient(restTemplate, objectMapper);
}
// ❌ 悪い例: 依存関係を直接newで解決
@Bean
public ApiClient apiClient() {
RestTemplate restTemplate = new RestTemplate(); // 問題: シングルトンにならない
return new ApiClient(restTemplate);
}
}

3. @PrimaryでデフォルトのBeanを指定する

@Configuration
public class AppConfig {
// デフォルトのRestTemplate(@Primary)
@Bean
@Primary
public RestTemplate restTemplate() {
return new RestTemplate();
}
// カスタムのRestTemplate(名前で指定して使用)
@Bean(name = "customRestTemplate")
public RestTemplate customRestTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setConnectTimeout(Duration.ofSeconds(30));
return restTemplate;
}
}
// 使用例
@Service
public class UserService {
// @Primaryが付いたBeanが自動注入される
private final RestTemplate restTemplate;
public UserService(RestTemplate restTemplate) {
this.restTemplate = restTemplate; // デフォルトのRestTemplateが注入される
}
}
@Service
public class ApiService {
// 名前を指定してカスタムのRestTemplateを注入
@Autowired
@Qualifier("customRestTemplate")
private RestTemplate restTemplate;
}

4. 条件付きBean定義を使用する

@Configuration
public class AppConfig {
// プロパティが有効な場合のみBeanを作成
@Bean
@ConditionalOnProperty(name = "app.feature.enabled", havingValue = "true")
public FeatureService featureService() {
return new FeatureService();
}
// クラスが存在する場合のみBeanを作成
@Bean
@ConditionalOnClass(name = "com.example.ExternalLibrary")
public ExternalService externalService() {
return new ExternalService();
}
// プロファイルが有効な場合のみBeanを作成
@Bean
@Profile("dev")
public DevService devService() {
return new DevService();
}
}

5. 初期化・破棄メソッドを適切に使用する

public class ResourceManager {
public void init() {
// 初期化処理
System.out.println("ResourceManager initialized");
}
public void cleanup() {
// クリーンアップ処理
System.out.println("ResourceManager cleaned up");
}
}
@Configuration
public class AppConfig {
@Bean(initMethod = "init", destroyMethod = "cleanup")
public ResourceManager resourceManager() {
return new ResourceManager();
}
}

1. @Configurationクラス内で@Beanメソッドを直接呼び出す

@Configuration
public class AppConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
// ❌ アンチパターン: @Beanメソッドを直接呼び出す
@Bean
public ApiClient apiClient() {
// 問題: restTemplate()が毎回新しいインスタンスを作成してしまう
return new ApiClient(restTemplate()); // シングルトンにならない
}
}
// ✅ 正しい: 依存関係をメソッド引数で注入
@Configuration
public class AppConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public ApiClient apiClient(RestTemplate restTemplate) {
// 正しく: restTemplateはシングルトンとして注入される
return new ApiClient(restTemplate);
}
}

2. 同じ型のBeanを複数定義する際に名前を付けない

@Configuration
public class AppConfig {
// ❌ アンチパターン: 同じ型のBeanを複数定義する際に名前を付けない
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public RestTemplate customRestTemplate() {
// 問題: どちらが注入されるか不明確
return new RestTemplate();
}
}
// ✅ 正しい: 名前を明確に指定する
@Configuration
public class AppConfig {
@Bean(name = "defaultRestTemplate")
@Primary
public RestTemplate defaultRestTemplate() {
return new RestTemplate();
}
@Bean(name = "customRestTemplate")
public RestTemplate customRestTemplate() {
return new RestTemplate();
}
}

3. Bean定義メソッド内でビジネスロジックを実行する

@Configuration
public class AppConfig {
// ❌ アンチパターン: Bean定義メソッド内でビジネスロジックを実行
@Bean
public UserService userService() {
UserService service = new UserService();
// 問題: Bean定義時にビジネスロジックを実行すべきではない
service.processAllUsers(); // ビジネスロジック
return service;
}
}
// ✅ 正しい: Bean定義はシンプルに保つ
@Configuration
public class AppConfig {
@Bean
public UserService userService(UserRepository userRepository) {
// Bean定義のみ
return new UserService(userRepository);
}
}
// ビジネスロジックはサービスクラスに
@Service
public class UserService {
public void processAllUsers() {
// ビジネスロジック
}
}

4. 状態を持つBeanを@Beanメソッドで定義する

@Configuration
public class AppConfig {
// ❌ アンチパターン: 状態を持つBeanを@Beanメソッドで定義
private int counter = 0; // 問題: 状態を持つ
@Bean
public CounterService counterService() {
counter++; // 問題: 状態を変更している
return new CounterService(counter);
}
}
// ✅ 正しい: 状態を持たないBean定義
@Configuration
public class AppConfig {
@Bean
public CounterService counterService() {
// 状態を持たない
return new CounterService();
}
}

5. @Beanメソッドをstaticにする(必要な場合を除く)

@Configuration
public class AppConfig {
// ❌ アンチパターン: 通常はstaticにしない
@Bean
public static RestTemplate restTemplate() {
// 問題: 他のBeanに依存できない
return new RestTemplate();
}
}
// ✅ 正しい: 通常はインスタンスメソッドとして定義
@Configuration
public class AppConfig {
@Bean
public RestTemplate restTemplate() {
// 他のBeanに依存できる
return new RestTemplate();
}
@Bean
public ApiClient apiClient(RestTemplate restTemplate) {
// restTemplateに依存できる
return new ApiClient(restTemplate);
}
}

アノテーションの組み合わせパターン

Section titled “アノテーションの組み合わせパターン”
// REST APIコントローラー
@RestController
@RequestMapping("/api/users")
@Validated
@Slf4j
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
log.info("Getting user: {}", id);
UserDTO user = userService.findById(id);
return ResponseEntity.ok(user);
}
}
// サービス層
@Service
@Transactional
@Slf4j
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Transactional(readOnly = true)
public UserDTO findById(Long id) {
log.debug("Finding user by id: {}", id);
User user = userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found: " + id));
return UserDTO.fromEntity(user);
}
}
// エンティティ
@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String name;
@Column(unique = true, nullable = false)
private String email;
@Column(name = "created_at", updatable = false)
@CreationTimestamp
private LocalDateTime createdAt;
}

AOP(Aspect Oriented Programming)の専門用語

Section titled “AOP(Aspect Oriented Programming)の専門用語”

**AOP(Aspect Oriented Programming、アスペクト指向プログラミング)**とは、本編のコードとは関係ない「お決まりの処理」(例:ロギング、トランザクション管理、セキュリティチェックなど)を、外側に追い出して自動で合体させる技術です。

RailsでいうBeforeActionに相当する機能です。

AOPは、動的プロキシを使用して、実行時にコードを生成します。具体的には、コンストラクタなどのボイラープレートテンプレートを作成し、実行時にメソッド呼び出しをインターセプトして、追加処理を実行します。

1. アスペクト(共通処理)を定義
2. ポイントカット(適用箇所)を指定
3. 実行時に動的プロキシが発動
4. メソッド呼び出しをインターセプト
5. アドバイス(追加処理)を実行
6. 元のメソッドを実行

アスペクトは、まとまった共通処理を定義する単位です。

例: 盛り付けルール

@Aspect
@Component
public class LoggingAspect {
// このアスペクトは、ロギングという共通処理を定義
// 例: すべてのサービスメソッドの実行前後にログを出力する
}

アドバイスは、アスペクトの中の具体的な中身、つまり「いつ」「何を」実行するかを定義します。

例: パセリを乗せる

@Aspect
@Component
public class LoggingAspect {
// @Before: メソッド実行前にログを出力
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
log.info("Before method: {}", joinPoint.getSignature());
// 例: パセリを乗せる(ログを出力)
}
// @After: メソッド実行後にログを出力
@After("execution(* com.example.service.*.*(..))")
public void logAfter(JoinPoint joinPoint) {
log.info("After method: {}", joinPoint.getSignature());
}
}

ジョインポイントは、アドバイスを差し込めるタイミングです。メソッドの実行前、実行後、例外発生時など、プログラムの実行における特定のポイントを指します。

例: 料理が完成した瞬間

// ジョインポイントの例:
// - メソッド実行前 (Before)
// - メソッド実行後 (After)
// - メソッド実行後(正常終了時) (AfterReturning)
// - 例外発生時 (AfterThrowing)
// - メソッド実行の前後 (Around)
@Aspect
@Component
public class LoggingAspect {
// メソッド実行前(ジョインポイント: Before)
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
// メソッド実行前に実行される
}
// メソッド実行後(ジョインポイント: After)
@After("execution(* com.example.service.*.*(..))")
public void logAfter(JoinPoint joinPoint) {
// メソッド実行後に実行される(正常終了・例外発生の両方)
}
}

ポイントカットは、どこにアドバイスを差し込むかのルールを定義します。どのクラス、どのメソッドにアドバイスを適用するかを指定します。

例: カレーの時だけ!

@Aspect
@Component
public class TransactionAspect {
// ポイントカット: サービス層のメソッドのみに適用
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {
// ポイントカットの定義(例: カレーの時だけ!)
}
// アドバイス: ポイントカットにマッチするメソッドに適用
@Around("serviceMethods()")
public Object aroundServiceMethod(ProceedingJoinPoint joinPoint) throws Throwable {
// 例: カレーの時だけトランザクションを開始
return joinPoint.proceed();
}
}
// アスペクトの定義
@Aspect
@Component
@Slf4j
public class LoggingAspect {
// ポイントカット: サービス層のすべてのメソッド
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {
}
// アドバイス: メソッド実行前にログを出力(ジョインポイント: Before)
@Before("serviceMethods()")
public void logBefore(JoinPoint joinPoint) {
log.info("Before method: {} with args: {}",
joinPoint.getSignature(),
Arrays.toString(joinPoint.getArgs()));
}
// アドバイス: メソッド実行後にログを出力(ジョインポイント: AfterReturning)
@AfterReturning(pointcut = "serviceMethods()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
log.info("After method: {} returned: {}",
joinPoint.getSignature(), result);
}
// アドバイス: 例外発生時にログを出力(ジョインポイント: AfterThrowing)
@AfterThrowing(pointcut = "serviceMethods()", throwing = "ex")
public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
log.error("Exception in method: {}", joinPoint.getSignature(), ex);
}
// アドバイス: メソッド実行の前後で処理(ジョインポイント: Around)
@Around("serviceMethods()")
public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
try {
Object result = joinPoint.proceed(); // 元のメソッドを実行
long duration = System.currentTimeMillis() - start;
log.info("Method: {} took {}ms", joinPoint.getSignature(), duration);
return result;
} catch (Exception e) {
long duration = System.currentTimeMillis() - start;
log.error("Method: {} failed after {}ms", joinPoint.getSignature(), duration, e);
throw e;
}
}
}
// 使用例: サービスクラス(何も変更不要)
@Service
public class UserService {
public User findById(Long id) {
// このメソッドは自動的にログ出力される(AOPにより)
return userRepository.findById(id).orElseThrow();
}
}

Spring AOPは、実行時に動的プロキシを生成して、メソッド呼び出しをインターセプトします。

具体例:

  1. UserServiceのメソッドを呼び出す
  2. Springが動的プロキシ(UserServiceのプロキシ)を生成
  3. プロキシがメソッド呼び出しをインターセプト
  4. アドバイス(ログ出力など)を実行
  5. 元のUserServiceのメソッドを実行
  6. アドバイス(ログ出力など)を実行
  7. 結果を返す

Spring Bootの@Transactionalアノテーションも、AOPの仕組みを使用して実装されています。

@Service
public class UserService {
// @Transactionalアノテーション
// → AOPにより、このメソッド実行時に自動的にトランザクションを開始・終了
@Transactional
public User createUser(UserCreateRequest request) {
User user = new User();
user.setName(request.getName());
return userRepository.save(user);
// メソッド終了時に自動的にコミット(例外発生時はロールバック)
}
}

動作の流れ:

  1. createUserメソッドを呼び出す
  2. AOPがメソッド呼び出しをインターセプト
  3. トランザクションを開始(アドバイス: Before)
  4. 元のメソッドを実行
  5. トランザクションをコミット(アドバイス: AfterReturning)またはロールバック(アドバイス: AfterThrowing)

★★★★★(必須レベル):

  • @Service@Repository@Controller@RestController
  • @Autowired@Component
  • @GetMapping@PostMapping@PathVariable@RequestParam@RequestBody
  • @Entity@Id@GeneratedValue
  • @Valid@NotNull@NotBlank
  • @Transactional
  • @Data@Slf4j(Lombok)

★★★★☆(推奨レベル):

  • @Value@RequestMapping
  • @PutMapping@DeleteMapping
  • @Table@Column
  • @Size@Email
  • @Getter@Setter@Builder(Lombok)
  • @Configuration@Bean

★★★☆☆(中頻度):

  • @PatchMapping
  • @Deprecated
  • @NoArgsConstructor@AllArgsConstructor(Lombok)
  1. 適切なアノテーションの選択: より具体的なアノテーションを優先(@Service > @Component
  2. コンストラクタインジェクション: @Autowiredのフィールドインジェクションは避ける
  3. バリデーション: @Validと組み合わせてリクエストデータを検証
  4. トランザクション管理: サービス層で@Transactionalを使用
  5. ロギング: Lombokの@Slf4jを使用してロガーを自動生成

これらのアノテーションを適切に使用することで、保守性の高いSpring Bootアプリケーションを構築できます。