블로그
전체 4#카테고리
- 백엔드
2024. 10. 27.
0
백엔드 프로젝트 2기 코틀린 - 4주차
스프링 시큐리티스프링 시큐리티를 사용하기 위한 의존성 추가implementation("org.springframework.boot:spring-boot-starter-security")스프링 시큐리티를 이용하여 로그인 을 구현하기 위해선 컨피그클레스를 추가해야 합니다. Configuration class AdminSecurityConfiguration { @Bean fun passwordEncoder(): PasswordEncoder { return BCryptPasswordEncoder() } @Bean fun filterChain(httpSecurity: HttpSecurity): SecurityFilterChain { return httpSecurity .authorizeHttpRequests { authorizeHttpRequests -> authorizeHttpRequests .requestMatchers(AntPathRequestMatcher("/admin/**")).authenticated() .anyRequest().permitAll() }.csrf { csrf -> csrf.disable() }.headers { headers -> headers.addHeaderWriter(XFrameOptionsHeaderWriter(XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)) }.formLogin { formLogin -> formLogin.defaultSuccessUrl("/admin") }.logout { logout -> logout.logoutRequestMatcher(AntPathRequestMatcher("/admin/logout")) .logoutSuccessUrl("/") }.build() } }스프링 시큐리티를 이용하기 위한 빈들을 생성합니다 BCryptPasswordEncoder 는 해시 함수를 이용하여 암호화된 비밀번호를 생성해줍니다.SecurityFilterChain 프로젝트의 보안 필터를 체인 형태로 구성하여 csrf설정을 허용하지 않을 수 있고로그인과 로그아웃의 설정을 추가할 수 있습니다. 프로젝트 배포Docker Compose 파일 설정version: '2' services: mysql: image: mysql container_name: mysql ports: -"3306:3306" environment: - "MYSQL_ROOT_PASSWORD=dkssudgktpdy" - "TZ=Asia/Seoul" - "LC_ALL=C.UTF-8" command: - --character-set-server=utf8mb4 volumes: - /var/lib/docker/volumes/mysql/_data:/var/lib/mysql도커 컴포즈 파일을 통해 도커에서의 포트와 로컬에서의 포트를 설정하여 DB를 연결할 수 있습니다. DockerfileFROM openjdk:17 LABEL maintainer="infomuscle10@gmail.com" VOLUME /tmp EXPOSE 8080 ARG JAR_FILE=build/libs/portfolio-0.0.1-SNAPSHOT.jar ADD ${JAR_FILE} portfolio-yongback.jar ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom", "-jar", "/portfolio-yongback.jar"build/libs/portfolio-0.0.1-SNAPSHOT.jar 의 자르 파일을 이용하여 빌드를 실행합니다. 그 실행된 파일은 도커에서 portfolio-yongback.jar 로 표시 됩니다. Google Clud Platform에서 Compute Engine 인스턴스 생성하기AWS, Azure와 같은 클라우드 컴퓨팅 서비스로서 컴퓨터의 일부를 금액을 지불하여 사용하는 기술 입니다. GCP의 컴퓨트 엔진을 이용하여 인스턴스를 생성, 프로젝트를 배포 합니다. 도메인 연결프로젝트가 빌드된 ip를 DNS를 이용하여 접속할 수 있게 도메인 이름을 구입하여 사용할 수 있습니다. HTTPS 연결하기http로 연결되는 프로젝트를 https를 이용하여 보다 안전하게 연결하여 사용할 수 있게 합니다.
2024. 10. 20.
0
코틀린 백엔드 프로젝트 2기 - 3주차
섹션 4 6강 ~ 섹션 5 9강까지의 내용 학습 타임리프 문법 정리xmlns:th="" 타임리프의 네임스페이스 선언, 타임리프 문법 사용을 위한 선언 문법 th:fragment="" 템플릿 일부 재사용을 위한 문법th:replace="" 해당 요소를 다른 요소로 대체할 때 사용, fragment로 대체할 영역에 사용th:href="" 링크의 URL을 동적으로 설정th:each="" 반복문, 반복할 데이터의 개수만큼 HTML 요소를 생성 th:class="" HTML 요소의 클래스를 동적으로 설정합니다th:text=""텍스트 컨텐츠를 동적으로 설정 컨트롤러 개발@RestController @controller와 @ResponseBody가 합쳐진 어노테이션, 리턴을 HTTP 바디에 문자열로 넣을 수 있으며 JSON형식으로도 지정이 가능@RequestMapping("/경로")클래스 단위에 달아주고 경로를 설정해주면 컨트롤러 클래스의 ENTRY 포인트 지정@GetMapping("/test")메서드 단위에 달아주면 컨트롤러에 진입한 메서드의 ENTRY 포인트 지정 http 메서드 get을 받음, post, put, patch, delete도 존재 @RestController @RequestMapping("/api") class PresentationApiController( private val presentationService: PresentationService ) { @GetMapping("/test") fun test(): String { return "OK" } }/api의 경로 에서 /test로 진입하면 fun test() 메소드가 실행됨현재 코드에선 "ok"가 리턴됨 즉, HTTP 바디에 OK하는 문자열이 담김 @Controller 해당 어노테이션이 붙은 클래스는 리턴값의 이름과 같은 html파일에 연동됨 해당 html파일에는 model을 이용해서 값을 넘겨줄 수 있으며 해당 html파일이 view가 되어 model에 담긴 값을 꺼내어 사용하고 타임리프 뷰 템플릿을 사용하여 동적으로 만들어 주는 것이 가능함@Controller class PresentationViewController( private val presentationService: PresentationService ) { @GetMapping("/resume") fun resume(model: Model): String { val resume = presentationService.getResume() model.addAttribute("resume", resume) model.addAttribute("skillTypes", SkillType.values()) return "presentation/resume" } }get 메서드로 /resume로 요청을 하면 fun resume() 메소드가 실행됨모델에 resume, SkillType.values() 값을 담고 리턴을 하면 리턴에 해당하는 html파일에서 모델에 담긴값을 이용해서 뷰를 꾸며줌 컨트롤러 테스트 코드@SpringBootTest Spring Boot 애플리케이션을 테스트하는 데 사용 실제 애플리케이션과 유사한 환경을 구성하여 테스트를 실행할 수 있습니다@AutoConfigureMockMVCSpring MVC를 모의 테스트 할 때 사용, MockMVC객체가 자동으로 구성되어 컨트롤러를 모의로 테스트 하는것이 가능함@SpringBootTest @AutoConfigureMockMvc @DisplayName("[API 컨트롤러 테스트]") class PresentationApiControllerTest( @Autowired private val mockMvc: MockMvc ) { @Test @DisplayName("Introductions 조회") fun testGetIntroductions() { // given val uri = "/api/v1/introductions" // when val mvcResult = performGet(uri) val contentAsString = mvcResult.response.getContentAsString(StandardCharsets.UTF_8) val jsonArray = JSONArray(contentAsString) // then assertThat(jsonArray.length()).isPositive() } private fun performGet(uri: String): MvcResult { return mockMvc .perform(MockMvcRequestBuilders.get(uri)) .andDo(MockMvcResultHandlers.print()) .andReturn() } }@Autowired private val mockMvc: MockMvcmockmvc를 주입받아서 모의 객체 생성을 함"/api/v1/introductions"의 uri로 요청을 하면 해당 경로의 메서드가 실행되어 응답을 받고 그 응답을검증함. 인터셉터 개발인터셉터의 호출 순서는HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 -> 컨트롤러이며 컨틀롤러 도달전의 호출된다HandlerInterceptor 를 구현하였고 프로젝트에선 afterCompletion만 오버라이딩 하여 모든 요청에 대한 정보를 데이터 베이스에 저장하도록 구현하였다 인터셉터는 경로를 설정하여 어떤 경로게 대하여 인터셉터를 실행할지 결정이 가능하다WebMvcConfigurer 를 구현한 클래스의 addInterceptors() 메소드를 통해 인터셉터를 등록하고 경로 설정이 가능하다override fun addInterceptors(registry: InterceptorRegistry) { registry.addInterceptor(presentationInterceptor) .addPathPatterns("/**") .excludePathPatterns("/assets/**", "/css/**", "/js/**", "/admin/**", "/h2**", "/favicon.ico", "/error") } 어드민 패키지AdminApiControllerAdvice@RestControllerAdvice 를 사용하면 전역적으로 모든 컨트롤러에 대한 예외를 처리하는 것이 가능해진다.@ExceptionHandler와 같이 사용해야 하며 특정 예외 클래스에 대한 핸들러 메서드를 작성할 수 있습니다.오류의 종류개발자가 직접 처리가 가능한 오류는 Exception의 하위 오류들이다. 애플리케이션에서 대응할 수 있는 오류로서 UnckechedException, ckechedException이 존재하고 UnckechedException은 실행중에 발생하는 오류이다.
2024. 10. 13.
0
코틀린 백엔드 프로젝트 2기 - 2주차
이번주차는 섹션 3-7~ 섹션 4-5까지의 수업이 있었습니다. 섹션 3데이터베이스 초기화, 리포지토리 개발, 테스트 코드 작성, 리포지토리의 성능 개선을 학습 하였습니다. 데이터베이스 초기화데이터 베이스 초기화는 DataInitializer 클래스에서 진행을 해주었습니다.해당 클래스를 빈으로 등록해주는 어노테이션인 @Component 를 사용 하였습니다. 생성자 주입을 통해 리포지토리 인터페이스를 주입 받고 @PostCunstruct 어노테이션을 이용한 메서드를 통해서 본격적인 데이터베이스 초기화를 실행하였습니다.@PostConstruct fun initializeData(){ }엔티티 클래스들을 생성자를 통해 초기화 하여 생성한 뒤 주입받은 리포지토리의 기본적인 CRUD기능의 메서드를 이용하여 데이터베이스에 저장하였고 1:N 관계를 가지는 엔티티들은 각 필드의 리스트에 해당 엔티티들을 초기화 해주었습니다.@PostConstruct fun initializeData(){ ... val experience1 = Experience( title = "캣홀릭대학교(CatHolic Univ.)", description = "컴퓨터공학 전공", startYear = 2018, startMonth = 9, endYear = 2022, endMonth = 8, isActive = true, ) experience1.addDetails( mutableListOf( ExperienceDetail(content = "GPA 4.3/4.5", isActive = true), ExperienceDetail(content = "소프트웨어 연구 학회 활동", isActive = true) ) ) val experience2 = Experience( title = "캣카오", description = "소셜 서비스팀 백엔드 개발", startYear = 2022, startMonth = 9, endYear = null, endMonth = null, isActive = true, ) experience2.addDetails( mutableListOf( ExperienceDetail(content = "유기묘 위치 공유 서비스 게발", isActive = true), ExperienceDetail(content = "신입 교육 프로그램 우수상", isActive = true) ) ) experienceRepository.saveAll(mutableListOf(experience1, experience2)) ... }이 작업들을 통해서 데이터베이스 초기화를 진행해 주었습니다. 리포지토리 개발스프링에서 제공하는 Spring Data JPA는 인터페이스를 상속하는것만으로 기본적인 CRUD 기능을 제공해줍니다.또한 메서드 이름을 기반으로 하는 쿼리 생성 기능도 있습니다.(커스텀 메서드) findBy, deleteBy, countBy같은 키워드 뒤에 필드들을 붙여주어 JPA가 자동으로 쿼리를 생성해줍니다.// 커스텀 메서드 interface ExperienceRepository : JpaRepository{ @Query("select e from Experience e left join fetch e.details where e.isActive = :isActive") fun findAllByIsActive(isActive : Boolean) :List @Query("select e from Experience e left join fetch e.details where e.id = :id") override fun findById(id: Long): Optional }@QuerySpring Data JPA에서 사용자 정의 JPQL (Java Persistence Query Language) 또는 네이티브 SQL 쿼리를 작성할 수 있게 해주는 어노테이션입니다.jqpl에서 fetchJoin을 사용할 수 있는데 이 기능을 통해 지연로딩으로 받아오는 연관된 엔티티를 한번에 즉시로딩을 할 수 있으며 n+1문제를 해결할 수 있습니다. N+1문제대표적인 JPA의 단점으로 연관관계를 가진 두 개의 부모-자식 테이블에서 부모를 호출할 때 JPA는 엔티티의 연관관계를 바탕으로 조회해온 데이터의 갯수만큼 부모에 매핑된 자식 테이블의 데이터를 조회하기 위한 쿼리를 생성합니다.예를 들어 프로젝트 - 프로젝트 멤버 의 관계에서 프로젝트가 3개 존재할 때 프로젝트를 조회하면 1번의 쿼리로 3개의 프로젝트가 조회 됩니다. 이때 각 조회된 프로젝트의 프로젝트 멤버를 1회 더 조회합니다. 프로젝트가 1회 조회될 때 3개의 프로젝트가 조회되었으므로 총 3번의 조회를 더 하게 됩니다.이를 N+1 문제(1+N 문제)라고 합니다. 리포지토리 성능 개선N+1문제가 발생하는 리포지토리의 성능을 개선하기위해선 엔티티에서 연관관계 매핑 시 설정가능한 FetchType의 종류에 대하여 알아야 합니다.fetch = FetchType.LAZY fetch = FetchType.EAGERLazy : 지연로딩 방법 부모 - 자식관계 엔티티에서 부모를조회하여도 자식을 호출하기 전까지는 조회하지 않습니다.Eager : 부모 조회 즉시 자식 조회N+1문제를 해결하기 위해 LAZY 타입을 사용합니다. 그 후 자식이 필요하면 @Query에 FetchJoin을 사용하여 부모와 자식의 데이터를 같이 조회하도록 할 수 있습니다. 테스트 코드테스트 코드 작성은 운영 전 사이드 이펙트 감지를 위한 매우 중요한 기능입니다.어노테이션@DataJpaTestJPA 관련 테스트 설정 제공, 내장 데이터 베이스 설정, @Entity, @Repository가 부여된 클래스들의 테스트 환경을 설정@TestInstance테스트 인스턴스의 라이프 사이클을 지정,Junit5는 각 테스트 메서드마다 새로운 인스턴스를 생성@BeforeAll테스트 클래스내의 모든 @Test메소드 실행전 한 번 실행되도록 함 테스트 데이터 초기화를 할 때 유용 @Test테스트 메소드 지정@Autowired필드 주입 방식 사용, 테스트 코드에서는 필드주입을 사용 @DisplayName테스트 실행 후 Run 창에서 실행한 테스트의 이름을 설정테스트코드는 개발자가 더미데이터를 생성한 후 해당 더미데이터가 비즈니스 로직에 알맞게 들어갔는지 검증하면서 테스트를 진행합니다. 섹션 4presentation패키지에 Controller와 DTO, Repository를 생성하였습니다. Controller레이어드 아키텍처에서 사용자의 요청이 진입하는 엔트리포인트로 실질적인 처리를 하는 service레이어로 넘겨주고 응답을 반환하는 기능을 합니다. @RequestMapping, @GetMapping 등 의 기능을 통해 진입 api를 설정합니다.@Controller, @RestController 어노테이션이 있습니다.@Controllerreturn 되는 문자열같은 html파일을 찾아 클라이언트에 응답모델에 값을 담아두면 해당 html에서 모델에 담긴 값을 꺼내어 사용이 가능@RestController@Response와 @Controller가 합쳐진 기능 CSR방식의 웹 개발, 앱 개발, 데이터의 처리만 담당하는 API 개발 시 사용return값은 그대로 HTTP 응답 메시지 바디에 들어가며 여러 타입으로 변환하여 반환 가능Service컨트롤러에서 받은 데이터를 비즈니스 로직에 맞게 처리하는 영역이며 필요에 따라 Repositry를 호출합니다. Repository데이터베이스와 상호작용 하는 영역 DTODTO(Data Transfer Object)는 데이터 전송 객체로, 주로 소프트웨어 애플리케이션에서 데이터의 전송 및 관리를 간편하게 하기 위해 사용되는 객체입니다. 데이터를 그룹화하여 전송할 때 유용합니다.데이터 전송을 단순화하고 최적화하기 위함으로 비즈니스 로직이 없는 단순한 데이터 구조로 시스템간의 데이터 교환을 간소화 할 수 있다. 리포지토리 개발이번 강의에서 리포지토리는 퍼사드 패턴을 적용하여 개발을 하였습니다. 퍼사드 패턴은 복잡한 여러 기능을 단순화해주는 소프트웨어 디자인 패턴입니다.@Repository class PresentationRepository ( private val achievementRepository: AchievementRepository, private val introductionRepository: IntroductionRepository, private val linkRepository: LinkRepository, private val skillRepository: SkillRepository, private val projectRepository: ProjectRepository, private val experienceRepository: ExperienceRepository, ){ fun getActiveAchievements(): List{ return achievementRepository.findAllByIsActive(true) } fun getActiveExperiences(): List{ return experienceRepository.findAllByIsActive(true) } fun getActiveAIntroductions(): List{ return introductionRepository.findAllByIsActive(true) } fun getActiveLinks(): List{ return linkRepository.findAllByIsActive(true) } fun getActiveSkills(): List{ return skillRepository.findAllByIsActive(true) } fun getActiveProjects(): List{ return projectRepository.findAllByIsActive(true) } } 생성자 주입을 통해 필드로 리포지토리들을 보유하고 있으며 메서드를 구현하여 각 리포지토리에서 활성화된 객체를 가지고 옵니다. 서비스 개발@Transactional 어노테이션을 이용하여 트랜잭션을 시작합니다.reagOnly일기전용 트랜잭션으로 설정, JPA를 사용시 더티체킹을 하지 않음rollbackFor어떤 예외시 롤백할지 설정isolation격리 수준을 정의@Service class PresentationService( private val presentationRepository: PresentationRepository ) { @Transactional(readOnly = true) fun getIntroductions(): List{ val introductions = presentationRepository.getActiveAIntroductions() return introductions.map { IntroductionDTO(it) } } @Transactional(readOnly = true) fun getLinks(): List{ val links = presentationRepository.getActiveLinks() return links.map { LinkDTO(it) } } @Transactional(readOnly = true) fun getResume(): ResumeDTO{ val experiences = presentationRepository.getActiveExperiences() val achievements = presentationRepository.getActiveAchievements() val skills = presentationRepository.getActiveSkills() return ResumeDTO( experiences = experiences, achievements = achievements, skills = skills, ) } @Transactional(readOnly = true) fun getProjects(): List{ val projects = presentationRepository.getActiveProjects() return projects.map { ProjectDTO(it) } } }생성자 주입으로 리포지토리를 주입받아 서비스 레이어에서 리포지토리를 호출하여 비즈니스 로직을 실행하고 있습니다 서비스 테스트 코드 작성@ExtendWith : Junit5에서 테스트 확장을 지원 @InjectionMocks, : Mockito에서 테스트 대상이 되는 클래스에 인서턴스 주입을 위해 사용@Mock : Mockito에서 Mock 객체를 생성 Mock는 실제 객체를 대체하는 가짜 객체를 의미합니다.테스트에서 특정 객체의 모의 객체를 사용하게 해줍니다.@Mock을 이용하여 모의 객체를 생성하고 @InjectionMocks를 사용하여 주입된 모의 객체를 사용하는 객체를 생성 합니다.@AutoWired를 사용하지 않고 의존성 주입을 받으면서 실제 DB에 의존하지 않고 테스트를 진행할 수 있습니다
2024. 10. 06.
0
코틀린 백엔드 프로젝트 2기 - 1주차
1주차에서 학습한 내용의 범위는 섹션2 ~ 섹션3 6 까지의 내용이다. 섹션 2 에서 학습한 내용은 Spring웹 프레임워크, HTTP, REST API, DBMS, JPA 에 관한 부분이다. Spring웹 프레임워크로서 동적 웹 개발을 도와주는 도구이다 백엔드에서 사용되는 다양한 종류의 프레임워크가 있는데 그 중 자바 진영에서 가장 많은 발전을 해왔고 가장 유명한 프레임워크가 바로 spring 프레임워크이다.스프링 프레임워크는 MVC 패턴을 주로 사용하는데사용자의 데이터를 모델에 담기 위해 작업을 하는 controller사용자의 데이터를 담아두는 model담긴 데이터를 꺼내와 사용자에게 보여주는 view이 3가지를 MVC 라고 한다.스프링은 controller - service - repository의 레이어드 아키텍쳐를 기반으로 개발을 한다.컨트롤러는 클라이언트로부터 전달받은 데이터를 검증하는 인터페이스로 서비스 레이어를 호출한다.서비스 레이어는 프로젝트 목적에 맞는 비즈니스 로직을 처리하는 레이어이며 리포지토리를 호출하여 여러가지 삽입, 조회, 수정, 삭제를 실행한다.리포지토리는 데이터베이스에 접근하여 서비스로직이 호출한 기능을 수행한다. HTTP네트워크를 통해 통신할때 지켜지는 통신 규약으로 다양한 요청 메서드를 통해서 API 호출을 할 수 있다. GET, POST, PUT, PATCH, DELETE의 메서드를 사용하며 각 메서드는 REST API의 행위로서 사용이 됩니다. REST API HTTP 통신을 하는 어플리케이션간의 규칙이며 강제성을 띄지는 않지만 핵심적인 내용으로 URL을 통한 자원의 표현, HTTP 메서드를 통해 행위를 표현하며 HATEOS를 준수하여 클라이언트의 행위 가이드가 되어줍니다. DB 여러 사용자가 공유하기 위한 목적으로 사용되는 통합, 관리데이터의 집합으로 DBMS를 통해서 데이터들을 관리할 수 있습니다. 가장 널리 쓰이는 관계형데이터베이스를 통해 강의를 진행할 예정이며 관계형 데이터베이스는 테이블을 통해 저장을 합니다. JPAJava Persistence API의 약자로 자바 ORM 기술의 표준 인터페이스입니다. 자바 또는 코틀린으로 생성된 객체를 데이터베이스의 테이블로 매핑하여 사용해주고 그 필드들을 컬럼으로 매핑해줍니다. ORM 기술을 사용하여 개발자가 직접 쿼리를 작성하지 않게 해줍니다. ORM기술은 특정 데이터베이스에 종속적이지 않은 사용이 가능하며 다양한 DBMS로 변환이 가능하고 데이터를 객체 중심적으로 볼 수 있어 유지보수에 유리합니다.단점으로 복잡한 쿼리를 작성하는 상황에서의 한계점, 성능 최적화가 이루어 지지 않은 점, 그리고 초기 학습 곡선이 크다는 점입니다.ORM은 많은 개념과 설정이 필요하기 때문에 학습에 많은 시간이 소요됩니다 영속성 컨텍스트JPA에서 엔티티를 관리하는 임시 메모리의 개념이다.특징 1차 캐시 영속성 컨텍스트는 DB에서 조회한 엔티티를 1차캐시에 저장합니다. 동일한 트랜잭션의 동일한 쿼리일경우 1차캐시의 엔티티를 반환합니다. 이로인해 성능을 향상시킵다더티체킹(변경감지)영속성 컨텍스트에 저장(스냅샷), 트랜잭션 진행 중 엔티티의 필드 변화 시 현재 상태와 저장된 스냅샷을 비교변경이 감지되면 트랜잭션이 종료되기전 해당 필드에 update 쿼리를 실행하고 커밋 시점에 해당 쿼리를 DB에 전송 지연로딩(쓰기지연, Lazy Loading)실제로 필요한 시점까지 데이터를 조회하지 않고 대기하는 방식해당 엔티티가 실제로 사용될 때 DB에서 가져오는 방법멤버 객체 내부에 소속이라는 객체가 존재한다고 가정맴버를 조회할 때는 소속 객체를 로드하지 않음맴버의 소속을 조회하는 시점이 되어야만 로드를 함동일성 보장 같은 트랜잭션 안에서 같은 엔티티에 대하여 동일한 객체를 보장한다.1차 캐시와 연관이 있는것 같음, 동일 엔티티를 여러번 조회하여도 동일한 객체를 반환함 1주차의 섹션 3 학습내용은 6강까지의 강의를 수강하였습니다.https://start.spring.io 사이트를 통해서 프로젝트를 생성하였고 git, github를 이용하여 프로젝트 진행 사항을 관리할 수 있게 세팅하였습니다.프로젝트의 클래스들을 생성하면서 각 클래스 엔티티에 @Id, @GeneratedValue, @ olumn 등 데이터베이스에서 인지할 수 있는 어노테이션들을 학습하였고추상클래스에는 @MappedSuperClass 어노테이션을 선언하여 클래스를 상속받는 클래스들의 필드를 테이블의 컬럼으로 사용할 수 있게 해주었습니다.
백엔드