diff --git a/week1/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/Controller/HelloController.java b/week1/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/Controller/HelloController.java new file mode 100644 index 0000000..69acb4a --- /dev/null +++ b/week1/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/Controller/HelloController.java @@ -0,0 +1,52 @@ +package likelion.spring_prac; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +public class HelloController { + + // 정적 + @GetMapping("hello") + public String hello(Model model) { + model.addAttribute("data", "hello!!"); + return "hello"; + } + + // MVC (파라미터로 값 받기) + @GetMapping("hello-mvc") + public String helloMvc(@RequestParam(value = "name") String name, Model model) { + model.addAttribute("name", name); + return "hello-template"; + } + + // API 단순 예 + @GetMapping("hello-string") + @ResponseBody // HTTP의 바디 부분에 data를 직접 넣겠다 + public String helloString(@RequestParam("name") String name) { + return "hello " + name; + } + + // API 사용 예 + @GetMapping("hello-api") + @ResponseBody + public Hello helloApi(@RequestParam("name") String name) { + Hello hello = new Hello(); // 객체 생성 + hello.setName(name); + return hello; + } + + static class Hello { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git "a/week2/taekyeongkim/01. \354\204\234\353\271\204\354\212\244 \352\260\234\353\260\234 & \355\205\214\354\212\244\355\212\270.md" "b/week2/taekyeongkim/01. \354\204\234\353\271\204\354\212\244 \352\260\234\353\260\234 & \355\205\214\354\212\244\355\212\270.md" new file mode 100644 index 0000000..39aebda --- /dev/null +++ "b/week2/taekyeongkim/01. \354\204\234\353\271\204\354\212\244 \352\260\234\353\260\234 & \355\205\214\354\212\244\355\212\270.md" @@ -0,0 +1,541 @@ +# 1. 비즈니스 요구사항 정리 + +### 일반적인 웹 어플리케이션 계층 구조 + ![struct](./img/struct.png) + - **컨트롤러** : 웹 MVC의 컨트롤러 역할 + - **서비스** : 핵심 비즈니스 로직 구현 + - **도메인** : 비즈니스 도메인 객체 - 주로 데이터베이스에 저장하고 관리 됨 + - **리포지토리** : 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리 + +### 간단한 비즈니스 예제 + - 데이터 : 회원ID, 이름 + - 기능 : 회원 등록, 조회 + - 아직 데이터 저장소가 선정되지 않음 (가상의 시나리오) + + - 클래스 의존 관계 + + : 아직 데이터 저장소가 선정 되지 않아서 일단은 메모리 기반 데이터 저장소 사용 + (메모리에 저장할 수 있도록!) + + → 나중에 데이터 저장소를 넣을 것이기 때문에 interface 필요 + + ⇒ 인터페이스로 구현 클래스를 변경할 수 있도록 설계 + ![class_relations](./img/class_relations.png) + + +# 2. 회원 도메인과 레포지토리 생성 + +### domain + : 프로젝트에 domain 패키지 추가 + + * Member class + : domain에 Member 클래스 추가 + + - 속성 추가 + ```java + package likelion.spring_prac.domain; + + public class Member { + + private Long id; + private String name; + } + ``` + + - Getter, Setter 추가 + + +### repository +: 프로젝트에 repository 패키지 추가 + + * MemberRepository + : Interface로 생성 + ```java + package likelion.spring_prac.repository; + + import likelion.spring_prac.domain.Member; + + import java.util.List; + import java.util.Optional; + + public interface MemberRepository { + Member save(Member member); + // Optional : Null인 경우를 대비 + Optional findById(Long id); + Optional findByName(String name); + List findAll(); // 저장된 모든 멤버 리스트 반환 + } + ``` + + * MemoryMemberRepository + : Class로 생성 + + - implement methods (Alt + Enter) + ![implement methods](./img/implement%20methods.png) + -> 뜨는 옵션 전부 선택하여 OK + + ```java + package likelion.spring_prac.repository; + + import likelion.spring_prac.domain.Member; + + import java.util.*; + + public class MemoryMemberRepository implements MemberRepository{ + + // 저장할 곳이 필요 + private static Map store = new HashMap<>(); + private static long sequence = 0L; // 키 값 생성 + + @Override + public Member save(Member member) { + member.setId(++sequence); + store.put(member.getId(), member); + return member; + } + + @Override + public Optional findById(Long id) { + // 단순히 store.get(id) 를 반환하지 않고 Optional로 감싸서 반환 + // -> Null인 경우에 클라이언트 측에서 조치 가능 + return Optional.ofNullable(store.get(id)); + } + + @Override + public Optional findByName(String name) { + return store.values().stream() + .filter(member -> member.getName().equals(name)) + .findAny(); + } + + @Override + public List findAll() { + // store의 member들 반환 + return new ArrayList<>(store.values()); + } + } + ``` + +# 3. 테스트 케이스 작성 +: 개발한 기능을 실행해서 테스트 할 때 자바의 main 메서드를 통해 실행하거나, 웹 어플리케이션의 컨트롤러를 통해서 해당 기능을 실행하면 준비하고 실행하는데 오래 걸리고, 반복 실행하기 어렵고, 여러 테스트를 한 번에 실행하기 어려움 + +→ JAVA : **JUnit** 이라는 프레임워크로 테스트 실행 + +* src/test/java/프로젝트 하위에 repository 생성 +* 생성한 repository 하위에 테스트하려는 Test 생성 + +### MemoryMemberRepositoryTest +: MemoryMemberRepositoryTest 클래스 생성 + ```java + package likelion.spring_prac.repository; + + import org.junit.jupiter.api.Test; + + // public 일 필요 없어서 public 없앰 + class MemoryMemberRepositoryTest { + + MemberRepository repository = new MemoryMemberRepository(); + + } + ``` + + * save() 기능 확인 + ```java + ... + class MemoryMemberRepositoryTest { + + MemberRepository repository = new MemoryMemberRepository(); + + @Test + public void save() { + + } + } + ``` + -> save 함수 실행(run) 해보면서 확인 + + * test 객체 생성 및 확인 + ```java + @Test + public void save() { + // 새로운 객체 생성, 이름 설정 + Member member = new Member(); + member.setName("spring"); + + repository.save(member); + + // optional -> get으로 꺼내와서 result라는 Member 객체에 받아 봄 + Member result = repository.findById(member.getId()).get(); + } + ``` +### 결과 확인 +1. 직접 출력 + + : 터미널에 직접 글자가 찍히지만 잘 사용하지 않음 + ```java + @Test + public void save() { + Member member = new Member(); + member.setName("spring"); + repository.save(member); + Member result = repository.findById(member.getId()).get(); + + System.out.println("result = " + (result == member)); + } + ``` + +2. Assertions + + : 직접 출력은 아니지만 성공 시 → Process finished with exit code 0 (초록 체크), 실패 시 → error 발생 + + * Assertions.assertEquals + ```java + @Test + public void save() { + Member member = new Member(); + member.setName("spring"); + repository.save(member); + Member result = repository.findById(member.getId()).get(); + + Assertions.assertEquals(member, result); + } + ``` + +) 강제로 error 발생 시키는 코드 : `(Assertions.assertEquals(member, actual:null);` + + * Assertions.assertThat + ```java + @Test + public void save() { + Member member = new Member(); + member.setName("spring"); + repository.save(member); + Member result = repository.findById(member.getId()).get(); + + Assertions.assertThat(member).isEqualTo(result); + } + ``` + +) static으로 사용 : static import 추가(Alt + Enter) + + → import로 import static org.assertj.core.api.Assertions.assertThat;가 추가 되고, 형태가 단순하게 (assertThat) 변함 + + => assertThat만 쳐서 사용 가능! + + +) 강제로 error 발생 시키는 코드 : `assertThat(member).isEqualTo(null);` + + +) 실무에서는 build 툴과 엮어서, testcase를 통과하지 못하면 넘어가지 않도록 설정 + +### 다른 기능 동작 test +* findByName() + ```java + @Test + public void findByName() { + Member member1 = new Member(); + member1.setName("spring1"); + repository.save(member1); + + Member member2 = new Member(); + member2.setName("spring2"); + repository.save(member2); + + Member result = repository.findByName("spring1").get(); + + assertThat(result).isEqualTo(member1); + } + ``` + +* findByAll() + ```java + @Test + public void findAll() { + Member member1 = new Member(); + member1.setName("spring1"); + repository.save(member1); + + Member member2 = new Member(); + member2.setName("spring2"); + repository.save(member2); + + List result = repository.findAll(); + + assertThat(result.size()).isEqualTo(2); + } + ``` + +### AfterEach, clearStore + * 전체 실행 : error 발생 + - 이유 : 순서 보장 x (test에 여러 함수가 있을 때 실행 순서 보장 x) + -> 처음 실행하며 저장 된 객체가 다른 함수의 test에 쓰이면서 error 발생 + => 각 test가 끝날 때마다 객체를 지워 repository를 깨끗하게 유지해야함 + + - 해결 : test 서로 의존 관계 없이 설계 + -> 하나의 test 가 끝날 때마다 저장소나 공용 data들을 깔끔하게 지워주기 + + * MemoryMemberRepositoryTest 시작부 수정 + + : MemoryMemberRepository 만 test 하는 것이라서 interface 말고 repository로 변경 + - 기존 : `MemberRepository repository = new MemoryMemberRepository();` + - 수정 : `MemoryMemberRepository repository = new MemoryMemberRepository();` + + * main의 MemoryMemberRepository에 clearStore() 추가 + ```java + public void clearStore() { + store.clear(); + } + ``` + + * AfterEach 추가 + + : test 가 실행 되고 끝날 때마다 repository를 한 번씩 지우도록 + ```java + @AfterEach + public void afterEach() { + repository.clearStore(); + } + ``` + ![AfterEach](./img/AfterEach.png) + +### test 방식 +: 우리는 구현 클래스를 먼저 생성 -> test 작성 + +* cf. TDD (테스트 주도 개발) + : test를 먼저 작성하고 main을 작성 + +# 4. 회원 서비스 개발 & 테스트 +* 회원 서비스 Class : 회원 레포지토리와 도메인을 활용하여 실제 비즈니스 로직 작성 + +### Service +: src/main/java/프로젝트/service 패키지 생성 + + * service에 MemberService 클래스 추가 + * repository 생성 + ```java + package likelion.spring_prac.service; + + import likelion.spring_prac.domain.Member; + import likelion.spring_prac.repository.MemberRepository; + import likelion.spring_prac.repository.MemoryMemberRepository; + + public class MemberService { + + // repository 생성 + private final MemberRepository memberRepository = new MemoryMemberRepository(); + } + ``` + * 회원 가입(join) + ``` + ... + public class MemberService { + private final MemberRepository memberRepository = new MemoryMemberRepository(); + + /** + * 회원 가입 + */ + public Long join(Member member) { + + } + } + ``` + +) **Optional** 형태 + + : `.get()` 으로 바로 꺼낼 수 있지만 권장하지는 않고, 위의 `.**ifPresent**()` 와 같은 메서드들을 사용하여 활용 + + : get 대신 `.orElseGet()` 을 더 사용 - 값이 있으면 꺼내고, 없으면 method 실행 or default 값 넣어서 꺼내기 등의 동작 + + - Optional 반환 명시해서 사용 + ```java + public Long join(Member member) { + // 로직 ex : 같은 이름의 중복 회원 불가 + // memberRepository.findByName(member.getName()); + Optional result = memberRepository.findByName(member.getName()); + result.ifPresent(m -> { + throw new IllegalStateException("이미 존재하는 회원입니다."); + }); + + memberRepository.save(member); + return member.getId(); + } + ``` + + - Optional 명시 사용 x + ```java + public Long join(Member member) { + // 로직 ex : 같은 이름의 중복 회원 불가 + // Optional -> ifPresent로 로직 구현 + memberRepository.findByName(member.getName()) + .ifPresent(m -> { + throw new IllegalStateException("이미 존재하는 회원입니다."); + }); + + memberRepository.save(member); + return member.getId(); + } + ``` + + - method로 추출 + + : 하나의 기능 -> method로 추출하여 사용하기 + (Ctrl + Alt + Shift + T) + ![ExtractMethod](./img/ExtractMethod.png) + + * 회원 조회(find~) + - 전체 회원 조회 + ```java + public List findMembers() { + return memberRepository.findAll(); + } + ``` + + - 한 회원 조회 + ```java + public Optional findOne(Long memberId) { + return memberRepository.findById(memberId); + } + ``` + + +### Test + * Test 간편하게 작성 + : Test를 만들고자하는 class에서 Ctrl + Shift + T + -> Create New Test + ![CreateNewTest](./img/CreateNewTest.png) + + * service 정의 + ```java + class MemberServiceTest { + + MemberService memberService = new MemberService(); + } + ``` + + * 함수 정의 + - given - when - then 패턴 + + : 어떤 데이터를 기반으로 함 - 어떤 것을 검증 - 검증부 + + - 회원가입 + ```java + @Test + void 회원가입() { // test code는 함수 이름이 심지어 한글이어도 괜찮음 + //given + Member member = new Member(); + member.setName("hello"); + + //when + Long saveId = memberService.join(member); + + //then + Member findMember = memberService.findOne(saveId).get(); + assertThat(member.getName()).isEqualTo(findMember.getName()); + + } + ``` + : 정상 flow test -> 예외 flow test가 더 중요하므로 중복 회원 예외 처리 필요 + + - 중복 회원 예외 + ```java + @Test + public void 중복_회원_예외() { + // given + Member member1 = new Member(); + member1.setName("spring"); + + Member member2 = new Member(); + member2.setName("spring"); + + //when + memberService.join(member1); + + try { + memberService.join(member2); + fail(); + } catch (IllegalStateException e) { + // 예외가 터지는 상황에서 예외가 터짐 = 정상적 동작 + assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.") + } + + //then + } + ``` + `assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.")` + : 실제 main 함수의 예외 처리에서 “이미 존재하는 회원입니다.”가 나오도록 되어 있어서 그 text와 일치하는지 check + + -> 코드 개선 - assertThrows + ```java + @Test + public void 중복_회원_예외() { + // given + Member member1 = new Member(); + member1.setName("spring"); + Member member2 = new Member(); + member2.setName("spring"); + + //when + memberService.join(member1); + assertThrows(IllegalStateException.class, () -> memberService.join(member2)); + //then + } + ``` + +) assertThrows는 메세지가 반환이 되므로 다음과 같이 확인 가능하다 + ``` + memberService.join(member1); + assertThrows(IllegalStateException.class, () -> memberService.join(member2)); + assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.") + ``` + + - 매 테스트마다 clear + : join을 반복하며 data가 쌓여 test가 제대로 되지 않으므로 clear 필요 + ```java + class MemberServiceTest { + + MemberService memberService = new MemberService(); + MemoryMemberRepository memoryMemberRepository = new MemoryMemberRepository(); + + @AfterEach + public void afterEach() { + memoryMemberRepository.clearStore(); + } + ... + } + ``` + + -> 그런데, MemberService에 이미 레포지터리 객체 생성이 존재함. + = 서로 다른 레포지터리 (서로 다른 인스턴스) + + (현재는 store 객체가 static으로 되어 있어 문제가 생기지 않지만, static이 아니면 다른 DB가 되며 문제 발생 (?)) + (+ 문제가 생기지 않더라도 같은 인스턴스로 구성하는 것이 맞음) + + => 객체 선언 수정 (DI) + + * DI (Dependenacy Injection) + + : 직접 new로 생성하지 않고, 외부에서 넣어줌 + + - main의 MemberService 수정 + ```java + ... + public class MemberService { + // repository 생성 + // 기존 : private final MemberRepository memberRepository = new MemoryMemberRepository(); + private final MemberRepository memberRepository; + // repository 를 직접 생성하는 것이 아니라 외부에서 넣어주도록 바꿈 + public MemberService(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + ... + } + ``` + + - test의 MemberServiceTest 수정 + ```java + MemberService memberService; + MemoryMemberRepository memberRepository; + + // 각 테스트를 실행하기 전에 만들고 넣어줌 + @BeforeEach + public void beforeEach() { + memberRepository = new MemoryMemberRepository(); + memberService = new MemberService(memberRepository); + } + ``` + + => 같은 MemoryMemberRepository 가 사용 됨! \ No newline at end of file diff --git "a/week2/taekyeongkim/02. Spring bin, \354\235\230\354\241\264\352\264\200\352\263\204.md" "b/week2/taekyeongkim/02. Spring bin, \354\235\230\354\241\264\352\264\200\352\263\204.md" new file mode 100644 index 0000000..f83c664 --- /dev/null +++ "b/week2/taekyeongkim/02. Spring bin, \354\235\230\354\241\264\352\264\200\352\263\204.md" @@ -0,0 +1,277 @@ +# 스프링 빈 + +### 정형화된 패턴 +: **컨트롤러**(외부 요청 받기), **서비스**(비즈니스 로직), **리포지토리**(데이터 저장) + +### 의존관계 + +- controller가 service에 의존 (의존관계가 있다) + + : controller가 service를 통해 로직을 거쳐 data가 저장 되고, data 조회를 할 수 있음 + + ![SpringContainer](./img/SpringContainer.png) + : memberService 와 memberRepository 가 스프링 컨테이너에 스프링 빈으로 등록 됨 + + : memberService는 memberRepository가 필요함 + +### 스프링 빈 등록 + : 스프링은 스프링 컨테이너에 스프링 빈을 등록할 때 기본적으로 싱글톤으로 등록 (유일하게 하나만 등록하여 공유) + + ⇒ 같은 스프링 빈이면 모두 같은 인스턴스 + + +) helloControler는 스프링이 제공하는 컨트롤러여서 스프링 빈으로 자동 등록 됨 + + * 스프링 빈 등록 방법 + 1. **컴포넌트 스캔과 자동 의존관계 설정** + - 컴포넌트 스캔 : @Controller, @Service, .. 등 명시 + - 자동 의존관계 : @Autowired + + +) @Autowired 는 스프링이 관리하는 객체에서만 동작 + + : 스프링 빈으로 등록하지 않고 내가 직접 생성한 객체에서는 동작 x + + + 2. **자바 코드로 직접 스프링 빈 등록** + + : SpringConfig 파일에 빈에 등록할 것들 정의 + + + → 정형화된 컨트롤러, 서비스, 리포지토리 같은 코드는 컴포넌트 스캔 사용 + + → 정형화되지 않거나, 상황에 따라 구현 클래스를 변경해야 하면 설정을 통해 빈 등록 + + ex) DB 대신 MemoryMemberRepository를 사용하다가 나중에 DB로 리포지토리를 + 바꿀 것인데, 이런 경우 컨피그 파일을 변경하면 되므로 간편 + + +- DI 주입 방법 + + : 생성자 주입 사용 권장 - 의존관계가 실행중에 동적으로 변하지 않으므로 + + - **생성자 주입** + + : 어플리케이션이 조립 되는 시점에 한 번 들어오고 끝 + + ```java + @Controller + public class MemberController { + + private final MemberService memberService; + + // 생성자 + @Autowired + public MemberController(MemberService memberService) { + this.memberService = memberService; + } + } + ``` + + - 필드 주입 + + ```java + @Controller + public class MemberController { + + @Autowired private final MemberService memberService; + } + ``` + + - Setter 주입 + + +) Generate : Alt + Insert → Getter and Setter 생성 + + ```java + @Controller + public class MemberController { + + private MemberService memberService; + + @Autowired + public void setmemberService(MemberService memberService) { + this.memberService = memberService; + } + } + ``` + + +# 1. 컴포넌트 스캔과 자동 의존관계 설정 +: 회원 컨트롤러가 회원서비스와 회원 리포지토리를 사용할 수 있도록 의존관계 준비 + +* 컴포넌트 스캔과 자동 의존관계 설정 + - 컴포넌트 스캔 : @Controller, @Service, .. 등 명시 + - 자동 의존관계 : @Autowired + +### Controller +: MemberController 생성 + * @Controller 어노테이션 + : 어노테이션이 붙은 class의 객체를 생성하여 spring 컨테이너에 넣어두고, sprig이 관리 = 컨테이너에서 Spring bin이 관리 됨 + ![MmberController](./img/MemberController.png) + -> MemberController 객체를 생성하여 관리 + + +### 객체 공용으로 선언 : 컨테이너에 등록 +**`private final** MemberService memberService = **new** MemberService();` + +위처럼 new로 선언할 수 있지만, +컨테이너에 등록을 하고 + 컨테이너에서 받아서 쓰도록 바꿔야 함 + +(다른 controller에서 MemberService를 가져다 쓰는 경우도 존재하므로) + +* 생성자 위에 **@Autowired** 어노테이션 + + : 스프링 컨테이너에 있는 객체 가져와서 연결 시켜 줌 + (스프링이 연관된 객체를 스프링 컨테이너에서 찾아서 넣어줌) + + = **DI** (Dependency Injection) : 의존성 주입 + : 객체 의존 관계를 외부에서 넣어주는 것 + + ```java + @Controller + public class MemberController { + + private final MemberService memberService; + + // 생성자 + @Autowired + public MemberController(MemberService memberService) { + this.memberService = memberService; + } + } + ``` + + → 이것만으로는 오류 발생 : ‘memberService가 스프링 빈으로 등록되어 있지 x’ + + (MemberService class가 순수 java 코드라 스프링이 알 수 없음) + + ⇒ **@Service** 어노테이션 추가 + + : 스프링이 서비스를 인지 → 컨테이너에 등록 해 둠 + ```java + @Service + public class MemberService{ + ... + } + ``` + + +) repository에는 @Repository 추가 + ```java + @Repository + public class MemoryMemberRepository{ + ... + } + ``` + + = 정형화된 패턴 + : 컨트롤러(외부 요청 받기), 서비스(비즈니스 로직), 리포지토리(데이터 저장) + + +- MemberService + + ```java + @Service + public class MemberService { + + private final MemberRepository memberRepository; + + **@Autowired** + public MemberService(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + ... + } + ``` + + : Service를 스프링 컨테이너에 등록하면서, MemberService 생성자 호출 + + → @Autowired 가 존재 ⇒ repository가 필요하구나 → 스프링 컨테이너에 있는 memberRepository 넣어 줌 + + +# 2. 자바 코드로 직접 스프링 빈 등록 +: @Service, @Repository, @Autowired 어노테이션 제거하고 진행 + +: 직접 설정 파일에 정의 + +### SpringConfig +: 프로젝트에 새로운 Class인 SpringConfig 생성 + +- **@Configuration** + + ```java + package likelion.spring_prac; + + import likelion.spring_prac.service.MemberService; + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + + @Configuration + public class SpringConfig { + + } + ``` + +* **@Bean** + + : 스프링 빈에 등록하라는 표시 + + ```java + package likelion.spring_prac; + + import likelion.spring_prac.service.MemberService; + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + + @Configuration + public class SpringConfig { + + @Bean + public MemberService memberService() { + return new MemberService(); + } + } + ``` + + → Ctrl + P 로 생성자에 넣어야 할 값 확인 + + ![CtrlP](./img/CtrlP.png) + + ⇒ MemberRepository 등록 필요 + + ```java + ... + @Configuration + public class SpringConfig { + + @Bean + public MemberService memberService() { + return new MemberService(memberRepository()); + } + + @Bean + public MemberRepository memberRepository() { + return new MemoryMemberRepository(); + } + } + ``` + + → 다음 그림의 구조가 완성 됨 + ![SpringContainer](./img/SpringContainer.png) + + : memberService 와 memberRepository 가 스프링 컨테이너에 스프링 빈으로 등록 됨 + : memberService는 memberRepository가 필요함 + + ++) Controller는 어쩔 수 없음 - **@Autowired** 사용 + +```java +@Controller +public class MemberController { + + private final MemberService memberService; + + // 생성자 + @Autowired + public MemberController(MemberService memberService) { + this.memberService = memberService; + } +} +``` \ No newline at end of file diff --git a/week2/taekyeongkim/img/AfterEach.png b/week2/taekyeongkim/img/AfterEach.png new file mode 100644 index 0000000..6173bb5 Binary files /dev/null and b/week2/taekyeongkim/img/AfterEach.png differ diff --git a/week2/taekyeongkim/img/CreateNewTest.png b/week2/taekyeongkim/img/CreateNewTest.png new file mode 100644 index 0000000..0a8b1e1 Binary files /dev/null and b/week2/taekyeongkim/img/CreateNewTest.png differ diff --git a/week2/taekyeongkim/img/CtrlP.png b/week2/taekyeongkim/img/CtrlP.png new file mode 100644 index 0000000..90a64f5 Binary files /dev/null and b/week2/taekyeongkim/img/CtrlP.png differ diff --git a/week2/taekyeongkim/img/ExtractMethod.png b/week2/taekyeongkim/img/ExtractMethod.png new file mode 100644 index 0000000..b4a2442 Binary files /dev/null and b/week2/taekyeongkim/img/ExtractMethod.png differ diff --git a/week2/taekyeongkim/img/MemberController.png b/week2/taekyeongkim/img/MemberController.png new file mode 100644 index 0000000..dc11790 Binary files /dev/null and b/week2/taekyeongkim/img/MemberController.png differ diff --git a/week2/taekyeongkim/img/SpringContainer.png b/week2/taekyeongkim/img/SpringContainer.png new file mode 100644 index 0000000..a46f523 Binary files /dev/null and b/week2/taekyeongkim/img/SpringContainer.png differ diff --git a/week2/taekyeongkim/img/class_relations.png b/week2/taekyeongkim/img/class_relations.png new file mode 100644 index 0000000..03c91bf Binary files /dev/null and b/week2/taekyeongkim/img/class_relations.png differ diff --git a/week2/taekyeongkim/img/implement methods.png b/week2/taekyeongkim/img/implement methods.png new file mode 100644 index 0000000..a634219 Binary files /dev/null and b/week2/taekyeongkim/img/implement methods.png differ diff --git a/week2/taekyeongkim/img/struct.png b/week2/taekyeongkim/img/struct.png new file mode 100644 index 0000000..5d857f2 Binary files /dev/null and b/week2/taekyeongkim/img/struct.png differ diff --git a/week2/taekyeongkim/spring-prac/.gitignore b/week2/taekyeongkim/spring-prac/.gitignore new file mode 100644 index 0000000..c2065bc --- /dev/null +++ b/week2/taekyeongkim/spring-prac/.gitignore @@ -0,0 +1,37 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ diff --git a/week2/taekyeongkim/spring-prac/build.gradle b/week2/taekyeongkim/spring-prac/build.gradle new file mode 100644 index 0000000..a4863f5 --- /dev/null +++ b/week2/taekyeongkim/spring-prac/build.gradle @@ -0,0 +1,29 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.3.3' + id 'io.spring.dependency-management' version '1.1.6' +} + +group = 'likelion' +version = '0.0.1-SNAPSHOT' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'org.springframework.boot:spring-boot-starter-web' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/week2/taekyeongkim/spring-prac/gradle/wrapper/gradle-wrapper.jar b/week2/taekyeongkim/spring-prac/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e644113 Binary files /dev/null and b/week2/taekyeongkim/spring-prac/gradle/wrapper/gradle-wrapper.jar differ diff --git a/week2/taekyeongkim/spring-prac/gradle/wrapper/gradle-wrapper.properties b/week2/taekyeongkim/spring-prac/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..a441313 --- /dev/null +++ b/week2/taekyeongkim/spring-prac/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/week2/taekyeongkim/spring-prac/gradlew b/week2/taekyeongkim/spring-prac/gradlew new file mode 100644 index 0000000..b740cf1 --- /dev/null +++ b/week2/taekyeongkim/spring-prac/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/week2/taekyeongkim/spring-prac/gradlew.bat b/week2/taekyeongkim/spring-prac/gradlew.bat new file mode 100644 index 0000000..25da30d --- /dev/null +++ b/week2/taekyeongkim/spring-prac/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/week2/taekyeongkim/spring-prac/settings.gradle b/week2/taekyeongkim/spring-prac/settings.gradle new file mode 100644 index 0000000..4f12dc5 --- /dev/null +++ b/week2/taekyeongkim/spring-prac/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'spring-prac' diff --git a/week2/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/SpringConfig.java b/week2/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/SpringConfig.java new file mode 100644 index 0000000..828527c --- /dev/null +++ b/week2/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/SpringConfig.java @@ -0,0 +1,22 @@ +package likelion.spring_prac; + +import likelion.spring_prac.repository.MemberRepository; +import likelion.spring_prac.repository.MemoryMemberRepository; +import likelion.spring_prac.service.MemberService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SpringConfig { + + @Bean + public MemberService memberService() { + return new MemberService(memberRepository()); + } + + @Bean + public MemberRepository memberRepository() { + return new MemoryMemberRepository(); + } + +} diff --git a/week2/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/SpringPracApplication.java b/week2/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/SpringPracApplication.java new file mode 100644 index 0000000..9bd31c2 --- /dev/null +++ b/week2/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/SpringPracApplication.java @@ -0,0 +1,13 @@ +package likelion.spring_prac; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringPracApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringPracApplication.class, args); + } + +} diff --git a/week1/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/controller/HelloController.java b/week2/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/controller/HelloController.java similarity index 100% rename from week1/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/controller/HelloController.java rename to week2/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/controller/HelloController.java diff --git a/week2/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/controller/MemberController.java b/week2/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/controller/MemberController.java new file mode 100644 index 0000000..dab568b --- /dev/null +++ b/week2/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/controller/MemberController.java @@ -0,0 +1,23 @@ +package likelion.spring_prac.controller; + +import likelion.spring_prac.service.MemberService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; + +@Controller +public class MemberController { + + private final MemberService memberService; + + // 생성자 + @Autowired + public MemberController(MemberService memberService) { + this.memberService = memberService; + } + +// @Autowired +// public void setmemberService(MemberService memberService) { +// this.memberService = memberService; +// } + +} diff --git a/week2/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/domain/Member.java b/week2/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/domain/Member.java new file mode 100644 index 0000000..1abaca3 --- /dev/null +++ b/week2/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/domain/Member.java @@ -0,0 +1,23 @@ +package likelion.spring_prac.domain; + +public class Member { + + private Long id; + private String name; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/week2/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/repository/MemberRepository.java b/week2/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/repository/MemberRepository.java new file mode 100644 index 0000000..bf48aaf --- /dev/null +++ b/week2/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/repository/MemberRepository.java @@ -0,0 +1,14 @@ +package likelion.spring_prac.repository; + +import likelion.spring_prac.domain.Member; + +import java.util.List; +import java.util.Optional; + +public interface MemberRepository { + Member save(Member member); + // Optional : Null인 경우를 대비 + Optional findById(Long id); + Optional findByName(String name); + List findAll(); // 저장된 모든 멤버 리스트 반환 +} diff --git a/week2/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/repository/MemoryMemberRepository.java b/week2/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/repository/MemoryMemberRepository.java new file mode 100644 index 0000000..59a3fd2 --- /dev/null +++ b/week2/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/repository/MemoryMemberRepository.java @@ -0,0 +1,44 @@ +package likelion.spring_prac.repository; + +import likelion.spring_prac.domain.Member; + +import java.util.*; + +//@Repository +public class MemoryMemberRepository implements MemberRepository{ + + // 저장할 곳이 필요 + private static Map store = new HashMap<>(); + private static long sequence = 0L; // 키 값 생성 + + @Override + public Member save(Member member) { + member.setId(++sequence); + store.put(member.getId(), member); + return member; + } + + @Override + public Optional findById(Long id) { + // 단순히 store.get(id) 를 반환하지 않고 Optional로 감싸서 반환 + // -> Null인 경우에 클라이언트 측에서 조치 가능 + return Optional.ofNullable(store.get(id)); + } + + @Override + public Optional findByName(String name) { + return store.values().stream() + .filter(member -> member.getName().equals(name)) + .findAny(); + } + + @Override + public List findAll() { + // store의 member들 반환 + return new ArrayList<>(store.values()); + } + + public void clearStore() { + store.clear(); + } +} diff --git a/week2/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/service/MemberService.java b/week2/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/service/MemberService.java new file mode 100644 index 0000000..57456cc --- /dev/null +++ b/week2/taekyeongkim/spring-prac/src/main/java/likelion/spring_prac/service/MemberService.java @@ -0,0 +1,50 @@ +package likelion.spring_prac.service; + +import likelion.spring_prac.domain.Member; +import likelion.spring_prac.repository.MemberRepository; + +import java.util.List; +import java.util.Optional; + +//@Service +public class MemberService { + + // repository 생성 + // private final MemberRepository memberRepository = new MemoryMemberRepository(); + private final MemberRepository memberRepository; + // repository 를 직접 생성하는 것이 아니라 외부에서 넣어주도록 바꿈 + //@Autowired + public MemberService(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + + /** + * 회원 가입 + */ + public Long join(Member member) { + // 로직 ex : 같은 이름의 중복 회원 불가 + // Optional -> ifPresent로 로직 구현 + validateDuplicateMember(member); // 중복 회원 검증 + memberRepository.save(member); + return member.getId(); + } + + // 중복 회원 검증 + private void validateDuplicateMember(Member member) { + memberRepository.findByName(member.getName()) + .ifPresent(m -> { + throw new IllegalStateException("이미 존재하는 회원입니다."); + }); + } + + /** + * 회원 조회 + */ + public List findMembers() { + return memberRepository.findAll(); + } + + public Optional findOne(Long memberId) { + return memberRepository.findById(memberId); + } +} diff --git a/week2/taekyeongkim/spring-prac/src/main/resources/application.properties b/week2/taekyeongkim/spring-prac/src/main/resources/application.properties new file mode 100644 index 0000000..2a12994 --- /dev/null +++ b/week2/taekyeongkim/spring-prac/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=spring-prac diff --git a/week2/taekyeongkim/spring-prac/src/main/resources/static/hello-static.html b/week2/taekyeongkim/spring-prac/src/main/resources/static/hello-static.html new file mode 100644 index 0000000..68254b2 --- /dev/null +++ b/week2/taekyeongkim/spring-prac/src/main/resources/static/hello-static.html @@ -0,0 +1,10 @@ + + + + static content + + + +정적 컨텐츠 입니다. + + \ No newline at end of file diff --git a/week2/taekyeongkim/spring-prac/src/main/resources/static/index.html b/week2/taekyeongkim/spring-prac/src/main/resources/static/index.html new file mode 100644 index 0000000..8ceff60 --- /dev/null +++ b/week2/taekyeongkim/spring-prac/src/main/resources/static/index.html @@ -0,0 +1,10 @@ + + + + + Document + + +Hello + + \ No newline at end of file diff --git a/week2/taekyeongkim/spring-prac/src/main/resources/templates/hello-template.html b/week2/taekyeongkim/spring-prac/src/main/resources/templates/hello-template.html new file mode 100644 index 0000000..0ba5236 --- /dev/null +++ b/week2/taekyeongkim/spring-prac/src/main/resources/templates/hello-template.html @@ -0,0 +1,5 @@ + + +

hello! empty

+ + diff --git a/week2/taekyeongkim/spring-prac/src/main/resources/templates/hello.html b/week2/taekyeongkim/spring-prac/src/main/resources/templates/hello.html new file mode 100644 index 0000000..d91952b --- /dev/null +++ b/week2/taekyeongkim/spring-prac/src/main/resources/templates/hello.html @@ -0,0 +1,10 @@ + + + + + Document + + +

안녕하세요. 손님

+ + \ No newline at end of file diff --git a/week2/taekyeongkim/spring-prac/src/test/java/likelion/spring_prac/SpringPracApplicationTests.java b/week2/taekyeongkim/spring-prac/src/test/java/likelion/spring_prac/SpringPracApplicationTests.java new file mode 100644 index 0000000..d2c4791 --- /dev/null +++ b/week2/taekyeongkim/spring-prac/src/test/java/likelion/spring_prac/SpringPracApplicationTests.java @@ -0,0 +1,13 @@ +package likelion.spring_prac; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class SpringPracApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/week2/taekyeongkim/spring-prac/src/test/java/likelion/spring_prac/repository/MemoryMemberRepositoryTest.java b/week2/taekyeongkim/spring-prac/src/test/java/likelion/spring_prac/repository/MemoryMemberRepositoryTest.java new file mode 100644 index 0000000..b4756f2 --- /dev/null +++ b/week2/taekyeongkim/spring-prac/src/test/java/likelion/spring_prac/repository/MemoryMemberRepositoryTest.java @@ -0,0 +1,73 @@ +package likelion.spring_prac.repository; + +import likelion.spring_prac.domain.Member; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +// public 일 필요 없어서 public 없앰 +class MemoryMemberRepositoryTest { + + // MemberRepository repository = new MemoryMemberRepository(); + // |-> MemoryMemberRepository만 test 하는 것이므로 interface가 아닌 MemoryMemberRepository로 변경 + MemoryMemberRepository repository = new MemoryMemberRepository(); + + @AfterEach + public void afterEach() { + repository.clearStore(); + } + + // save 기능이 동작하는지 test 해보고자 함 + @Test + public void save() { + Member member = new Member(); + member.setName("spring"); + + repository.save(member); + + // optional -> get으로 꺼내와서 result라는 Member 객체에 받아 봄 + Member result = repository.findById(member.getId()).get(); + + // 직접 글자 찍기 + // : System.out.println("result = " + (result == member)); + + // 직접 출력은 아니지만 돌렸을 때 아무것도 안 나오고 초록색 체크 + // Assertions.assertEquals(member, result); + // 만약 실패하면 (ex) (Assertions.assertEquals(member, actual:null);) -> error 뜸 + + assertThat(member).isEqualTo(result); + } + + @Test + public void findByName() { + Member member1 = new Member(); + member1.setName("spring1"); + repository.save(member1); + + Member member2 = new Member(); + member2.setName("spring2"); + repository.save(member2); + + Member result = repository.findByName("spring1").get(); + + assertThat(result).isEqualTo(member1); + } + + @Test + public void findAll() { + Member member1 = new Member(); + member1.setName("spring1"); + repository.save(member1); + + Member member2 = new Member(); + member2.setName("spring2"); + repository.save(member2); + + List result = repository.findAll(); + + assertThat(result.size()).isEqualTo(2); + } +} diff --git a/week2/taekyeongkim/spring-prac/src/test/java/likelion/spring_prac/service/MemberServiceTest.java b/week2/taekyeongkim/spring-prac/src/test/java/likelion/spring_prac/service/MemberServiceTest.java new file mode 100644 index 0000000..7118e19 --- /dev/null +++ b/week2/taekyeongkim/spring-prac/src/test/java/likelion/spring_prac/service/MemberServiceTest.java @@ -0,0 +1,86 @@ +package likelion.spring_prac.service; + +import likelion.spring_prac.domain.Member; +import likelion.spring_prac.repository.MemberRepository; +import likelion.spring_prac.repository.MemoryMemberRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class MemberServiceTest { + + // MemberService memberService = new MemberService(); + // MemoryMemberRepository memoryMemberRepository = new MemoryMemberRepository(); + + MemberService memberService; + MemoryMemberRepository memberRepository; + + // 각 테스트를 실행하기 전에 만들고 넣어줌 + @BeforeEach + public void beforeEach() { + memberRepository = new MemoryMemberRepository(); + memberService = new MemberService(memberRepository); + } + + + @AfterEach + public void afterEach() { + memoryMemberRepository.clearStore(); + } + + @Test + void 회원가입() { // test code는 함수 이름이 심지어 한글이어도 괜찮음 + //given + Member member = new Member(); + member.setName("hello"); + + //when + Long saveId = memberService.join(member); + + //then + Member findMember = memberService.findOne(saveId).get(); + assertThat(member.getName()).isEqualTo(findMember.getName()); + + } + + @Test + public void 중복_회원_예외() { + // given + Member member1 = new Member(); + member1.setName("spring"); + + Member member2 = new Member(); + member2.setName("spring"); + + //when + memberService.join(member1); + IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2)); + + assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.") +/* + try { + memberService.join(member2); + fail(); + } catch (IllegalStateException e) { + // 예외가 터지는 상황에서 예외가 터짐 = 정상적 동작 + assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.") + } + + //then + } + */ + + @Test + void findMembers() { + // given + //when + //then + } + + @Test + void findOne() { + } +} \ No newline at end of file diff --git a/week3/taekyeongkim/MemberPrac.md b/week3/taekyeongkim/MemberPrac.md new file mode 100644 index 0000000..efb6422 --- /dev/null +++ b/week3/taekyeongkim/MemberPrac.md @@ -0,0 +1,409 @@ +# 프로젝트 생성 +[start.spring.io](https://start.spring.io/) + +![startspring](./img/startspring.png) + +→ 인텔리제이 내부에서 바로 생성 가능 (하긴 한데 사이트 사용이 편했음) + +# h2 디비 설치/연결 +### h2 디비 설치 + [Downloads](https://www.h2database.com/html/download.html) + + h2 디비 설치 + + ![h2](./img/h2.png) + + → Zip 파일 다운로드 + + h2 데이터베이스 설치 후 → 압축 풀고 → h2/bin 들어가기 (window : h2 bat 파일) + + ![h2bat](./img/h2bat.png) + +### 디비 연결 + * 만약 접속 URL이 IP주소로 되어 있다면 → localhost로 변경 + + - 처음 : IP 주소로 되어 있음 + ![ip](./img/ip.png) + + - -> localhost로 변경 + ![local](./img/local.png) + + * jdbc:h2:~/example 형태로 url 설정 (example 자리에는 자기가 원하는 이름으로) + ![url](./img/url.png) + + * 로컬에 .mv.db 파일 생성 됨 + ![mv](./img/mv.png) + + * yml 파일 설정을 통해 프로젝트와 디비 연결 가능 + + : 파일 설정 후 프로젝트 실행 + + ![run](./img/run_dbcheck.png) + ![run](./img/run_h2dbcheck.png) + + +# 코드 +### application.yml + : resources 패키지 내부에 [application.properties](http://application.properties) 삭제 후 → application.yml 파일 생성 + + ```java + spring: + datasource: + url: jdbc:h2:tcp://localhost/~/week2Prac //본인 h2 디비 url + username: sa //기본 설정 값 그대로 두기 + password: //"00" //본인 설정 따라 : 없애도 됨 + driver-class-name: org.h2.Driver + + jpa: + hibernate: + ddl-auto: create + properties: + hibernate: + format_sql: true + + logging: + level: + org.hibernate.SQL: debug + org.hibernate.orm.jdbc.bind: trace + ``` + +### domain/Member.java + : 멤버로 id, name, email을 갖는 멤버 엔티티 정의 + + ```java + package likelion.week2Prac.domain; + + import jakarta.persistence.*; + import lombok.AllArgsConstructor; + import lombok.Builder; + import lombok.Getter; + import lombok.NoArgsConstructor; + import org.springframework.data.annotation.CreatedDate; + import org.springframework.data.jpa.domain.support.AuditingEntityListener; + + import java.time.LocalDateTime; + + @Entity //entity 클래스임 + @Builder //객체 생성 패턴 - 빌더 패턴 사용 + @NoArgsConstructor // 파라미터가 없는 디폴트 생성자 자동 생성 + @AllArgsConstructor // 모든 필드 값을 파라미터로 받는 생성자 자동 생성 + @Getter // 멤버 필드의 getter 들 자동 생성 (이 경우 : getId, getName, getEmail) + @EntityListeners(AuditingEntityListener.class) + public class Member { + @Id // PK 명시 + @GeneratedValue(strategy = GenerationType.IDENTITY) // 기본키 값 자동 생성 + @Column(name="member_id") // 필드와 디비 컬럼 매핑 + + private Long id; + private String name; + private String email; + + @CreatedDate + private LocalDateTime createdAt; + } + ``` + +### controller/MemberController.java + : 클라이언트로부터 request를 받고 response를 하는 모든 api 정의 + * **`@RequiredArgsConstructor`** + + : Lombok으로 스프링에서 DI(의존성 주입)의 방법 중에 + 생성자 주입을 임의의 코드 없이 자동으로 설정해주는 어노테이션 + (Lombok에서 @Getter, @Setter 어노테이션처럼 클래스에 선언된 final 변수들, 필드들을 매개변수로 하는 생성자를 자동으로 생성해주는 어노테이션) + + - 새로운 필드를 추가할 때 다시 생성자를 만들어서 관리해야하는 번거로움 x + - 초기화 되지않은 final 필드나, @NonNull 이 붙은 필드에 대해 생성자를 생성 + + = `@Autowired` 사용 없이 의존성 주입 + + - @RequiredArgsConstructor 어노테이션 사용하지 않았을 때 + + ```java + @RestController + @RequestMapping("/example") + public class RequiredArgsConstructorControllerExample { + + private final FirstService firstService; + private final SecondService secondService; + private final ThirdService thirdService; + + @Autowired + public RequiredArgsConstructorControllerExample(FirstService firstService, SecondService secondService, ThirdService thirdService) { + this.firstRepository = firstRepository; + this.secondRepository = secondRepository; + this.thirdRepository = thirdRepository; + } + ``` + + - @RequiredArgsConstructor 어노테이션 사용했을 때 + ```java + @RestController + @RequiredArgsConstructor + @RequestMapping("/example") + public class RequiredArgsConstructorControllerExample { + + private final FirstService firstService; + private final SecondService secondService; + private final ThirdService thirdService; + + ... + } + ``` + * `ResponseEntity` + + : 반환 타입이 명확하지 않아도 return이 가능한데, (ex : `ResponseEntity`) 타입을 여러 개 받고 싶은 경우에 `Object` 대신 와일드카드(`?`)를 사용한다. + + [ResponseEntity의 사용법 및 유지보수](https://stir.tistory.com/343) + + + + ```java + package likelion.week2Prac.controller; + + import likelion.week2Prac.domain.Member; + import likelion.week2Prac.dto.MemberRequestDTO; + import likelion.week2Prac.service.MemberService; + import lombok.RequiredArgsConstructor; + import org.springframework.http.ResponseEntity; + import org.springframework.web.bind.annotation.*; + + import java.util.List; + import java.util.Optional; + + @RestController // restcontroller 임 + @RequiredArgsConstructor // 생성자 의존성 주입 자동 + @RequestMapping("/members") // 공통 엔드포인트 + public class MemberController { + + private final MemberService memberService; + + // POST 요청 처리 + @PostMapping + public ResponseEntity createMember(@RequestBody MemberRequestDTO memberRequestDTO) { + Member member = memberService.createMember(memberRequestDTO); + return ResponseEntity.status(201).body(member); + } + + // GET 요청 처리 + @GetMapping + public ResponseEntity getMemberList() { + try{ + List response; + response = memberService.getMemberList(); + if(response.isEmpty()){ + return ResponseEntity.status(200).body("사용자가 존재하지 않습니다."); //200, 빈 리스트 반환 + } + return ResponseEntity.ok(response); + } catch (Exception e){ + return ResponseEntity.status(500).body("서버 내부 에러"); + } + } + + @GetMapping("/{memberId}") + public ResponseEntity getOneMember(@PathVariable Long memberId) { + try{ + Optional response = memberService.getOneMember(memberId); + if (response.isPresent()) { + return ResponseEntity.ok(response.get()); + } else { + return ResponseEntity.status(404).body("해당 사용자를 찾을 수 없습니다."); + } + }catch (Exception e){ + return ResponseEntity.status(500).body("서버 내부 에러"); + } + } + } + ``` + +### service/MemberService.java + : 비지니스 로직 - controller가 요청받은 작업을 할 수 있는 메소드들 정의 + + * **`@RequiredArgsConstructor`** + + : `@Autowired` 사용 없이 의존성 주입 + + - @RequiredArgsConstructor 어노테이션 사용하지 않았을 때 + + ```java + @Service + public class MemberService { + private final MemberRepository memberRepository; + + ... + + **@Autowired + public MemberService(MemberRepository memberRepository){ + this.memberRepository = memberRepository; + }** + } + ``` + + - @RequiredArgsConstructor 어노테이션 사용했을 때 + + ```java + @Service + @RequiredArgsConstructor + public class MemberService { + private final MemberRepository memberRepository; + + ... + + } + ``` + + +```java +package likelion.week2Prac.service; + +import likelion.week2Prac.domain.Member; +import likelion.week2Prac.dto.MemberRequestDTO; +import likelion.week2Prac.repository.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service // service 클래스임 +@RequiredArgsConstructor // 생성자 자동 생성 +public class MemberService { + + private final MemberRepository memberRepository; + + public Member createMember(MemberRequestDTO memberRequestDTO){ + Member member = Member.builder() + .name(memberRequestDTO.name()) + .email(memberRequestDTO.email()) + .build(); + + Member savedMember = memberRepository.save(member); + return savedMember; + } + public List getMemberList(){ + return memberRepository.findAll(); + } + + public Optional getOneMember(Long memberId){ + return memberRepository.findById(memberId); + } +} +``` + + +### repository/MemberRepository.java + : DB와 통신 + + : interface로 생성 + + * JPA 활용 + + : **JpaRepository**를 extends 하는 repository 클래스 생성 + + ⇒ JPA 제공 기본 메서드 = 기본적인 CRUD 기능 제공 (→ 복잡한 쿼리문 사용 x) + (findAll(), findById(), save() 등) + + - **JPA** (Java Persistence Api) : 객체와 관계형 디비 사이에서 매핑 역할 + - **JpaRepository** : Spring Data JPA에서 제공하는 인터페이스 + + [JpaRepository (Spring Data JPA Parent 3.3.3 API)](https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/JpaRepository.html) + + + + if) JPA를 활용하지 않고 jdbc (쿼리문 api)를 사용한다면? + + ```java + // 1. 데이터베이스 연결 + Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "username", "password"); + + // 2. SQL 쿼리 작성 및 실행 + String sql = "SELECT * FROM users WHERE id = ?"; + PreparedStatement statement = connection.prepareStatement(sql); + statement.setInt(1, 1); + ResultSet resultSet = statement.executeQuery(); + + // 3. 결과 처리 + while (resultSet.next()) { + String name = resultSet.getString("name"); + System.out.println(name); + } + + // 4. 연결 해제 + resultSet.close(); + statement.close(); + connection.close(); + ``` + + ⇒ JPA 활용 + + ```java + public void printUserNameById(Long id) { + User user = userRepository.findById(id).orElse(null); + if (user != null) { + System.out.println(user.getName()); + } + } + ``` + + + ```java + package likelion.week2Prac.repository; + + import likelion.week2Prac.domain.Member; + import org.springframework.data.jpa.repository.JpaRepository; + import org.springframework.stereotype.Repository; + + @Repository // repository 클래스임 + public interface MemberRepository extends JpaRepository { + } + ``` + +### dto/MemberRequestDTO.java + : (serializer와 비슷한 기능을 하는) Data Transmition Object + + * Record 문법을 사용하지 않은 DTO + ```java + package mutsa.second.demo.dto; + + import lombok.AllArgsConstructor; + import lombok.Builder; + import lombok.Data; + import lombok.NoArgsConstructor; + + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Data // getter, setter, toString, equals, hashCode, 기본 생성자까지 자동으로 생성 + public class example { + private String name; + private String email; + } + ``` + + ```java + package likelion.week2Prac.dto; + + public record MemberRequestDTO(String name, String email) { + } + ``` + +# 포스트맨 +### POST +: localhost:8080/members + + * createdAt이 null로 들어가는 오류 발견 + + => **@EnableJpaAuditing** + + : domain에서 createdAt을 위한 어노테이션 중 `@EntityListeners(AuditingEntityListener.class)` 가 있었는데, 이것은 Auditing을 사용하겠다는 코드이고, 이를 활성화하기 위한 코드(`@EnableJpaAuditing`)를 application.java 파일에 추가해줘야 함 + + ![post](./Postman_img/POST_members-1.png) + ![post](./Postman_img/POST_members-2.png) + +### GET + * localhost:8080/members + ![get](./Postman_img/GET_members.png) + * localhost:8080/members/{id} + ![get](./Postman_img/GET_members-1.png) + ![get](./Postman_img/GET_members-2.png) + +### DB 확인 + ![db](./img/DB.png) \ No newline at end of file diff --git a/week3/taekyeongkim/Postman_img/GET_members-1.png b/week3/taekyeongkim/Postman_img/GET_members-1.png new file mode 100644 index 0000000..3a66690 Binary files /dev/null and b/week3/taekyeongkim/Postman_img/GET_members-1.png differ diff --git a/week3/taekyeongkim/Postman_img/GET_members-2.png b/week3/taekyeongkim/Postman_img/GET_members-2.png new file mode 100644 index 0000000..28e0cef Binary files /dev/null and b/week3/taekyeongkim/Postman_img/GET_members-2.png differ diff --git a/week3/taekyeongkim/Postman_img/GET_members.png b/week3/taekyeongkim/Postman_img/GET_members.png new file mode 100644 index 0000000..1b28299 Binary files /dev/null and b/week3/taekyeongkim/Postman_img/GET_members.png differ diff --git a/week3/taekyeongkim/Postman_img/POST_members-1.png b/week3/taekyeongkim/Postman_img/POST_members-1.png new file mode 100644 index 0000000..950eb80 Binary files /dev/null and b/week3/taekyeongkim/Postman_img/POST_members-1.png differ diff --git a/week3/taekyeongkim/Postman_img/POST_members-2.png b/week3/taekyeongkim/Postman_img/POST_members-2.png new file mode 100644 index 0000000..1aba275 Binary files /dev/null and b/week3/taekyeongkim/Postman_img/POST_members-2.png differ diff --git a/week3/taekyeongkim/img/DB.png b/week3/taekyeongkim/img/DB.png new file mode 100644 index 0000000..141aed2 Binary files /dev/null and b/week3/taekyeongkim/img/DB.png differ diff --git a/week3/taekyeongkim/img/h2.png b/week3/taekyeongkim/img/h2.png new file mode 100644 index 0000000..ce07ffc Binary files /dev/null and b/week3/taekyeongkim/img/h2.png differ diff --git a/week3/taekyeongkim/img/h2bat.png b/week3/taekyeongkim/img/h2bat.png new file mode 100644 index 0000000..04a4f08 Binary files /dev/null and b/week3/taekyeongkim/img/h2bat.png differ diff --git a/week3/taekyeongkim/img/ip.png b/week3/taekyeongkim/img/ip.png new file mode 100644 index 0000000..ceb94ed Binary files /dev/null and b/week3/taekyeongkim/img/ip.png differ diff --git a/week3/taekyeongkim/img/local.png b/week3/taekyeongkim/img/local.png new file mode 100644 index 0000000..5c9224e Binary files /dev/null and b/week3/taekyeongkim/img/local.png differ diff --git a/week3/taekyeongkim/img/mv.png b/week3/taekyeongkim/img/mv.png new file mode 100644 index 0000000..1bbb032 Binary files /dev/null and b/week3/taekyeongkim/img/mv.png differ diff --git a/week3/taekyeongkim/img/run_dbcheck.png b/week3/taekyeongkim/img/run_dbcheck.png new file mode 100644 index 0000000..157e39d Binary files /dev/null and b/week3/taekyeongkim/img/run_dbcheck.png differ diff --git a/week3/taekyeongkim/img/run_h2dbcheck.png b/week3/taekyeongkim/img/run_h2dbcheck.png new file mode 100644 index 0000000..8a19bf2 Binary files /dev/null and b/week3/taekyeongkim/img/run_h2dbcheck.png differ diff --git a/week3/taekyeongkim/img/startspring.png b/week3/taekyeongkim/img/startspring.png new file mode 100644 index 0000000..6760eb0 Binary files /dev/null and b/week3/taekyeongkim/img/startspring.png differ diff --git a/week3/taekyeongkim/img/url.png b/week3/taekyeongkim/img/url.png new file mode 100644 index 0000000..abfb407 Binary files /dev/null and b/week3/taekyeongkim/img/url.png differ diff --git a/week3/taekyeongkim/week2Prac/.gitignore b/week3/taekyeongkim/week2Prac/.gitignore new file mode 100644 index 0000000..c2065bc --- /dev/null +++ b/week3/taekyeongkim/week2Prac/.gitignore @@ -0,0 +1,37 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ diff --git a/week3/taekyeongkim/week2Prac/build.gradle b/week3/taekyeongkim/week2Prac/build.gradle new file mode 100644 index 0000000..c847557 --- /dev/null +++ b/week3/taekyeongkim/week2Prac/build.gradle @@ -0,0 +1,39 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.3.3' + id 'io.spring.dependency-management' version '1.1.6' +} + +group = 'likelion' +version = '0.0.1-SNAPSHOT' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +configurations { + compileOnly { + extendsFrom annotationProcessor + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-web' + compileOnly 'org.projectlombok:lombok' + runtimeOnly 'com.h2database:h2' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/week3/taekyeongkim/week2Prac/gradle/wrapper/gradle-wrapper.jar b/week3/taekyeongkim/week2Prac/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..a4b76b9 Binary files /dev/null and b/week3/taekyeongkim/week2Prac/gradle/wrapper/gradle-wrapper.jar differ diff --git a/week3/taekyeongkim/week2Prac/gradle/wrapper/gradle-wrapper.properties b/week3/taekyeongkim/week2Prac/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..0aaefbc --- /dev/null +++ b/week3/taekyeongkim/week2Prac/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/week3/taekyeongkim/week2Prac/gradlew b/week3/taekyeongkim/week2Prac/gradlew new file mode 100644 index 0000000..f5feea6 --- /dev/null +++ b/week3/taekyeongkim/week2Prac/gradlew @@ -0,0 +1,252 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/week3/taekyeongkim/week2Prac/gradlew.bat b/week3/taekyeongkim/week2Prac/gradlew.bat new file mode 100644 index 0000000..9d21a21 --- /dev/null +++ b/week3/taekyeongkim/week2Prac/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/week3/taekyeongkim/week2Prac/settings.gradle b/week3/taekyeongkim/week2Prac/settings.gradle new file mode 100644 index 0000000..61b9bfe --- /dev/null +++ b/week3/taekyeongkim/week2Prac/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'week2Prac' diff --git a/week3/taekyeongkim/week2Prac/src/main/java/likelion/week2Prac/Week2PracApplication.java b/week3/taekyeongkim/week2Prac/src/main/java/likelion/week2Prac/Week2PracApplication.java new file mode 100644 index 0000000..d844ada --- /dev/null +++ b/week3/taekyeongkim/week2Prac/src/main/java/likelion/week2Prac/Week2PracApplication.java @@ -0,0 +1,15 @@ +package likelion.week2Prac; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +@SpringBootApplication +@EnableJpaAuditing +public class Week2PracApplication { + + public static void main(String[] args) { + SpringApplication.run(Week2PracApplication.class, args); + } + +} diff --git a/week3/taekyeongkim/week2Prac/src/main/java/likelion/week2Prac/controller/MemberController.java b/week3/taekyeongkim/week2Prac/src/main/java/likelion/week2Prac/controller/MemberController.java new file mode 100644 index 0000000..579e21a --- /dev/null +++ b/week3/taekyeongkim/week2Prac/src/main/java/likelion/week2Prac/controller/MemberController.java @@ -0,0 +1,55 @@ +package likelion.week2Prac.controller; + +import likelion.week2Prac.domain.Member; +import likelion.week2Prac.dto.MemberRequestDTO; +import likelion.week2Prac.service.MemberService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Optional; + +@RestController // restcontroller 임 +@RequiredArgsConstructor // 생성자 의존성 주입 자동 +@RequestMapping("/members") // 공통 엔드포인트 +public class MemberController { + + private final MemberService memberService; + + // POST 요청 처리 + @PostMapping + public ResponseEntity createMember(@RequestBody MemberRequestDTO memberRequestDTO) { + Member member = memberService.createMember(memberRequestDTO); + return ResponseEntity.status(201).body(member); + } + + // GET 요청 처리 + @GetMapping + public ResponseEntity getMemberList() { + try{ + List response; + response = memberService.getMemberList(); + if(response.isEmpty()){ + return ResponseEntity.status(200).body("사용자가 존재하지 않습니다."); //200, 빈 리스트 반환 + } + return ResponseEntity.ok(response); + } catch (Exception e){ + return ResponseEntity.status(500).body("서버 내부 에러"); + } + } + + @GetMapping("/{memberId}") + public ResponseEntity getOneMember(@PathVariable Long memberId) { + try{ + Optional response = memberService.getOneMember(memberId); + if (response.isPresent()) { + return ResponseEntity.ok(response.get()); + } else { + return ResponseEntity.status(404).body("해당 사용자를 찾을 수 없습니다."); + } + }catch (Exception e){ + return ResponseEntity.status(500).body("서버 내부 에러"); + } + } +} diff --git a/week3/taekyeongkim/week2Prac/src/main/java/likelion/week2Prac/domain/Member.java b/week3/taekyeongkim/week2Prac/src/main/java/likelion/week2Prac/domain/Member.java new file mode 100644 index 0000000..169379b --- /dev/null +++ b/week3/taekyeongkim/week2Prac/src/main/java/likelion/week2Prac/domain/Member.java @@ -0,0 +1,30 @@ +package likelion.week2Prac.domain; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@Entity //entity 클래스임 +@Builder //객체 생성 패턴 - 빌더 패턴 사용 +@NoArgsConstructor // 파라미터가 없는 디폴트 생성자 자동 생성 +@AllArgsConstructor // 모든 필드 값을 파라미터로 받는 생성자 자동 생성 +@Getter // 멤버 필드의 getter 들 자동 생성 (이 경우 : getId, getName, getEmail) +@EntityListeners(AuditingEntityListener.class) +public class Member { + @Id // PK 명시 + @GeneratedValue(strategy = GenerationType.IDENTITY) // 기본키 값 자동 생성 + @Column(name="member_id") // 필드와 디비 컬럼 매핑 + + private Long id; + private String name; + private String email; + + @CreatedDate + private LocalDateTime createdAt; +} diff --git a/week3/taekyeongkim/week2Prac/src/main/java/likelion/week2Prac/dto/MemberRequestDTO.java b/week3/taekyeongkim/week2Prac/src/main/java/likelion/week2Prac/dto/MemberRequestDTO.java new file mode 100644 index 0000000..1a2ccdc --- /dev/null +++ b/week3/taekyeongkim/week2Prac/src/main/java/likelion/week2Prac/dto/MemberRequestDTO.java @@ -0,0 +1,4 @@ +package likelion.week2Prac.dto; + +public record MemberRequestDTO(String name, String email) { +} \ No newline at end of file diff --git a/week3/taekyeongkim/week2Prac/src/main/java/likelion/week2Prac/repository/MemberRepository.java b/week3/taekyeongkim/week2Prac/src/main/java/likelion/week2Prac/repository/MemberRepository.java new file mode 100644 index 0000000..1702228 --- /dev/null +++ b/week3/taekyeongkim/week2Prac/src/main/java/likelion/week2Prac/repository/MemberRepository.java @@ -0,0 +1,9 @@ +package likelion.week2Prac.repository; + +import likelion.week2Prac.domain.Member; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository // repository 클래스임 +public interface MemberRepository extends JpaRepository { +} diff --git a/week3/taekyeongkim/week2Prac/src/main/java/likelion/week2Prac/service/MemberService.java b/week3/taekyeongkim/week2Prac/src/main/java/likelion/week2Prac/service/MemberService.java new file mode 100644 index 0000000..d7738ac --- /dev/null +++ b/week3/taekyeongkim/week2Prac/src/main/java/likelion/week2Prac/service/MemberService.java @@ -0,0 +1,34 @@ +package likelion.week2Prac.service; + +import likelion.week2Prac.domain.Member; +import likelion.week2Prac.dto.MemberRequestDTO; +import likelion.week2Prac.repository.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service // service 클래스임 +@RequiredArgsConstructor // 생성자 자동 생성 +public class MemberService { + + private final MemberRepository memberRepository; + + public Member createMember(MemberRequestDTO memberRequestDTO){ + Member member = Member.builder() + .name(memberRequestDTO.name()) + .email(memberRequestDTO.email()) + .build(); + + Member savedMember = memberRepository.save(member); + return savedMember; + } + public List getMemberList(){ + return memberRepository.findAll(); + } + + public Optional getOneMember(Long memberId){ + return memberRepository.findById(memberId); + } +} diff --git a/week3/taekyeongkim/week2Prac/src/main/resources/application.yml b/week3/taekyeongkim/week2Prac/src/main/resources/application.yml new file mode 100644 index 0000000..2acc829 --- /dev/null +++ b/week3/taekyeongkim/week2Prac/src/main/resources/application.yml @@ -0,0 +1,18 @@ +spring: + datasource: + url: jdbc:h2:tcp://localhost/~/week2Prac + username: sa + password: + driver-class-name: org.h2.Driver + + jpa: + hibernate: + ddl-auto: create + properties: + hibernate: + format_sql: true + +logging: + level: + org.hibernate.SQL: debug + org.hibernate.orm.jdbc.bind: trace \ No newline at end of file diff --git a/week3/taekyeongkim/week2Prac/src/test/java/likelion/week2Prac/Week2PracApplicationTests.java b/week3/taekyeongkim/week2Prac/src/test/java/likelion/week2Prac/Week2PracApplicationTests.java new file mode 100644 index 0000000..8086f8a --- /dev/null +++ b/week3/taekyeongkim/week2Prac/src/test/java/likelion/week2Prac/Week2PracApplicationTests.java @@ -0,0 +1,13 @@ +package likelion.week2Prac; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class Week2PracApplicationTests { + + @Test + void contextLoads() { + } + +}