블로그

왜 자바 백엔드 실무에선 스프링 부트가 중요할까?

한국은 물론, 세계적으로도 가장 인기 있는 서버 개발 스택은 자바(Java) 언어 기반의 스프링(Spring) 프레임워크를 이용한 백엔드 기술입니다. 스프링은 불필요하거나 반복적인 코드를 줄임으로써 코드의 복잡성을 낮추고, 개발자가 핵심 비즈니스 로직에 집중할 수 있도록 돕는 역할을 합니다.하지만 스프링을 사용하려면 초기 환경을 일일이 설정해야 하는 등 번거롭고 어려운 면이 있었는데요. 이런 스프링의 복잡한 부분을 개선하고 보다 손쉬운 웹 애플리케이션 개발을 가능하게 한 게 바로 스프링 부트(Spring Boot)입니다. 스프링 부트를 통해 XML 구성을 할 필요도 없고, Tomcat 등의 기본 HTTP 서버가 내장되어 있어 편의성은 높으면서도 더 빠른 개발이 가능하게 되었죠.이러한 스프링 부트를 통해 자바/스프링 개발자들은 초기 설정처럼 핵심적인 부분은 아니지만 빼놓을 수 없는 공정의 부담을 덜어내고, 프로그램 및 시스템 운용이라는 관점에 집중하여 개발할 수 있게 된 셈입니다.•••베테랑 시니어 개발자들이 알려주는 스프링 부트 노하우가 궁금하신가요?지금 인프런 프리즘 [스프링 부트 로드맵]을 통해 학습해보세요. https://www.inflearn.com/roadmaps/649•••인프런 프리즘 브랜드 스토리 읽어보기 >>

백엔드SpringSpringBootJava스프링스프링부트백엔드Back-End인프런프리즘InflearnPrism

요즘 백엔드 취업 시장에서 코프링이 핫하다던데?

코틀린(Kotlin)은 젯브레인즈(JetBrains)에서 개발한 크로스 플랫폼 범용 프로그래밍 언어입니다. JVM 기반의 언어이면서 자바(Java)와 100% 호환되도록 설계되었습니다. 구글은 2019년부터 코틀린을 안드로이드 개발 공식 언어로 지정했어요. 간결한 문법, 안정성, 다양한 기능이 있다는 장점과 함께 전 세계적으로 사랑받고 있는 언어입니다.그동안 백엔드에선 자바 언어와 스프링 프레임워크의 조합이 가장 압도적인 점유율을 차지하고 있었는데요. 최근엔 코틀린을 도입하거나 자바를 코틀린으로 대체하려는 기업이 늘면서 코틀린 언어와 스프링 프레임워크의 조합, 일명 '코프링'이 주목받기 시작했습니다. 실제로 현재 취업 시장을 살펴보면 코틀린 언어를 다루는 능력을 자격이나 우대 사항으로 기재해 두는 기업을 어렵지 않게 찾아볼 수 있어요. 하지만 비교적 최근에 주목받고 있는 만큼 백엔드 현업에서의 코틀린 혹은 코프링 관련 사례나 자료를 찾는 건 쉽지 않죠.앞으로 사용이 더 늘어날 것으로 전망되는 코틀린, 코틀린과 코프링의 세계에 발 빠르게 뛰어들고 싶다면 지금 시도해 보는 건 어떨까요?•••Java 개발자를 위한실무밀착형 코프링을 배우고 싶다면?지금 인프런 프리즘 [자바 개발자를 위한 실전 코프링 입문 (Kotlin + Spring)]을 통해 학습해보세요.https://www.inflearn.com/roadmaps/703•••인프런 프리즘 브랜드 스토리 읽어보기 >>

백엔드코틀린Kotlin스프링SpringSpringBoot스프링부트코프링백엔드인프런프리즘InflearnPrism

윤대

[인프런 워밍업 0기 Day2] 한 걸음 더! 메서드로 코드 재사용해보기!

!! 해당 글은 독자가 인프런 워밍업 0기를 수강하고 있다는 전제 하에 작성되었습니다 !!과제 수행에 있어 스프링부트 3.2.2 버전을 사용하고 있다는 점을 미리 알려드립니다! 안녕하세요🙌! 인프런 워밍업 2일차 과제입니다!2일차에는 API를 작성하는 방법에 대해서 배우고 그에 대한 과제를 받았습니다😎과제를 자세히 살펴보는 도중, 문제 1번과 3번 모두 덧셈을 수행하는 공통로직이 있다는 사실을 발견했습니다!따라서! 오늘은 제가 한 걸음 더 성장하기 위해 메서드를 통해 공통로직을 처리한 과정을 공유할 예정입니다~ ⚙️메서드 설계하기메서드를 만들 때, 제가 중요하다고 생각하는 것은 3가지입니다!메서드가 무엇을 할 것인가 (메서드 명)메서드가 무엇을 반환할 것인가 (리턴 타입)메서드가 무엇을 필요로 하는가 (파라미터)여기서, 중요한 점은 어떻게는 생각하지 않는다는 것입니다! 🙅‍♂️위의 3가지만 만족한다면 공통로직으로 볼 수 있기 때문에 메소드의 내부 로직은 추후에 생각해도 무방합니다! 자, 그렇다면 지금부터 문제 1번과 3번에서 각 절차를 수행해보겠습니다.첫째, 무엇을 할 것인가?문제 1번과 3번 모두 2개 이상의 숫자를 더해야 하는 상황입니다! 따라서, 저는 메서드 명을 addNumbers로 정했습니다.둘째, 무엇을 반환할 것인가?우리는 두 수를 더 한 값을 반환 받아야 합니다! int와 Inteager 등 다양한 숫자가 가능하겠지만, 저는 Inteager를 택했습니다!셋째, 무엇을 필요로 하는가?우리는 숫자를 더하기 위해, 2개 이상의 숫자를 필요로 합니다! 또한, 문제 3번의 경우 몇 개의 숫자가 입력될 지 알 수가 없습니다! 따라서 저는 List<Integer> numbers로 파라미터 값을 설정했습니다.이제 위의 세 부분을 합치면 제가 만들 메서드는 아래와 같은 형식이 될 것입니다!public Integer addNumbers(List<Integer> numbers);💡 int 타입의 경우 List로 받을 수가 없어 Integer를 반환 값으로 설정하였습니다! ⚙메서드 구현하기이제 설계가 끝났으니, 메서드를 구현할 시간입니다! for each문으로 List에 담긴 값을 하나씩 꺼내 더하여 값을 반환만 해주면 구현은 어렵지 않게 끝이 납니다!😎 public Integer addNumbers(List<Integer> numbers) { Integer result = 0; for (Integer number : numbers) { result += number; } return result; }자~ 이제 구현이 끝났으니 메서드를 적용해봐야겠죠? ⚙메서드 적용하기문제 3번먼저, 문제 3번입니다! 3번부터 풀이하는 이유는 1번 문제를 풀며 발생하는 문제 때문입니다!DTO (데이터를 전송 받고 보내는 객체)@Getter @Setter public class NumbersRequest { private List<Integer> numbers; }Controller @PostMapping("/api/v1/calc") public Integer addNumbers(@RequestBody NumbersRequest numbersRequest) { return addNumbers(numbersRequest.getNumbers()); }List<Integer>를 필드 값으로 갖는 NumberRequest를 입력받아 addNumbers()에 필드값을 전달했습니다!입력받은 숫자가 몇 개던지 메서드 내부에서 값이 잘 합산되어 반환될 것입니다! 문제 1번이번엔, 문제 1번을 해결해보죠! 😎DTO@Getter @Setter public class CalcResponse { private int add; private int minus; private int multiply; }Controller @GetMapping("/api/v1/calc") public CalcResponse calc(Integer num1, Integer num2) { List<Integer> numbers = new ArrayList<>(); numbers.add(num1); numbers.add(num2); CalcResponse response = new CalcResponse(); response.setAdd(addNumbers(numbers)); // minus와 multiply도 메서드로 구현해주었습니다! response.setMinus(minusNumbers(numbers)); response.setMultiply(multiplyNumbers(numbers)); return response; }값을 Integer num1과 Integer num2를 입력받기 때문에 입력 값들을 List로 변환해줘야 합니다.이렇게 하여도 문제는 없겠지만, 코드가 지저분한 게 영 마음에 들지 않습니다!그래서, 다음과 같이 코드를 변경해보았습니다!DTO (Request) 추가@Getter @Setter public class CalcRequest { private Integer num1; private Integer num2; public List<Integer> getNumbers() { List<Integer> numbers = new ArrayList<>(); numbers.add(this.num1); numbers.add(this.num2); return numbers; } }입력 값을 각각의 값이 아닌 하나의 객체로 변환하여 받고, 내부에 getNumbers()를 구현하여 입력받은 값을 바로 List<Integer>로 변환하여 받도록 하였습니다!이 DTO를 적용하면 Controller의 코드가 다음과 같이 바뀌게 됩니다! @GetMapping("/api/v1/calc") public CalcResponse calc(@ModelAttribute CalcRequest calcRequest) { CalcResponse response = new CalcResponse(); response.setAdd(addNumbers(calcRequest.getNumbers())); response.setMinus(minusNumbers(calcRequest.getNumbers())); response.setMultiply(multiplyNumbers(calcRequest.getNumbers())); return response; }훨씬 간결해졌죠? 참고로 @ModelAttribute는 쿼리 스트링으로 입력받은 값들을 확인하여 파라미터 타입으로 지정한 타입의 필드 값과 일치하면 자동으로 객체로 변환해주는 Annotation입니다!이렇게, 메서드를 사용하면 공통로직을 단일 메서드 내부에서 관리할 수 있게 되고, 코드의 반복을 줄일 수 있게 됩니다! 여러분도 코드의 중복이 보인다면 메서드로 한 번 만들어보는 것은 어떨까요? 지금 까지의 내용을 정리하면서 2일차 포스팅을 마치도록 하겠습니다! 💡정리 💡메서드를 설계할 때 중요한 것!메서드를 설계할 때는 어떻게는 생각하지 말자!무엇을 하고, 반환하고, 필요한 지 3가지만 충족된다면 공통로직으로 만들 수 있다!객체 내부에서 변환로직 만들기!여러 개의 Integer를 Controller에서 List로 직접 변환할 시 코드가 지저분해진다! @ModelAttribute를 활용하여 객체 내부에서 값을 변환하여 반환하자!

백엔드Spring인프런워밍업메서드API인프런SpringBoot

도롱이

[인프런 워밍업 클럽_0기] 1주차, 첫 번째 발자국 #1

1주차, 첫 번째 발자국 1주차는 어려운 내용은 딱히 없었다! 어느정도 기반기가 있었다면 다들 어렵지 않게 해냈었을 것 같다.강의 요약은 강의를 들으면서 노션에 하나하나 요약했기 때문에 노션 링크를 남긴다.https://abalone-copper-ebe.notion.site/d2e9b3e27b3348abbde60994cf627ebd?pvs=4 그래도 너무 노션 링크만 띡 남기면 정 없으니 한 번 쭉 훑어보며 하루하루 대략적으로 어떤 것을 공부했고, 어떤 것들을 알게 되었나 작성해보자. Day2 02/19 서버 개발을 위한 환경 설정 및 네트워크 기초(1~5강 + 52강)첫 날은 프로젝트 소스를 다운받고, 프로젝트의 spring boot 버전을 2.7점대에서 3.0.x로 업데이트를 진행했다.Java, IntelliJ, PostMan, MySQL, Git은 이미 설치가 되어 있어서 따로 영상을 챙겨보진 않았다.52강을 들으면서 느낀 건 안 그래도 저번에 2점대 버전에서 3점대 버전으로 마이그레이션 하려는 시도를 했었었는데, 그때는 spring이라는 프레임워크를 잘 몰랐었던 때고, 3점대가 나온지 얼마 안돼서 정보도 그렇게 많지 않아 장렬히 실패했었던 기억이 있었다. 이번에도 에러가 엄청 날까봐 걱정을 많이 했는데 강의가 잘 정리되어 있어 어렵지 않게 마이그레이션 할 수 있었다. MySQL이 원래 깔려 있어서 비밀번호 입력하는 부분만 빼면 말이다! (MySQL 오류) 본격적으로 강의를 들어가기 전에 Java를 공부하기 전에 알아두면 좋을 것들!이라는 유튜브를 두 개 시청했다. 사실 Java를 공부한지는 꽤 됐는데 JVM의 이점 부분만 대략적으로 알았지 JRE나 JDK은 스킵하고 넘어갔었다. JVM이 제일 중요하다고 알려져있으니까. 이번 강의에서 본격적인 내용을 시작하기 전에 한 번 짚어주는 유튜브가 있어서 별 거 아닌데도 갑자기 많은 생각이 들기 시작했다.나는 왜 Java를 공부하면서 이런 것들도 몰랐을까?나는 Java를 잘 안다고 할 수 있을까?대충 공부함으로써 내가 얻을 수 있는 것들이 뭐였을까?라는 생각들이 스쳐지나 갔다...! 앞으로는 조금 더 꼼꼼한 사람이 돼야 겠다는 목표도 생겼다...! 본격적인 강의 시작에서는 Spring Boot 프로젝트를 실행하는 법과 네트워크, HTTP, API, GET API를 공부했다. 강사님이 최대한 이해하기 쉽게 이것 저것 비유해가면서 얘기해주셔서 이해가 잘 됐었던 거 같다.제일 기억에 남는 것은 함수 파라미터를 변수에서 객체로 변경한 이유가 기억에 남았다. 초보 입장에서는 이런 부분을 놓치는경우가 많고, 생각조차 안 나는 경우가 많은데 이렇게 사소한 것 까지도 짚어주시면서 강의를 진행해주시니 더 꼼꼼하게 코드를 작성할 수 있던 거 같다. 미션https://devhan.tistory.com/318어노테이션에 관한 미션이었다.어노테이션을 단순히 쓰라해서 사용하기만 했는데 어노테이션의 역할이 한 개만 있는 것이 아니라 목적에 따른 다양한 종류의 어노테이션이 있다는 걸 알게되었다!강사님의 코멘트어노테이션이 '마법' 같은 일을 해주기 위해서는 리플렉션이라는 기술이 사용된다.리플렉션은 라이브러리나 프레임워크를 개발할 때 간혹 사용되는 기술로, 코드를 직접적으로 호출하지 않고 코드를 제어하는 기술이다.   Day3 02/20 첫 HTTP API 개발 (6~10강) Day3에서는 GET API 이외에 POST API 개발, User 생성 API 개발, User 조회 API 개발, MySQL 사용에 대해서 공부했다.이번에도 기초적인 부분을 다루었기 때문에 딱히 어려운 것은 없었다. 강의를 따라가면서 느낀 건 API 스펙을 정하는 부분이 아주 좋았다! 다른 강의에서는 API 스펙을 정하는 부분이 없이 그냥 말로만 진행하는 강의도 다수 있었는데 이 강의에서는 미리 API 스펙을 알려주니 스펙을 보고 먼저 개발해본 다음에 강의를 들으면서 고치거나 할 수 있어서 좋았다. 미션https://devhan.tistory.com/319여태 했던 미션 중에 제일 오래 걸린 미션이 아닌가 싶다.. 왜냐면 미션 하는 중에 에러가 발생했기 때문!에러 내용은 @RequestBody 사용 시 해당 DTO 생성자에 파라미터가 한 개만 존재하는 생성자가 있고, 기본 생성자가 없어서 발생하는 에러였다.해결 방법은 @JsonCreator를 기존 생성자 메서드에 붙여주거나, 기본 생성자를 만들어주면 된다.강사님의 코멘트1번 - 본인이라면 DTO 쪽에 사칙 연산 기능을 넣었을 것이다. Service 계층의 코드를 깔끔하게 만들기 위해서는 일부 계산 로직을 DTO 쪽으로 넣는 방법을 사용할 수 있다.2번 - LocalDate를 사용! query parameter가 1개라서 바로 LocalDate를 사용해서 요청을 받을 것 같다.3번 - List를 받아보도록 연습! POST API + List 필드가 있는 DTO를 사용하면 쉽게 해결할 수 있다.  Day4 02/21 기본적인 데이터베이스 사용법 (11~13강)이번 강의에서는 MySQL에서 DDL, DML을 이용해 테이블을 생성 및 삭제, 데이터의 CRUD, Spring Boot에서 MySQL 연동을 해봤다. 이번 강의에서는 에러가 발생했다! MySQL 설정 시 발생하는 에러였는데 간단한 구글링을 통해 빠르게 해결할 수 있었다. (MySQL 연동 오류) 기본적인 SQL 문법을 간단하게 훑어 넘어가는 식으로 강의가 진행됐다. 기초가 없었으면 약간 따라가기 힘들었을 것 같기도 하다!그리고 User 테이블을 생성하고 Java 코드를 메모리에 저장하는 방식에서 데이터베이스(MySQL)에 저장하는 방식으로 변경하도록 코딩했다. 이번 강의에서 람다가 처음으로 나왔는데 람다에 대해서 따로 공부해본 적이 없어서 생소하게만 다가왔다. 이번에 람다를 보면서 OT때 강사님이 얘기했던 모던자바 인 액션 책을 꼭 공부해봐야겠다고 생각했다..! 미션https://devhan.tistory.com/320 익명 클래스와 람다에 대해 알아보는 시간이었다.이번 미션을 하면서 하루라도 빨리 모던자바 인 액션을 읽어야겠다고 생각해 책을 얼른 구매했다. Day5 02/22 데이터베이스를 사용해 만드는 API (14~16강)이번 강의에서는 JdbcTemplate을 사용한 API 개발을 구현하기 위해 기존에 있던 코드들을 변경하는 강의 내용이었다.User 업데이트, 삭제 부분을 코딩하는거였는데 14강에서는 단순히 변경만 했고 15강에서는 예외 상황을 대비해 예외 코드를 추가했다! 이 코드가 제일 신기했는데, 결과가 하나라도 있으면 0을 반환하게하는 코드이다. 그리고 최종적으로 0은 List로 반환된다.결과가 0건이면 빈 List가 반환된다! 미션https://devhan.tistory.com/321이번엔 Fruit 테이블을 생성하고, 요구사항에 맞는 API들을 개발하는 미션이었다.제일 고민이었던 건 판매 여부의 컬럼명과 데이터를 0과 1로 할지 아니면 Enum을 사용해서 String으로 저장할지 고민했는데 상태값이 두 개밖에 없어서 그냥 0과 1을 사용했다.강사님 코멘트select * from table을 사용하고 덧셈을 하는 경우는 데이터베이스에서 서버로 네트워크를 타고 모든 데이터가 넘어온 이후에 서버에서 직접 덧셈 -> 네트워크 대역폭도 많이 잡아 먹고 서버의 연산 비용도 들어감.반면 sum()을 사용하면 합산 결과만 네트워크를 타고 이동하며, 서버는 그 결과를 DTO로 감싸 전송만 하면 되기에 네트워크 및 연산 비용이 훨씬 저렴하다.이런 다양한 방법을 비교할 수 있으려면 1) 일차적으로는 방법들을 알아야하고 (지식의 넓이) 2) 다음으로는 각 방법의 매커니즘을 이해해야 함(지식의 깊이)Day6 02/23 클린코드의 개념과 첫 리팩토링 (17~18강)이번 강의에서는 좋은 코드(Clean Code)의 개념과 기존에 작성했던 코드를 Layered Architecture로 변경하는 작업을 했다.클린 코드는 아직 읽어보지 않았지만 워낙 유명한 책이라 강의에서 만난게 마치 오래전에 알던 친구를 만난 것처럼 재밌었다! 이 기회에 또 읽어봐야할 책이 하나 더 늘었다..!클린 코드에서 가장 기억에 남았던 건 유명 회사 앱이 클린 코드로 코드를 작성하지 않아 점차 망해가는 얘기였다. 그런 얘기가 떠돌아다닐 정도로 코드의 깔끔함은 앞으로의 유지보수에 있어 많은 부분에서 좋은 효과를 줄 수 있다는 걸 배웠다!클린 코드 얘기는 너무 많이 들었지만 어떻게 해야 깔끔하고 좋은 코드인지 가늠하기는 어려웠다. 나는 솔루션 회사에 재직해서 spring boot를 실무에서 쓸 일이 없어서 더욱 가늠이 안 갔던 거 같다. 이번 강의를 통해 조금이나마 클린 코드로 가는 틀을 잡을 수 있어서 좋았다!그리고 또 Layered Architecture란 이름을 알게되었다. Controller, Service, Repository로 구성된 애플리케이션은 여태 수도 없이 보았던 거 같은데 이런 명칭이 있는지는 처음 알았다. 대부분 그냥 MVC 패턴이라하며 갑자기 뭉뚱그려 넘어가서 몰랐었던 거 같다.  미션https://devhan.tistory.com/322작성된 주사위 놀이 코드에 클린 코드를 적용해 리팩토링해보는 미션이었다.제일 고민됐던 것은 Dice를 클래스로 따로 뺄지 말지였다.뭔가 빼면 과하게 빼는 거 같기도 하고,,? Main 메서드에 너무 아무것도 없는 거 같아 뭔가 심심해보이기도 했다.그리고 그 다음으로 고민했던 건 한 걸음 더! 내용이었다.주사위의 범위가 달라지더라도 코드를 적게 수정할 수 있도록 하는 거였는데 사용자에게 주사위 면체의 정보를 입력받을까 하다가 그런 얘기는 나와있지 않아서 그냥 Dice 클래스에 면체와 관련된 필드와 생성자를 추가해주었다..!마지막 1주차 느낀점 정리나는 되게 무언가를 대충 아는 정도였던 거 같다.하루빨리 자바8과 관련된 책을 읽고 지식을 습득해야 할 것 같다. (람다 관련 응용이 아예 안 되는 중이다.)클린 코드의 책도 읽고 클린 코드의 감을 잡아보도록 해야겠다.직장인이라 시간적 여유가 매우 부족해서 아쉬웠다. 저번주 주말에 미리미리 진도를 안빼놨었으면 진작에 수료 기준에 벗어날 뻔했다..! 직장인이니 남들보다 더 미리미리 진도를 나가야겠다. 이번주에만 글쎄 야근을 3일이나 해서 죽는 줄 알았다...생각보다 내가 강의를 잘 따라가고 있는 거 같다. 뭐 실력적으로 잘은 모르겠지만 그래도 꾸준히 놓치지 않고 하려는 모습이 약간은 기특해보일정도! 앞으로도 놓치지말고 꾸준히해서 이번 스터디를 완주했으면 좋겠다!   

백엔드인프런워밍업클럽스터디최태현자바와스프링부트로생애최초서버만들기SpringBootbackend

suover

인프런 워밍업 클럽 스터디 2기 - 백엔드 프로젝트 4주차 발자국

입문자를 위한 Spring Boot with Kotlin - 나만의 포트폴리오 사이트 만들기강의와 함께한 인프런 워밍업 클럽 스터디 2기 - 백엔드 프로젝트 (Kotlin, Spring Boot) 4주차 발자국 입니다. 강의 수강이번 주에는 Spring Boot와 JPA를 사용한 뷰 개발 및 Thymeleaf를 활용한 프론트엔드 템플릿 작업에 중점을 두었습니다. 주로 Bootstrap 템플릿을 가져와 활용하고, Thymeleaf의 fragment 기능을 사용하여 재사용 가능한 HTML 구조를 만들었으며, 배포 작업까지 진행했습니다.주요 학습 내용Bootstrap 템플릿 가져오기 및 적용Bootstrapmade 사이트에서 Admin 템플릿을 다운로드하여 프로젝트에 적용했습니다.템플릿 파일을 프로젝트의 resources 디렉토리에 추가하고, 필요한 CSS, JS 파일들을 적용했습니다.Thymeleaf와 프론트엔드 분리 작업Thymeleaf의 fragment 기능을 활용해 HTML 구조를 모듈화하였습니다. head, header, footer 등 여러 공통 요소를 별도의 fragment로 분리하여 재사용성을 높였습니다.타임리프 문법을 통해 동적으로 페이지를 구성하고 유지보수가 용이하도록 개선했습니다.프로젝트 배포 작업Docker를 이용해 MySQL을 컨테이너로 실행하고, Spring Boot 애플리케이션을 Docker 이미지로 빌드하여 배포했습니다.Nginx를 사용해 80포트와 8080포트를 연결하여 클라이언트 요청을 처리하도록 설정했습니다.Let's Encrypt를 이용해 HTTPS 인증서를 설정하고, 웹사이트를 HTTPS로 안전하게 접근할 수 있도록 구성했습니다. 회고이번 주는 Thymeleaf와 Bootstrap을 활용하여 프론트엔드 작업을 진행하며, 뷰를 모듈화하고 효율적으로 구성하는 방법을 배울 수 있었습니다. 특히 Thymeleaf의 fragment 기능은 HTML 코드의 재사용성과 유지보수성을 크게 개선해 주어 프로젝트의 구조를 더욱 체계적으로 만들 수 있었습니다. 또한, 직접 템플릿을 가져와 수정하고 적용하는 과정을 통해 프론트엔드 개발 역량을 키울 수 있었고, Docker와 Nginx를 이용한 배포 작업을 통해 실제 서비스 환경에서의 배포 경험도 쌓을 수 있었습니다.칭찬할 점Thymeleaf를 사용하여 공통 레이아웃을 fragment로 분리하여 재사용성을 높인 점.Bootstrap 템플릿을 프로젝트에 무리 없이 적용하고, 필요한 수정 작업을 성공적으로 수행한 점.Docker와 Nginx를 이용한 배포 과정을 원활히 진행한 점.아쉬웠던 점 및 보완할 점Thymeleaf 문법에 대한 익숙함이 아직 부족하여 일부 동적 작업에서 어려움을 겪었습니다. 추가적인 연습과 학습을 통해 더 익숙해질 필요가 있습니다.프론트엔드 요소의 커스터마이징에 있어 부족함을 느꼈으며, 좀 더 세밀한 스타일 수정 방법을 연구하고자 합니다. 미션이번 주 미션은 삽입, 수정, 삭제 REST API를 개발하고, 이를 검증하는 테스트 코드를 작성하는 것이었습니다. API는 각 기능별로 POST, PUT, DELETE 요청을 처리하도록 설계하였으며, 모든 케이스에 대해 성공적으로 테스트를 완료했습니다.미션 과정게시글 삽입 API 설계 (POST /api/posts)새로운 게시글을 작성하고, 작성된 데이터를 JSON 형태로 반환하도록 설계했습니다.게시글 수정 API 설계 (PUT /api/posts/{postId})특정 게시글을 수정하고, 수정된 결과를 반환하는 기능을 구현했습니다.게시글 삭제 API 설계 (DELETE /api/posts/{postId})특정 게시글을 삭제하고, 삭제 후 상태 코드 204 No Content를 반환하도록 했습니다.테스트 코드 작성 및 검증각 엔드포인트에 대해 최소 3개의 테스트 케이스를 작성해, 다양한 상황에서의 시스템 동작을 검증했습니다.필수 값이 누락된 경우, 잘못된 ID로 요청했을 때의 예외 처리 등도 함께 테스트하여 안정성을 확보했습니다.  미션 과정추가적으로 가상 프로필을 나의 프로필로 바꾸기 미션과 배포한 프로젝트 공유하기 미션까지 모두 마무리하며, 스터디의 모든 미션을 마무리 하였습니다. 이 발자국을 끝으로 모든 미션과 발자국, 강의 수강 100% 까지 마무리 하며, 스터디를 성공적으로 완주하였습니다. 회고이번 미션을 통해 RESTful한 API 설계 및 테스트의 중요성을 다시 한번 체감할 수 있었습니다. 또한, Thymeleaf를 활용해 프론트엔드를 모듈화하고 코드의 재사용성을 높이는 작업이 실제 프로젝트에서 어떻게 활용될 수 있는지 직접 경험할 수 있는 시간이었습니다. 배포 작업을 통해 실제 서비스 환경에서 발생할 수 있는 문제들을 다루며, 더 나은 품질의 소프트웨어를 개발하기 위한 실무 경험을 쌓을 수 있었습니다. 앞으로 JPA 연관 관계에 대한 이해를 깊이 있게 다지고, 테스트 코드의 가독성을 더욱 높이는 방법을 연구하여 더 나은 품질의 소프트웨어를 개발해 나가겠습니다.

백엔드인프런워밍업클럽스터디백엔드프로젝트발자국회고SpringBoot

suover

인프런 워밍업 클럽 스터디 2기 - 백엔드 프로젝트 3주차 발자국

입문자를 위한 Spring Boot with Kotlin - 나만의 포트폴리오 사이트 만들기강의와 함께한 인프런 워밍업 클럽 스터디 2기 - 백엔드 프로젝트 (Kotlin, Spring Boot) 3주차 발자국 입니다. 강의 수강이번 주에는 실습 중심으로 Spring Boot와 JPA를 사용한 컨트롤러 개발과 API 설계를 진행했습니다.API 테스트 코드를 작성해보고, Thymeleaf를 활용한 프론트 개발 작업도 수행했습니다.주요 학습 내용Spring MVC와 JPA를 활용한 컨트롤러 개발PresentationApiController 및 PresentationViewController에서 다양한 API 엔드포인트를 구현했습니다.JPA를 통해 데이터베이스와 상호작용하며 데이터를 처리하는 방법을 학습했습니다.Thymeleaf 프론트엔드 개발템플릿을 활용하여 데이터를 프론트엔드에 전달하고 뷰를 생성했습니다.커스터마이징을 통해 유지 보수가 용이한 뷰를 구성했습니다.컨트롤러 테스트 코드 작성API 테스트 코드를 작성하여 정확하게 동작하는지 검증했습니다.실제 API가 예상대로 동작하는지 검증하는 방법을 배웠습니다. 회고이번 주는 Spring Boot와 JPA를 활용한 컨트롤러 개발과 API 설계, 그리고 테스트 코드 작성까지 다양한 실습을 통해 실질적인 개발 경험을 쌓는 데 집중한 한 주였습니다. 특히, 데이터를 조회하고 처리하는 API를 설계하면서 RESTful 구조에 대해 깊이 고민했고, 테스트 코드 작성이 얼마나 중요한지 다시 한번 깨닫게 되었습니다. 실습을 통해 새로운 개념들을 익히고, 실제 프로젝트에 적용해보며 기술적 성장을 이뤘습니다.칭찬할 점테스트 코드를 작성하여 API의 주요 기능들을 효율적으로 검증했습니다.Thymeleaf 템플릿 커스터마이징을 통해 프론트엔드를 구성하고, 유지 보수가 용이하도록 최적화했습니다.아쉬웠던 점 및 보완할 점Thymeleaf 문법에 대한 이해가 부족하여 복잡한 작업에서 어려움이 있었습니다.테스트 코드를 더 간결하고 효율적으로 작성하기 위해 리팩토링 기법을 학습할 계획입니다.미션이번 주 미션은 게시글 조회 REST API를 개발하고, 이를 검증하기 위한 테스트 코드를 작성했습니다.API는 전체 게시글 조회와 특정 게시글 조회 두 가지 기능을 제공합니다. 미션 과정전체 게시글 조회 API 설계GET /api/posts 엔드포인트에서 전체 게시글을 조회하고, JSON 형식으로 데이터를 반환합니다.특정 게시글 조회 API 설계GET /api/posts/{postId}로 특정 게시글 ID를 기반으로 게시글을 조회하여 반환하는 기능을 구현했습니다.테스트 코드 작성 및 검증각 엔드포인트에 대해 단위 테스트를 작성하여 모든 기능이 예상대로 동작하는지 검증했습니다.특히 게시글이 없는 경우와 같은 예외 상황을 처리하는 테스트도 포함시켜 로직의 완성도를 높였습니다.  회고이번 미션을 통해 RESTful한 API 설계의 중요성을 다시 한번 느꼈습니다. 엔드포인트를 직관적이고 간결하게 설계하기 위해 많은 고민을 했으며, 테스트 코드를 작성하며 API 동작을 검증하는 과정에서 테스트의 중요성을 다시 한번 실감할 수 있었습니다.아쉬운 점 및 보완할 점테스트 코드의 가독성과 효율성을 높이기 위해 추가적인 리팩토링이 필요합니다.JPA의 복잡한 연관 관계 처리에서 어려움을 겪었으며, 이러한 부분을 더 명확하게 이해하기 위해 추가적인 학습이 필요합니다.이번 주의 학습과 미션을 통해 부족했던 부분들을 확인할 수 있었고, 앞으로도 지속적인 학습과 실습을 통해 문제를 개선해 나갈 계획입니다. 특히, JPA 연관 관계와 테스트 코드의 리팩토링에 집중하여 더 나은 품질의 코드를 작성하고, 실무에 적용할 수 있도록 역량을 키워가겠습니다.

백엔드인프런워밍업클럽스터디백엔드프로젝트발자국회고SpringBoot

river_bori

인프런 워밍업 클럽 2기 - 백엔드 프로젝트 (Spring, Kotlin) 2주차 발자국

일주일간 학습한 내용 요약개발 - Domain프로젝트 생성Jar로 해야지 스프링부트에서 제공하는 내장 키트를 사용할 수 있다.Dependensies: 프로젝트에서 쓸 외부 라이브러리들을 추가해 주는 작업6개의 라이브러리 추가Spring web: MVC사용 등Thymeleaf: 템플릿과 데이터를 합쳐서 최종적으로 완성된 html 파일을 만들어준다. (없으면 개발자가 html 파일까지 코드를 짜야함)Spring Data JPA: JPA에 껍데기를 씌서 사용성을 높임My SQL DriverH2 Database: 인메모리 DB로 스프링이 켜질때 같이 켜짐(스프링과 같은 메모리 사용), 꺼질때 데이터 사라짐Validation: 검증기능그외Spring Security: 로그인 기능에 사용하지만 지금 설치하면 스프링 킬때마다 로그인 해야해서 일단 설치 제외 IntelliJ 설정프로젝트 스트럭쳐프리퍼런시스 Git과 GithubGit 용어commit: 현재 작업한 내용을 하나의 버전으로 반영(저장)rollback:작업한 내용 이전 버전으로 되돌리기branch: 하나의 프로젝트에서 독립적, 병렬적인 버전으로 가지같이 여러명이 동시에 개발이 가능하게 만듬merge: 서로 다른 branch를 합치는 동작conflict(충돌): merge할 때 하나의 파일이 두 브랜치에서 수정이 발생해, 어떤 수정본을 반영할지 알 수 없는 상황repository: github의 저장소 (=remote repository) push: 원격 저장소로 브랜치를 업로드 하는 동작pull: 다운받는 것Git 명령어git status, add, pull, clone, push, commit -m터미널에서'pwd' 입력 => 폴더의 경로 확인'git init' 입력 => 깃 폴더 초기화'git status'입력 => 깃에서 관리하지 않는 파일들이 빨간색으로 표시됨. 그 중 관리하지 않아도 된는 파일들을 배제하고 등록해줌.'.gitignore' 파일에 입력해서 배제 가능함 => gitignore.io 에서 목록 개발환경 입력 후 복사 가능git add README.md : 특정 파일을 기초적 대상으로 추가하는 명령어git commit -m "first commit" : 현재 변경된 내용을 새 버전으로 반영하는 명령어'-m' 옵션을 통해 ""(쌍따옴표)안에 있는 내용을 커밋 메시지로 입력 git remote add origin http://github.com/주소경로: 원경 저장소를 추가 프로젝트 환경 변수 설정데이터 소스와 jpa설정 => 설정해놓은 값을 복붙함.위와 관련된 중요 개념이런 설정 값들은 보통 상수(=변하지 않는 값)다.DB url 등자바 코드에 " "(literal, 문자열 방식)으로 관리해도 되지만, 같은 값을 여러 클래스에서 사용할 때, 값이 수정되면 모든 클래스에서 사용한 값들을 다 찾아서 수정해줘야 한다. 이때, 하나라도 놓치면 에러가 난다.실제 운영할 때는 개발용 서버, 운영용 서버, 개발용 DB, 운영용 DB로 나눠서 사용한다.서로 다른 서버 컴퓨터에서 똑같은 프로그램이 돌아가는데 서로 다른 DB서버에 붙어있다. 개발DB와 운영DB의 주소는 다르다 => 환경변수(=환경마다 바뀌는 값)개발서버에게 개발DB URL을, 운영서버에게 운영DB URL을 알려줘야하는데 " "(문자열 방식)으로는 관리가 어렵다.Spring Profile과 application.ymlSpring Profile: 스프링은 돌아가는 애플리케이션의 프로필을 정의하는 기능을 제공, 스프링을 실행시키는 시점에서 환경변수로 정의 가능개발 서버에 스프링 프로젝트를 띄울 때 dev라는 프로필로 돌릴거라고 지정하고, 운영서버에 prod라는 프로필로 지정하면, 각자 dev, prod로 세팅이 된다. 아무것도 세팅을 안하면 기본값은 default라는 이름으로 돈다. No active profile set, falling back to 1 default profile: "default" application.yml: 스프링은 프로필마다 환경변수를 설정하는 기능을 제공, application.properties: YML 파일과 똑같은 기능을 한다. (문법이 좀 다르다)기존에 있는 properties 파일을 yml로 변경. => application-default.ymlyml 파일을 복사해서 application-docker.yml 을 만듦.application-{Profile} 형식으로 위와 같이 파일을 네이밍 해주면, Profile에 따라 상수 값 설정이 가능하다.스프링이 실행될 때 프로필이 default면 application-default.yml 에서 환경변수를 가져오고, 프로필 이름이 docker면 application-docker.yml 에서 환경변수를 가져온다.키-밸류 형식으로 등록할 수 있다. 같은 키에 값만 다르게 등록한 것. 소스 코드에는 String DATASOURE_URL_PROPERTY = "spring.datasource.url";로 등록해주면 프로필에 따라 각 키에 맵핑값을 찾아 각 DB와 연결하고 동작한다. ymljpa에 대한 설정open-in-view: false => 나중에 따로 설명show-sql: true => sql을 로그에 보이게 할지hibernate: ddl-auto: create => JPA 엔티티를 바탕으로 jpa에서 데이터베이스에 테이블을 새로 만들어주는 기능, 개발 테스트할 때는 써도 되지만 운영에서는 무조건 None으로.properties: hibernate: format_sql: false => sql 로그를 찍을 때 좀 더 보기 쉽고 이쁘게 만들어주는 것, 근데 한줄로 보이게 false처리# default_batch_fetch_size: 10 => 강의에서 따로 설명 예정datasource에 대한 설정(docker.yml과 내용이 다름)url: jdbc:h2:mem:portfolio => db에 url을 알려주고username: sapassword: => 접속하기 위해 필요한 사용자명과 pw알려준다.driver-class-name: org.h2.Driverh2에 대한 설정(default.yml에만 있다. Mysql에는 아래와 같은 설정이 없기 때문)console: => H2에서 DB에 접속하기 위해 사용하는 H2콘솔enabled: true => 을 사용하고path: /h2-console => 어떤 경로로 접속할 건지 지정해주는 옵션클래스 생성도메인 패키지에서 개발할 클래스들을 미리 껍데기만 만듦포트폴리오 패키지도메인 패키지constant (in 상수 관련 클래스)entity (in 총 11개의 클래스)repository (in 총 8개의 인터페이스)configuration (암호관련-나중에 만듦)entity 패키지 (11개 클래스)BaseEntity(추상클래스):모든 테이블들이 공통적으로 갖는 Created Date Time, Updated Date Time 컬럼들은 각 클래스에 직접 넣지 않고 상속을 활용할 예정@MappedSupercass: 이 어노테이션이 있는 클래스를 상속 받는 엔티티 클래스가 이 클래스 안에 있는 필드들을 해당 엔티티에 있는 테이블의 컬럼과 맵핑 할 수 있다.Achievement: BaseEntitiy클래스를 상속받는다.@Entity: 이 어노테이션을 달아줘야 JPA에서 테이블과 맵핑되는 엔티티 클래스라는 것을 알 수 있다.@Id: JPA엔티티에는 필수인 어노테이션, 필드 위에 입력. (var id가 하나의 필드) @Id를 붙여줘야지 이 필드가 PK라는 것을 알 수 있다.=> match case(설정)을 끄면 자동완성을 도와준다.@GeneratedValue(strategy = GenerationType.{다양}: PK생성 전략을 정해준다. strategy 파라미터를 통해 정한다.{다양}TABLE: pk를 만들기 위한 테이블을 전용으로 만들어 PK생성(?)SEQUENCE: DB가 제공하는 순서대로 번호를 지정해주는 시퀀스라는 기능을 사용(MySQL에서는 사용불가)IDENTITY: 기본 키 생성을 DB에 위임. MySQL의 경우 Auto Increment라는 기능을 이용. => 이거로 사용AUTO: JPA가 내부 알고리즘을 따라 자동적으로 결정하는데 MySQL에서는 AUTO로 하면 앞의 TABLE을 사용한다.@Column(name = "achievement_id"): 이 필드가 DB에서 어떤 이름을 가진 컬럼이랑 맵핑되는지 개발자가 직접 지정해주는 기능.안붙여도 필드는 CamelCase(isCamelCase), DB는 SnakeCase(is_snake_case)로 되어 있으면 알아서 맵핑 컬럼을 찾아준다.테이블 pk는 테이블명_id로 지정하고, 코틀린 엔티티에서는 필드명을 id로만 지정. (나중에 이해 안가면 강의 다시 듣기 (7:00) )엔티티 인스턴스를 사용할 때val achievement: Achievement로 변수명을 해줌. 필드명을 id로 줄이지 않으면 achievement.achievementId로 id를 조회해야 함.achievement.id로 직관적이고 보기도 좋게 사용하고 싶음=> 때문에@Column(name = "achievement_id") var id: Long? = null로 지정자료형 뒤에 ?를 붙이면 null이 허용된다는 의미, 코틀린은 자바보다 null에 대해 엄격하다.id는 엔티티를 처음 생성할 때 들어가지 않고 이 엔티티를 DB에 저장할 때 DB에서 생성해 주는 값이기 때문에 인스턴스를 처음 만든 순간에는 null일 수 밖에 없다.Achievement 클래스를 복사해서 다른 클래스들을 만든다. (@Column의 name등 바꾸기) repository 패키지 (8개 인터페이스)Spring Data JPA RepositoryRepository: DB 접근하는 역할Spring Data JPA: 스프링에서 jpa를 좀더 쉽게 쓰기 위해 한번 랩핑한 라이브러리인터페이스를 추가하는 것만으로 DB CRUD와 관련된 기본적인 기능을 사용 가능각 엔티티에 대응해 interface로 각각 repository를 만들어야한다.BaseEntity 클래스 제외스프링을 시작할 때 SpringDataJPA에서 인터페이스를 보고 알아서 repository 클래스를 만든다. AchievementRepositoryinterface AchievementRepository : JpaRepository<Achievement, Long>JpaRepository<Achievement, Long>를 상속받음.<>을 Generic으로 명칭 나머지 엔티티에 대응하는 레퍼지토리 만들기...Detail 클래스에 대응하는 repository는 안만든다. JPA가 연관관계를 가진 엔티티를 통해서 엔티티를 불러올 수 있기 때문...Skill 클래스는 따로 만들어준다 => 왜? 뭔가 다르데 constant 패키지SkillType: enum 클래스 => 상수값(언어, 프레임워크, BD, Tool) 엔티티 개발 - 연관관계 없음BaseEntity.kt@CreatedDate: JPA엔티티가 생성된 시간을 자동으로 세팅@Column(nullable = false, updatable = false): 지난번 Name 파라미터를 이용해 필드와 맵핑될 Column의 이름을 별도로 지정해주는 기능(@Column(name = "achievement_id")) 설정함. 그것과는 다른 기능을 설정. 위 내용은 null일 수 없고, 변경 불가능 하다는 뜻. (다른 엔티티와) 연관관계가 없는 엔티티Skill 같은 경우, 프로젝트와 프로젝트 스킬을 통해서 연관관계를 가지지만, 스킬을 통해 프로젝트에 직접 접근하는 일이 없다 -> 때문에 연관관계가 없는 엔티티와 다를게 없다.엔티티 같은 경우, 연관관계에 상관없이 생성자를 이용해 처음 인스턴스를 생성할 때 필요한 값들을 전부 받으려고 한다.때문에 기본 생성자부터 만든다생성자(영어: constructor, 혹은 약자로 ctor)는 객체 지향 프로그래밍에서 객체의 초기화를 담당하는 서브루틴을 가리킨다. 생성자는 객체가 처음 생성될 때 호출되어 멤버 변수를 초기화하고, 필요에 따라 자원을 할당하기도 한다. 객체의 생성 시에 호출되기 때문에 생성자라는 이름이 붙었다.[위키백과]Achievement.kt기본 생성자를 만든다id 아래에 필드들을 만든다. -> 생성자에서 받은 값들을 넣어준다. (초기화한다.)Introduction.kt와 Link.kt도 비슷하다.Skill.kt생성자 중 type: String(일단은 문자열로 받음)=> 데이터를 처음 만들 때, 어드민 프론트에서 데이터를 받아서 세팅을 해주는데, 어드민에서는 이런 타입 같은 것을 알 방법이 없기에 문자로 보냄=> 문자로 받고 생성자 내부적으로 타입 스트링에 맞는 스킬 타입을 찾아 필드에 넣어줄 것임.var type: SkillType = SkillType.valueOf(type)SkillType.kt에서 문자열과 일치하는 enum을 찾아서 리턴해줌.jpa에서 활용하려면 좀 더 지정해줘야 함.@Column(name = "skill_type")(type을 예약어로 쓰는 DB가 있기 때문에 테이블 컬럼명으로 'type' 쓰는 것을 지양해야 함.)@Enumerated(value = EnumType.STRING)자료형이 enum클래스일 때 쓰는 어노테이션.EnumType.{STRING|ORDINAL} 두 개 중 선택 가능ORDINAL: enum이 선언된 순서대로 1, 2, 3...의 값을 DB에 넣어줌.1) DB를 봤을 때 직관적으로 이 데이터의 실질적 의미를 알기 어렵다.2) 어떤 개발자가 enum의 순서를 바꿨을 때, 데이터의 정합성이 깨짐STRING: enum의 이름 그대로 DB에 넣음(지정 필수)(DB의 용량을 약간 더 차지하는 단점 존재)HttpInterface.kthttp 요청 정보를 저장하는 엔티티class HttpInterface(httpServletRequest: HttpServletRequest): 스프링에서 요청을 받을 때 그 request의 정보를 여기에 담아서 준다. => 클라이언트 정보를 꺼낸다.var cookies: String? = httpServletRequest.cookies?.map{"${it.name}:${it.value}"}?.toString():.map{ }은 cookies라는 객체가 배열인데 안의 것들을 하나씩 순차적으로 돌면서 {중괄호}안에 들어간 함수대로 변환해주는 기능. it은 cookies 객체. cookies안에 name과 value가 있어 중괄호 안의 방식으로 포맷팅 되어진다.=> 쿠키에는 이용자가 본 내용, 상품 구매 내역, 신용카드 번호, 아이디(ID), 비밀번호 IP 주소 등이 배열로 담겨 있어 위 작업은 그 중 name과 value를 꺼내는 동작이다.(?).toString()으로 문자열로 바꾼다. => "name:value"HTTP 쿠키(HTTP cookie)란 웹 서버에 의해 사용자의 컴퓨터에 저장되는, '이름을 가진 작은 크기의 데이터'이다. 인터넷 사용자가 어떠한 웹사이트를 방문할 경우 사용자의 웹 브라우저를 통해 인터넷 사용자의 컴퓨터나 다른 기기에 설치되는 작은 기록 정보 파일을 일컫는다. 쿠키, 웹 쿠키, 브라우저 쿠키라고도 한다. 이 기록 파일에 담긴 정보는 인터넷 사용자가 같은 웹사이트를 방문할 때마다 읽히고 수시로 새로운 정보로 바뀐다. 이 수단은 넷스케이프의 프로그램 개발자였던 루 몬툴리가 고안한 뒤로 오늘날 많은 서버 및 웹사이트들이 브라우저의 신속성을 위해 즐겨 쓰고 있다. (=> 신속성 = 서버크기 예측..?)쿠키는 소프트웨어가 아니다. 쿠키는 컴퓨터 내에서 프로그램처럼 실행될 수 없으며 바이러스를 옮길 수도, 악성코드를 설치할 수도 없다. 하지만 스파이웨어를 통해 유저의 브라우징 행동을 추적하는데에 사용될 수 있고, 누군가의 쿠키를 훔쳐서 해당 사용자의 웹 계정 접근권한을 획득할 수도 있다.[위키백과]referer: nullable한 필드, http 요청 정보에서 referer을 가져온다. 구글을 통해 검색해 어떤 사이트에 들어갔을 때, google.com의 도메인이 referrer(조회인)가 되는 것임웹 브라우저로 월드 와이드 웹을 서핑할 때, 하이퍼링크를 통해 각각의 사이트로 방문시 남는 흔적, 웹 사이트의 서버 관리자가 사이트 방문객이 어떤 경로로 자신의 사이트를 방문했는지 알아볼 때 유용, referer은 but 조작 가능, HTTP 리퍼러를 정의한 RFC에서 'referrer'을 'referer'로 잘못 입력한 것이 계속 사용됨[위키백과]localAddr, remoteAddr, remoteHost:클라이언트와 관련된 ip 주소들requestUri: 우리 서버에서 어떤 uri로 접속을 했는지, 메인이면 그냥 루트 or /, 프로젝트면 /프로젝트, resume면 /resume 로 어떤 uri로 접속했는지 그 정보가 들어온다. (referer과 다른 점은 어디에서 검색해서 사이트에 들어왔는지와, 사이트에서 이동 경로 추적 차이..?)통합 자원 식별자(Uniform Resource Identifier, URI)는 인터넷에 있는 자원을 나타내는 유일한 주소이다. URI의 존재는 인터넷에서 요구되는 기본조건으로서 인터넷 프로토콜에 항상 붙어 다닌다.URI의 하위개념으로 URL, URN 이 있다. [위키백과]userAgent:사용하는 브라우저 정보, 크롬, 사파리, 모바일, 데스크탑 등등 엔티티 개발 - 연관관계 있음Experience.kt생성자에 초기값을 넣는다.필드를 선언한다.Experience Entity는 ExperienceDetail과 1:N의 관계jpa에서는 List로 N쪽에 해당하는 필드를 가져올 수 있다.@OneToMany(targetEntity = ExperienceDetail::class, fetch = FetchType.LAZY, cascade = [CascadeType.ALL]) @JoinColumn(name = "experience_id") var details: MutableList<ExperienceDetail> = mutableListOf()@OneToMany(targetEntity = ExperienceDetail::class, fetch = FetchType.LAZY, cascade = [CascadeType.ALL]):One은 Experience, Many는 ExperienceDetail. 아래 필드가 1대 다의 관계를 가지고 있다고 jpa에 알려주는 어노테이션(targetEntity = ExperienceDetail::class,...): 어노테이션의 옵션 => targetEntity는 나중에 별도의 강의에서 설명 예정fetch = FetchType.{EAGER|LAZY}:EAGER은 더 열심히고 열정적인 경찰이래, 사건이 일어나면 용의자인 experience를 잡아야하는데, experienceDetail이 자식같은 관계니까 연관된 detail까지 다 잡아온다.개발자가 DB에서 experience만 조회하려고 했는데, detail까지 같이 인스턴스 안에 들어가 있다.그래서 EAGER는 쓰면 안된다. 오래걸린다. N+1의 문제인다(부모를 조회하려고 쿼리가 나가고 그다음 자식이 있다는 것 알고 자식을 조회하려고 쿼리가 N번 더 왔다갔다함, 부모 100명 조회 1번, 자식 100명 조회 100번 => 총 101번 쿼리 발송)LAZY는 좀더 효율적이다. 부모를 조사하다가 자식도 혐의가 있을 때만 잡으러 간다.부모 엔티티에서 실제로 자식 엔티티 필드를 호출하는 그 순간에만 조회쿼리가 나간다. 호출한 부모 엔티티의 자식 엔티티를 모두 조회해야할 때에는 EAGER과 다를 바가 없기 때문에 근본적인 해결책은 안된다.처음부터 부모와 자식을 한꺼번에 조회하는 방법은 레포지토리 개발하면서 설명할 예정cascade = [CascadeType.ALL]:영속성 콘테스트와 관련있는 개념, experience 엔티티가 영속성 콘테스트와 관련해서 발생하는 모든 변화에 자식 엔티티도 똑같이 적용할지 정해주는 옵션. ALL이면 모두 똑같이 적용한다는 뜻@JoinColumn(name = "experience_id")맵핑에 기준이 되는 컬럼을 알려준다.var details: MutableList<ExperienceDetail> = mutableListOf()mutableListOf: 빈 리스트를 만들어 준다.Mutable: '변할 수 있다' 라는 뜻 fun getEndYearMonth():종료연월을 각각 널체크하고 처리하면 서비스 코드가 복잡해지기 때문에 필요한 데이터를 한 번에 깔끔하게 서비스에서 가져올 수 있도록 엔티티 안에서 묶어줌fun update(생성자 모두 받음): put...각각 호출해서 수정하는 것보다 update하나를 호출해서 모두 한꺼번에 데이터 변경 가능하게 함jpa는 엔티티의 데이터를 바꾸기만하면 트랜젝션이 끝날 때, 처음 데이터를 가져올 때 따로 백업했던 스냅샷과 지금 엔티티의 상태를 비교해서 수정된 부분이 있으면 알아서 업데이트를 날린다.fun addDetails(details...):null 체크를 포함한 기본 방어 로직, 사용하는 쪽에서 깔끔한 디테일 데이터 추가 가능ExperienceDetail.kt: 연관관계 없는 엔티티와 비슷experienceDetail만 가지고는 experience를 찾을 수 없는 일대다 단방향 연관관계fun update(content: String, isActive: Boolean):Project.kt, ProjectDetail.kt 는 experience, ...detail과 비슷@OneToMany(mappedBy = "project", fetch = FetchType.LAZY, cascade = [CascadeType.PERSIST]) var skills: MutableList<ProjectSkill> = mutableListOf()@OneToMany(mappedBy = "project", fetch = FetchType.LAZY, cascade = [CascadeType.PERSIST]):mappedBy:양방향 연관관계에서 연관관계의 주인을 지정할 때 사용. ProjectSkill.kt안에 var project를 추가하는데, 이 var project를 통해 맵핑이 되고 맵핑하는 것은 ProjectSkill(연관관계에서 주인)이다.cascade = [CascadeType.PERSIST]:영속성 '전이'와 관련된 설정,cascade를 별도로 지정하지 않을 경우, Project 엔티티를 생성하고 save() 메소드를 호출해 영속성 컨텍스트에 persist한는 등 따로 persist를 해줘도 엔티티에 포함된 skills, 즉 ProjectSkills 엔티티들은 persist 되지 않는다.CascadeType.PERSIST를 지정해주면, Project 엔티티만 persist 해도, 거기 포함된 skills의 엔티티들이 모두 같이 persist가 된다.PERSIST 외에도 DETACH, MERGE, REMOVE, REFRESH 등의 상태를 적용할 수 있다.var skills: MutableList<ProjectSkill> = mutableListOf()ProjectSkill.kt: 다대일의 관계라서 프로젝트와 스킬을 각각 연결@ManyToOne(targetEntity = Project::class, fetch = FetchType.LAZY) @JoinColumn(name = "project_id", nullable = false) var project: Project = project @ManyToOne(targetEntity = Skill::class, fetch = FetchType.LAZY) @JoinColumn(name = "skill_id", nullable = false) var skill: Skill = skill 데이터베이스 초기화프로필소개글 3줄깃허브, 링크드인 링크학력/경력(Experience)수상/자격증(Achievement)기술스택(Skill)프로젝트(Project)사용기술: 기술스택(Skill)과 다대다 관계데이터 초기화 코드 작성도메인 패키지 안에 DataInitializer.kt 생성 (개발 편의를 위해 임의로 만든 것임)총 6개의 repository에 의존한다.생성자로 6개 입력 private val achievementRepository: AchievementRepository 등등 => DataInitializer를 빈으로 등록하려면 생성자인 repository들도 빈으로 등록됨 => 이런식으로 스프링 초기화가 진행됨class DataInitializer( //이게 바로 생성자 주입 private val achievementRepository: AchievementRepository, private val introductionRepository: IntroductionRepository, private val linkRepository: LinkRepository, private val skillRepository: SkillRepository, private val projectRepository: ProjectRepository, private val experienceRepository: ExperienceRepository )@Component: 스프링에서 관리하는 인스턴스 => bean(빈)스프링 처음 실행시 컴포넌트 스캔 과정을 거침이때, 스프링에게 어떤 것을 빈으로 등록할지 알려주는 역할자바로 클래스를 사용하려면 개발자가 직접 생성자를 이용해서 클래스의 인스턴스를 만들어야하는데, 스프링 프레임워크가 개발자 대신 인스턴스 제어를 한다.다른 인스턴스에서 이렇게 만들어진 빈들을 사용하려면 그 인스턴스를 주입받아 사용한다. =>"의존성 주입DI" => DI 방법은 잠시 후 해볼 예정 이런 빈들을 사용할 때에는 생성자, setter, 필드 주입등의 방식을 통해 의존성 주입을 받아 사용 가능@Controller, @Service, @Repository:세 어노테이션에는 component의 기능이 포함되어 있다.@Profile(value = ["default"]):스프링이 빈으로 등록하는데 프로필이 default일 때만 이 클래스를 생성해서 빈으로 등록한다. (개발자가 임의로 데이터 등록 못하게) @PostConstruct:메인 메소드가 실행이 되면서 스프링을 구축한다. 이때 Spring DI가 컴포넌트 스캔을 해서 인스턴스(빈)를 생성하고 의존성을 주입한다. 이런 식으로 스프링 프로젝트를 construct(구축)한다. 이런 스프링 초기화 작업이 완료되면, PostConstruct가 붙은 메소드를 찾아서 한번 더 실행한다(이때는 빈들이 다 등록되어 있어서 필요한 빈을 찾아 사용가능, 그 빈들을 이용해 테스트 데이터를 초기화 함 ). 이게 끝나면 스프링 실행이 완료된 것.fun initializerData(): //이게 아마 메인메소드println(" "): => logger를 써라java의 'System.out.println'과 똑같다.내부적으로 Synchronized를 달고 있어 성능에 좋지 않다. (자원을 하나씩 순차적으로 여러 스레드가 사용하고 있고, 동시에 사용할 수 없다. 그래서 성능에 안좋고 운영에 절대 쓰면 안된다.)logger:출력하려는 내용과 더불어 시간, 스레드 등 여러 정보들이 같이 출력됨. (때문에 강의할 때는 깔끔하게 보기위해 println을 쓸것임)val achievements = mutableListOf<Achievement>(엔티티 2개 입력함):mutableListOf: 리스트로 정의한다.2개의 Achievement Entity를 가진 리스트를 achievements필드에 초기화 함 => 엔티티를 만들어 주입받는 jpa repository들을 이용해 DB에 데이터를 넣어주는 작업achievementRepository.saveAll(achievements-리스트): 레파지토리에 리스트로 insert한다.achievementRepository interface에 아무것도 없는데 메소드에 사용이 가능하다(Spring Data JPA에서 만들어주는 기능이다.AchievementRepository가 상속하는 JpaRepository에 다양한 메소드들이 정의되어 있다.스프링이 실행되면서, 만든 인터페이스와 상속하는 인터페이스들 안에 실제 동작하는 기능을 가진 코드를 가지고 있는 repository 클래스를 만들어준다. 그 클래스들이 빈으로 등록된다.때문에 기본적인 기능들은 - list로 insert하는 것 등등 - 개발자가 하나하나 쿼리를 짤 필요 없이 간단하게 사용 가능하다. ) => 헷갈림 val introductions = mutableListOf<Introduction>(3개의 엔티티): ..복붙experienceRepository.saveAll(mutableListOf(experience1, experience2))experience를 리스트로 만들어 saveAll() 함수로 넘김. saveAll()을 통해 영속성 컨텍스트에 들어감. 현재 트랜잭션이 종료 될 때 영속성 컨텍스트에 있는 내용들이 insert로 DB에 들어감. 그때, Experience가 가진 detail들이 같이 insert로 들어간다.experience1.addDetails( mutableListOf( ExperienceDetail(content = "GPA 4.3/4.5", isActive = true), ExperienceDetail(content = "소프트웨어 연구 학회 활동", isActive = true) ) ) experience2.addDetails( mutableListOf( ExperienceDetail(content = "유기묘 위치 공유 서비스 개발", isActive = true), ExperienceDetail(content = "신입 교육 프로그램 우수상 수상", isActive = true) ) )이 때 만약, Experience.kt 안의 var details에 @OneToMany(targetEntity = ExperienceDetail::class, fetch = FetchType.LAZY, cascade = [CascadeType.ALL])에서 CascadeType을 ALL로 안하면, DB에 Experience는 입력되지만 detail은 insert 쿼리에서 제외된다.만든 엔티티(Skill)를 변수에 다 할당해준다.나중에 Project에서 projectSkill과 연결해서 재사용할 예정임.val java = Skill(name = "Java", type = SkillType.LANGUAGE.name, isActive = true) val kotlin = Skill(name = "Kotlin", type = SkillType.LANGUAGE.name, isActive = true) val python = Skill(name = "Python", type = SkillType.LANGUAGE.name, isActive = true) val spring = Skill(name = "Spring", type = SkillType.FRAMEWORK.name, isActive = true) val django = Skill(name = "Django", type = SkillType.FRAMEWORK.name, isActive = true) val mysql = Skill(name = "MySQL", type = SkillType.DATABASE.name, isActive = true) val redis = Skill(name = "Redis", type = SkillType.DATABASE.name, isActive = true) val kafka = Skill(name = "Kafka", type = SkillType.TOOL.name, isActive = true) skillRepository.saveAll(mutableListOf(java, kotlin, python, spring, django, mysql, redis, kafka))변수로 초기화를 하지 않고Skill(name = "Java", type = SkillType.LANGUAGE.name, isActive = true)생성자만 가지고 skillRepository를 이용해 한번에 DB에 넣으면 나중에 project에서 가져오기 복잡해진다. 그래서 미리 정의해준다.Project는 experience와 비슷addDetails()와 같이, skills.addAll() 함수로 묶어서 project1에 넣어줄 수 있다.// 방법1 project1.addDetails( mutableListOf( ProjectDetail(content = "구글 맵스를 활용한 유기묘 발견 지역 정보 제공 API 개발", url = null, isActive = true), ProjectDetail(content = "Redis 적용하여 인기 게시글의 조회 속도 1.5초 → 0.5초로 개선", url = null, isActive = true) ) ) // 방법2 => 다양한 방법이 있다 project1.skills.addAll( mutableListOf( ProjectSkill(project = project1, skill = java), ProjectSkill(project = project1, skill = spring), ProjectSkill(project = project1, skill = mysql), ProjectSkill(project = project1, skill = redis) ) )val: 불변(Immutable) 변수로, 값의 읽기만 허용되는 변수. 값(Value)의 약자이다.변수를 선언할 때 지정한 값에서 더이상 변경하지 않는 경우var: 가변(Mutable) 변수로, 값의 읽기와 쓰기가 모두 허용되는 변수. 변수(Variable)의 약자이다.변수의 값을 바꿔야 하는 경우출처: https://kotlinworld.com/173 리포지토리 개발JAP엔티티를 미리 정의해 두고 인터페이스만 만들면 Spring이 실행되면서, 리포지토리 인터페이스를 기반으로 리포지토리 클래스들을 만들어서 Spring Bean으로 등록한다.@Entity class Experience(... interface AchievementRepository : JpaRepository<Achievement, Long> {...서비스 Bean에서 리포지토리 빈들을 주입받아서 바로 사용 가능하다. 이때 사용하는 기능들은 Insert, Update, ID로 조회하기, ID로 삭제하기 등이 있다. 특정 컬럼 조회하기 등은 기본 메소드에 없다.인터페이스에 미리 정해진 규칙대로 메소드 이름을 정의해주면, 메소드 이름을 기반으로 쿼리를 작성해준다. => A부터 Z까지의 컬럼이 있을 때, 개발자가 A, B를 조회하고 싶다면 'Find by A and B' 이런 식으로 메소드 이름을 정의하고 파라미터로 A와 B를 넣어 주도록 인터페이스에 메소드를 정의하면 된다.// select * from achievement where is_active = :isActive fun findAllByIsActive(isActive: Boolean): List<Achievement>SkillRepository.kt 에는 메소드를 하나 더 만든다.// select * from skill where lower(name) = lower(:name) and skill_type = :type fun findByNameIgnoreCaseAndType(name: String, type: SkillType): Optional<Skill>Optional<Skill> 로 Skill 단건을 조회하게 함.case를 무시하라고 했기에, 전부 다 대문자나 소문자로 변경(컬럼도) => 뭔가 추가적인 지식이 있는 듯위 내용을 순수한 쿼리로 작성하면, 구체적인 DB 시스템에 종속된다. => 예를 들어, lower 함수 같은 경우. mySQL은 lower이라고 써도 오라클이나 다른 DBMS에서는 같은 기능을 다른 함수로 쓸 수 있기 때문.=> Spring Data JPA에서 이런 부분을 개발자가 신경 안쓰게 하기 위해 'IgnoreCase'로 각각 DBMS에 맞게 변경해줌 리포지토리 테스트 코드 작성테스트 코드는 매우 정말 중요하다.=> 강의용 프로젝트같이 규모가 작은 경우에는 덜 중요할 수 있다.IntelliJ는 특정 클래스의 테스트 클래스를 쉽게 만들어주는 기능을 제공한다.DataInitializerTest.kt[테스트할 클래스 -> 마우스 오른쪽 -> Generate -> test]도메인 등 원래 클래스가 있던 것과 같은 경로로 test패키지 안에 test 클래스가 생성된다.=> DataInitializerTest.kt 삭제 (테스트할 대상이 Spring Data JPA Repository Interface 이기 때문)인터페이스여서 테스트 클래스를 만들 수 없고 같은 규칙으로 직접 만듦test>kotlin>com>bohui>portfolio>domain 안에 패키지 '리포지토리'를 만든다.테스트 코드 작성은 많은 작업이 필요해서 오래 걸린다. => 때문에 찐 테스트 코드를 작성하지 않고, 작성하는 방식을 보여줄 예정Experience와 Project Repository 에 대해서만 테스트 클래스 생성ExperienceRepositoryTest.kt@DataJpaTest @TestInstance(TestInstance.Lifecycle.PER_CLASS) class ExperienceRepositoryTest( @Autowired val experienceRepository: ExperienceRepository // 테스트할 대상을 주입받음 ) @DataJpaTest:jpa 관련 테스트 할 때 사용하는 어노테이션.이 테스트 코드가 실행될 때, jpa 사용이 가능한 만큼 스프링 빈을 만들어 준다.Transactional이라는 어노테이션을 가지고 있다.@Transactional: 테스트 메소드 하나를 하나의 트랜잭션으로 보고, 메소드가 종료될 때 그 트랜잭션에서 발생한 모든 작업을 롤백함.테스트 코드에서 중요 원칙 중 하나는 독립적으로 항상 같은 결과를 내야한다는 것. => 인메모리 DB를 쓰면 상관없지만 안쓰면 롤백해야함. 안하면 테스트 코드 돌릴 때마다 테스트용 데이터가 계속 쌓여 다음 테스트에 영향을 줄 수 있다.=> 때문에 Transactional 어노테이션을 달아서 자동 롤백이 되게 함.@TestInstance(TestInstance.Lifecycle.PER_CLASS):TestInstance.Lifecycle.PER_CLASS:TestInstance의 라이프 사이클이 클래스 단위가 됨.원래 기본값으로, 이 테스트 코드를 돌리는 로직이 NewExperienceRepositoryTest 해서 메소드 한개 돌리고 또 NewExperienceRepositoryTest 해서 두번째 메소드 돌리는 식으로 수행이 됨. (같은 클래스에 있는 메소드들 이지만, 메소드 마다 인스턴스 생성)라이프 사이클을 클래스 레벨로 해주면, 인스턴스를 한번 만들어서 그 안에 있는 여러 메소드들을 수행한다.그래도, 메소드마다 TestInstance를 만들어 테스트를 돌리면, 메소드간 의존적이지 않다는(독립적) 장점이 있다.내부적 메소드 간에 의도적으로 의존적이게 만들고 싶을 때, 클래스를 만들어서 메소드를 1번, 2번, 3번 다 돌리는게 낫다...(이해가 더 필요)@BeforeAll: 테스트 데이터를 초기화하는 메소드. 다른 메소드가 돌기 전에 제일 처음에 딱 한번 돌아야 함. 때문에, TestInstance의 라이프 사이클을 클래스 단위로 해줘야 함.'DataInitializer.kt'는 개발 편의를 위해 임의로 만든 것으로, 이렇게 데이터를 초기화하는 방식은 좋지는 않다(왜?). 때문에 독립적으로 사용하기 위해 BeforeAll로 초기화할 예정. 그리고 @DataJpaTest를 사용하면 그 스프링 데이터 jpa를 테스트하기에 필요한 기능들만 초기화 가능하다. 그래서 테스트 돌릴 때, DataInitializer의 내용들은 빈으로 등록 안됨.TestInstance.Lifecycle.PER_METHOD:라이프사이클을 메소드로 할때, BeforeAll이 돌아 초기화를 해줬지만 다음에 돌아가는 테스트 메소드들은 BeforeAll의 영향을 받을 수 없다.@Autowired val experienceRepository: ExperienceRepository: 생성자로 테스트할 대상을 주입받음private fun createExperience(n: Int): Experience:테스트 데이터 초기화를 할 때 더미 엔티티를 만들어주는 기능, 받은 'n'의 개수만큼 이 'Experience' 안에 디테일을 넣어준다.val experience: 비어있는 더미 객체(entity) 생성기능단위로로 메소드를 분리해 주는 것이 구조적으로 소스 코드를 파악하기 더 용이하다.@BeforeAll: 테스트 데이터 초기화Assertions (org,assertj.core.api)Assertions.assertThat(beforeInitialize).hasSize(0): 테스트를 검증하는 메소드 (의도한대로 동작을 했는지)'beforeInitialize'에서 받은 데이터의 사이즈를 체크'0' 이면 테스트를 통과, 그 외에는 테스트 실패// 테스트 데이터 초기화 @BeforeAll fun beforeAll() { println("----- 데이터 초기화 이전 조회 시작 -----") val beforeInitialize = experienceRepository.findAll() assertThat(beforeInitialize).hasSize(0) // 테스트를 검증하는 메소드 println("----- 데이터 초기화 이전 조회 종료 -----") println("----- 테스트 데이터 초기화 시작 -----") val experiences = mutableListOf<Experience>() for (i in 1..DATA_SIZE) { val experience = createExperience(i) experiences.add(experience) } experienceRepository.saveAll(experiences) println("----- 테스트 데이터 초기화 종료 -----") }@Test: 메소드를 테스트 메소드로 인식되게 함@Test fun testFindAll() { println("----- findAll 테스트 시작 -----") val experiences = experienceRepository.findAll() assertThat(experiences).hasSize(DATA_SIZE) println("experiences.size: ${experiences.size}") for (experience in experiences) { assertThat(experience.details).hasSize(experience.title.toInt()) println("experience.details.size: ${experience.details.size}") } println("----- findAll 테스트 종료 -----") 리포지토리 성능 개선JPQL의 fact join을 활용해 jpa에서 발생하는 n+문제를 해결하고, ProjectRepository와 ExperienceRepository의 성능을 개선.ExperienceRepositoryTest.kt의 fun testFindAllByIsActive() 실행11개의 쿼리가 실행됨 => jpa에서의 n+1 문제부모데이터 1번 조회 (결과: 10개) -> 각 자식데이터 조회 10번 JPA에서 proxy를 쓰는데, proxy는 가짜 객체이다.디테일을 바로 가져오는게 아닌 한번 랩핑된 가짜객체를 가지고 있고, 그 가짜 객체 안에 var details가 호출될 때 Query가 나가는 로직이 있음.=> 디테일 호출 -> 가짜 객체 호출 -> 가짜 객체에서 진짜 데이터를 안가지고 있으니, DB에서 쿼리를 가져옴근데 너무 비효율적임 => FetchJoin 활용ExperienceRepository.kt의 fun findAllByIsActive위에 @Query("select e from Experience e left join fetch e.details where e.isActive = :isActive") 달아줌- 'e' alias 별칭jpql: 자바의 객체지향적인 쿼리. sql과 비슷한데, 좀 더 객체의 관점에서 작성할 수 있는 sql. JPA에서 JPQL을 가지고 실제로 DBMS에 맞는 쿼리로 바꿔서 DB로 쿼리를 보냄 (ex. @Query)쿼리가 한개만 나갔다.ProjectRepository.ktprojectSkill, projectDetail과 관계를 맺고 있다.패치조인의 단점, 한계점이 위와 같이 여러 개의 엔티티와 관계를 맺고 있을 때, 이것들을 한꺼번에 조회할 수 없다.=> 네이티브 쿼리로 풀거나, 쿼리 DSL or something=> yml의 default_batch_fetch_size: 10 을 통해 어느정도의 성능 문제를 해결 => n+1의 완전히 해결하는 것이 아닌 fetchSize의 값에 따라, m의 팻치사이즈가 n번 나가는 쿼리를 n/m으로 줄여준다.

백엔드SpringBootKotlinWeb

river_bori

인프런 워밍업 클럽 2기 - 백엔드 프로젝트 (Spring, Kotlin) 1주차 발자국

일주일간 학습한 내용 요약웹 개발 기본과 프로젝트 준비웹 서비스를 구성하는 요소클라이언트(브라우저, 서버컴 중 요청쪽 등) - 서버(응답주체, CRUD작업, 서버컴퓨터 집합인 클러스터를 구성) - DB(DBMS)DBMS 서버의 IP주소는 인터넷 세팅을 할때 등록해서 컴퓨터에 알려줘야 한다.웹 프레임워크와 Spring웹 프레임워크: 동적 웹 서비스 개발을 편리하게 만들어주는 도구백엔드 : (Java, Kotlin - Spring), (JavaScript, TypeScript - Express.js, Nest.js), (Python - Django), (Rudy - Rudy On Rails)프론트엔드 : (JavaScript - React(라이브러리), Angular, Vue.js) 프레임워크 vs 라이브러리 -> 제어의 주도권 차이프레임워크: DIY 가구 키트 - 사용자가 틀 안에서 주어진 것을 활용하여 원하는 것을 만드는 것라이브러리: 공구 상자 - 사용자가 주도권을 가지고 원하는 것을 만듦Spring Framework: Java기반의 웹 프레임워크, 웹 서버 개발의 상당 부분을 편리하게 모듈화 해놓음MVC 패턴: 요청 처리, 데이터, 화면 간의 결합도를 낮춰 유지보수 용이하게 함View: 사용자와 상호작용Controller: 요청받아 작업을 수행Model: 데이터 담는다. View는 데이터를 꺼내고, Controller는 데이터를 넣는다. 디자인 패턴? 경험적으로 특정 문제 상황을 해결하기에 최적이라고 생각되는 설계, 방법론.코드의 가독성, 유지보수성, 결합도, 응집도 등을 고민 하며 최적화된 코드를 작성하는 자세가 중요레이어드 아키텍처: 데이터를 컨트롤러에서 받아서 모델에 넣기 전까지 처리하는 과정을 이해하기 쉽고 관리하기 쉽게 구조화하는 방법 중 하나. 입력받은 데이터에 이상은 없는지 검증하고, 한번 가공해서 받은 데이터 기반으로 다른 데이터를 조회하는 등을 할 수 있다.MVC의 컨트롤러와 분리Controller(Presentation) - Service(Business) - Repository(Data Access) 각 레이어 별 역할 분리컨트롤러: 사용자와 상호작용서비스: 주요 로직 처리레파지토리: DB와의 상호작용스프링 Bean과 의존성 주입(Dependency Injection)스프링Bean: 스프링에서 관리하는 인스턴스어플리케이션 동작에 필요한 클래스들을 개발자가 직접 만들지 않고 스프링이 만들어서 관리하도록 위임한다. "제어의 역전 IoC(Inversion of Control)" 제어의 주체가 개발자가 아닌 프레임 워크가 되었다.스프링부트에서는 어노테이션으로 간단하게 스프링 빈을 정의해서 스프링이 그 인스턴스를 만들게 할 수 있다. (컨트롤러,서비스,레파지토리,컴포넌트 등의 어노테이션)스프링 실행 - 컴포넌트 스캔 - 클래스들을 하나하나 살피면서 어노테이션이 있으면 Bean으로 만듦(인스턴스를 만들어 빈으로 등록)의존성 주입(DI)는 역제어(IoC)의 한 형태레퍼지토리를 서비스에 넣어주고, 서비스를 컨트롤러에 넣어주고..의존성 주입"주입"은 의존성(서비스)를 사용하려는 객체(클라이언트)로 전달하는 것. 어떤 서비스를 호출하려는 클라이언트는 그 서비스가 어떻게 구성되었는지 알지 못해야 한다.의존성 주입의 의도는 객체의 생성과 사용의 관심을 분리하는 것. 이는 가독성과 코드 재사용을 높혀줌.클래스는 더 이상 객체 생성에 대한 책임이 없음.클라이언트의 생성에 대한 의존성을 클라이언트의 행위로부터 분리"매개변수 전달"과 동일하게 동작한다. 주입으로써 "파라미터 전달"은 클라이언트를 세부 사항과 분리하기 위해 수행되고 있는 부가적인 의미를 전달의존성 주입은 네 가지 역할을 담당하는 객체 및 인터페이스를 전제로 한다.사용될 서비스 객체사용하는 서비스에 의존하는 클라이언트 객체클라이언트의 서비스 사용 방법을 정의하는 인터페이스서비스를 생성하고 클라이언트로 주입하는 책임을 갖는 주입자비유하자면,서비스 - 전기, 가스, 하이브리드 또는 디젤 자동차클라이언트 - 엔진에 상관 없이 동일하게 차를 사용하는 운전자인터페이스 - 운전자가 기어와 같은 엔진의 세부 사항을 이해할 필요가 없도록 보장해주는 자동변속기주입자 - 아이에게 어떤 차를 사줄지 결정하고 구매해준 부모사용될 수 있는 모든 객체는 서비스로 여겨진다. 다른 객체를 사용하는 모든 객체는 클라이언트로 여겨진다.[위키백과]생성자(costructor) 주입수정자(setter) 주입필드(Field) 주입추가 공부 필요 - 이해가 안됨생성자 주입 방식을 권장의존성이 바뀌는 것을 방지할 수 있다순환참조 시 컴파일 오류가 발생해, 런타임 단계에서의 메소드가 서로 호출하는 스택오버플로우 에러 방지한다.의존하는 빈이 누락되면 컴파일 오류가 난다.HTTP와 REST APIHTTP: 네트워크로 통신하는 두 컴포넌트 간 통신규약HTTP 요청/응답Request: Start Line(HTTP 메서드, URL, HTTP 버전), Header(키/밸류 형태 메타데이터, 컨텐츠의 길이/유형, 클라이언트 정보), Body(본문, JSON 포맷)Response: Start Line(HTTP 상태코드 - 아래 있음)HTTP 요청메서드GET: 서버 자원을 조회하고 가져온다. 브라우저 주소창은 항상 GET 메서드로 요청,POST: 리소스를 생성메소드(CREATE)PUT, PATCH, DELETEHTTP 상태 코드200 OK: 요청이 정상처리됨300 Multiple Choices(Redirection): 사파리에서 네이버 입력 시, 네이버 서버가 300번대 코드와 m.naver... 주소를 주면 사파리가 다시 m.naver...으로 요청하고 m.naver..서버가 200번대 코드와 html 파일을 사파리에 준다 400 Bad Request: 클라이언트의 오류500 Internal Server Error: 서버의 오류(DB 다운 등)--> 응답코드는 서버 개발자가 정한다.Http는 규약이지만 자유도가 높은 규약이다.REST API: URL도 개발자가 정하기 나름이지만, 일종의 표준처럼 사용하는 아키텍처(기억 장치의 주소 방식)로 REST API를 사용한다.* 아키텍처: 기능 면에서 본 컴퓨터의 구성 방식. 기억 장치의 주소 방식, 입출력 장치의 채널 구조 따위REST 원칙을 알면 새로운 API를 봐도 어떤 API인지 직관적인 추측이 가능해진다. -> 개발자 간의 커뮤니케이션 비용이 줄어든다.REST API의 핵심URL 이용한 자원 표현: URL만 보고 어떤 요청인지 이해 가능HTTP 메서드 이용한 행위의 표현: 의미에 맞는 적절한 메서드 사용HATEOAS(헤이티어스) 준수: 응답에 링크 포함해 클라이언트의 다음 행동을 가이드 클라이언트에서 서버로 데이터 전달 방법Query Parameter: URL에 있다. ((get) inflearn.com/roadmaps?terms=5&page=1) -->'?'뒤 내용이 쿼리파라미터이다.HTTP Request Body: Http 메시지 안에 들어있기 때문에 Post등을 쓰지 않는 이상 눈으로 보기 어렵다.Path Variable(경로변수): URL에 있다.코딩 컨벤션: 코딩을 하는 프로그래머 사이의 규칙 규약, 읽고 관리하기 쉬운 코드 작성을 위한 코딩 스타일 규약데이터베이스란DBMS: 데이터를 체계적으로 관리하기 위한 프로그램관계형/비관계형 데이터베이스로 나뉜다관계형 DB(RDBMS): 행과 열로 이루어진 표의 형태후보키: 유일성(중복x) 최소성(최소한의 컬럼조합 사용)기본키PK: 후보키 중 하나학과와 학생은 1:N의 관계복수전공시학생 join 학과를 하면 학번의 유일성이 깨진다때문에 중간에 맵핑 역할을 하는 테이블 필요 "학생전공"테이블 학생전공ID(기본키)학과와 학생이 N:M 관계 가능해짐학생전공 join 학생 join 학과DB는 프로젝트의 골격과도 같기 때문에 서비스 운영 중 DB구조를 바꾸는 것은 매우 어렵다. 프로젝트 설계가 중요하다.오라클(유료), MySQL(유/무료),PostgreSQL(꾸준히 점유율 올라가는 오픈소스DB, 플러그인 확장성이 좋다.)비관계형 DB: 관계형DB를 제외한 모든 종류의 DB(키-값형, 문서형 등)개발하려는 서비스에 따라 비관계형DB도 공부 필요MongoDB: 문서형, 데이터를 비정형적으로 저장가능Redis: 키-값, 주로 캐시 용도로 사용(자주 조회된 데이터를 레디스에 저장해서 사용-빠르다)  JPA란JAP: Java Persistence API, 자바 ORM 기술의 표준 인터페이스, Java의 객체를 관계형DB의 테이블로 또는 그 반대로 변환해주는 기능/맵핑을 해주는 라이브러리서비스를 좀더 객체지향적인 관점에서 설계 및 유지보수할 수 있게 도와주고개발자가 직접 작성해야하는 코드를 줄여준다.ORM: Object Relational Mapping, 객체 관계 매핑, 객체지향 프로그래밍의 인스턴스와 관계형DB를 매핑해주는 기술학생, 학과 테이블 -> 클래스 (class Project)각 컬럼들 -> 클래스에 있는 필드 (val title)각 레코드 -> 클래스로 만들어진 인스턴스여러 레코드 -> 클래스의 리스트 (List<ExperienceDetail>)이렇게 테이블에 매핑되는 자바 클래스를 엔티티라고 한다. (class Project)DB엔티티에 해당하는 자바 클래스 = JPA 엔티티ORM은 테이블들이 갖는 관계까지 이용해 DB를 좀 더 쉽게 다룰 수 있는 기능을 제공ORM의 장점원래 자바로 DB를 다루려면 SQL문을 개발자가 직접 작성해서 DB로 보내야한다.JPA는 개발자가 정의해둔 엔티티의 필드들과 연관관계를 통해서 테이블 구조를 이해하고 있고 이를 바탕으로 대신 SQL을 작성함 => 개발 생산성 증가엔티티라고 부르는 Java 객체를 기반으로 쿼리 작성하기 때문에 데이터를 객체지향적으로 관점에서 접근 가능스프링과 구체적인 RDBMS(오라클, MySQL 등)에 대한 종속성을 끊어 의존성이 줄어든다. -> DB를 쉽게 변경 가능하다(ex. 오라클에서 MySQL로) ORM의 단점충분한 학습이 없으면 의도와 다르게 쿼리가 동작할 수 있음 ex: (n+1) 직접 쿼리를 작성했을 때는 한번만 쿼리가 전송됨, JPA를 사용하면 쿼리가 여러번 전송됨. => 해결법은 있지만 별도로 공부가 필요간단한 쿼리작성에는 효과적, 복잡한 쿼리에서는 한계점이 있어 불가피하게 구체적인 RDBMS에 종송적인 네이티브 쿼리 작성 => 의존성을 줄이는 장점이 사라짐트랜잭션: 데이터베이스의 개념, 여러 DB 작업을 하나로 묶는 논리적 단위(계좌이체 예시)커밋: 트랜잭션으로 묶인 모든 작업을 DB에 영구히 반영하는 작업롤백: 트랜잭션으로 묶인 모든 작업을 원상복구 하는 작업영속성 컨텍스트: 어플리케이션의 로직과 DB 사이에 있는 임시 메모리 또는 버퍼 공간개발자가 java코드로 CRUD 명령을 수행할 때 JPA는 이 영속성 컨텍스트를 거쳐 DB와 상호작용트랜잭션과 주기가 같다. 하나의 트랜잭션의 DB 작업들을 영속성 컨텍스트를 거쳐 좀 더 효율적으로 처리 영속성 컨텍스트 처리과정데이터 조회시 1차 캐시라는 곳에서 해당 데이터가 있는지 먼저 확인 -> 데이터 없으면 DB로 조회쿼리 날림1차 캐시(엔티티1)에 조회한 데이터를 저장 -> 엔티티1 상태를 스냅샷 저장그 후 또 같은 엔티티 조회시 DB까지 쿼리가 날라가지 않고 1차 캐시에 있는 데이터를 그대로 가져감(쿼리 날리는 과정이 생략)영속성 컨텍스트 더티체킹(변경감지)데이터 처음 조회 시 처음 상태를 스냅샷 저장 후 영속성 컨텍스트가 종료될 때 현재 있는 엔티티1와 엔티티1스냅샷을 비교두개가 다를 경우 업데이트가 있다고 JPA가 판단하고 알아서 쿼리를 작성해 DB에 전송함.영속성 컨텍스트 쓰기 지원한 트랜잭션 안에서 for문을 가지고 1~100까지 데이터를 insert할 때,for문 반복이 수행될 때마다 DB로 insert 쿼리를 전송하지 않고, 영속성 컨텍스트에 저장할 데이터를 넣어 뒀다가 트랜잭션 종료될 시점에 한번에 모든 쿼리를 날림때문에 JPA로 insert를 날린 시점과 log에서 insert, update 쿼리가 찍히는 시점이 차이가 날 수 있다.영속성 컨텍스트 플러시(Flush)개발자가 원하는 시점에 DB에 쿼리 전송 가능패키지 구조[패키지 구조]패키지: 연관된 자바파일들을 묶어주는 디렉토리(폴더)다른 개발자들이 프로젝트 구조를 이해하는데 도움이 된다다양한 방법론이 있다.presentation(방문자), admin(관리자), domain(프로젝트가 공통으로 가진 뼈대(코어)가 되는 기능들이 모인 패키지, DB 접근 기능 등)도메인 패키지가 베이스가 되고 그 위에 방문자, 관리자 패키지를 병렬로 쌍은 느낌의 구조 각 패키지의 하위 패키지 => 다양한 방법론 중 하나의 구조다.domain: 4개의 하위 패키지configuration(암호와 관련된 설정 클래스 들어갈 패키지),constant(도메인 패키지에 사용되는 상수들),entity(DB의 테이블에 대응하는 java객체들이 들어갈 패키지) ,repository(각 엔티티 별 DB에서 CRUD를 수행할 객체들)presentation:  5개의 하위 패키지controller,dto(화면에서 필요로 하는 데이터를 담는 클래스),interceptor(컨트롤러까지 요청이 들어가기 전에 그 모든 컨트롤러에 대해 공통 처리를 해주는 클래스, 인터셉터에서 각 화면을 조회할 때 그 요청에 대한 정보를 따로 DB에 저장하는 기능 개발),repository(DB와 직접 상호작용을 하지 않고 도메인의 레퍼지토리 패키지를 활용해서 프레젠테이션 레이어에서 필요로 하는 DB작업을 쉽게 할 수 있도록 중간에 랩핑 해주는 기능, 디자인패턴 중 Facade 패턴),=> 퍼사드(Facade) 패턴: 복잡한 시스템을 보다 쉽게 사용 가능하도록 단순화된 인터페이스를 제공. 시스템의 복잡성 숨기고, 사용자 친화정 인터페이스로 시스템 접근을 용이하게 함.service=> 도메인과 프레젠테이션 패키지를 결합한 것도 하나의 프로젝트로 볼 수 있다. 나중에 확장 될때, 도메인과 어드민을 또 따로 결합해 사용할 수 있도록 만들어진 구성 admin:advice(컨트롤러 어드바이스, 예외를 처리하는 클래스),context(데이터베이스에 만들 테이블들 별로 context 패키지 안에 하위 패키지들이 있다.각 테이블 별로 화면 조회부터 데이터 추가/수정/삭제 기능들을 분리해 넣을 예정ex) achievement(이 안에 각각 controller, service=view,form 하위 패키지가 또 있다), dashboard, experience, ...etc),=> 새로운 테이블이 추가되고 관리할 기능이 필요하다면, 컨텍스트 하위에다가 새로운 패키지를 만들어 기존 기능에 대해 영향이 없이 확장성있게 개발 가능하게 구성됨data(API 공통 응답 포맷, 입력 폼 정보, 테이블 정보 등을 담는 클래스),exception(어드민 레이어에서 따로 커스터마이징한 예외들 들어갈 패키지),interceptor(화면에서 보여지는 사이드바를 초기화 하는 클래스),security(로그인 관련된 기능들)테이블 설계(명세)메인페이지(=index page) 사용 테이블introduction 테이블 (소개글 등)created_date_time, updated_date_time 컬럼은 메타 데이터 타입으로 데이터의 생성과 수정을 추적하기 위한 목적이다.link 테이블 (링크 아이콘 등)name을 기반으로 이미지 Resume 페이지 사용 테이블experience(학력, 경력, 한줄 요약 등)experience_detail(어떤 경험에 대한 내용인지, 설명 등)=> experience와 experience_detail은 1:N으로 묶인 테이블achievement(수상, 자격증 등)skill(사용할 줄 아는 기술들)Projects 페이지 사용 테이블projectproject_detail(url 컬럼이 추가로 있다)project_skill(프로젝트와 skill을 맵핑해주는 테이블)=> 프로젝트와 스킬은 N:M의 관계기타http_interface(사용자가 페이지 조회를 한번 할 때마다 사용자의 브라우저에서 서버로 요청이 들어올 때 요청의 정보를 저장하는 테이블.조회수, 일자별 조회수 통계, ip주소를 통한 중복방문제거, 모바일/데스크탑 방문 통계 등이 가능하다)=> interceptor를 사용해 보기 위해 넣었다.개발 환경 구성윈도우 사용자는 이 강의를 스킵하고 강의자료로 진행Homebrew(Mac Only)JDK(zulu): 버전 17로IntelliJ IDEAH2 DatabaseDBeaverDocker

백엔드SpringBootKotlinWeb

suover

인프런 워밍업 클럽 스터디 2기 - 백엔드 프로젝트 2주차 발자국

입문자를 위한 Spring Boot with Kotlin - 나만의 포트폴리오 사이트 만들기강의와 함께한 인프런 워밍업 클럽 스터디 2기 - 백엔드 프로젝트 (Kotlin, Spring Boot) 2주차 발자국 입니다. 강의 수강이번 주에는 JPA를 활용해 데이터를 초기화하고, 기본적인 CRUD 작업을 진행하면서 프로젝트의 기초적인 부분을 다졌습니다. 강의를 통해 실습을 진행하며, 코드 작성과 실행 과정을 통해 JPA와 Spring Boot에 익숙해질 수 있었습니다. 특히, 강의를 통해 코드를 작성하는 과정에서 JPA의 기본 사용법을 익히는 데 집중했습니다.주요 학습 내용Spring Data JPA의 기본 CRUD 기능 실습엔티티 간의 기본적인 연관 관계 설정JPA와 데이터베이스 초기화를 통한 실습 환경 설정테스트 코드 작성과 JUnit을 활용한 기본적인 테스트 환경 설정 회고강의와 함께 실습 프로젝트를 진행하면서 JPA와 Spring Boot의 기능을 익혔습니다. 이론 학습보다는 코드 작성에 집중하여, Spring Boot와 JPA의 실질적인 사용법을 몸에 익히는 데 중점을 두었습니다. 실습을 통해 프로젝트 구조와 JPA의 기초 개념을 이해하는 데 도움을 받았습니다.칭찬할 점매일 강의를 듣고 실습 프로젝트를 꾸준히 진행한 점새로운 기술을 익히며 이를 프로젝트에 바로 적용해 본 점실습을 통해 테스트 코드 작성법을 익혀본 점 아쉬웠던 점 및 보완할 점강의를 따라가는 데 집중하다 보니 개념적인 이해가 부족한 부분이 있습니다.실습을 통해 얻은 질문이나 궁금증을 정리해두고, 추가적인 학습을 통해 보완할 계획입니다.미션이번 주 미션은 REST API 설계하기로, 사용자가 게시글과 댓글을 작성하고, 좋아요를 추가하거나 삭제할 수 있는 RESTful API를 설계하는 것이 목표였습니다. 이를 통해 사용자 관리, 게시글과 댓글 관리, 좋아요 기능을 포함한 API를 구축했습니다. 미션 과정API 설계사용자 API: 회원가입, 로그인, 로그아웃, 사용자 정보 조회 및 수정, 비활성화 기능을 설계했습니다.게시글 API: 게시글 작성 및 수정, 삭제 기능과 더불어 게시글 목록 조회 및 특정 게시글 조회를 위한 엔드포인트를 설계했습니다. 또한, 좋아요 추가 및 취소 기능도 포함하여 사용자의 상호작용을 풍부하게 했습니다.댓글 및 답글 API: 댓글 작성, 수정, 삭제 기능과 게시글별 댓글 목록을 조회할 수 있는 기능을 설계했습니다. 댓글에 좋아요를 추가하거나 삭제할 수 있는 기능도 포함하여 사용자가 게시물과 댓글에 대해 보다 활발히 상호작용할 수 있도록 했습니다.  구체적인 API 엔드포인트 및 메서드사용자 API: /api/users 및 /api/users/{userId}를 통해 사용자를 관리할 수 있도록 하였고, 로그인, 로그아웃, 비활성화 기능을 위한 별도의 엔드포인트를 설정했습니다.게시글 API: /api/posts 및 /api/posts/{postId}를 통해 게시글 CRUD와 좋아요 기능을 관리할 수 있도록 설계했습니다.댓글 및 답글 API: /api/posts/{postId}/comments 및 /api/comments/{commentId}로 댓글 CRUD와 좋아요 기능을 관리할 수 있도록 했습니다. 회고이번 API 설계는 RESTful한 접근 방식을 유지하며, 간결하고 직관적인 구조를 갖도록 신경을 썼습니다. 테이블 간의 관계를 고려하여 API를 설계하는 과정에서 실무에서의 데이터 흐름과 REST API의 설계 원칙을 이해하는 데 도움이 되었습니다.아쉬운 점 및 보완할 점아직 JPA 관련 용어와 개념이 생소해서, 이해도가 높아지기까지 시간이 걸렸습니다. 다음 주에는 API 설계를 더욱 구체화하고, 실습하면서 느낀 궁금한 점들을 정리해 해결해 나갈 계획입니다.이번 주는 강의를 통해 프로젝트를 진행하고 API 설계를 하는 경험을 통해 많은 것을 배울 수 있었고, 다음 주에도 꾸준히 학습하며 부족한 부분을 보완해 나가겠습니다.

백엔드인프런워밍업클럽스터디백엔드프로젝트발자국회고SpringBoot

suover

인프런 워밍업 클럽 스터디 2기 - 백엔드 프로젝트 1주차 발자국

입문자를 위한 Spring Boot with Kotlin - 나만의 포트폴리오 사이트 만들기강의와 함께한 인프런 워밍업 클럽 스터디 2기 - 백엔드 프로젝트 (Kotlin, Spring Boot) 1주차 발자국 입니다. 강의 수강이번 주에는 웹 개발의 기본 개념을 학습하며, Spring Boot를 중심으로 한 웹 개발에 대한 전반적인 내용을 다루었습니다. 특히, 웹 서비스의 구성 요소인 클라이언트, 서버, 데이터베이스의 상호작용 방식을 이해하고, 클라이언트와 서버가 데이터를 주고받는 과정에서 발생하는 HTTP 통신과 REST API의 개념을 명확히 정리했습니다.Spring Boot를 활용한 프로젝트 구조와 레이어드 아키텍처(Controller, Service, Repository)를 학습하며, 각 레이어의 역할과 책임에 대해 더욱 깊이 이해할 수 있었습니다. 또한, MVC 패턴과 디자인 패턴의 중요성도 학습하며, 어떻게 하면 코드의 결합도를 낮추고 유지보수성을 높일 수 있는지 고민하는 시간을 가졌습니다. 회고일주일간 강의를 들으면서 웹 개발의 전반적인 흐름과 기본 개념을 더 체계적으로 정리할 수 있었습니다. 이전에도 어느 정도 개념을 알고 있었지만, 이번에는 MVC 패턴이나 레이어드 아키텍처의 필요성에 대해 더 명확히 이해하게 되어 좋았습니다. 프레임워크와 라이브러리의 차이도 명확해져, 앞으로 어떤 상황에서 어떤 도구를 선택할지에 대한 기준이 생긴 것 같아 뿌듯합니다.칭찬할 점매일 꾸준히 강의를 들으며 웹 개발의 기본기를 다진 점학습한 내용을 직접 실습 프로젝트에 적용하면서 이론과 실제를 연결 지은 점이번 주 계획했던 학습 목표를 모두 달성한 점 아쉬웠던 점스프링의 의존성 주입(Dependency Injection)에 대한 이해가 조금 부족하다는 느낌이 있었습니다. 강의에서 개념을 배웠지만, 실제로 이를 프로젝트에 어떻게 적용해야 하는지 고민하는 시간이 좀 더 필요할 것 같습니다. 보완할 점의존성 주입을 주제로 더 깊이 파고들어 실습해보고, 관련된 자료를 찾아보면서 이해도를 높여야겠습니다. 특히, 생성자 주입 방식을 더 명확하게 습득하고 싶습니다. 다음 주 목표다음 주에는 이번 주에 학습한 내용을 바탕으로 더 구체적인 프로젝트를 진행하며, 의존성 주입과 스프링 빈 관리에 대한 이해를 심화할 계획입니다. 이를 통해 Spring Boot의 기능을 제대로 활용할 수 있도록 하겠습니다.미션이번 주 미션은 미니 프로젝트 개발을 주제로, 웹 개발의 기본적인 데이터베이스 테이블을 설계하고 이를 GitHub에 업로드하는 것이 목표였습니다. 프로젝트의 주제는 다양한 활동에서 함께할 사람을 찾고 소통할 수 있는 게시판 서비스로, 사용자, 카테고리, 게시글, 댓글, 그리고 좋아요 기능을 포함한 테이블을 설계했습니다. 미션 과정테이블 설계 사용자 테이블 (users): 사용자의 이메일, 비밀번호, 이름, 닉네임 등을 저장하며, user_role 필드로 사용자 권한을 설정할 수 있습니다.카테고리 테이블 (categories): 게시글의 카테고리를 관리하기 위해 설계된 테이블로, 카테고리 이름을 저장할 수 있습니다.게시글 테이블 (posts): 사용자가 작성한 게시글을 저장하는 테이블로, 사용자와 카테고리와의 관계를 참조하도록 했습니다.댓글 테이블 (comments): 게시글에 달린 댓글을 저장하는 테이블로, 답글까지 고려한 설계를 진행했습니다.좋아요 테이블 (post_likes, comment_likes): 게시글과 댓글에 대한 '좋아요' 기능을 구현하기 위한 테이블을 설계했습니다.  프로젝트 깃허브에 업로드미니 프로젝트를 깃허브에 올리는 작업을 진행했습니다. 이번 작업을 통해 버전 관리와 협업의 중요성을 다시 한번 확인할 수 있었습니다. 회고테이블 설계를 처음부터 끝까지 진행하면서, 데이터베이스 간의 관계를 설계하는 것이 얼마나 중요한지 다시 한번 깨달았습니다. 또한, 테이블 관계를 명확히 이해하지 못했던 부분에서 여러 번 수정을 거치면서 실무적인 감각도 더 키울 수 있었습니다. 특히, 외래 키 제약 조건을 걸어 테이블 간의 참조 무결성을 유지하는 방법을 다시 복습하게 된 것이 큰 수확이었습니다.아쉬운 점테이블 설계를 처음 진행할 때, 부모-자식 관계나 외래 키 제약 조건을 제대로 설정하지 못해 몇 번의 수정을 반복해야 했습니다. 이를 통해 초기 설계의 중요성을 다시 한번 느끼게 되었고, 더 많이 연습해야 한다는 생각이 들었습니다.보완할 점초기 설계 단계에서 더 철저하게 준비하여, 데이터베이스 구조를 명확하게 하고 오류를 최소화하는 방향으로 나아갈 계획입니다.이번 주는 강의와 미션 모두에서 많은 것을 배웠고, 실습을 통해 이론을 실제 프로젝트에 적용하면서 자신감을 얻었습니다. 다음 주에도 꾸준히 학습하며 부족한 부분을 보완해 나갈 계획입니다.

백엔드인프런워밍업클럽스터디백엔드프로젝트발자국회고SpringBoot

hmkim199

[워밍업 클럽 0기 BE] 3주차 발자국

학습 내용출처: 자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지] 3주차에서는 배포와 AWS, Git, Github, Spring Boot에 대해 알게 되었습니다. 익숙한 내용이 많아 미션을 하며 학습한 내용 위주로 작성하려 합니다.  미션https://github.com/hmkim199/spring-commute-app미니 프로젝트를 하며 강의와 실습, 과제를 통해 배웠던 것들을 스스로 적용해보고 고민해볼 수 있어서 유의미했습니다. @ManyToOne과 @OneToMany를 좀 더 이해하게 되었고 둘 다 써주기 보다는 무한 참조가 걸리는 것을 방지하기 위해 @ManyToOne 을 많이 쓴다는 것을 찾아보며 알게 되었습니다. 또한 fetch 방식은 LAZY로 설정해주어 불필요한 부하를 방지하는 방법도 알게 되었습니다. 현업에서는 왜래키를 잘 쓰지 않는데, 왜래키를 쓰지 않고 테이블간 연관 관계를 표시해주고 동작하도록 로직을 구현했습니다.LocalDate 타입을 Postman을 통해 Json으로 넘겨주는 것에서 오류가 있었는데, JsonFormat이라는 애노테이션을 이용해서 오류를 수정할 수 있었습니다. 다만 패턴에 따라 오류 발생 유무의 차이가 있어 이 부분은 차후 좀 더 알아보아야 할 것 같습니다.@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "Asia/Seoul") // 이렇게 오류 해결 @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyyMMdd", timezone = "Asia/Seoul") // 이렇게는 오류가 발생 회고 및 목표회사와 스터디를 병행하다 보니 어려운 점도 많았지만 피곤해도 강의를 들으며 헷갈렸던 개념들이 이해가 되기 시작하니 뿌듯하고 개운한 느낌이 들기도 했습니다. 설명을 너무 잘 해주셔서 도움이 많이 되었고 스터디가 끝나더라도 다시 강의를 들으면서 하나하나 따라해보고 과제나 미션들도 다시 해보면서 복습하고 싶습니다. 부족했던 부분을 스스로 보완하는 시간을 가지려고 합니다. 누군가에게 보여주기 위한 공부가 아닌 스스로 만족하기 위한 학습을 해나갈 수 있어 좋았습니다. 

백엔드SpringBootBackend

hmkim199

[워밍업 클럽 0기 BE] 2주차 발자국

학습 내용출처: 자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]섹션 4. 생애 최초 JPA 사용하기문자열 SQL을 직접 작성하는 것의 단점문자열을 작성하기 때문에 실수할 수 있고, 실수를 인지하는 시점이 느리다.특정 데이터베이스에 종속적이게 된다. 반복작업이 많아진다.데이터베이스의 테이블과 객체는 패러다임이 다르다. => 그래서 JPA가 등장! JPA란? Java Persistence API의 약자로 자바 진영의ORM(Object-Relational Mapping) 기술 표준을 의미.데이터를 영구적으로 보관하기 위해 Java 진영에서 정해진 규칙Spring Data JPA4. 영속성 컨텍스트테이블과 매핑된 Entity 객체를 관리/보관하는 역할을 수행스프링에서는 트랜잭션을 사용하면 영속성 컨텍스트가 생겨나고, 트랜잭션이 종료되면 영속성 컨텍스트가 종료변경 감지 (Dirty Check): 영속성 컨텍스트 안에서 불러와진 Entity는명시적으로 save 를 해주지 않더라도 알아서 변경을 감지하여 저장쓰기 지연 : 트랜잭션이 commit 되는 시점에 SQL을 모아서 한 번만 수행1차 캐싱 : ID를 기준으로 Entity를 기억하는 기능지연 로딩: 필요한 순간에 연결되어 있는 객체를 가져온다. (fetch 옵션 LAZY, EAGER 중 @OneToMany는 LAZY가 기본)섹션 5. 책 요구사항 구현하기상대 테이블을 가리키는 테이블이 연관관계의 주인이다. 연관관계의 주인이 아닌 객체는 mappedBy 를 통해 주인에게 매여 있음을 표시해 주어야 한다.양쪽 모두 연관관계를 갖고 있을 때는 양쪽 모두 한 번에 맺어주는 게 좋다.cascade 옵션을 활용하면, 저장이나 삭제를 할 때 연관관계에 놓인 테이블까지 함께 저장 또는 삭제가 이루어진다.orphanRemoval 옵션을 활용하면, 연관관계가 끊어진 데이터를 자동으로 제거해 준다. 미션6일차7일차의존성 주입을 어디에서 어떻게 하는지를 강의를 들으며, 미션을 하면서 자연스럽게 익히게 되어서 좋았습니다. sql을 직접 사용하는 것과 JPA를 통한 변화가 어떤 차이가 있는지 코드 상으로 확인할 수 있어서 기존에 헷갈리고 불명확한 부분들이 또렷이 보여서 좋았습니다. 회고 및 목표이전에는 @OneToMany와 같은 애노테이션을 어디에 사용하는지, 왜 사용하는 지를 모르고 그냥 동작하게 만들기 위해서 썼는데 2주차 강의를 수강하면서 각 테이블끼리 객체지향적으로 연결시키면서 자연스럽게 이해할 수 있었던 것 같아서 의미있고 재밌게 느껴졌습니다. 영속성 컨텍스트도 어렵게 생각하다가 강의를 들으니 이해가 잘 되어서 좋았고 전반적인 스프링 부트 기술들에 대해 필요성과 관계성 등을 알게 되어 좋았습니다.3주차에는 미니 프로젝트를 하면서 배웠던 것들을 스스로 적용해보고 다시 복습하는 시간을 가지고 싶습니다.

백엔드SpringBootJAVABackend

hmkim199

[워밍업 클럽 0기 BE] 1주차 발자국

학습 내용출처: 자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지] 섹션 1. 생애 최초 API 만들기1. (웹을 통한) 컴퓨터 간의 통신은 HTTP 라는 표준화된 방식이 있다.2. HTTP 요청은 HTTP Method (GET, POST)와 Path(/portion)가 핵심이다.3. 요청에서 데이터를 전달하기 위한 2가지 방법은 쿼리와 바디이다.4. HTTP 응답은 상태 코드가 핵심이다.5. 클라이언트와 서버는 HTTP를 주고 받으며 기능을 동작하는데 이때 정해진 규칙을 API라고 한다.@RestController주어진 Class를 Controller로 등록한다. (Controller : API의 진입 지점)@GetMapping("/add")아래 함수를 HTTP Method가 GET이고 HTTP path가 /add인 API로 지정한다.@RequestParam주어지는 쿼리를 함수 파라미터에 넣는다. @PostMapping(”/multiply”)아래 함수를 HTTP Method가 POST이고 Path가 /multiply인 API로 지정한다.@RequestBodyHTTP Body로 들어오는 JSON을 요청 DTO 로 바꿔준다!HTTP body를 객체로 바꾸는 @RequestBody 를 사용하는 경우는, 생성자를 만들지않아도 괜찮다. API의 응답 결과를 JSON으로 반환하는 방법: Controller에서 그냥 객체를 반환하면, JSON으로 응답되며 객체에는 getter가 있어야 함-> getter 없는 객체 반환하면? 에러났음. (Resolved [org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation]) 섹션 2. 생애 최초 Database 조작하기CPU : 연산담당RAM : 메모리, 단기기억DISK : 장기기록우리가 서버를 실행시켜 API를 동작시키기까지 이 3가지 장치는 다음과 같은 역할을 수행1. 우리가 개발하고 있는 스프링 부트 서버는 DISK에 파일로 잠들어 있다.2. 서버를 실행시키면 DISK에 있는 코드 정보가 RAM으로 복사된다.3. API가 실행되면 ‘연산'이 수행되며, CPU와 RAM을 왔다 갔다 한다.4. 즉 POST API를 통해 생긴 유저 정보는 RAM에 쓰여 있다.5. 만약 서버가 종료되면 RAM에 있는 모든 정보가 사라진다.6. 때문에 다시 서버를 시작하면, 유저 정보가 없는 것이다!-> 서버에서는 Java에 있는 File 클래스를 사용해 직접 DISK에 접근할 수도 있지만, 이럴 때 바로 Database를 사용할 수 있다. 섹션 3. 역할의 분리와 스프링 컨테이너함수 하나로 모든 기능을 구현한다면?1. 그 함수를 동시에 여러 명이 수정할 수 없다.2. 그 함수를 읽고, 이해하는 것이 너무 어렵다.3. 그 함수의 어느 부분을 수정하더라도 함수 전체에 영향을 미칠 수 있기 때문에 함부로건들 수 없게 된다.4. 너무 큰 기능이기 때문에 테스트도 힘들다.5. 종합적으로 유지 보수성이 매우 떨어진다. 기존 컨트롤러에서 모든 기능을 구현했을 때의 컨트롤러 역할1. API의 진입 지점으로써 HTTP Body를 객체로 변환하고 있다.2. 현재 유저가 있는지, 없는지 등을 확인하고 예외 처리를 해준다.3. SQL을 사용해 실제 DB와의 통신을 담당한다.-> 각각을 Controller, Service, Repository로 분리! 미션1일차2일차3일차4일차 1주차 미션을 수행하면서는 학습한 것을 스스로 적용할 수 있도록 하는 것에 가장 집중하였습니다. 2일차 과제를 해결하며 Json 배열 받는 방법(List를 요청 DTO에서 필드로 갖기), Getter나 생성자가 없는 경우 발생하는 에러 등을 겪으면서 새로운 문제를 마주하고 해결하는 방법을 알게 되었습니다. 회고 및 목표회사와 함께 스터디를 병행하니 4-5일차 과제는 수행하지 못해서 아쉬웠습니다. 하지만 2주차 학습을 진행하며 틈틈이 1주차에 미흡했던 부분을 보완하고자 합니다.4일차 추가: 2024-02-26 

백엔드SpringBootJAVABackend

[워밍업 스터디 클럽 0기 BE] 1강 정리 및 공부

✏ ️강의 링크 - https://inf.run/XKQg 스프링 프로젝트 설정 시작 및 실행서버란 ? 네트워크와 HTTP, API란 ? JSON ? 서버 개발에 필요한 다양한 개념 이해스프링 부트를 이용해 GET API, POST API 만들기 스프링 프로젝트 설정 시작 및 실행: https://start.spring.io- Project : 빌드 툴 Gradle, Maven- Language : 사용하는 언어- Spring Boot : 버전- Packaging : Jar, War ( 부트는 톰캣 - Jar)- Java : 의 버전! Dependencies ( 의존성 설정 )라이브러리란 ?: 프로그래밍을 개발할 때 미리 만들어져 있는 기능을 가져다 사용하는 것프레임워크란 ?: 프로그래밍을 개발할 때 미리 만들어져 있는 구조에 코드를 가져다 끼워 넣는 것서버(Server)란 ?: 어떠한 기능을 제공하는 프로그램, 실행시키고 있는 컴퓨터 ( ex: 회원가입 기능, 추천 기능 )but 기능 제공을 위해서는 누군가의 요청이 필요, 서버 요청은 인터넷을 통해 함네트워크란 ?IP, portIP를 외우기 어려운 숫자 대신 Domain Name 등장Domain Name System (DNS)- IP 244.66.51.9 = 도메인 이름 spring.com HTTP, API란?: HTTP (HyperText Transfer Protocol)Protocol : 표준, 약속지켜야할 규칙GET /portion?color=red/portion : Path? : QueryHTTP Method ( 요청을 받는 컴퓨터에게 요청하는 행위 )GET 제공 Query(ket & value)POST 저장 BodyPUT 수정 BodyDELETE 삭제 QueryHost: spring.com:3000 URL (Uniform Resource Locator): 주소창 http://spring.com:3000/portion?color=red  Post에는 @RequestBody를 사용 Response 부분에서 User user 적용하면users.get 으로 간편하게 users.get으로 선언가능 따로 name과 age 각각 쓰지않아도됨

워밍업JAVASpringBoot

Groot

워밍업 클럽 - 백엔드 2일차 과제

문제 1 - add, minus, plus API쿼리로 num1과 num2의 값을 받은 후 합, 차, 곱을 구해서 Response 객체로 반환했다. @GetMapping("/api/v1/calc") public CalculatorCalcResponse calcTwoNumbers(CalculatorCalcRequest request) { CalculatorCalcResponse response = new CalculatorCalcResponse(); response.setAdd(request.getNum1() + request.getNum2()); response.setMinus(request.getNum1() - request.getNum2()); response.setMultiply(request.getNum1() * request.getNum2()); return response; }결과의 Response 객체는 다음과 같이 구현했다.package com.warmup.libraryapp.dto.calculator.response; public class CalculatorCalcResponse { private int add; private int minus; private int multiply; public int getAdd() { return add; } public void setAdd(int add) { this.add = add; } public int getMinus() { return minus; } public void setMinus(int minus) { this.minus = minus; } public int getMultiply() { return multiply; } public void setMultiply(int multiply) { this.multiply = multiply; } }결과는 다음과 같다.문제 2 - 날짜 to 요일 API컨트롤러에서 LocalDate를 이용하여 요일을 반환하도록 구현하였다.@RestController public class DateController { @GetMapping("/api/v1/day-of-the-week") public DayOfWeekResponse getDayOfWeek(@RequestParam String date) { LocalDate localDate = LocalDate.parse(date, DateTimeFormatter.ISO_DATE); String dayOfWeek = localDate.getDayOfWeek().getDisplayName(TextStyle.SHORT, Locale.US).toUpperCase(); DayOfWeekResponse response = new DayOfWeekResponse(); response.setDayOfTheWeek(dayOfWeek); return response; } }결과의 Response 객체는 다음과 같이 구현했다.public class DayOfWeekResponse { private String dayOfTheWeek; public String getDayOfTheWeek() { return dayOfTheWeek; } public void setDayOfTheWeek(String dayOfTheWeek) { this.dayOfTheWeek = dayOfTheWeek; } }결과는 다음과 같다.문제 3 - Sum API구현 방법POST http://localhost:8080/api/v1/sums 로 json 오브젝트에 담긴 numbers 배열을 바디로 가져오도록 DTO를 만든다. package com.warmup.libraryapp.dto.calculator.request; import java.util.List; public class CalculatorSumRequest { private List<Integer> numbers; public List<Integer> getNumbers() { return numbers; } public void setNumbers(List<Integer> numbers) { this.numbers = numbers; } }그리고 Stream API를 이용해서 sum을 반환하도록 했다.@PostMapping("/api/v1/sums") public int sumNumbers(@RequestBody CalculatorSumRequest request) { return request.getNumbers().stream().mapToInt(Integer::intValue).sum(); }결과는 다음과 같다.

백엔드SpringBoot

채널톡 아이콘