블로그
전체 42025. 03. 30.
0
[인프런워밍업클럽 3기 BE] 4주차 발자국
1. 강의1.1 학습내용도커로 프로젝트 빌드 및 배포하기docker-compose.ymlDocker Compose에서 사용하는 설정 파일이다.여러 개의 Docker 컨테이너를 한꺼번에 설정하고 실행할 수 있는 스크립트 파일이다.Dockerfile애플리케이션 실행 환경을 정의하는 파일이다.실행 순서프로젝트 빌드Gradle > Tasks > build > buildDockerfile 실행docker-compose.yml 실행발생했던 문제들Public Key Retrieval is not allowed at com.mysql.cj.jdbc.exceptions.SQLError.createSQLExceptionurl에 추가하였다.SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/portfolio?useSSL=false&serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=trueorg.thymeleaf.TemplateEngine Exception processing template 맨 앞에 있는 "/" 가 문제였다.소스 수정 후 재빌드 하니까 정상적으로 되었다.1.2. 느낀 점도커로 프로젝트 빌드를 해본적이 없어서 고생을 했다. 빌드시 발생하는 오류를 잡느라 미션7도 기한내 제출하지 못했다.직장과 병행하다 보니 평일에는 시간 내기가 좀 힘든데 스터디 기간이 좀 길었으면 좋았을것 같다는 생각을 하였다.출처https://inf.run/J5Bawhttps://inf.run/hke19https://inf.run/Qo91Z
백엔드
・
발자국
・
인프런워밍업클럽
2025. 03. 22.
0
[인프런워밍업클럽 3기 BE] 3주차 발자국
1. 강의1.1 학습내용@RestControllerAdviceSpring에서 예외 처리를 전역적으로 관리할 수 있도록 도와주는 어노테이션이다.JSON 형태로 응답을 반환하는 데 최적화 되어 있다.@ExceptionHandler와 함께 사용하여 세부적인 특정 예외 유형 처리가 가능하다.@ExceptionHandler는 컨트롤러 내부에서 사용하면 개별 처리, @RestControllerAdvice와 함께 사용하면 전역 예외 처리 가능하다.여러 개의 예외를 한 메서드에서 처리 가능하다.@RestControllerAdvice class AdminApiControllerAdvice { val log = LoggerFactory.getLogger(AdminApiControllerAdvice::class.java) @ExceptionHandler fun handleException(e: AdminException):ResponseEntity { log.info(e.message , e) return ResponseEntity.status(e.httpStatus).body(e.message) } @ExceptionHandler fun handleException(e: MethodArgumentNotValidException):ResponseEntity { log.info(e.message , e) val fieldError = e.bindingResult.fieldErrors[0] val message = "[${fieldError.field} ${fieldError.defaultMessage}]" return ResponseEntity.badRequest().body(message) } }1.2 느낀 점템플릿 라이센스 문제로 이번주 봐야될 강의가 줄어들어 미션에 좀 더 집중 할 수 있었다.2. 미션2.1. 조회 REST API 만들기2.2. 삽입, 수정, 삭제 API 만들기테스트 케이스를 만들면서 상품, 주문 정보가 없는 경우 사용자 정의 에러를 만들어서 던지게 변경하였다.class OrderNotFoundException(val orderNo: Long) : CustomException("해당 주문정보가 없습니다. [주문번호 : $orderNo]")class ProductNotFoundException(val productCd: Long) : CustomException("해당 상품코드가 없습니다. [상품코드 : $productCd]")주문 등록 @Transactional fun create(requestDTO: RequestCreateDTO): ResponseEntity { // 주문 마스터 생성 val orderHeader = Order(orderSts = OrderSts.PAYMENT_COMPLETED.code) orderRepository.save(orderHeader) // 주문 상세 생성 requestDTO.items.forEach { item -> val product = productRepository.findById(item.productCd).orElseThrow { ProductNotFoundException(item.productCd) // 상품 코드가 없을 경우 예외 던지기 } val orderDetail = OrderDetail( order = orderHeader, product = product, price = item.price, qty = item.qty, memo = item.memo ) orderDetailRepository.save(orderDetail) } val response = ResponseDTO( code = 200, msg = "주문 등록 성공", orderNo = orderHeader.orderNo, orderSts = orderHeader.orderSts ) return ResponseEntity.status(HttpStatus.OK).body(response) }주문 수정@Transactional fun update(orderNo: Long, requestDTO: RequestUpdateDTO): ResponseEntity { // 주문 마스터 val orderHeader = orderRepository.findById(orderNo).orElseThrow { OrderNotFoundException(orderNo) // 주문 정보가 없을 경우 예외 던지기 } if ("60".equals(orderHeader.orderSts)) { val errorRes = ResponseDTO( code = 400, msg = "배송완료 상태는 수정 할 수 없습니다. [주문번호 : ${orderNo}]", orderNo = null, orderSts = null ) return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorRes) } if (requestDTO.orderSts > 0 ) { // 주문 상태가 유효한 코드인지 확인하고 적용 val orderSts = OrderSts.values().find { it.code == requestDTO.orderSts } ?: throw ApiException("유효하지 않은 주문 상태 코드입니다. [주문상태 : ${requestDTO.orderSts}]") // 주문 마스터 업데이트 orderHeader.orderSts = orderSts.code // code 값으로 업데이트 orderRepository.save(orderHeader) // 주문 상태를 업데이트 } // 주문 상세 val orderDetail = orderDetailRepository.findByOrderNo(orderNo) requestDTO.items.forEach { item -> val product = productRepository.findById(item.productCd).orElseThrow { ProductNotFoundException(item.productCd) // 상품 코드가 없을 경우 예외 던지기 } // 주문 상세 업데이트 val updateOrderDetail = orderDetail.find { it.product == product } if (updateOrderDetail != null) { updateOrderDetail.price = item.price updateOrderDetail.qty = item.qty updateOrderDetail.memo = item.memo orderDetailRepository.save(updateOrderDetail) // 업데이트 } } // 응답 반환 val response = ResponseDTO( code = 200, msg = "주문 수정 성공", orderNo = orderNo, orderSts = orderHeader.orderSts ) return ResponseEntity.status(HttpStatus.OK).body(response) }주문 삭제 @Transactional fun delete(orderNo: Long): ResponseEntity { val orderHeader = orderRepository.findById(orderNo).orElseThrow { OrderNotFoundException(orderNo) // 주문 정보가 없을 경우 예외 던지기 } val orderSts = orderHeader.orderSts if ("60".equals(orderSts)) { val errorRes = ResponseDTO( code = 400, msg = "배송완료 상태는 삭제 할 수 없습니다. [주문번호 : ${orderNo}]", orderNo = null, orderSts = null ) return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorRes) } // 주문 마스터 orderHeader.delYn = "Y" orderRepository.save(orderHeader) // 주문 상세 val orderDetailList = orderDetailRepository.findByOrderNo(orderNo) orderDetailList.forEach { it.delYn = "Y" } // delYn 필드 값을 "Y"로 변경 orderDetailRepository.saveAll(orderDetailList) // 변경된 리스트를 저장 val response = ResponseDTO( code = 200, msg = "주문 삭제 성공", orderNo = orderNo, orderSts = null ) return ResponseEntity.status(HttpStatus.OK).body(response) }2.3. 느낀 점조회 API 만들기 미션은 제출하지 못했고 삽입, 수정, 삭제 API 만들기 미션은 겨우 제출하였다.생각보다 내가 원하는 기능은 구현하는게 빠르게 되지 않아서 많이 검색해보았다. 개발하고 나서 사용하지 않는 테이블과 컬럼들을 정리하였다. 초기 ERD와 좀 다르게 되어서 ERD도 다시 수정하였다.출처https://www.inflearn.com/course/%EC%9E%85%EB%AC%B8%EC%9E%90-spring-boot-kotlin-%ED%8F%AC%ED%8A%B8%ED%8F%B4%EB%A6%AC%EC%98%A4/dashboard
백엔드
・
발자국
・
인프런워밍업클럽
2025. 03. 04.
0
[인프런워밍업클럽 3기 BE] 2주차 발자국
1. 강의강의 진도 : 섹션3 ~ 섹션41.1 학습내용@PostConstruct빈(Bean)이 생성되고 의존성 주입이 완료된 후 실행할 초기화 메서드를 지정하는 데 사용한다.애플리케이션 실행 시 해당 빈이 초기화될 때 한 번만 실행된다.데이터 로드, 캐시 초기화, 설정 값 검증 등에 주로 사용된다.테스트 코드 작성시 관련 어노테이션@DataJpaTestJPA 관련 빈만 로드하여 테스트 할 수 있게 만들어 준다.테스트가 끝나면 자동 롤백 됩니다.@TestInstanceJUnit 5에서 테스트 클래스의 인스턴스 생명 주기를 지정하는 어노테이션이다.테스트 메서드간의 인스턴스 변수가 공유되지 않는다.@TestInstance(TestInstance.Lifecycle.PER_CLASS) 사용시 테스트 클래스의 인스턴스를 한 번만 생성하고 모든 테스트 메서드에서 공유한다.@ExtendWith(MockitoExtension::class)JUnit 5에서 Mockito를 사용하여 테스트할 때 사용하는 어노테이션이다.Mockito 관련 설정을 간결하게 해준다.@InjectMocksMock 객체(가짜객체)를 테스트 대상 객체에 주입해준다.@SpringBootTest서버를 띄워서 테스트를 할때 사용하는 어노테이션이다.전체 애플리케이션의 통합 테스트에 사용된다.@AutoConfigureMockMvcMockMvc를 자동으로 설정하는 어노테이션이다.Spring MVC를 모의로 테스트할때 사용된다.테스트 코드 작성 예시@Test fun getIntroductions() { /** * given : 조건 설정 */ val list = mutableListOf() // DATA_SIZE 만큼 반복하면서 isActive는 i가 짝수일 경우 true 홀수일 경우 false로 설정한다. for (i in 1..DATA_SIZE) { val introduction = Introduction(content = "${i}", isActive = i%2 == 0) list.add(introduction) } /** * when : 동작 설정 및 실행 */ // isActive가 true인 항목들만 필터링하여 activeList를 만든다. val activeList = list.filter { item -> item.isActive } // presentationRepository.getActiveIntroductions()가 호출되었을 때 반환할 값으로 설정한다. // when이 코틀린에서 예약어여서 `when`이 된거 Mockito.`when`(presentationRepository.getActiveIntroductions()) .thenReturn(activeList) // 실제로 테스트할 대상인 presentationService.getIntroductions() 메서드를 호출하고 그 결과를 introductionDTO에 저장한다. val introductionDTO = presentationService.getIntroductions() /** * then : 결과 검증 */ // introductionDTO의 크기가 DATA_SIZE / 2인지 확인한다. assertThat(introductionDTO).hasSize(DATA_SIZE / 2) // 각 introduction의 content 값이 짝수인지 확인한다. // content는 문자열이지만 이를 toInt()로 변환하여 짝수인지 검증한다. for (introduction in introductionDTO) { assertThat(introduction.content.toInt()).isEven() } }코틀린 관련 정리mutableListOf()코틀린에서 변경 가능한 리스트(MutableList)를 생성하는 함수이다.ArrayList를 기반으로 한 가변 리스트(MutableList) 를 생성한다. 1.2 느낀 점이번 주는 바쁜 일정 속에서도 커리큘럼에 맞춰 강의를 모두 수강할 수 있어 뿌듯하다.특히 DB, 서비스, 컨트롤러별로 테스트 코드를 작성해 보면서 중요성을 다시 한번 느꼈고 실무에서도 적용을 해봐야겠다고 생각했다.2. 미션2.1. 테이블 설계하기주문 관리 시스템을 하기로 결정하고 떠오르는 테이블을 작성해보았다.테이블 목록TB_ORDER(주문) : 주문 마스터 정보를 저장하는 테이블TB_ORDER_DETAIL(주문 상세) : 주문 상세 정보를 저장하는 테이블TB_PRODUCT(상품) : 상품 정보를 저장하는 테이블TB_CODE(공통코드) : 코드 및 설명을 저장하는 테이블ERD설명TB_ORDER(주문) → TB_ORDER_DETAIL(주문 상세) → TB_PRODUCT(상품) 순서로 주문 데이터가 저장된다.TB_CODE는 주문 상태, 상품 상태 등 다양한 공통 코드를 별도의 테이블로 관리하고자하여 추가하였다.2.2. REST API 설계하기URL : https://github.com/pej4303/inflearn-pjt/blob/main/kotlin-mission/README.md 추후 swagger UI를 적용해볼 예정이다.2.3. 느낀 점짧은 기한 내에 미션을 하는 게 힘들긴 했지만 스터디를 계기로 간단하게나마 테이블과 API 설계를 해보게 되어서 재밌었다.출처https://www.inflearn.com/course/%EC%9E%85%EB%AC%B8%EC%9E%90-spring-boot-kotlin-%ED%8F%AC%ED%8A%B8%ED%8F%B4%EB%A6%AC%EC%98%A4/dashboard
백엔드
・
발자국
・
인프런워밍업클럽
2025. 03. 03.
0
[인프런워밍업클럽3기 BE] 1주차 발자국
1. 강의강의 진도 : 섹션2 ~ 섹션 31.1 학습 내용JPA(Java Persistence API)JDBC처럼 다형성을 기반으로 DB가 달라져도 소스 코드를 거의 수정하지 않아도 된다.자바 표준 ORM내부적으로 JDBC API를 이용한다.@Entity class Achievement( title: String , description: String , achievedDate: LocalDate? , host: String , isActive: Boolean): BaseEntity() { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "achievement_id") var id: Long? = null var title: String = title var description: String = description var achievedDate: LocalDate? = achievedDate var host: String = host var isActive: Boolean = isActive }엔티티 클래스테이블과 매핑되는 자바 클래스를 의미한다.기존 VO(Value Object)와 동일한 개념이다.보통 테이블명 동일한 이름을 사용한다.@Entity엔티티 클래스를 의미한다.이름이 중복되어서는 안된다.name속성을 생략하면 클래스명이 엔티티명이 된다.@Table엔티티와 매핑될 테이블을 설정한다.엔티티명과 테이블명 다를경우에만 지정해주면 된다.@IdPK 컬럼인 경우에만 사용한다.@GeneratedValue PK를 자동으로 생성해준다.방법GenerationType.IDENTITYMySQL의 Auto Increment 기능을 지원하는 DB에서만 가능하다.Oracle 12g 이상 버전도 사용 가능하다.GenerationType.SEQUENCE시퀀스를 지원하는 DB에서만 사용이 가능하다.GenerationType.TABLEDB 종류와 상관없이 사용 할 수 있는 방법이다. GenerationType.AUTO(기본값)DB의 특성에 맞게 알아서 적용해준다.연관관계 매핑 @OneToMany1:N 관계를 맺을 때 사용한다.@ManyToOneN:1 관계를 맺을 때 사용한다.N:M관계에서는 @OneToMany 쪽에서 mappedBy를 설정하고 @ManyToOne 쪽에서 @JoinColumn을 설정해야 한다. 코틀린 기본 문법변수 선언val불변 변수, 자바의 final 변수와 비슷하다.var변경 가능한 변수기본 데이터 타입val number: Int = 42 val pi: Double = 3.14 val isKotlinFun: Boolean = true val letter: Char = 'A' val text: String = "Hello, Kotlin!"null처리var name: String? = null // null 허용 println(name?.length) // null이면 실행하지 않음 println(name?.length ?: 0) // null이면 0 반환 1.2 느낀 점올해 목표였던 코틀린 학습을 실천하게 되어 뿌듯하다. 새로운 언어인 코틀린을 직접 사용해보니 흥미로웠고 재미있었다.다음 주에는 평일마다 최소 1시간씩 꾸준히 강의를 듣는 것을 목표로 삼고자 한다.2. 미션2.1 깃허브 리포지토리에 올리기인프런에서 만든 리포지토리만 따로 모으고 싶어서 서브트리를 이용해서 프로젝트를 추가하였다.URL : https://github.com/pej4303/inflearn-pjt/tree/main/kotlin-missionGit Subtree메인 레포지토리와 함께 관리되고 프로젝트마다 독립적인 커밋 관리가 가능하다.2.2 느낀 점서브트리를 처음 이용해봐서 찾아보고 설정하느라 시간이 좀 걸렸다. 그래도 내가 원하는 기능이 되어서 기분이 좋았다. 출처https://www.inflearn.com/course/%EC%9E%85%EB%AC%B8%EC%9E%90-spring-boot-kotlin-%ED%8F%AC%ED%8A%B8%ED%8F%B4%EB%A6%AC%EC%98%A4/dashboard
백엔드
・
발자국
・
인프런워밍업클럽