SpringのComponent、Service、Repositoryの違いを解説する記事のアイキャッチ。Java道場風の空間で若手エンジニアがBeanの役割分担を学ぶイラスト。

Spring

Springの@Component・@Service・@Repositoryの違い|どれを付けるべき?

AIによる要約

@Service@Repositoryは、どちらも@Componentを土台にしたステレオタイプで、コンポーネントスキャンによりBean登録されます。違いは主に役割の表現です。業務処理はService、DBなどの永続化アクセスはRepository、どちらにも当てはまらない共通部品はComponentを基本にします。Repositoryにはデータアクセス例外変換の対象になる意味もあるため、全部をComponentへ統一しない方が設計意図を伝えられます。

新人SE
新人SE
どのアノテーションでもDIできるなら、全部@Componentで統一してもよいですか?
ポンコツSE
ポンコツSE
動作だけならBean登録できますが、コードを読む人に役割が伝わりません。クラスの責務に合わせて選ぶのが基本です。

Springのクラスへ@Component@Service@Repositoryのどれを付けるかは、新人がレビューでよく質問されるポイントです。実際、いずれもSpringのBeanとして検出され、コンストラクタDIできます。

しかし、同じように動くことと、同じ意味であることは別です。業務システムでは、障害調査や改修で初めて見るクラスを短時間で読むため、アノテーションも設計上の案内になります。

この記事のポイント

  • 3つともコンポーネントスキャンでBean登録できる
  • 業務のユースケースや判断は@Serviceを基本にする
  • 永続化やデータアクセスは@Repositoryを基本にする
  • 汎用部品や補助クラスは@Componentを検討する
  • Spring DataのRepositoryインターフェースは通常アノテーション不要

3つともSpringのBeanとして登録される

@Component
public class CsvLineParser {
}

@Service
public class OrderService {
}

@Repository
public class OrderQueryRepository {
}

@Service@Repositoryには、内部的に@Componentが付いています。そのため、Spring Bootのコンポーネントスキャン対象パッケージにあればBeanとして登録されます。

ここだけを見ると「全部@Componentでよい」と感じますが、専用アノテーションにはクラスの役割を伝える意味があります。フレームワークの追加処理やAOPの適用対象として利用される場合もあります。

@Serviceは業務処理の入口に付ける

@Service
@Transactional
public class OrderService {

    private final OrderRepository orderRepository;
    private final InventoryService inventoryService;

    public void place(OrderCommand command) {
        inventoryService.reserve(
                command.productId(),
                command.quantity());

        Order order = Order.create(command);
        orderRepository.save(order);
    }
}

Serviceには、注文する、承認する、解約するなど、利用者や業務の目的に対応する処理を置きます。複数Repositoryや外部サービスを組み合わせ、トランザクションの境界になることも多くあります。

単に「何か処理しているクラス」へ全部Serviceを付けるのではありません。日付の書式変換、CSVの1行解析、ハッシュ計算のように業務ユースケースではない補助処理なら、Componentや普通のJavaクラスの方が自然です。

@Repositoryはデータアクセスに付ける

@Repository
public class OrderQueryRepository {

    private final JdbcTemplate jdbcTemplate;

    public List<OrderSummary> findSummaries(
            LocalDate from,
            LocalDate to) {
        // SQLを実行して検索結果を返す
        return List.of();
    }
}

Repositoryは、DB、JPA、JDBCなどの永続化技術を隠し、保存・検索・削除を担当します。Serviceから見ると「注文を保存する」「期間内の注文を探す」というインターフェースになります。

@Repositoryが付いたクラスは、対応する仕組みが有効な環境では、JPAなどの技術固有例外をSpringのDataAccessException体系へ変換する対象になります。役割表示だけでなく、データアクセス層として扱う意味があります。

例外変換を目的に何でも@Repositoryへしない

外部API Clientやファイル出力クラスは、DBへアクセスしていないならRepositoryとは限りません。技術的な効果だけでなく、業務上どの境界を表すクラスかを優先します。

Spring DataのRepositoryインターフェースは通常アノテーション不要

public interface OrderRepository
        extends JpaRepository<Order, Long> {

    List<Order> findByStatus(OrderStatus status);
}

Spring Data JPAが実装を生成するRepositoryインターフェースには、通常@Repositoryを手動で付けなくても検出されます。付けても意味が重複することが多いため、既存プロジェクトの規約へ揃えます。

一方、JdbcTemplateやEntityManagerを使って自分で実装する検索クラスには、@Repositoryを付けると役割が明確です。

@Componentはどこで使うか

@Component
public class OrderCsvParser {

    public OrderRow parse(String line) {
        // CSV文字列をOrderRowへ変換する
        return new OrderRow();
    }
}

Componentは汎用的なステレオタイプです。ServiceやRepositoryの役割には当てはまらないが、設定値や別BeanをDIしたい補助クラスで使います。

  • CSV・固定長ファイルのParser
  • テンプレートを使うメール本文Builder
  • アプリ固有のID採番器
  • 複数クラスから使う変換・検証部品
  • バッチ処理の補助クラス

ただし、依存を持たない単純な計算クラスまで必ずBeanにする必要はありません。普通にnewして使える値オブジェクトや純粋関数的なクラスは、Springへ登録しない選択もあります。

判断基準を表で整理する

質問候補
業務のユースケースや判断を担当するか@Service
DBなどの永続化先へアクセスするか@Repository
どちらでもないアプリ共通部品か@Component
設定を作るメソッド群か@Configuration + @Beanを検討
状態を持つデータそのものかBean登録せず普通のクラスを検討

「DIしたいから何を付けるか」ではなく、「このクラスは何の責任を持つか」からアノテーションを選びます。

全部@Componentにした場合の困りごと

@Component
public class OrderManager {
    // DB検索
    // 在庫判定
    // メール送信
    // CSV出力
}

このクラス名とアノテーションだけでは、業務処理の入口なのか、データアクセスなのか、補助部品なのか判断できません。さらにManagerという曖昧な名前が重なると、改修時に責務を追うコストが増えます。

アノテーションを変えるだけで巨大クラスが直るわけではありませんが、Service・Repository・Componentを分ける検討が、責務整理のきっかけになります。

コンポーネントスキャンされない原因

正しいアノテーションを付けてもBeanが見つからない場合、パッケージ位置を確認します。Spring Bootは通常、@SpringBootApplicationがあるパッケージとその配下をスキャンします。

com.example.app
├── Application.java        // @SpringBootApplication
├── controller
├── service
└── repository             // 通常はスキャン対象

com.example.shared
└── parser                 // app配下でなければ対象外の可能性

対象外パッケージを使うなら@ComponentScanなどの設定を検討します。ただし、設定を増やす前に、アプリケーションの基点パッケージを上位へ置けないか確認します。

同じ型のBeanが複数ある場合

public interface NotificationSender {
    void send(String message);
}

@Component
public class MailNotificationSender
        implements NotificationSender {
}

@Component
public class SlackNotificationSender
        implements NotificationSender {
}

NotificationSenderが2つあるため、型だけでは注入先を決められません。この問題は@Serviceへ変えても解決しません。@Qualifier@Primary、または用途別インターフェースを検討します。

テストでも役割を意識する

対象主なテスト観点
Service業務分岐、Repository・外部Clientとの連携
RepositorySQL、JPAマッピング、DB制約
Component変換・解析・計算の入出力

役割が分かれていれば、Repositoryのテストへ業務判断を詰め込む、Serviceの単体テストで実DBまで毎回起動するといった混乱を避けやすくなります。

現場レビューでよくある指摘

// レビューコメント例
注文確定というユースケースを担当しているため、
@Componentではなく@Serviceで役割を示してください。

// レビューコメント例
JdbcTemplateでDB検索を行うクラスなので@Repositoryが適切です。
データアクセス例外の扱いも既存Repositoryへ揃えてください。

// レビューコメント例
このFormatterは依存を持たない単純な変換クラスです。
Spring Beanにする必要があるか、普通のJavaクラスでよいか確認してください。

提出前のセルフチェック

  • クラスの責務を一文で説明できるか
  • 業務ユースケースなら@Serviceを選んだか
  • 永続化アクセスなら@Repositoryを選んだか
  • 汎用部品なら@Componentまたは普通のクラスを検討したか
  • Spring Dataインターフェースへ不要な@Repositoryを足していないか
  • コンポーネントスキャン対象パッケージにあるか
  • 同じ型のBeanが複数ないか

Springの役割分担を体系的に学ぶ参考書

アノテーションの暗記ではなく、DI、AOP、トランザクション、データアクセス層までつなげて学ぶと、クラスの責務から判断できるようになります。

書籍「Spring徹底入門 第2版」の表紙
商品画像:Amazon.co.jp
PR Springを体系的に学ぶ若手向け

Spring徹底入門 第2版

株式会社NTTデータ (著), 株式会社NTTデータグループ (編集)

Spring Frameworkの主要機能を、実務レベルで整理する。

DI、MVC、データアクセス、テスト、セキュリティまで幅広く確認できます。ピンポイント検索から体系学習へ進みたい人向けです。

  • Springの全体像を整理したい
  • 現場で使う機能を体系的に学びたい

当サイトはAmazonアソシエイト・プログラムの参加者です。価格・在庫・配送条件はAmazonでご確認ください。

この記事とあわせて読みたい

まとめ

@Component@Service@RepositoryはいずれもBean登録できますが、表す役割が違います。業務処理はService、永続化アクセスはRepository、その他の共通部品はComponentを基本にします。

動くアノテーションを選ぶのではなく、クラスの責務を一文で説明してから選びます。Spring Data Repositoryの扱い、コンポーネントスキャン、複数Beanまで確認し、アノテーションと実装内容を一致させましょう。

-Spring
-, , , , , ,