16.

【Spring】@Repositoryアノテーションとは

編集
この記事の要点
  • @Repositoryデータアクセス層 (DAO) を表すアノテーション
  • 機能的には @Component と同じだが、DB 例外を DataAccessException 階層に自動変換
  • Spring Data JPA では Repository インタフェースの自動実装でも使う
  • MyBatis / JdbcTemplate / 手書き JDBC でも、DAO クラスに付ける

 

@Repository の役割

@Repository は次の 3 つの役割を持ちます:

  1. Bean 登録@Component と同じ)
  2. 意味付け: 「これは DAO 層のクラス」と明示
  3. 例外変換: JDBC / JPA の例外を DataAccessException 階層(Spring 標準)に変換

基本的な使い方

① 手書き DAO

@Repository
public class UserRepository {
    private final JdbcTemplate jdbc;

    public UserRepository(JdbcTemplate jdbc) {
        this.jdbc = jdbc;
    }

    public User findById(Long id) {
        return jdbc.queryForObject(
            "SELECT id, name FROM users WHERE id = ?",
            new BeanPropertyRowMapper<>(User.class),
            id
        );
    }

    public int save(User user) {
        return jdbc.update(
            "INSERT INTO users (name) VALUES (?)",
            user.getName()
        );
    }
}

② Spring Data JPA のインタフェース

// インタフェースだけ書く(実装は Spring が自動生成)
@Repository  // 省略しても認識される (Spring Boot)
public interface UserRepository extends JpaRepository {
    // メソッド名から SQL を自動生成
    Optional findByEmail(String email);
    List findByStatusOrderByCreatedAtDesc(String status);
    long countByStatus(String status);

    // カスタムクエリ
    @Query("SELECT u FROM User u WHERE u.email LIKE %:keyword%")
    List searchByEmail(@Param("keyword") String keyword);

    // 更新クエリ
    @Modifying
    @Query("UPDATE User u SET u.status = :status WHERE u.id = :id")
    int updateStatus(@Param("id") Long id, @Param("status") String status);
}

例外変換の効果

@Repository が付いていると、Spring の PersistenceExceptionTranslationPostProcessor が例外を変換します:

// 変換前 (生の SQLException や Hibernate 例外)
java.sql.SQLException: Duplicate entry 'user@example.com' for key 'users.email_unique'

// 変換後 (DataAccessException 階層)
org.springframework.dao.DuplicateKeyException
    extends DataIntegrityViolationException
    extends NonTransientDataAccessException
    extends DataAccessException
    extends RuntimeException

これによって:

  • RuntimeException 化: throws SQLException 等を書かなくて済む(checked → unchecked)
  • DB 非依存: MySQL でも PostgreSQL でも同じ例外クラスでハンドリング
  • サブクラス階層が利く: catch(DataIntegrityViolationException) で UNIQUE 違反等を一括キャッチ

主な DataAccessException

例外意味
EmptyResultDataAccessExceptionqueryForObject で結果が 0 件
IncorrectResultSizeDataAccessExceptionqueryForObject で複数件
DuplicateKeyExceptionUNIQUE 制約違反
DataIntegrityViolationException制約違反(NOT NULL / FK / CHECK)
DeadlockLoserDataAccessExceptionデッドロック
QueryTimeoutExceptionクエリタイムアウト
BadSqlGrammarExceptionSQL 構文エラー
OptimisticLockingFailureException楽観ロック失敗(@Version 使用時)
PessimisticLockingFailureException悲観ロック失敗

例外ハンドリング例

@Service
public class UserService {
    private final UserRepository userRepository;

    public User register(User user) {
        try {
            return userRepository.save(user);
        } catch (DuplicateKeyException e) {
            throw new BusinessException("このメールアドレスは既に登録済みです");
        } catch (DataIntegrityViolationException e) {
            throw new BusinessException("入力データに問題があります: " + e.getMessage());
        } catch (DataAccessException e) {
            log.error("DB エラー", e);
            throw new SystemException("システムエラーが発生しました");
        }
    }
}

@Repository vs @Service vs @Component

アノテーション用途特殊機能
@Component汎用 Bean
@Serviceビジネスロジック意味付けのみ
@Repositoryデータアクセス例外変換

JPA Repository での自動実装

Spring Data JPA を使うと、インタフェースだけ書けば実装は自動生成されます:

// インタフェース宣言
public interface UserRepository extends JpaRepository { }

// Spring が自動でプロキシ実装を生成
// 主要メソッドが利用可能:
userRepository.save(user);
userRepository.findById(id);
userRepository.findAll();
userRepository.deleteById(id);
userRepository.count();
userRepository.existsById(id);

// 派生クエリ (Method Name Query)
List findByName(String name);
List findByStatusAndCreatedAtBetween(String status, LocalDateTime from, LocalDateTime to);
Page findByName(String name, Pageable pageable);

// @Query で JPQL / Native SQL
@Query("SELECT u FROM User u WHERE u.email = ?1")
Optional findByEmailCustom(String email);

@Query(value = "SELECT * FROM users WHERE email = ?1", nativeQuery = true)
Optional findByEmailNative(String email);

関連記事

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. @After
  2. @Autowired
  3. @Bean
  4. @Before
  5. @Column
  6. @Component
  7. @Configuration
  8. @Controller
  9. @Data
  10. @Entity
  11. @GeneratedValue
  12. @Id
  13. @Modifying
  14. @PathVariable
  15. @PropertySource
  16. @Repository
  17. @RequestBody
  18. @RequestMapping
  19. @ResponseBody
  20. @RestController
  21. @Service
  22. @SpringBootApplication
  23. @Table
  24. @Transactional
  25. @Value