JPA 까먹지 말자! (2)

오늘은 저번시간에 이어서 JPA 까먹지 말자! (2) 를 시작해보자. JPA라 했지만 구현체는 hibernate 기준으로 설명하니 다른 구현체들은 조금씩 다를 수도 있으니 참고하면 되겠다. 또한 종종 hibernate 이야기도 있을 수도 있다.

@GeneratedValue strategy

JPA에서는 @GeneratedValue 어노테이션의 strategy 속성으로 기본키 전략을 설정할 수 있다. 물론 직접 기본키를 생성해주는 것도 좋지만 그보다는 자동생성 전략도 나쁘지 않게 생각된다. 필요하다면 비지니스 키를 따로 만들어서 직접 생성해주는 방법도 생각해 볼 수 있다.

JPA에서는 기본키의 전략이 3가지가 있다. TABLE, SEQUENCE, IDENTITY 전략이다. AUTO 전략도 있지만 AUTO는 해당 벤더에 따라 위의 세가지중 하나를 선택하게 된다. 3가지의 전략은 따로 설명하지 않겠다.

근데 이때 알아야 할 것은 기본 키 전략이 IDENTITY 일경우에는 쓰기 지연이 동작하지 않는다. 예를들어 다음과 같다.

public class Account {

 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Long id;
//..
}

@Transactional
public void saveAccount() {
 Account account = new Account();
 account.setName("wonwoo");
 entityManager.persist(account);
}

위와 같이 작성할 경우에는 entityManager.persist(account) 이 메서드를 호출 할때 바로 insert 쿼리라 작성되어 DB에 날라간다. 하지만 TABLE전략이나 SEQUENCE 전략은 Transaction이 끝나고 flush 혹은 commit이 호출 될 때 insert 쿼리가 만들어져서 날라 가게 된다. 그 이유는 아마 데이터베이스에서 ID를 가져와야 하므로 insert 쿼리라 먼저 만들어져서 DB에 날라가는 것 같다.

만약 쓰기 지연 효과를 얻고 싶다면 TABLE 또는 SEQUENCE 전략으로 설계를 해야 한다.

@Basic LAZY

JPA 에서는 @Basic 어노테이션이라는 것이 있다. 하지만 우리는 거의 사용하지 않는다. 왜냐하면 사용할 일이 없기 떄문이다.. 기본 타입을 말하는 건데. 굳이 쓰지 않아도 암시적으로 JPA가 이거슨 기본타입이라고 설정해 주기 때문이다.

public class Account {

 @Id
 @GeneratedValue
 @Basic
 private Long id;

 @Basic
 private String name;

}

public class Account {

 @Id
 @GeneratedValue
 private Long id;

 private String name;
}

위의 코드는 암묵적으로 동일하다. 그런데 우리는 기본타입을 Lazy 로딩 할 수 있다. @Basic 어노테이션에는 fetch 속성이 존재한다. @OneToMany@OneToOne 기타 매핑하는 어노테이션에서 사용하는 fetch와 동일하다.

@Basic(fetch = FetchType.LAZY)
@Lob
private String content;

위와 같이 fetch 속성에 FetchType을 LAZY 설정해 주면 된다. 그럼 실제 content가 사용 될때 쿼리가 작성되어 날라간다.

Account account = entityManager.find(Account.class, 1L);
System.out.println(account.getContent()); //LAZY

실제 쿼리를 보면 아래와 같다.

select
 account0_.id as id1_0_0_,
 account0_.name as name3_0_0_
from
 account account0_
where
 account0_.id=?

select
 account_.content as content2_0_
from
 account account_
where
 account_.id=?

위 처럼 content를 사용할 때 한번 더 쿼리를 날린다. 근데 사용할 일이 있을까 싶다. content의 크기가 어마어마하게 크면 모를까..
근데 바로는 되지 않고 설정을 조금 해야한다. 여러방법이 있는 것 같은데 그 중에서 가장 쉬운 방법은 메이븐을 사용한다면 아래와 같이 plugin을 작성해야 한다.

<plugin>
 <groupid>org.hibernate.orm.tooling</groupid>
 <artifactid>hibernate-enhance-maven-plugin</artifactid>
 <version>${hibernate.version}</version>
 <executions>
  <execution>
   <configuration>
    <failonerror>true</failonerror>
    <enablelazyinitialization>true</enablelazyinitialization>
   </configuration>
   <goals>
    <goal>enhance</goal>
   </goals>
  </execution>
 </executions>
</plugin>

근데 설정을 좀 잘해야 될거 같다. 다른 프록시들과 조금 꼬이는 듯하다. 만약 사용할 경우에는 다른 프록시들이 잘되는지 테스트를 많이 해봐야 될 것 같다.

Criteria

요즘의 개발자들은 거의 사용하지 않겠지만 (필자 역시도 요즘개발자라) 예전 개발자분들은 익숙한 클래스일 듯 싶다. 요즘 Criteria 보다는 훨씬 쉬운 Querydsl 이라는게 있어 대부분이 Querydsl을 사용하지 Criteria를 사용하지는 않는 것 같다. Criteria와 Querydsl은 동일하게 JPQL을 편하게 작성하도록 만든 빌더 클래스들이다. Criteria는 JPA표준이고 Querydsl은 비표준이지만 훨씬 간단하고 알아보기 좋고 쉽다. 하지만 이런게 있다고만 알아두면 좋을 것 같다.

CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<account> criteria = builder.createQuery(Account.class);
Root<account> root = criteria.from(Account.class);
criteria.select(root);
criteria.where(builder.equal(root.get("id"), 1L));
Account account = entityManager.createQuery(criteria).getSingleResult();

위와 같이 조금 어렵다. 뭔가 많은 작업을 해야 된다. 그리고 조금 알아보기도 힘든 것같다. 익숙하지 않아서 그런가? 그래도 사용하고 싶다면 사용해도 된다. JPA 표준이니까. 이것의 장점은 타입세이프해서 좋은 것 같다. 물론 root.get("id") 이 부분도 타입세이프하게 만들 수 있다.

참고로 spring data jpa 에서도 Criteria를 사용한다. 당연히 그래야 했을 거고 그럴꺼라고 생각했다.

Proxy

JPA에서는 바로 쿼리를 날려서 가져오는 find 메서드와 나중에, 해당 객체를 사용할 떄 가져오는(Lazy) getReference 메서드가 존재한다.

Account account = entityManager.find(Account.class, 1L);
Account account1 = entityManager.getReference(Account.class, 2L);

위와 같이 딱 두줄만 코드를 작성했을 때 쿼리는 몇번 날아갈까?
정답은 한번이다. getReference 메서드를 사용할 때는 실제 쿼리를 날리지 않고 프록시 객체만 전달 된다. 왜 그럼 굳이 proxy 객체만 전달하는 getReference 를 만들었을까?
예를들어 연관관계를 맺을 경우 account의 엔티티가 필요한 상황이라고 가정해보자. 물론 account의 엔티티들을 사용한다면 프록시 객체가 필요 없겠지만 단지 연관관계를 맺을 목적이라면 굳이 데이터베이스에 쿼리를 날릴 필요가 없다. 그럴 경우 이 프록시 객체를 사용하면 된다.

Account account = entityManager.getReference(Account.class, 2L);
Address address = new Address();
address.setAddress("seoul");
address.setAccount(account);
entityManager.persist(address);

위와 같이 작성했을 경우 insert 쿼리 한번만 데이터베이스에 날라가게 된다. 좀 더 성능적으로 최적화를 할 수 있다. 이런 경우에라면 정말 좋은 기능이지 않나 싶다.

참고로 다른 JPA의 구현체들은 어떤 프록시를 사용하는지 모르겠지만 hibernate 경우에는 javassist를 사용했다. 하지만 이 글을 쓰는 기준으로 최신버전인 5.3.7 버전은 bytebuddy로 proxy를 변경하였다. 아마도 5.3 이후부터 bytebuddy로 변경한 듯 싶다. 요즘에 code generation 으로 bytebuddy 를 많이 이용하는 것 같다.

또한 Spring data jpa 프로젝트에서도 해당 메서드를 사용가능하다. 현재 버전은 findById 이전버전은 findOne 메서드로 데이터베이스에서 바로 조회했다면 getOne 메서드로 해당 프록시 엔티티를 가져올 수 있다.

FlushMode

hibernate 경우에는 FlushMode 6가지 정도 지원하는데 JPA 스펙에는 2가지가 있다. 하이버네이트 API를 사용한다면 6개 모두 사용할 수 있겠지만 JPA API만 사용한다면 2가지 타입만이 존재한다. COMMITAUTO 가 JPA에서 지원해주는 FlushMode 타입이다.
일반적으로 JPA는 트랜잭션이 commit 되기 직전에 flush도 자동으로 된다. 또 한가지 자동으로 flush 가 될 때가 있는데 그때는 jpql이나 쿼리를 날릴 때 자동으로 flush가 호출 된다.

jpql이나 쿼리를 작성해서 날릴 때 flush 되는 설정이 FlushMode.AUTO 설정이다. 이것은 JPA의 기본값이다. 굳이 아래처럼 명시해주지 않아도 된다.

Account account = new Account();
account.setName("wonwoo");
entityManager.setFlushMode(FlushModeType.AUTO);
entityManager.persist(account);
entityManager.createQuery("select a from Account a", Account.class).getResultList();

위와 같이 코드를 작성할 경우 insert 쿼리는 jpql 쿼리를 날리 전에 먼저 데이터베이스에 날라간다.

insert
 into
account
 (content, name, id)
values
 (?, ?, ?)

select
 account0_.id as id1_0_,
 account0_.content as content2_0_,
 account0_.name as name3_0_
from
 account account0_

만약 AUTO가 아닌 COMMIT으로 했을 경우에는 insert 는 commit 되고 날라가고 그전에 jpql이나 쿼리가 먼저 날라간다. 그래서 원하는 데이터가 나오지 않을 수도 있다.

entityManager.setFlushMode(FlushModeType.COMMIT);

아래는 FlushMode를 COMMIT으로 했을 떄 쿼리이다. select 쿼리가 먼저 데이터베이스에 날라갔다.

select
 account0_.id as id1_0_,
 account0_.content as content2_0_,
 account0_.name as name3_0_
from
 account account0_

insert
 into
account
 (content, name, id)
values
 (?, ?, ?)

쿼리를 날릴때 마다 매번 flush를 하지 않아 성능은 더 좋겠지만 원하는 데이터가 나오지 않을 수도 있으니 상황에 맞게 고민을 해서 사용하면 되겠다.

@NamedQuery

필자는 잘은 사용하지 않지만 사용하면 나쁘지 않은 기능이다. 정적쿼리를 사용할 때 매우 유용한 어노테이션이다. 매번 동일한 쿼리를 작성하지 않고 해당 Name만 지정해줘서 쿼리를 날리면 된다.

@Entity
@NamedQuery(name = "findByname", query = "select a from Account a where name = :name")
public class Account {
  //...
}

위와 같이 @NamedQuery 어노테이션을 이용해서 정적 쿼리를 만들 수 있다. 아주 심플하다. 근데 많으면 보기 불편할거 같다.

List<account> accounts = entityManager.createNamedQuery("findByname", Account.class)
  .setParameter("name", "wonwoo")
  .getResultList();

매번 같은 쿼리를 작성하지 않고 해당 name만 작성해서 쿼리를 날릴 수 있으니 재사용성도 있다. 만약 여러개를 작성하고 싶다면 @NamedQueries 어노테이션을 이용하면 된다.

@Entity
@NamedQueries({
  @NamedQuery(name = "findByname", query = "select a from Account a where name = :name"),
  @NamedQuery(name = "findByEmail", query = "select a from Account a where email = :email")
})
public class Account {
  //...
}

이것은 JPA2.1 기준이며 JPA 2.2부터는 좀 더 간편하게 @NamedQuery 어노테이션만 이용해도 된다.

@Entity
@NamedQuery(name = "findByname", query = "select a from Account a where name = :name")
@NamedQuery(name = "findByEmail", query = "select a from Account a where email = :email")
public class Account {
}

@NamedQueries 어노테이션을 사용하는 것보다 @NamedQuery를 사용하는 것이 더 보기에 좋아 보인다. 하지만 JPA 2.2 부터 지원하니 그 전 사용자라면 @NamedQueries를 사용해야 한다.

오늘은 이렇게 JPA에 관련해서 두번째 시간을 가져봤다. 물론 필자도 JPA를 잘 사용하지 못한다. (나도 잘사용하고 싶다고오오..)

계속계속 사용해야 하는데 말이다.

Spring Controller 파라미터 타입 (2)

오늘은 예전에 작성했던 Spring Controller 파라미터 타입 에 이어서 두번째 시간을 가져보도록 하자. Spring webmvc 뿐만아니라 다른 프로젝트에서도 다양한 파라미터 타입을 지원주고 있으니 web 뿐아니라 다른 프로젝트에서도 어떤 파라미터를 지원하고 있는지 알아보도록 하자.

webmvc

일단 먼저 Spring mvc 부터 시작하자. 저번에 대부분 spring webmvc에 관련해서 이야기했으나 그래도 많은 부분은 이야기 하지 못했다. 많지는 않지만 몇가지 추가적으로 sprig mvc 파라미터 타입을 살펴보자.

@Value

@Value 어노테이션을 파라미터 타입에 작성할 수 있다. 흠 글쎄다.  Value 어노테이션을 굳이 파라미터에 작성할 이유가 있나 싶기도 하지만 지원은 해주고 있으니..  사용은 하겠지? 

@GetMapping("/value")
public String value(@Value("${name}") String name) {
    //
}

위와 같이 작성후에 properties 에 name이라는 프로퍼티를 작성해주면 된다.

name=wonwoo

아주 간단하다.  이렇게 하면 value를 호출 할때 name이라는 파라미터에 wonwoo  가 자동으로 들어가게 된다.

RedirectAttributes

리다이랙션 할 때 유용한 인터페이스이다. 해당 인터페이스를 사용하면 손쉽게 파라미터등을 전달 할 수 있다. 

@GetMapping("/hi")
public String redirectAttributes(RedirectAttributes redirectAttributes) {
    redirectAttributes.addAttribute("id", 1);
    redirectAttributes.addAttribute("foo", "bar");
    return "redirect:redirectAttributes/{id}";
}

@GetMapping("/redirectAttributes/{id}")
public String redirectAttributesHi(@PathVariable Long id, String foo) {
   //

}

위와 같이 리다이랙션하는 부분에 RedirectAttributes  인터페이스를 파라미터 타입으로 받은 후 파라미터 등을 해당 attribute에  작성하면 된다. 그럼 실제 리다이랙트 부분의 uri는 아래와 같이 만들어진다.

/redirectAttributes/1?foo=bar

리다이랙트 할 때 유용한 파라미터 타입이다.

@RequestAttribute

RequestAttribute 어노테이션은 request attribute 를 가져올 수 있는 어노테이션이다. 미리 정의된 속성을 가져올 수도 있고 지정한 속성을 가져올 수 도 있다. 코드를 보면 더 이해하기 쉬울 것이다.

@ModelAttribute
public void foo(HttpServletRequest request) {
    request.setAttribute("foo", "bar");
}

@GetMapping("/requestAttribute")
@ResponseBody
public String requestAttribute(@RequestAttribute String foo) {
    return foo;
}

위와 같이 필자가 정의한 foo라는 속성을 @RequestAttribute 어노테이션을 이용해서 foo라는 속성을 가져왔다. 굳이 controller가 아닌 interceptor에서 해당 속성을 넣어서 컨트롤러에 파라미터로 넣을 수도 있다. 다양한 방법으로 사용할 수 있으니 참고하면 되겠다.

@MatrixVariable

@MatrixVariable 어노테이션은 조금 생소한 기능을 가지고 있다. 매트릭 형태의 파라미터라고 할까? 잘 쓰면 유연한 url이 되겠지만 필자는 아직 잘 모르겠다. 잘 쓰지 않아서 그런건지도.. 


@GetMapping("/matrix/{id}")
public String matrix(@PathVariable String id, @MatrixVariable int q) {
    
}

만약 위와 같은 코드가 있다면 우리는 아래와 같이 호출하면 된다.

http://localhost:8080/matrix/1;q=11

위와 같이 ; 세미콜론으로 구분이 된다. 조금 어색한 url이 된거 같다.  문서에서 다른 예제도 가져왔다. 어떤지 한번 보고 넘어가자.

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable(name="q", pathVar="ownerId") int q1,
        @MatrixVariable(name="q", pathVar="petId") int q2) {

    // q1 == 11
    // q2 == 22
}

/owners/42;q=11/pets/21;q=22

흠.. 좋은건가? 괜찮은 건가? 관심이 있다면 한번 살펴보는 것도 나쁘지 않다. 

참고로 @MatrixVariable 어노테이션을 사용하려면 UrlPathHelper 의 removeSemicolonContent 속성을 false로 작성해야 된다.

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        UrlPathHelper urlPathHelper = new UrlPathHelper();
        urlPathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(urlPathHelper);
    }
}

UriComponentsBuilder

사용할 일이 있긴 하나? 아무튼 지원은 해주고 있으니 한번 살펴보자. 아니 뭐 살펴볼 것도 없다. 코드를 보자.

@GetMapping("/uriComponentsBuilder")
@ResponseBody
public String uri(UriComponentsBuilder uriComponentsBuilder) {

}

글쎄다. 딱히 유용한 정보들이 들어가는 것도 아니고. 언제 어디에 쓰일지는 잘..

Spring data

spring webmvc를 살펴봤는데 spring data 에서도 몇가지 파라미터를 제공해주고 있다. 한번 살펴보도록 하자.

Pageable 와 Sort

Spring data에는 Pageable 인터페이스와 Sort 클래스가 존재한다. Pageable은 페이지 사이즈, 페이지 번호 Sort등을 넣을 수 있다.  Sort만 사용할 경우에는 Sort 클래스만 파라미터 타입으로 작성하면 된다.

@GetMapping("/page")
public String page(Pageable pageable, Sort sort) {
    
}

위와 같이 작성하였을 경우에 우리는 다음과 같이 호출 할 수 있다.

/page?size=10&page=1&sort=id,desc

그럼 Spring data 에서 적절하게 파싱해 Pageable과 Sort에 값을 넣어 준다. 그럼 손쉽게 spring data api를 사용할 수 있다. 

@PageableDefault 어노테이션을 사용해서 해당 디폴트 값도 변경 가능하다.

@GetMapping("/page")
public String page(@PageableDefault(size = 100, page = 1, sort = "id", direction = Direction.ASC) Pageable pageable) {
 
}

Predicate

Querydsl에서 지원하는 Predicate도 사용할 수 있다. Spring data 와 querydsl을 사용하면 좀 더 손쉽게 사용할 수 있다. 

@GetMapping("/dsl")
public Iterable<Bar> dsl(@QuerydslPredicate(root = Bar.class) Predicate predicate) {
    return barRepository.findAll(predicate);
}

public interface BarRepository extends QuerydslBinderCustomizer<QBar>, JpaRepository<Bar, Long>, QuerydslPredicateExecutor<Bar> {

    @Override
    default void customize(QuerydslBindings bindings, QBar user) {
        bindings.bind(String.class)
            .first((SingleValueBinding<StringPath, String>) StringExpression::containsIgnoreCase);
    }
}

사용하고 싶다면 다음과 같이 호출하면 된다.

/dsl?name=foo

그럼 name이 foo인것을 like검색 한다. 좀 더 자세한 내용은 문서를 통해 확인하면 되겠다.

@ProjectedPayload

Spring data 에서 지원해주는 @ProjectedPayload 어노테이션이다. 인터페이스로 작성하면 된다. 

@GetMapping("/payload")
public String payload(PayLoadExample payLoad) {

}

@ProjectedPayload
public interface PayLoadExample {
    String getName();
}

위와 같이 인터페이스에 @ProjectedPayload 어노테이션을 작성하면 된다. 간단하다. 그러고 나서 아래와 같이 호출하면 된다.

/payload?name=wonwoo

그럼 PayLoadExample 에 getName() 메서드를 호출해보면 wonwoo라는 값을 꺼낼 수 있다.

또한 파라미터 말고도 body도 가능하다. 

@PostMapping("/payload")
public String payload(@RequestBody PayLoadExample payLoad) {
    return payLoad.getName();
}

위와 같이 @RequestBody 어노테이션을 작성하고 호출하면 body로도 받을 수 있다. 

또한 만약 json으로 body를 받는다면 @JsonPath 어노테이션을 이용해서 해당 필드도 변경할 수 있다. 

@ProjectedPayload
public interface PayLoadExample {
    @JsonPath("$..firstname")
    String getName();
}

그럼 firstname 으로 body를 작성해서 보내면 된다. xml을 이용한다면 @XBRead 을 이용해서 변경할 수 있다.

@ProjectedPayload
public interface PayLoadExample {

    @XBRead("//firstname")
    String getName();
}

@JsonPath, @XBRead 어노테이션을 사용하려면 적절한 디펜더시가 필요하다. 

<!-- JsonPath -->
<dependency>
    <groupId>com.jayway.jsonpath</groupId>
    <artifactId>json-path</artifactId>
    <version>${version}</version>
</dependency>
<!-- XBRead -->
<dependency>
    <groupId>org.xmlbeam</groupId>
    <artifactId>xmlprojector</artifactId>
    <version>${version}</version>
</dependency>

Spring cloud web gateway

spring cloud gateway mvc 에서도 ProxyExchange 클래스를 파라미터 타입으로 지정할 수 있다.

프록시 역할을 하는 클래스이다. 아주 간편하게 프록시 역할을 할 수 있다.

@GetMapping("/proxy")
public String proxy(ProxyExchange<String> exchange) {
    ResponseEntity<String> response =
        exchange.uri("https://start.spring.io")
            .get();
    return response.getBody();
}

적절하게 헤더 정보도 변경할 수 있고 파라미터, body도 정보도 변경 할 수도 있다. 

spring webflux

webflux에도 파라미터 타입이 몇가지 추가 되었다. 예를들어 ServerWebExchange 인터페이스나 ServerHttpRequest , ServerHttpResponse 인터페이스를 추가로 파라미터 타입으로 받을 수 있다. 

@GetMapping("/server")
public Mono<?> webflux(ServerWebExchange serverWebExchange) {
    ServerHttpRequest request = serverWebExchange.getRequest();
    ServerHttpResponse response = serverWebExchange.getResponse();
    return Mono.empty();
}

ServerWebExchange 파라미터를 받아서 getRequest 와 getRepsonse 를 꺼내서 사용할 수도 있고 ServerHttpRequest, ServerHttpResponse 를 각각 파라미터로 받아서 사용해도 된다.

@GetMapping("/server")
public Mono<?> webflux(ServerHttpRequest request, ServerHttpResponse response) {
    return Mono.empty();
}

또 한 Mono를 파라미터로 받을 수 도 있다.

@GetMapping("/webflux")
public Mono<?> webflux(Mono<Foo> foo) {
    return foo;
}

위와 같이 Mono를 사용해서 파라미터로 받을 수도 있고 @RequestBody 어노테이션을 이용해서  body로도  받을 수 있다. 여기서 주의할 점은 wrapper Type 은 지원하지 않는다.  model object만 지원하고 있으니 그것만 주의해서 사용하면 되겠다.

오늘은 이렇게 Spring Controller 파라미터 타입에 대해서 좀 더 알아봤다. 유용한 파라미터 타입이 있다면 적절하게 사용하면 좋을 것 같다. 

기존에 있던 web 과 새로운 webflux는 호환되지 않는다. 하지만 이미 기존의 webmvc에 존재 했던 예를들어 @RequestParam, @RequestBody , … 기타 등등 들 파라미터 타입들이  webflux 에서 다시 구현되어 있다. (왜냐면 인터페이스가 완전히 다르기 때문이다. ) 하지만 우리는 그것을 신경쓰지 않고 마치 webmvc 처럼 동일하게 사용할 수 있었던 것이다. 그래서 webflux에서도 @RequestParam, @RequestBody … 기타 등등 webmvc에서 사용했던 어노테이션, 클래스들을 사용할 수 있다. 물론 모두는 아니겠지만 대부분은 사용가능하다. 

Spring Controller 파라미터 타입

Spring boot 2.1 변화

몇 일전에 Spring boot 2.1이 릴리즈 되었다. 그래서 오늘 이와 관련된 이야기를 해보려고 한다. Spring boot 2.1의 변화. 릴리즈 된 내용들을 살펴보도록 하자. 물론 다 알아보지는 못하고 필자가 아는 내용, 혹은 잘 사용했던 내용 위주로만 설명할 예정이니 더 많은 내용들은 문서를 통해 확인하길 바란다. 또한 꼭 문서를 보는 것은 추천한다.
그럼 한번 살펴보도록 하자.

Deprecations from Spring Boot 2.0

Spring boot 2.0 에서 deprecated 되었던 메서드, 클래스, 생성자, 필드 등이 모두 삭제 되었다. CouchbaseHealthIndicatorProperties, EnvironmentTestUtils, RouterFunctionMetrics 클래스가 삭제 되었고, 여러개의 필드, 메서드들이 삭제 되었다. 더 많은 정보를 원한다면 해당 문서를 찾아보길 바란다.
만약 업그레이드를 한다면 없어진 클래스, 메서드를 사용하지 않길 바란다.

Bean Overriding

Spring boot 2.1 부터는 Bean 오버라이딩 기능이 불가능하다. 이는 실수를 막기위함이라고 하는데.. 예를들어 다음과 같이 코드를 작성했을 경우 에러가 발생한다.



@Configuration
public class SuperConfig {

  @Bean
  Foo foo() {
  return new Foo();
  }
}

@Configuration
public class Config extends SuperConfig {

  @Override
  @Bean
  Foo foo() {
  return new Foo();
  }
}

그럼 다음과 같은 에러가 발생한다.

The bean 'foo', defined in class path resource [SuperConfig.class], could not be registered. A bean with that name has already been defined in class path resource [Config.class] and overriding is disabled.

만약 Bean Overriding 을 허용하고 싶다면 다음과 같이 프로퍼티에 작성하면 된다.

spring.main.allow-bean-definition-overriding=true

Actuator ‘info’ and ‘health’ Endpoint Security

이전 Actuator 를 사용할 경우 spring-security가 클래스패스에 있을 경우 모든 Endpoint가 인증을 시도했다. 하지만 이제부터는 spring-security 가 클래스 패스에 있어도 info, health endpoint는 인증을 시도 하지 않고 사용할 수 있다. 이 것은 디펜더시 이외에 아무 설정 하지 않았을 경우를 의미한다.

Servlet Path

server.servlet.path 프로퍼티가 spring.mvc.servlet.path 프로퍼티로 변경 되었다.

Logging Refinements

이건 뭔소린지 잘 모르겠다. 아래에 logging group하고 관련있는 건지..? 아무튼..

logging.level.web=debug

web 과 관련된 로그를 debug로 남기고 싶다면 위와 같이 작성하면 된다. 그럼 org.springframework.core.codec 패지키, org.springframework.http 패키지, org.springframework.web 패키지 아래의 로그들이 debug으로 출력 된다.

2018-11-04 19:08:37.928 DEBUG 11085 --- [nio-8080-exec-3] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public java.lang.String ml.wonwoo.springbootnew.SpringBootNewApplication$TestController.hello()
2018-11-04 19:08:37.928 DEBUG 11085 --- [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet : GET "/?test=1", parameters={masked}
2018-11-04 19:08:37.929 DEBUG 11085 --- [nio-8080-exec-3] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public java.lang.String ml.wonwoo.springbootnew.SpringBootNewApplication$TestController.hello()
2018-11-04 19:08:37.930 DEBUG 11085 --- [nio-8080-exec-3] m.m.a.RequestResponseBodyMethodProcessor : Using 'text/html', given [text/html, application/xhtml+xml, image/webp, image/apng, application/xml;q=0.9, */*;q=0.8] and supported [text/plain, */*, text/plain, */*, application/json, application/*+json, application/json, application/*+json]
2018-11-04 19:08:37.930 DEBUG 11085 --- [nio-8080-exec-3] m.m.a.RequestResponseBodyMethodProcessor : Writing ["hello"]

위와 같이 유용한 로그들이 출력된다. Http 메서드, URL, 파라미터 등이 출력 되니 참고하면 된다. 근데 여기서 파라미터들은 마스킹 처리되어 있다. 민감한 정보들이 노출될 수 있어 마스킹 처리는 했지만 해당정보도 보여주고 싶다면 다음과 같이 프로퍼티에 작성하면 된다.

spring.http.log-request-details=true

그럼 이와 같이 파라미터 정보도 출력된다.

2018-11-04 20:47:24.115 DEBUG 11176 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : GET "/?test=1", parameters={test:[1]}

Narayana JTA Support

Narayana JTA 는 공식지원은 중단 되었고 이를 대체할 narayana-spring-boot-starter 가 존재한다. 만약 필요하다면 아래와 같이 업그레이드를 해야 한다.


<dependency>
    <groupId>me.snowdrop</groupId>
    <artifactId>narayana-spring-boot-starter</artifactId>
    <version>2.0.1</version>
</dependency>

HttpPutFormContentFilter

HttpPutFormContentFilter 클래스가 Deprecated 되었고 이를 대체할 FormContentFilter 클래스가 추가 되었다. 또 한 이와 관련된 spring.mvc.formcontent.putfilter.enabled 프로퍼티는 spring.mvc.formcontent.filter.enabled 프로퍼티로 변경 해야 된다.

InfluxDB HttpClient Customization

이전에는 OkHttpClient.Builder 를 빈으로 선언해서 InfluxDB를 사용했다면 이제는 InfluxDbOkHttpClientBuilderProvider 이용해서 사용하는 것은 권장한다. 아마도 다음 버전에는 지원하지 않을 예정으로 보인다. 현재 Spring boot 2.1에서 OkHttpClient.Builder 빈으로 등록해 사용한다면 (InfluxDB를) 경고 로그가 출력 될 것이다.

Spring Version POM Property

spring-boot-dependencies에 정의되어 있는 spring.version 프로퍼티가 변경되었다. 이제는 spring-framework.version을 프로퍼티로 사용해야 한다.

<spring-framework.version>5.0.0.RELEASE</spring-framework.version>

하지만 이것은 권장하지 않는다. Spring boot가 지원해주는 Spring version을 사용하는 것을 더욱 권장하고 있다.

Removal of ‘spring.provides’ Files

spring.provides 파일들이 사라졌다. 이전에는 STS, 다른 IDE들의 의존성들을 파악 할 수 있었으나 starter POM으로 스캐닝이 충분하기 때문에 사라졌다. 맞나?

Thymeleaf Spring Security Extras

thymeleaf-extras-springsecurity4 에서 thymeleaf-extras-springsecurity5 로 업데이트 되었다. 만약 이 모듈을 사용하고 있다면 thymeleaf-extras-springsecurity5로 변경 해야 된다.

Json Simple

json-simple가 이제는 의존성 관리에서 제거 되었다. 또한 json-simple 를 사용하는 JsonParser의 구현체(JsonSimpleJsonParser)도 사라졌다.

Jersey 1

Jersey 1은 이제 더이상 지원하지 않는다 Jersey 2로 업그레이드 해야 한다.

Endpoint ID names

자신만의 Endpoint를 만들 경우 Id를 좀 더 엄격하게 체크한다. 예를들어 id는 영숫자이어야 하며 문자로 시작해야 한다. 만약 숫자로 시작할 경우 에러가 발생한다.
또 한 -, . 특수문자를 사용할 경우 경고 메세지가 출력 된다. 해당 클래스는 EndpointId를 이용하니 참고하면 되겠다.

Third-party Library Upgrades

다들 아시다피시 Spring boot 2.1은 Spring Framework 5.1을 기반으로 하고 있다. 특별한 경우가 아니라면 Spring boot 가 명시해준 Spring Framework를 사용하길 권장한다.
– Tomcat 9
– Undertow 2
– Hibernate 5.3
– JUnit 5.2
– Micrometer 1.1

기타 등이 업그레이드 되었다.

Java 11 Support

Sprig boot 2.1은 java8 도 지원하지만 java10, java11 까지도 지원한다.

DataSize Support

이거슨 옛날 포스팅 참고

Context ApplicationConversionService Support

ApplicationConversionService 에 DataSize 컨버터 도 추가 되었다. 그래서 아래와 같이 사용할 수 있다.

@Value("${my.duration:10s}")
private Duration duration;

이것 역시 위의 포스터 참고
– 수정: 실제 이내용은 아닌데 왜 내가 이런내을 썼나 의문이다.(아마도 바로 위에 DataSize 가 나와서 그런듯 싶다.) 근데 문서도 조금 의아한게 ApplicationConversionService 클래스는 spring boot 2.0 부터 추가 된 클래스이고 DataSizeConverter 가 2.1 부터 추가된 것도 사실이다. 근데 Duration을 언급한게 조금..

Profile Expression

Profile 표현식이 좀 더 향상 되었다. 예를들어 다음과 같은 표현식을 사용할 수 있다.
production &amp; (us-east | eu-central) 프로덕션이 활성화 되어있고 us-east, 또는 eu-central 활성화 되었을 경우 일치하는 표현식이 된다. 해당 Profile을 파싱하는 클래스는 ProfilesParser이다. 참고 하면 되겠다.

Task Execution

이제 Spring boot 는 ThreadPoolTaskExecutor를 자동설정을 제공해준다. @EnableAsync을 사용할 경우 이전에는 SimpleAsyncTaskExecutor 가 빈으로 등록 되었지만 이제는 ThreadPoolTaskExecutor가 기본적으로 빈으로 등록 된다. spring.task.execution 프로퍼티를 이용해서 속성들을 변경 할 수 있다. 또한 TaskExecutorBuilder 클래스로 좀 더 쉽게 설정 할 수 있다.
– 수정: 실제 기본적으로는 SimpleAsyncTaskExecutor 클래스가 빈으로 등록 되지 않는다.

Task Scheduling

위와 동일하게 ThreadPoolTaskScheduler가 자동설정을 제공해준다. 둘다 모두 해당 어노테이션을 선언해야 한다. Task Scheduling 은 @EnableScheduling 어노테이션을 설정 후 spring.task.scheduling 프로퍼티로 속성들을 변경 할 수 있다. 이 역시 TaskSchedulerBuilder를 제공해 준다.

Logging Groups

Logging을 그룹 별로 묶어서 선언할 수 있다. 아까 위에서 봤던 내용이다. 해당 그룹은 사용자들이 직접 만들 수 도 있다.

logging.group.tomcat=org.apache.catalina, org.apache.coyote, org.apache.tomcat
logging.level.tomcat=TRACE

위와 같이 org.apache.catalina, org.apache.coyote, org.apache.tomcat
l 패키지를 tomcat 그룹으로 묶었다. 그 후 그룹을 TRACE 레벨로 로그를 출력하겠다는 의미이다.

Bootstrap mode for JPA setup

Bootstrap mode 가 추가 되었다. 아마도 JPA Repsitories를 초기화 지연을 시킬지 결정하는 설정같다. 하지만 필자는 잘 모르겠다. 이정도까지..
해당 설정은 다음과 같이 하면 된다.

spring.data.jpa.repositories.bootstrap-mode=deferred

deferred 말고도 lazy 도 존재하지만… 별도의 스레드에서 동작한다고 하니 참고하면 되겠다. deferred 와 lazy 차이는 잘 모르겠다.

Kafka Streams Support

Kafka Streams를 지원한다. 해당 자동 클래스는 KafkaStreamsAnnotationDrivenConfiguration 참고하면 되겠다.

Spring Data JDBC Support

Spring Data JDBC를 지원한다. spring-boot-starter-data-jdbc 디펜더시를 받아 사용하면 된다.

JUnit 5

slice test를 Junit5 이용해서 테스트를 진행한다면 @ExtendWith(SpringExtension.class) 어노테이션을 작성하지 않아도 된다. 이미 각 slice test에 해당 어노테이션이 메타 어노테이션으로 작성되어 있기 때문이다.

Actuator Endpoints

actuator endpoint들이 추가 되었다. Caches Endpoint, Spring Integration Graph Endpoint, Health Endpoint 이 추가 및 변경 되었다. 각 엔드포인트들은 해당 문서를 참고 하면 되겠다.

기타 Micrometer, 잡다한 내용들이 변경이 되었으니 이 글은 참고만 하고 해당 공식 문서를 참고하길 바란다.
오늘은 이상으로 Spring boot 2.1 릴리즈 변화에 대해서 알아봤다.