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


Springのクラスへ@Component、@Service、@Repositoryのどれを付けるかは、新人がレビューでよく質問されるポイントです。実際、いずれもSpringのBeanとして検出され、コンストラクタDIできます。
しかし、同じように動くことと、同じ意味であることは別です。業務システムでは、障害調査や改修で初めて見るクラスを短時間で読むため、アノテーションも設計上の案内になります。
この記事のポイント
- 3つともコンポーネントスキャンでBean登録できる
- 業務のユースケースや判断は
@Serviceを基本にする - 永続化やデータアクセスは
@Repositoryを基本にする - 汎用部品や補助クラスは
@Componentを検討する - Spring DataのRepositoryインターフェースは通常アノテーション不要
実行用サンプル
3つのステレオタイプを付けたクラスと、Spring Data JPAのRepositoryが登録されることを比べます。 ソース全文、JUnitの検証コード、pom.xml、READMEを収録しています。JDK 17とMaven 3.9系で mvn test を実行します。Spring Boot 3.5.16のサンプルです。
3つともSpringのBeanとして登録される
OrderRegistrationTest.java の抜粋。登録したBeanと、Spring Dataのプロキシを確認する箇所
@Test
void ステレオタイプの三つのクラスとSpringDataのインターフェースがBeanになる() {
assertThat(context.getBeansOfType(OrderCsvParser.class)).hasSize(1);
assertThat(context.getBeansOfType(OrderService.class)).hasSize(1);
assertThat(context.getBeansOfType(OrderQueryRepository.class)).hasSize(1);
assertThat(context.getBeansOfType(OrderRow.class)).isEmpty();
assertThat(AopUtils.isAopProxy(context.getBean(OrderRepository.class))).isTrue();
}@Service と @Repository は、どちらも @Component を土台にしています。サンプルでは、3つのクラスを OrderSampleApplication の探索範囲に置き、それぞれが1件ずつ登録されることを確認します。
ここだけを見ると「全部@Componentでよい」と感じますが、専用アノテーションにはクラスの役割を伝える意味があります。フレームワークの追加処理やAOPの適用対象として利用される場合もあります。
@Serviceは業務処理の入口に付ける
OrderService.java の全文
package info.syachiku.registration.orders;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class OrderService {
private final OrderCsvParser parser;
private final OrderRepository orders;
private final OrderQueryRepository queries;
public OrderService(OrderCsvParser parser, OrderRepository orders, OrderQueryRepository queries) {
this.parser = parser;
this.orders = orders;
this.queries = queries;
}
@Transactional
public long place(String orderLine) {
var row = parser.parse(orderLine);
return orders.save(new OrderRecord(row)).getId();
}
@Transactional(readOnly = true)
public String findStatus(long id) {
return queries.findStatus(id);
}
}注文を登録する OrderService#place は、入力の変換とDB保存を順番に呼び、注文登録のトランザクションを始めます。変換処理とSQLは、それぞれ別のクラスへ任せます。
単に「何か処理しているクラス」へ全部Serviceを付けるのではありません。日付の書式変換、CSVの1行解析、ハッシュ計算のように業務ユースケースではない補助処理なら、Componentや普通のJavaクラスの方が自然です。
@Repositoryはデータアクセスに付ける
OrderQueryRepository.java の全文
package info.syachiku.registration.orders;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class OrderQueryRepository {
private final JdbcTemplate jdbc;
public OrderQueryRepository(JdbcTemplate jdbc) {
this.jdbc = jdbc;
}
public String findStatus(long id) {
return jdbc.queryForObject("select status from registered_order where id = ?", String.class, id);
}
}自作の OrderQueryRepository は、JdbcTemplate で注文IDに対応する状態を読みます。呼ぶ側はSQLや結果の取得方法を知る必要がありません。
@Repository は役割の目印で、対応する変換の仕組みが有効なら永続化例外を変換する対象にもなります。この例で該当行がない場合の EmptyResultDataAccessException は JdbcTemplate が返す例外です。アノテーション単独の効果とは分けて考えます。
例外変換を目的に何でも@Repositoryへしない
外部API Clientやファイル出力クラスは、DBへアクセスしていないならRepositoryとは限りません。技術的な効果だけでなく、業務上どの境界を表すクラスかを優先します。
Spring DataのRepositoryインターフェースは通常アノテーション不要
OrderRepository.java の全文
package info.syachiku.registration.orders;
import org.springframework.data.jpa.repository.JpaRepository;
public interface OrderRepository extends JpaRepository<OrderRecord, Long> {
}OrderRepository はアノテーションなしで、Spring Data JPAのRepository探索とFactoryがプロキシを登録します。通常のコンポーネントスキャンでインターフェースをインスタンス化するわけではありません。テストで取得したBeanがプロキシであることを確認します。
一方、JdbcTemplateやEntityManagerを使って自分で実装する検索クラスには、@Repositoryを付けると役割が明確です。
@Componentはどこで使うか
OrderCsvParser.java の全文
package info.syachiku.registration.orders;
import org.springframework.stereotype.Component;
@Component
public class OrderCsvParser {
public OrderRow parse(String line) {
String[] fields = line.split(",", -1);
if (fields.length != 2 || fields[0].isBlank() || fields[1].isBlank()) {
throw new IllegalArgumentException("注文番号と状態の2列が必要です");
}
return new OrderRow(fields[0], fields[1]);
}
}補助部品の OrderCsvParser は @Component で登録します。サンプルは引用符のない固定2列だけを対象とし、変換した値の OrderRow はBeanにしません。
- 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が複数ある場合
同じ型に一致するBeanが2件あれば、クラスの役割を @Service へ変更しても、単一注入の候補は1件に絞られません。選択ルールとList注入は末尾の @Qualifier と @Primary の記事で試せます。
テストでも役割を意識する
| 対象 | 主なテスト観点 |
|---|---|
| Service | 業務分岐、Repository・外部Clientとの連携 |
| Repository | SQL、JPAマッピング、DB制約 |
| Component | 変換・解析・計算の入出力 |
役割が分かれていれば、Repositoryのテストへ業務判断を詰め込む、Serviceの単体テストで実DBまで毎回起動するといった混乱を避けやすくなります。
配布コードのテストでは、起動したコンテナから4種類のBeanを取り出した後、注文を1件保存し、別の呼び出しでH2へSQLを実行して READY を読みます。起動できたことと、各層が意図した仕事をしたことを分けて確かめます。
OrderRegistrationTest.java の全文
package info.syachiku.registration.orders;
import org.junit.jupiter.api.Test;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.dao.EmptyResultDataAccessException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@SpringBootTest(classes = OrderSampleApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.NONE,
properties = {"spring.jpa.hibernate.ddl-auto=create-drop", "spring.jpa.open-in-view=false"})
class OrderRegistrationTest {
@Autowired
private ApplicationContext context;
@Test
void ステレオタイプの三つのクラスとSpringDataのインターフェースがBeanになる() {
assertThat(context.getBeansOfType(OrderCsvParser.class)).hasSize(1);
assertThat(context.getBeansOfType(OrderService.class)).hasSize(1);
assertThat(context.getBeansOfType(OrderQueryRepository.class)).hasSize(1);
assertThat(context.getBeansOfType(OrderRow.class)).isEmpty();
assertThat(AopUtils.isAopProxy(context.getBean(OrderRepository.class))).isTrue();
}
@Test
void 入力変換と業務処理を通して保存し検索用RepositoryでDBから状態を読める() {
var service = context.getBean(OrderService.class);
long id = service.place("order-42,READY");
assertThat(service.findStatus(id)).isEqualTo("READY");
assertThatThrownBy(() -> context.getBean(OrderQueryRepository.class).findStatus(-1))
.isInstanceOf(EmptyResultDataAccessException.class);
}
}このプロジェクトでは、上の2件に加え、決済Bean登録の4件とMVCスライスの1件を実行します。
Tests run: 7, Failures: 0, Errors: 0, Skipped: 0現場レビューでよくある指摘
// レビューコメント例 注文確定というユースケースを担当しているため、 @Componentではなく@Serviceで役割を示してください。 // レビューコメント例 JdbcTemplateでDB検索を行うクラスなので@Repositoryが適切です。 データアクセス例外の扱いも既存Repositoryへ揃えてください。 // レビューコメント例 このFormatterは依存を持たない単純な変換クラスです。 Spring Beanにする必要があるか、普通のJavaクラスでよいか確認してください。
提出前のセルフチェック
- クラスの責務を一文で説明できるか
- 業務ユースケースなら@Serviceを選んだか
- 永続化アクセスなら@Repositoryを選んだか
- 汎用部品なら@Componentまたは普通のクラスを検討したか
- Spring Dataインターフェースへ不要な@Repositoryを足していないか
- コンポーネントスキャン対象パッケージにあるか
- 同じ型のBeanが複数ないか
Springの役割分担を体系的に学ぶ参考書
アノテーションの暗記ではなく、DI、AOP、トランザクション、データアクセス層までつなげて学ぶと、クラスの責務から判断できるようになります。
Spring徹底入門 第2版
アプリを作った後に、DIやDB更新の仕組みを調べる。
DI、MVC、データアクセス、テスト、セキュリティまで幅広く確認できます。小さなWebアプリを一通り作り、Springの仕組みを詳しく学びたい人に向く参考書です。
- Springで小さなWebアプリを作ったことがある
- DIやトランザクションの仕組みを確認したい
Spring Framework 5向けです。Boot 3の実装に使うコード・設定は、採用している版の公式資料と照合してください。
当サイトはAmazonアソシエイト・プログラムの参加者です。価格・在庫・配送条件はAmazonでご確認ください。
この記事とあわせて読みたい
まとめ
@Component、@Service、@RepositoryはいずれもBean登録できますが、表す役割が違います。業務処理はService、永続化アクセスはRepository、その他の共通部品はComponentを基本にします。
動くアノテーションを選ぶのではなく、クラスの責務を一文で説明してから選びます。Spring Data Repositoryの扱い、コンポーネントスキャン、複数Beanまで確認し、アノテーションと実装内容を一致させましょう。



