spring swagger-ui

spring swagger-ui

swagger-ui는 테스트? 혹은 문서? 가 있는 UI를 제공 해준다.
한번 살펴 보자
maven에 다음과 같이 추가 하자

... 

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.4.0</version>
</dependency>

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.4.0</version>
</dependency>

...

swagger 설정을 해주자

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket restApi() {
        // @formatter:off
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                .build()
                .useDefaultResponseMessages(false)
                .globalResponseMessage(RequestMethod.GET,
                        Arrays.asList(
                                new ResponseMessageBuilder()
                                        .code(500)
                                        .message("server error")
                                        .responseModel(
                                                new ModelRef("Error")
                                        ).build()
                        )
                );
        // @formatter:on
    }

    private ApiInfo apiInfo() {
        // @formatter:off
        return new ApiInfoBuilder()
                .title("Spring boot Swagger")
                .description("api list")
                .contact(new Contact("wonwoo", "wonwoo.ml", "test@test.com"))
                .version("1.0.0")
                .build();
        // @formatter:on
    }
}

별거 없다. 상태 코드 500은 전부 적용을 한다는 뜻이다.
그리고 제목, 내용, 연락처, 버전 등 입력 할 수 있다.

그런다음에 controller class에 다음과 같이 어노테이션을 추가 하자

...

@Api(description = "사용자 API")
public class AccountController {
....
}

...

속성으론 value, tags, hidden 등 여러가지가 있다.

그리고 해당 메소드에 다음과 같이 추가하자.

...

@ApiOperation(value = "사용자 리스트", notes = "사용자 리스트를 가져옵니다.")
@RequestMapping(value = "/accounts", method = RequestMethod.GET, headers = "Accept=application/json")
public List<Account> getAccounts() throws JsonProcessingException {
    List<Account> accounts = accountRepository.findAll();
    return accounts;
}
...

해당 API 의 제목 및 내용이다.
그리고 해당 프로퍼티 명을 지정해 줄 수 있다.

...

@ApiModelProperty(value = "사용자 아이디")
private Long id;

...

또한 감출수도 있다.

...

@ApiModelProperty(hidden = true)
private List<Ordered> ordered;

...

완료 되었다.
한번 UI 에들어 가서 확인해보자
http://localhost:8080/swagger-ui.html

spring data jpa 의 jsonfilter

spring data jpa 의 jsonfilter

이번엔 spring data jpa의 json 으로 보내기위한 방법을 한개더 포스팅 하겠다.
jsonfilter 라는 어노테이션을 사용하여 해보자

소스는 dto와 비슷하다.
다른 부분만 올려서 설명하겠다.
jsonfilter는 jackson 라이브러리의 어노테이션이다.
일단 entity에 jsonfilter를 추가하자

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonFilter("accountFilter")
public class Account {
    @Id
    @GeneratedValue
    @Column(name = "account_id")
    private Long id;

    @NotNull
    private String name;

    @OneToMany(mappedBy = "account")
    private List<Ordered> ordered;
}

기존 소스와 동일하지만 JsonFilter 어노테이션만 추가 하였다.
작업 할 것이 한개더 있다.

    @RequestMapping(value = "/accounts", method = RequestMethod.GET, headers = "Accept=application/json")
    public String getAccounts() throws JsonProcessingException {
        List<Account> accounts = accountRepository.findAll();
        ObjectMapper mapper = new ObjectMapper();
        FilterProvider filters = new SimpleFilterProvider().addFilter("accountFilter",
                SimpleBeanPropertyFilter.filterOutAllExcept("id", "name"));
        String accountsStr = mapper.setFilterProvider(filters).writeValueAsString(accounts);
        return accountsStr;
    }

    @RequestMapping(value = "/account/{id}", method = RequestMethod.GET, headers = "Accept=application/json")
    public String getAccount(@PathVariable Long id) throws IOException {
        Account account = accountRepository.findOne(id);
        ObjectMapper mapper = new ObjectMapper();
        FilterProvider filters = new SimpleFilterProvider().addFilter("accountFilter",
                SimpleBeanPropertyFilter.filterOutAllExcept("id", "name"));
        String accountStr = mapper.setFilterProvider(filters).writeValueAsString(account);
        return accountStr;
    }

ObjectMapper에 filter를 적용해야 된다. entity에 추가 했던 filter 값을 넣으면 된다.
SimpleBeanPropertyFilter.filterOutAllExcept 메소드는 추가할 property명만 적어주면 된다.
SimpleBeanPropertyFilter.serializeAllExcept 메소드는 제외 시킬 property명을 적어주면 된다.

어떤것이 좋을까 고민중
바로 다음에 @JsonView를 사용해서 해보자!

spring data jpa 의 json dto

spring data jpa 의 json

오늘은 spring data jpa를 사용하여 json을 출력해보는 포스팅을 하겠다.
글로버 페치 전략에 즉시로딩이 아닌 지연로딩을 사용하면 영속성 상태가 아닐 경우 데이터를 가져올 때 에러가 발생한다.
그래서 open session in view 이하 osiv를 사용하는데 messageconverter일 경우 즉 json일 경우 그래도 에러가 발생한다.(연관관계가 양방향 일 경우)
jackson에서 에러는 내뿜는듯 하다. 계속 순환하는거 같다.
필자가 모르는거 일수도 있다.
그래서 고민하기 시작했다. 흠!
방법은 몇가지 있다.
@JsonIgnore, @JsonBackReference, @JsonManagedReference 혹은 JSON Filter, 와 @JsonView
현재까지 알아본 결과 위와 같다.
다른 방법은 나중에 설명 하도록 하고 이번 시간에는 dto를 만들어서 해보겠다.
dto(data transfer object)란 말그대로 데이터가 이동하는 객체다.
일단 소스로 보자 boot기준이다.


... <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.6</version> </dependency> <dependency> <groupId>org.modelmapper</groupId> <artifactId>modelmapper</artifactId> <version>0.7.5</version> </dependency> ...

일단 dto가 정신사납지 않게 lombok은 필수로 하자
object을 mapping 시키는? 말이 맞나? 아무튼 modelmapper를 메이븐에 등록하자.
그래야 코드가 깔끔해진다. 물론 java8을 써야.

Account class

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {

  @Id
  @GeneratedValue
  @Column(name = "ACCOUNT_ID")
  private Long id;

  private String name;

  @OneToMany(mappedBy = "account")
  private List<Orders> orders;

}

딱히 없다 테스트를 하느라 OneToMany도 같이 넣어놨다. oneToMany의 기본페치 전략은 LAZY다. 또한 ManyToMany도 마찬가지다.

orders class

@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
public class Orders {

  @Id
  @GeneratedValue
  @Column(name = "ORDER_ID")
  private Long id;

  private String orderName;

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "ACCOUNT_ID")
  private Account account;

}

여기도 Account 클래스와 동일하다. ManyToOne의 기본페치 전략은 즉시로딩이다. OneToOne도 마찬가지다.

AccountRepository class

@Repository
public interface AccountRepository extends JpaRepository<Account, Long> {
}

일반적인 Repository 클래스다. 따로 설명은 안하겠다.

AccountDto class

public class AccountDto {

  @Data
  public static class Account{
    private Long id;
    private String name;
  }
}

dto 클래스를 만들었다. AccountDto에 한개의 inner class로 만들었다. 물론 여러개가 될수 있다. 저게 테스트 용도라 필드가 얼마 없지만
만약 많다면 getter setter가 어마어마 할 것이다. 그런데 lombok을 써서 관심사를 집중 할 수 있다. 아무튼!

modelmapper를 쓰기 위해 빈으로 등록하자

@Bean
public ModelMapper modelMapper() {
  return new ModelMapper();
}

AccountController class

@RestController
public class AccountController {

  @Autowired
  private AccountRepository accountRepository;

  @Autowired
  private ModelMapper modelMapper;

  @RequestMapping(value = "/accounts")
  public List<AccountDto.Account> accountList() {
    List<Account> accounts = accountRepository.findAll();
    return accounts.stream().map(account -> modelMapper.map(account, AccountDto.Account.class))
      .collect(toList());
  }

  @RequestMapping(value = "/account/{id}")
  public AccountDto.Account account(@PathVariable Long id){
    Account account = accountRepository.findOne(id);
    return modelMapper.map(account, AccountDto.Account.class);
  }
}

간단한 테스트용도라 서비스르 없애버렸다. spring boot의 기본전략이 open session in view를 적용한 듯하다. 그래서 서비스계층을 없앴다.
modelmapper로 기존 account를 accountDto로 변경하여 뷰로 보내줬다.
이러면 뷰에선 id와 name만을 볼 수 있다.

글쎄다. 뭐가 좋은지 어떤게 효율적인지는 필자도 잘 모르겠다.
그때그때 상황을 봐가면서 상황에 맞게 하는게 나을지도 모른다.
JPA 책을보면 특수한 경우가 아니라면 페치 전략을 LAZY하는게 성능에 좋다고 한다.
위에서 설명은 하지 않았지만 방법은 적어놨으니 한번 살펴 보자!