All Articles

토비의 스프링을 2021년에 진행하며 메모한 것들

좋은 책이나 너무 오래되어서 따라하기가 쉽지 않을 때가 있다. 그 시대의 IDE, 자바 버전, 컴퓨터를 사용하지 않는 한 조금씩 삐그덕대기는 한다. 그나저나 2회독인데, 스프링을 그 동안 안 써서 그런지 처음 읽는 거 같은 느낌이 들었다 ㅎㅎ;;; 작년에는 스프링 버전을 책과 최대한 맞춰가며 진행했는데, 이번에는 스프링 부트로 진행을 해 보았다.

  • Spring Boot Repository (2021.03) : https://github.com/youngminz/toby-spring-boot
  • Spring 3.1 Repository (2020.08) : https://github.com/youngminz/toby-spring
  • 최근에는 Spring Boot가 대세다

    • 의존성 라이브러리의 버전을 자동으로 Gradle이 찾아준다는 것은 엄청난 행운. maven repository를 직접 찾아가며 책에 있는 의존성들을 일일히 검색하고 다운로드해가며 의존성 추가 하는 것은 시간이 너무 오래 걸린다
    • Gradle을 사용하는 덕분에 CI도 손쉽게 설정할 수 있었다
    • MySQL 5.7 이하는 유니코드를 제대로 지원하지 않기 때문에 매번 설정하기 진짜 짜증난다
    • 테스트 커버리지를 알아내는 CodeCov도 설정해 보았다
    • Spring 버전이 3.1 에서 5.3.4 으로 올라왔지만, 몇 가지 API의 변화 말고는 큰 변화는 없다
    • 1권 전체를 따라해 보았다. 주말 + 3.1절 연휴 내내 1권을 달라붙어서 어떻게든 끝냈다!
  • 높은 자바 버전을 사용하면서 책에 있는 낮은 스프링이나 라이브러리 버전을 사용하면 크래시가 발생하기도 한다. 스프링 3.1 + 자바 8에서는 괜찮았음. 자바 11에서는 가끔 크래시.
  • 180p : @Before 어노테이션이 @BeforeEach 어노테이션으로 변경되었다.
  • ???p : assertThat 보다는 assertEquals, assertTrue, assertIsNull 등으로 사용하였다. 개인 취향.
  • 185p : @RunWith(SpringJUnit4ClassRunner.class)@ExtendWith(SpringExtension.class) 으로 변경되었다. 그런데 @SpringBootTest 어노테이션을 사용하면 더 깔끔하게 적용된다.
  • ???p : assertThrows(EmptyResultDataAccessException.class, () -> dao.get("unknown_id")); 으로 사용법이 바뀌었다.
  • ???p : Auto resource management 기능을 이용해 close 를 자동 호출할 수 있다.
  • 231p : 익명 내부 클래스보다 더 간결한 람다를 사용할 수 있다.
  • 235p : 테스트에서 ContextConfiguration 써야된다.
  • 253p : resources 폴더에 numbers.txt 파일을 만들었으면, getResource("numbers.txt") 가 아닌 getResource("/numbers.txt") 처럼 / 를 붙여야 한다.
  • 264p : queryForInt 가 Deprecated 되었다. queryForObject로 넘기고 두번째 인자를 Integer.class 넘기면 된다.
  • 265p : queryForObject(java.lang.String, java.lang.Object[], org.springframework.jdbc.core.RowMapper)’ is deprecated. https://stackoverflow.com/a/65301152/3096304

    위에서 아래처럼 varargs를 이용하면 된다.

    public User get(String id) {
            return this.jdbcTemplate.queryForObject(
                    "select * from users where id = ?",
                    new Object[]{id},
                    (rs, rowNum) -> {
                        User user = new User();
                        user.setId(rs.getString("id"));
                        user.setName(rs.getString("name"));
                        user.setPassword(rs.getString("password"));
                        return user;
                    }
            );
        }
    public User get(String id) {
        return this.jdbcTemplate.queryForObject(
                "select * from users where id = ?",
                (rs, rowNum) -> {
                    User user = new User();
                    user.setId(rs.getString("id"));
                    user.setName(rs.getString("name"));
                    user.setPassword(rs.getString("password"));
                    return user;
                },
                id
        );
    }
  • 352p - 위의 코드는 아래로 대체할 수 있다.

    try {
        testUserService.upgradeLevels();
        fail("TestUserServiceException expected");
    } 
    catch (TestUserServiceException e) {}
    assertThrows(TestUserServiceException.class, testUserService::upgradeLevels);
  • 381p - javax.mail.Session 클래스는 자바11에서는 쓸 수 없다. 따라서 javax.mail 사용하는 코드는 실습해볼 수 없다. 대신 스프링부트가 제공하는 API 사용할것.

    implementation 'org.springframework.boot:spring-boot-starter-mail'
  • 452p - @ContextConfiguration 에서 설정파일 이름을 지정하지 않으면 -context.xml 을 사용한다고 했지만, 스프링 부트 기본 설정에는 그렇게 되어 있지 않나 보다. Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test 오류가 발생한다. 그냥 @ContextConfiguration(locations = "/FactoryBeanTest-context.xml") 으로 경로를 붙여 주었다.
  • 어드바이저 = 포인트컷(메소드 선정 알고리즘) + 어드바이스(부가기능)
  • AOP는 정말 어렵군…
  • 492p - 아래 의존성 추가해줘야 한다.

    implementation 'org.springframework.boot:spring-boot-starter-aop'
  • 492p - 책의 org.aspectj:aspectjweaver:1.6.6 에서 버그 동작이 1.6.7 에서 수정되었다. https://www.eclipse.org/aspectj/doc/released/README-167.html 를 읽어 봐도 이거에 대한 내용은 없는 거 같은데.. 리팩토링 하다가 버그가 잡혔나?

    assertFalse(pointcut.getClassFilter().matches(Bean.class));
    implementation 'org.aspectj:aspectjweaver:1.6.6' // Error
    implementation 'org.aspectj:aspectjweaver:1.6.7' // OK
  • 535p - (5, 6) → (4) → (2, 3) → (1) 순서로 Transactional 정책이 적용된다고 했는데, 실제로 적용해보니 그렇지 않더라. (5, 6) → (2, 3) → (4) → (1) 순서로 적용되는 것 같다. https://github.com/youngminz/toby-spring-boot/commit/9c9f8a8cbfe9a5ead8fda390ec8e458f7b8d6921

  • 570p : xjc가 자바 11부터 제공되지 않는다. https://nieldw.medium.com/compile-xsd-files-into-java-classes-using-xjc-with-gradle-and-kotlin-d468f8ae33fa 이 방법대로 하면 package com.epril.sqlmap 에 생성된다. https://github.com/youngminz/toby-spring-boot/commit/38a545e920674271293fed5fa348c76c10dfb124
  • 589p : throw new SqlRetrievalFailureException(e) → …(e.getMessage())
  • 599p : 아래 의존성을 추가해줘야 한다.

    implementation 'org.springframework.integration:spring-integration-xml'
  • 601p : Castor 스프링 지원이 스프링 5.2에서 제거되었다. 매핑 XML 타이핑 열심히 했는데.. https://github.com/spring-projects/spring-framework/commit/89a7e752efcacdc07558c03a3e1dcb6dd981acf1
  • 632p : SimpleJdbcTemplate이 Deprecated 되어서 결국 제거되었다. JdbcTemplate으로 대체해도 아무런 문제가 없다. https://devday.tistory.com/entry/SimpleJdbcTemplate-deprecated 또한 @After@AfterEach 으로 변경되었다. 또한 아래 의존성을 추가해줘야 한다.

    runtimeOnly 'org.hsqldb:hsqldb'
  • 659p : 자바 코드에서 import com.mysql.cj.jdbc.Driver; 를 진행하려면 아래와 같이 변경해야 한다

    runtimeOnly 'mysql:mysql-connector-java'
    ->
    implementation 'mysql:mysql-connector-java'
  • 665p : embeddedDatabase 타입을 EmbeddedDatabase로 하면 아래 오류가 발생하면서 안됨. 어차피 EmbeddedDatabase는 DataSource를 상속받았으니, DataSource로 변경해주자(public interface EmbeddedDatabase extends DataSource). 그래도 테스트 로그에 hsqldb 관련 로그가 찍히니 잘 동작하는 거 같다.

    @Resource
    EmbeddedDatabase embeddedDatabase;
    -> Error creating bean with name 'testApplicationContext': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'embeddedDatabase' is expected to be of type 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabase' but was actually of type 'org.springframework.jdbc.datasource.SimpleDriverDataSource'
  • 669p : @AutoWired SqlService sqlService 는 아래에 public SqlService sqlService() 를 구현했기 때문에 불필요하다.
  • 708p : @Import(SqlServiceContext.class)@EnableSqlService 으로 빼내었다. 그런데 이렇게 어노테이션을 바꾸자 SqlServiceContext 가 적용이 안된다. 왜 그럴까? https://github.com/youngminz/toby-spring-boot/commit/a9817bf5e745fc6e33ccb2eca738cef5db585dd7

    Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userDaoJdbc': Unsatisfied dependency expressed through field 'sqlService'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'springbook.user.sqlservice.SqlService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}