Study/Spring

[Spring] @Repository vs @Component 삽질로그

dev_kong 2023. 4. 23. 22:07
728x90
728x90

결론부터 얘기하자면 @Repository 와 @Component는 bender 마다 다른 DB관련 예외를 @Repository어노테이션을 사용하면, DataAccessException으로 전환해준다고 한다.
이하의 글은 @Repository의 예외전환을 직접 확인해보고픈 욕심을 가진 의심병말기환자의 삽질 글임.

오늘 Spring Core 강의 에서 Dao에 @Repository 를 사용하냐 @Component 를 사용하냐 얘기를 나누던 도중 어떤 크루가 Dao 에 붙어있던 @Repository를 @Component로 바꿨더니, 잘 되던게 안된다고 했다.


나도 궁금해져서 해보니까 난 잘되더라.
근데 얘네 무슨 차이가 있는 걸까.

공식문서 확인

가장 신뢰도 높은 확인방법이라 생각하고 공식문서를 확인해봤다.
(근데 내가 못찾는 건지 모르겠는데 공식문서에 검색기능 없음..?)

 

The @Repository annotation is a marker for any class that fulfills the role or stereotype of a repository (also known as Data Access Object or DAO). Among the uses of this marker is the automatic translation of exceptions, as described in Exception Translation.

Spring provides further stereotype annotations: @Component, @Service, and @Controller. @Component is a generic stereotype for any Spring-managed component. @Repository, @Service, and @Controller are specializations of @Component for more specific use cases (in the persistence, service, and presentation layers, respectively). Therefore, you can annotate your component classes with @Component, but, by annotating them with @Repository, @Service, or @Controller instead, your classes are more properly suited for processing by tools or associating with aspects. For example, these stereotype annotations make ideal targets for pointcuts. @Repository, @Service, and @Controller can also carry additional semantics in future releases of the Spring Framework. Thus, if you are choosing between using @Component or @Service for your service layer, @Service is clearly the better choice. Similarly, as stated earlier, @Repository is already supported as a marker for automatic exception translation in your persistence layer.

 

가장 첫 문단에 @Repository에 대한 설명이 나온다.
대충 읽어 보면, Repository(또는 DAO)에 사용되고, 이걸 사용하면 데이터 접근 시 발생하는 예외를 예외를 전환 해준다고 한다.

 

그렇다면 @Component를 사용할 때와 @Repository를 사용할 때 서로 다른 예외가 발생할 것 이라고 예상이 되는데,

의심병 말기 환자기 때문에 직접 확인을 해보자.

 

테스트

CREATE TABLE `user1`  
(  
    `id`    long PRIMARY KEY NOT NULL AUTO_INCREMENT,  
    `email` varchar(255)     NOT NULL UNIQUE  
);

 

대충 테이블을 생성했다.
email column에 UNIQUE 속성을 추가했기에, 동일안 email을 들어갈 수 없도록 설정했다.

 

@Repository  
public class UserDao {  
    private final JdbcTemplate jdbcTemplate;  

    public UserDao(JdbcTemplate jdbcTemplate) {  
        this.jdbcTemplate = jdbcTemplate;  
    }  

    public void saveUser(User user) {  
        String sql = "INSERT INTO user1 (email) VALUES (?)";  
        jdbcTemplate.update(sql, user.getEmail());  
    }
}

 

마찬가지로 대충 UserDao를 만들고, user table에 insert하는 코드를 작성했다.

 

@JdbcTest  
class UserDaoTest {  
    @Autowired  
    JdbcTemplate jdbcTemplate;  
    UserDao userDao;  

    @BeforeEach  
    void setUp() {  
        userDao = new UserDao(jdbcTemplate);  
    }  

    @Test  
    void duplicateEmailTest() {  
        userDao.saveUser(new User("qwer@naver.com"));  
        userDao.saveUser(new User("qwer@naver.com"));  
    }  
}

 

같은 email을 집어넣으려고 시도 하면 에러가 발생할 것이다.

UserDaoTest > duplicateEmailTest() FAILED
    org.springframework.dao.DuplicateKeyException at UserDaoTest.java:28
        Caused by: org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException at UserDaoTest.java:28

 

예상했던 대로 JdbcSQLIntegrityConstraintViolationException 에서 DuplicateKeyException 로 예외가 전환된 것을 확인 할 수 있다.

어떤 예외가 발생하는지 확인했으니까 테스트 코드를 assertThatThrownBy로 변경해주고
UserDao의 어노테이션을 @Repository에서 @Component로 변경했다.

 

@Test  
void duplicateEmailTest() {  
    userDao.saveUser(new User("qwer@naver.com"));  
    assertThatThrownBy(() -> userDao.saveUser(new User("qwer@naver.com")))  
        .isInstanceOf(DuplicateKeyException.class);
}  

 

@Component  
public class UserDao {  
...
}

 

공식문서를 보고 예상했던 대로라면,
이제 테스트가 실패해야 한다.

 

근데 막상 테스트를 돌려보니 테스트가 통과된다.
왜지.

 

jdbcTemplate 의 예외 전환

공식문서가 거짓말을 하는 건 아닐거 같아서 jdbcTemplate으로 코드 여행을 떠났다.


매우 친절하게도 JdbcTemplate 구현체에 java doc 주석이 달려있었는데 Jdbc예외를 캐치하여
org.springframework.dao 에 정의된 예외로 전환해준다는 내용이 포함되어있다.

 

This is the central class in the JDBC core package. It simplifies the use of JDBC and helps to avoid common errors. It executes core JDBC workflow, leaving application code to provide SQL and extract results. This class executes SQL queries or updates, initiating iteration over ResultSets and catching JDBC exceptions and translating them to the generic, more informative exception hierarchy defined in the org.springframework.dao package.

 

아니 그럼.. @Repository의 예외 전환 기능은 언제 사용된다는 거임..?

좀 전에 봤던 공식문서에 걸려있는 DataAccessException 링크에서 해당 내용을 확인할 수 있었다.

 

5.2.2. Exception Translation

When you use Hibernate or JPA in a DAO, you must decide how to handle the persistence technology’s native exception classes. The DAO throws a subclass of a HibernateException or PersistenceException, depending on the technology. These exceptions are all runtime exceptions and do not have to be declared or caught. You may also have to deal with IllegalArgumentException and IllegalStateException. This means that callers can only treat exceptions as being generally fatal, unless they want to depend on the persistence technology’s own exception structure. Catching specific causes (such as an optimistic locking failure) is not possible without tying the caller to the implementation strategy. This trade-off might be acceptable to applications that are strongly ORM-based or do not need any special exception treatment (or both). However, Spring lets exception translation be applied transparently through the @Repository annotation.

뭐 .. 그렇다고 한다.


여기서 접었다. 사실 좀 더 가보긴 했는데, 본다고 이해할 수 있는 내용이 아니었다.
이때 옆에서 코치님이 나 대신 브레이크 씨게 밟아주셨다.

 

위에서 언급했듯, 내가 보고싶었던건
**@Component를 사용할 때와 @Repository를 사용할 때 서로 다른 예외가 발생한다**. 였다.


그냥 어노테이션만 바꿔주면 바로 쉽게 확인할 수 있을 줄 알았는데, 꼴랑 그거 보기 참 힘들다.

@Repository의 예외 전환기능은 아직 경험해보질 않아서 도저히 와닿지는 않지만,


명시적으로 해당 클래스가 데이터접근하기 위한 클래스라는 것을 명시적으로 보여주기 위해서라도 @Repository를 쓰는건 대찬성이다.

@Service도 그냥 쓰잖음.

 

나중에 예외전환기능의 내부구현을 이해하게 되는 순간이라던가,
아니면, @Repository의 예외전환 기능을 마주하게 되는 순간이 올거라 믿는다;;

 

그때 가서 이글 다시보면 재밌지 않을까 싶어서 남기는 글이긴한데
다쓰고 나니까 이게 대체 뭐하는 글인지는 나도 잘 모르겠다.

728x90
728x90