여태까지 정리해온 문서를 토대로
외부 API 에서 데이터를 가져와 DB 에 등록후, Client 에게 Body를 내려주는 간단한 미니성 프로젝트를 만들었다.
** IntelliJ 환경세팅 **
[ 개발환경 기준 ]
✔ OS : MAC Intel Ventura 13.2.1
✔ Tool : IntelliJ IDEA
✔ JDK : OpenJDK 17
✔ Spring Boot 3.1.5 (릴리즈)
✔ Gradle/Groovy
✔ Spring Data JPA (프레임워크)
✔ 외부 API : https://jsonplaceholder.typicode.com/todos?userId=1
Maven보단 스크립트 방식으로 가독성이 좋고 빌드캐시와 병렬빌드를 지원하는 Gradle 로 진행하기로 하였다.
Spring Boot 3.x 대 부터는 Java 17 버전이 필수이므로, Open JDK 를 설치하였다.(참고로 Oracle JDK는 유료이다.)
(패키지관리를 위해 Homebrew가 설치되었다는 가정하에 진행하였다.)
1. Open JDK 설치
brew install openjdk@17
(설치후) 환경변수 설정
echo 'export PATH="/usr/local/opt/openjdk@17/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
(설치확인)
java -version
2. 프로젝트 생성
File -> New -> Project
Next를 눌러 Spring Boot 버전 세팅과, 필요한 Dependency 를 미리 세팅한다(나는 간단히 테스트만 할 용도로 4개정도만 하였다.)
의존성 추가는 추후 build.gradle 에서 수정이 가능하다.
( ** Spring security 를 따로 추가하지 않으면 WhiteError Label Page 가 보일것이다.)
2-1. 혹시나 Gradle JVM 설정이 안되어있을경우 아래와같은 에러가 뜰 수 있다.
고럴땐
Settings -> build,Excution,Devlopment -> Gradle 로 진입해서
Gradle JVM 에 값을 세팅해주면된다.
나는 API 문서를 Swagger 로 관리할 예정이므로 스웨거를 이용하였다.
*** 스웨거 세팅 ***
1. build.gradle 에 다음 의존성을 추가한다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
// swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0'
// mysql
implementation 'mysql:mysql-connector-java:8.0.22'
// webclient
implementation 'org.springframework.boot:spring-boot-starter-webflux'
// Json
implementation 'org.json:json:20230227'
// mapper(순서중요)
implementation 'org.mapstruct:mapstruct:1.5.5.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0'
annotationProcessor 'org.projectlombok:lombok'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
}
Entity <-> Dto 객체 변환을 위해 MapSturct 를 쓸 예정인데, 여기서 순서를 잘조합해야한다.
참고로 순서를 잘못 조합했을시 lombok이 setter를 부르지 못해, 값이 Null 이 나올 수 있다. (이걸로 엄청나게 삽질했다.)
2. Swagger Class 를 추가한다.
@OpenAPIDefinition(
info = @Info(
title = "API TITLE",
description = "API DESCRIPTION",
version = "v1"
)
)
@Configuration
public class SwaggerConfig {
@Bean
public OpenAPI openAPI() {
return new OpenAPI();
}
}
io.swagger.v3.oas.annotations 패키지에 있는 어노테이션을 사용하여OpenAPI 를 정의 하고 빈(@Bean)을 등록한다.
- title: API의 이름 또는 제목
- description: API에 대한 간단한 설명을 제공
- version: API의 버전
(Spring Security 를 사용한다면)
Spring Security 기본설정에 의해 보호된 리소스에 대한 접근 시 로그인이 필요하도록 설정되어 있으므로, Security 설정을 해줘야 한다.
3. SecurityFilterChain 설정
- SecurityConfig.java
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private static final String[] AUTH_WHITELIST = {
"/api/test/**",
"/swagger-ui/**",
"/v3/api-docs/**",
};
/**
* Spring Security 필터에서 제외
* @return
*/
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers(AUTH_WHITELIST);
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.httpBasic(HttpBasicConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable)
.build();
}
}
Spring Security 5.7.0-M2 부터 WebSecurityConfigurerAdapter가 Deprecated 되었고 기존에 security 예외 url을 설정하던 antMatchers는 아예 삭제되었으므로 Spring Boot 3.x 버전부터는 SecurityFilterChain 를 사용하여 구현해야한다.
또한 non-lamda-DSL -> lamda-DSL로 변경되었으며 인증,인가 옵션등을 HttpSecurity 쪽에 등록해야 한다.
@RequiredArgsConstuctor
@Autowired는 DI가 타입(Type)이 같은 빈(Bean)이 발견되면 그냥 주입한다.
지금까지 의존성 주입이 간편했던 이유인데 이것이 문제가 되는 이유는 같은 Type의 다른 객체가 여러 개일 때 문제가 발생한다.
DI가 Type만 보고 내려주기에 서로 다른 A타입의 객체가 2개 존재한다면 @Autowired는 Error를 띄울 수밖에 없다.
3. 애플리케이션에서 쓸 공통 모듈 정의(common)
Custom Exception 과 Custom ErrorCode, Custom Return 객체정의
- Result.java (리턴객체)
@Data
public class Result<T> {
public static final Boolean OK = Boolean.TRUE;
public static final Boolean NOK = Boolean.FALSE;
private int code;
private Boolean result;
private T data;
private String message;
public Result(int code, Boolean result, String message, T data) {
this.code = code;
this.result = result;
this.message = message;
this.data = data;
}
public Result(int code, Boolean result, String message) {
this.code = code;
this.result = result;
this.message = message;
}
public Result(ResultErrorCode resultErrorCode, String message) {
this.code = resultErrorCode.getCode();
this.result = NOK;
this.message = message;
}
public Result gridPut(Page<?> page){
Map<String,Object> pageData = new HashMap<String,Object>();
pageData.put("contents", page.getContent());
pageData.put("pagination", Map.of("page", page.getNumber()+1 , "totalCount", page.getTotalElements()));
this.data = (T) pageData;
return this;
}
public Result(ResultErrorCode resultErrorCode) {
this(resultErrorCode,resultErrorCode.getMessage());
}
public static <T> Result<T> ok(T data){
return new Result<>(Constants.SUCCESS_STATUS, OK, Constants.SUCCESS_MESSAGE, data);
}
public static <T> Result<T> ok(){
return new Result<>(Constants.SUCCESS_STATUS, OK, Constants.SUCCESS_MESSAGE);
}
public static <T> Result<T> nok(ResultErrorCode resultErrorCode){
return new Result<>(resultErrorCode, resultErrorCode.getMessage());
}
public static <T> Result<T> nok(ResultErrorCode resultErrorCode, String message){
return new Result<>(resultErrorCode, message);
}
public static <T> Result<T> nok(ResultException resultException){
return new Result<>(resultException.getResultErrorCode() , resultException.getMessage());
}
}
- ResultErrorCode.java (에러코드정의)
@AllArgsConstructor
@Getter
public enum ResultErrorCode {
// API 관련
API_RESPONSE_ERROR(4000, "API 통신 실패하였습니다."),
// DB 관련
DATABASE_REGIST_ERROR(5000, "등록에 실패하였습니다."),
DATABASE_SEARCH_ERROR(5001, "조회에 실패하였습니다."),
// 공통에러
COMMON_UNKNOWN_ERROR(9999, "시스템 에러입니다.");
private final int code;
private final String message;
}
- ResultException.java (런타임예외커스텀클래스 정의)
@StandardException
public class ResultException extends RuntimeException {
@Getter
ResultErrorCode resultErrorCode;
public ResultException(ResultErrorCode errorCode){
super(errorCode.getMessage());
this.resultErrorCode = errorCode;
}
public ResultException(String errorMsg){
super(errorMsg);
}
}
-GlobalExceptionHandler.java 정의(공통 Exception 정의)
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity systemException(Exception ex){
log.error("시스템 에러",ex);
return new ResponseEntity(Result.nok(ResultErrorCode.COMMON_UNKNOWN_ERROR), HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(ResultException.class)
public ResponseEntity resultExcpetion(ResultException ex){
log.error("Application 에러",ex);
return new ResponseEntity(Result.nok(ex.getResultErrorCode()),HttpStatus.INTERNAL_SERVER_ERROR);
}
}
- Constants.java (공통상수)
public class Constants {
public static final String SUCCESS_MESSAGE = "SUCCESS";
public static final String FAIL_MESSAGE = "FAILED";
public static final int SUCCESS_STATUS = 200;
public static final String TEST_API_URI ="https://jsonplaceholder.typicode.com/todos?userId=1";
}
- HttpWebClient.java ( 외부 API 통신을 위한 Http 통신모듈 만들기 )
@Slf4j
public class HttpWebClient {
private WebClient webClient; // webclient 인스턴스
private DefaultUriBuilderFactory factory; // URI 템플릿의 인코딩을 위한 설정(제대로된 파라미터 셋팅을 하기위해 필수)
/**
* 생성자를 통해 WebClient 초기화
* @param uri
*/
public HttpWebClient(String uri){
factory = new DefaultUriBuilderFactory(uri);
factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY); // URI 변수값만 인코딩
webClient = WebClient.builder()
.uriBuilderFactory(factory)
.baseUrl(uri)
.filter(ExchangeFilterFunction.ofRequestProcessor( // 요청정보로깅
clientRequest -> {
log.debug("[HTTP_Request] : {} {}", clientRequest.method(), clientRequest.url());
clientRequest.headers().forEach((name,values) -> values.forEach(value -> log.debug("[HTTP_PARAM] {} : {}", name, value)));
return Mono.just(clientRequest);
}
))
.build();
}
/**
* Http GET
* @param url
* @param params
* @return
* @throws WebClientResponseException
* @throws Exception
*/
public Mono<String> httpRequestGet(String url, MultiValueMap<String,String> params) throws WebClientResponseException {
//log.debug("[Request_Param]:"+params);
return webClient.mutate().build() // 기존 설정값을 상속해서 사용
.get() // Http Method
.uri(uriBuilder -> uriBuilder.path(url).queryParams(params).build()) // url 설정후, 쿼리매개변수 추가
.accept(MediaType.APPLICATION_JSON) // 응답형식 JSON
.retrieve() // HTTP 요청을 실행하고 응답을 받아옴
.onStatus(httpStatus -> httpStatus != HttpStatus.OK, // 실패시 WebClientResponseException 예외생성(던짐)
clientResponse -> {
return clientResponse.createException();
})
.bodyToMono(String.class);
}
public Mono<String> httpRequestGet() throws WebClientResponseException {
return httpRequestGet("", null);
}
/**
* Http POST
* @param url
* @param params
* @return
* @throws WebClientResponseException
* @throws Exception
*/
public Mono<String> httpRequestPost(String url, JSONObject params) throws WebClientResponseException {
//log.debug("[Request_Param]:"+params);
return webClient.mutate().build()
.post()
.uri(url)
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(params.toString()))
.retrieve()
.onStatus(httpStatus -> httpStatus != HttpStatus.OK,
clientResponse -> {
return clientResponse.createException();
})
.bodyToMono(String.class);
}
}
4 . API Component(config) 만들기
@Slf4j
@Component
public class ApiConfig {
private static HttpWebClient httpWebClient;
public ApiConfig(){
// Test 전용 WebClient
httpWebClient = new HttpWebClient(Constants.TEST_API_URI);
}
/**
* API Request
* @return
*/
public JSONObject getTotalList(){
try{
String monoResult = httpWebClient.httpRequestGet().block();
return ConvertUtils.strToJson(monoResult);
} catch (WebClientResponseException we){
String exceptMsg = we.getResponseBodyAsString(Charset.forName("UTF-8"));
log.error("[API WEBCLIENT EXCEPTION "+we.getRequest().getURI()+"] : "+we.getStatusCode()+"/"+exceptMsg,we);
throw new ResultException(ResultErrorCode.API_RESPONSE_ERROR);
}
}
/**
* API Request_Async
* @return
*/
public JSONObject getAsyncTotalList() {
try {
CountDownLatch cdl = new CountDownLatch(1);
JSONObject resultJson;
httpWebClient.httpRequestGet()
.doOnSuccess(e -> {
result = e;
cdl.countDown(); // 성공적으로 완료되었을 때만 countDown
})
.doOnError(error -> {
// 에러 처리
log.error("통신실패:"+error.getMessage());
cdl.countDown(); // 에러 발생 시에도 countDown
})
.subscribe();
cdl.await();
resultJson = ConvertUtils.strToJson(result);
return resultJson;
}catch (InterruptedException ie){
log.error("인터럽트 Exception:"+ie.getMessage());
return null;
}
}
}
참고로 비동기와 동기방식 모두 만들어두었다. 나는 간단히 URL 1개만 사용예정이므로, 동기방식으로 진행하였다.
4-1. Utils 성 클래스 작성
날짜나 문자열, 데이터 타입등 변환을 위한 Util 클래스를 만들었다.
- ConvertUtils.java
@Slf4j
public class ConvertUtils {
/**
* String To JsonObject
* @param jsonStr
* @return
*/
public static JSONObject strToJson(String jsonStr){
try {
JSONObject jsonObject = new JSONObject(jsonStr);
return jsonObject;
} catch (Exception e) {
log.error("String to JSON Parsing EXCEPTION:"+e.getMessage(),e);
return new JSONObject();
}
}
/**
* JsonString TO DTO Object
* @param jsonStr
* @param valueType
* @param <T>
* @return
* @throws JsonProcessingException
*/
public static <T> T strToDTO(String jsonStr, Class<T> valueType) {
try {
ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);
T obj = mapper.readValue(jsonStr, valueType);
return obj;
} catch (JsonProcessingException ex){
log.error("String TO Dto Parsing Exception:"+ex.getMessage(),ex);
return null;
}
}
/**
* DTO Object to JsonString
* @param obj
* @return
* @throws JsonProcessingException
*/
public static String dtoToJsonString(Object obj) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
String result = mapper.writeValueAsString(obj);
return result;
}
/**
* DTO Object TO JsonObject
* @param obj
* @return
*/
public static JSONObject dtoToJson(Object obj) throws JsonProcessingException {
return strToJson(dtoToJsonString(obj));
}
/**
* Map To JsonString
* @param map
* @return
*/
public static String mapToJsonStr(Map<String,Object> map){
JSONObject json = new JSONObject(map);
return json.toString();
}
/**
* Json Object To Map
* @param obj
* @return
*/
public static Map<String, Object> jsonToMap(JSONObject obj){
if (ObjectUtils.isEmpty(obj)) {
log.error("BAD REQUEST obj : {}", obj);
throw new IllegalArgumentException(String.format("BAD REQUEST obj %s", obj));
} try {
return new ObjectMapper().readValue(obj.toString(), Map.class);
} catch (Exception e) {
log.error(e.getMessage(), e); throw new RuntimeException(e);
}
}
/**
* Json Array To List Map
* @param jsonArray
* @return
*/
public static List<Map<String, Object>> jsonArrToListMap(JSONArray jsonArray) {
if (ObjectUtils.isEmpty(jsonArray)) {
log.error("jsonArray is null.");
throw new IllegalArgumentException("jsonArray is null");
}
List<Map<String, Object>> list = new ArrayList<>();
for (Object jsonObject : jsonArray) {
list.add(jsonToMap((JSONObject) jsonObject));
} return list;
}
/**
* map to Dto
* @param obj
* @param valueType
* @param <T>
* @return
* @throws JsonProcessingException
*/
public static <T> T mapToDto(Map<String,Object> obj, Class<T> valueType) throws JsonProcessingException {
return strToDTO(mapToJsonStr(obj), valueType);
}
/**
* Json String to Map
* @param jsonString
* @return
*/
public static Map<String,Object> strToMap(String jsonString) {
try {
return new ObjectMapper().readValue(jsonString, Map.class);
} catch (JsonProcessingException e) {
log.error("String to MAP Parsing EXCEPTION:"+e.getMessage(),e);
return new HashMap<>();
}
}
}
5. DB 생성
Test URI 를 포스트맨으로 호출해본다.
해당 정보를 토대로 DB 를 만들자.
-- todo.todo_info definition
CREATE TABLE `todo_info` (
`seq` int unsigned NOT NULL AUTO_INCREMENT,
`user_id` int unsigned DEFAULT NULL,
`id` int unsigned DEFAULT NULL,
`title` varchar(300) DEFAULT NULL,
`completed` tinyint(1) DEFAULT NULL,
`create_at` datetime DEFAULT NULL,
`update_at` datetime DEFAULT NULL,
PRIMARY KEY (`seq`)
) ENGINE=InnoDB AUTO_INCREMENT=41 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
5-1. 프로퍼티추가
logging.level.com.temp.temp1=debug
server.error.whitelabel.enabled=false
## datasource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/todo?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Seoul
spring.datasource.username=root
spring.datasource.password=1234
## JPA 설정
spring.jpa.database=mysql
# SQL 쿼리로그 출력레벨 설정
logging.level.org.hibernate.SQL=debug
# SQL 로그형식 포맷팅여부
spring.jpa.properties.hibernate.format_sql=true
# SQL 내부에 주석추가
spring.jpa.properties.hibernate.use_sql_comments=true
# SQL문에 색상부여
spring.jpa.properties.hibernate.highlight_sql=true
# 바인딩된 파라미터 확인가능
hibernate.type.descriptor.sql=trace
6. Model 생성(Dto,Entity)
- TodoEntity.java
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "todo_info")
public class TodoEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer seq;
@Column(name = "user_id")
private Integer userId;
private Integer id;
private String title;
private Boolean completed;
@CreatedDate
@Column(name = "create_at")
private LocalDateTime createAt;
@LastModifiedDate
@Column(name = "update_at")
private LocalDateTime updateAt;
}
- TodoDto.java
@Data
public class TodoDto {
private Integer userId;
private Integer id;
private String title;
private Boolean completed;
private LocalDateTime createAt;
private LocalDateTime updateAt;
}
Entity 는 실제 DB 테이블과 1:1 매핑되는 데이터므로 영속성목적으로 사용되는 객체며,
DTO는 데이터 교환을 위한 객체로 클라이언트단에서 필요한 데이터를 가공하여 추가할수있고, 실제로 결과를 내려줄때도 DTO를 사용한다.
7. Mapper 생성
- EntityMapper.java
public interface EntityMapper<D, E> {
E toEntity(final D dto);
D toDto(final E entity);
List<D> toDtoList(List<E> entityList);
List<E> toEntityList(List<D> dtoList);
}
Entity와 DTO로 변환시켜줄 상위 인터페이스부터 먼저 만든다
-TodoMapper.java
@Mapper
public interface TodoMapper extends EntityMapper<TodoDto, TodoEntity> {
TodoMapper MAPPER = Mappers.getMapper(TodoMapper.class);
}
8. Repository 생성
JPA(ORM) 인터페이스를 좀 더 편하게 쓰기위해 Spring Data JPA 프레임워크를 이용하였다.
참고로 JPA 는 인터페이스 이고, Hibernate는 구현체이다. (JPA 인터페이스를 implement)
Spring Data JPA 는 JPA 개발을 편하게 해주는 프레임워크/라이브러리 개념이다.
DB접근자로 Repository를 쓰며 인터페이스로 생성한다. 단순히 인터페이스를 생성 후, JpaRepository<Entity 클래스, PK 타입>를 상속하면 기본적인 CRUD 메소드가 자동으로 생성된다.
(JpaRepository 인터페이스를 상속받는 것만으로도, 많은 JPA 관련 메소드를 사용할 수 있다)
-TodoRepository.java
public interface TodoRepository extends JpaRepository<TodoEntity, Integer> {
}
9. 컨트롤러 및 서비스 작성
테스트를 위해 API endpoint 정의와 Service 를 각각 간단하게 작성한다.
- TestContorller.java
@Tag(name = "test API", description = "test 관련 API") // swagger 에서 API 타이틀, 설명
@RestController
@RequestMapping("/api/test")
@RequiredArgsConstructor
@Slf4j
public class TempController {
final private TempService tempService;
@GetMapping("/result")
@Operation(summary = "Total API Result", description = "API 전체 호출")
@ApiResponse(responseCode = "200", description = "OK")
public Result getTotalResult(){
return Result.ok(tempService.getResultTotalList());
}
@GetMapping("/list")
@Operation(summary = "Todo API List", description = "TODO List API 조회")
@ApiResponse(responseCode = "200", description = "OK")
public Result getTodoResult(){
return Result.ok(tempService.getTodoTitleList());
}
@PostMapping("/dbregist")
@Operation(summary = "API Todo Data Regist DB", description = "DB에 API DATA를 등록시킨다")
@ApiResponse(responseCode = "200", description = "OK")
public Result registTodoListAPIData(){
return tempService.registTodo();
}
@GetMapping("/dblist")
@Operation(summary = "DB Todo List", description = "DB List 조회")
@ApiResponse(responseCode = "200", description = "OK")
public Result getTodoListFromDB(@Parameter(description = "현재 페이지번호",example = "1") @RequestParam(required = false) Integer page,
@Parameter(description = "페이지에 보여줄 컬럼수",example = "10") @RequestParam(required = false) Integer cntPerPage){
return tempService.searchTodoList(page,cntPerPage);
}
}
@Contoller 와 @RestController 의 차이는 대부분 다 알고있을꺼라 넘어가려한다. 간단하게 설명하자면,
@Controller
-> 예전에 Spring MVC에서 View를 반환하기 위해 사용했었다. 그래서 주로 ViewResolver를 통해 뷰를 찾아 랜더링한다.
ex) return "/users/detailView";
@RestController
-> @Controller+@ResponseBody가 추가된 것으로 주 용도는 Json 형태로 객체 데이터를 반환하는 것이다.(ResponseEntity)
REST API를 개발할 때 주로 사용한다.
(실제로 @Controller에 @ReponseBody를 붙인 것과 동일하게 작동한다.)
- TestService.java
@Service
@Slf4j
@RequiredArgsConstructor
public class TempService {
private final TodoRepository todoRepository;
private final ApiConfig apiConfig;
public List<TodoDto> getResultTotalList(){
JSONObject totalObj = apiConfig.getTotalList();
JSONArray jsonArray = new JSONArray(totalObj);
List<TodoDto> todoList = new ArrayList<>();
for(int i=0; i<jsonArray.length(); i++){
TodoDto dto = ConvertUtils.strToDTO(jsonArray.get(i).toString(), TodoDto.class);
todoList.add(dto);
}
return todoList;
}
public List<TodoDto> getTodoTitleList(){
List<TodoDto> titieList = new ArrayList<>();
JSONObject totalObj = apiConfig.getTotalList();
if(totalObj.has("title")){
JSONArray titleArr = totalObj.getJSONArray("title");
for(int i=0;i<titleArr.length();i++){
TodoDto todoDto = ConvertUtils.strToDTO(titleArr.get(i).toString(), TodoDto.class);
titieList.add(todoDto);
}
}
return titieList;
}
public Result registTodo(){
try {
// 데이터등록
todoRepository.saveAll(TodoMapper.MAPPER.toEntityList(getTodoTitleList()));
return Result.ok();
} catch (DataAccessException ex) {
log.error("[등록 EXCEPTION]:"+ex.getMessage(),ex);
return Result.nok(ResultErrorCode.DATABASE_REGIST_ERROR);
}
}
public Result searchTodoList(Integer page, Integer cntPerPage){
try {
// 패이지세팅
Pageable pageable = ObjectUtils.isNotEmpty(page) && ObjectUtils.isNotEmpty(cntPerPage)
&& page > 0 && cntPerPage > 0 ? PageRequest.of(page-1,cntPerPage) : null;
// 데이터조회
Page<TodoDto> pageList = todoRepository.findAll(pageable).map(TodoMapper.MAPPER::toDto);
return Result.ok().gridPut(pageList);
} catch (DataAccessException ex) {
log.error("[조회 EXCEPTION]:"+ex.getMessage(),ex);
return Result.nok(ResultErrorCode.DATABASE_SEARCH_ERROR);
}
}
}
@Service
@Component 는Spring에서 관리되는 객체임을 표시하기 위해 사용하는 가장 기본적인 annotation 이고,
@Service도 @Component 안에 포함되지만, 역할을 명시적으로 구분해주기 위해 각자 분리해서 사용한다. 즉 Spring AOP 에 부합하는 개념이고 Spring 에서 권장된다.
10. 구동
@EnableJpaAuditing
@SpringBootApplication
public class Temp1Application {
public static void main(String[] args) {
SpringApplication.run(Temp1Application.class, args);
}
}
'web > SpringBoot' 카테고리의 다른 글
[WebClient] 사용시 주의사항 (0) | 2023.11.23 |
---|---|
[REST API] @Primary / @Qualifier (0) | 2023.11.20 |
[REST API] @Autowired vs @RequiredArgsConstructor (0) | 2023.11.16 |
[REST API] CustomException 처리(@StandardException) (0) | 2023.11.16 |
[REST API] @Configuration / @Component (1) | 2023.11.14 |