Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Spring MVC] 한성재 미션 제출합니다. #358

Open
wants to merge 30 commits into
base: bingle625
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d5890c7
build: Spring web 의존성 추가
bingle625 Oct 26, 2024
92c31db
feat: 일단계 테스트 통과하도록 컨트롤러 구현
bingle625 Oct 26, 2024
f60ca1f
build: 타임리프 의존성 추가
bingle625 Oct 26, 2024
89c9355
feat: 어드민 홈 페이지 연결
bingle625 Oct 26, 2024
bc14e15
feat: 예약 조회 페이지 index
bingle625 Oct 26, 2024
aa53458
feat: 예약리스트 api
bingle625 Oct 26, 2024
e9f1555
test: read api 테스트
bingle625 Oct 26, 2024
dfc3f82
feat: 예약 추가, 삭제 api
bingle625 Oct 27, 2024
156e220
feat: 예약 추가, 취소 시 예외 처리
bingle625 Oct 27, 2024
8963ac0
build: jdbc, h2 의존성 추가
bingle625 Oct 27, 2024
df35ea5
build: h2 의존성 추가
bingle625 Oct 30, 2024
13e16ce
add: jdbc test
bingle625 Oct 31, 2024
aa59507
test: 5단계 테스트
bingle625 Oct 31, 2024
2e8d057
test: 6단계 테스트
bingle625 Oct 31, 2024
09f939f
feat: 예약 조회 기능 데이터베이스 연동
bingle625 Oct 31, 2024
4aec219
feat: 예약 추가/삭제 기능 데이터베이스 연동
bingle625 Oct 31, 2024
d7133d8
add: repository
bingle625 Nov 1, 2024
d41fef9
feat: jpaRepository 이용하도록 코드 변경
bingle625 Nov 1, 2024
8b7ffcd
test: Hello 테스트 Spring Bean
bingle625 Nov 16, 2024
251ca70
test: Hello 테스트 Spring Bean
bingle625 Nov 16, 2024
e311d68
Merge branch 'features/jpa'
bingle625 Nov 16, 2024
2662b29
feat: delete 기능 jpa repository 사용하도록 변경
bingle625 Nov 16, 2024
cfc3c26
modify: 도메인별로 클래스 분리 및 사용하지 않는 jdbcTemplate 의존성 삭제
bingle625 Nov 16, 2024
610e556
feat: Time 엔티티, 스케마 추가 및 TimeApiController 클래스 추가
bingle625 Nov 16, 2024
2318082
feat: Time store, index, delete api 구현
bingle625 Nov 16, 2024
2c735cc
feat: 9단계 기존코드 수정
bingle625 Nov 16, 2024
32c827a
test: 10단계 테스트 ReservationApiController에 jdbcTemplate 관련 의존성 없는지 확인하는 코드
bingle625 Nov 16, 2024
d08d455
refactor: ReservationService, TimeService 분리
bingle625 Nov 16, 2024
a7af10e
feat: 일단계 미션 통과하도록 LoginController 구현
bingle625 Dec 27, 2024
14bc007
feat: jpaRepository를 통해 로그인 정보 가져오도록 수정
bingle625 Dec 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,19 @@ repositories {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

타임리프를 추가하셨군요! 이번 미션에선 사용 안한 것 같은데, 어떤 이유로 쓰셨나요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테스트하다가 지우지 않은 흔적으로 보입니다..;

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.rest-assured:rest-assured:5.3.1'
// DB
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'

//jwt
implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation 'javax.xml.bind:jaxb-api:2.3.1'

}

test {
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/Hello/Hello.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package Hello;

public class Hello {
String name;
Printer printer;

public Hello() {
}

public Hello(String name, Printer printer) {
this.name = name;
this.printer = printer;
}

public void setName(String name) {
this.name = name;
}

public void setPrinter(Printer printer) {
this.printer = printer;
}

public String sayHello() {
return "Hello " + this.name;
}

public void print() {
this.printer.print(sayHello());
}
}
8 changes: 8 additions & 0 deletions src/main/java/Hello/Printer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package Hello;

public class Printer {

public void print(String data) {
System.out.println(data);
}
}
5 changes: 5 additions & 0 deletions src/main/java/Hello/StringPrinter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package Hello;

public class StringPrinter extends Printer{

}
25 changes: 25 additions & 0 deletions src/main/java/jdbctest/Customer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package jdbctest;

public class Customer {
private long id;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

자바엔 long과 Long이 있습니다. 둘은 무엇이 다르고 각각 어떤 장단점이 있나요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrapper 클래스의 차이는 null값을 가질 수 있냐, 없냐 로 알고 있는데, 좀더 자세히 알아보고 정리해보겠습니다.

long 과 Long 타입의 차이

1. long은 원시(Primitive) 타입, Long 타입은 참조(Reference) 타입

  • 원시 타입은 변수에 데이터 자체를 저장함.

  • 참조 타입은 변수에 데이터가 저장된 주소를 저장함.

  • 원시타입은 null 할당 불가능, 참조타입은 null값 할당 가능

  • 원시타입은 값이 stack에 저장됨.

  • 참조 타입은 주소가 stack에 저장되고, 실제 데이터는 heap에 저장되기 때문에, 원시타입이 더 성능상의 이점이 있음.

  • 보통 엔티티의 Id 가 Long 타입인 이유는, null 값이 배정될 수 있는 편이 여러모로 편하기 때문

  • 의문이 생긴 부분1 : 출처에서 원시타입은 실제 메모리에 값을 저장함 이라고 했는데, 참조 타입도 실제 메모리에 값을 저장하지 않나?

  • 의문이 생긴 부분2 : heap과 stack?


추가 조사: heap과 스택

자바 프로그램 실행시, JVM(자바가상머신)은 하드웨어로부터 전달받은 메모리를 특정 부분들로 나눈다.

메모리 공간(Runtime Data Area)

image
  • Method(static) 영역
  • Stack 영역
  • Heap 영역
    데이터 타입(자료형)에 따라 각 영역에 나눠서 할당 되게 된다.

자바 변수의 종류

  1. 클래스 변수 (클래스 영역에서 static 붙는 변수)
  2. 인스턴스 변수 (클래스 영역에서 static 아닌 변수, 인스턴스 더이상 참조되지 않으면 GC에서 처리함.)
  3. 지역 변수 (메서드 내에서 선언되고 메서드 수행 끝나면 소멸되는 변수)
  4. 매개 변수 ( 메서드 호출 시 '전달하는 값을 가지고 있고, 선언된 부분 부터 수행이 끝날때까지 유효함)

각 변수의 생성 시기
클래스변수 : 클래스가 메모리에 올라갈 때
인스턴스변수 : 인스턴스가 생성되었을 때
지역변수 / 매개변수 : 위치하고 있는 메서드가 수행되었을 때

Method(static) 영역

  • JVM이 동작해서 클래스가 로딩될 때 생성.
  • JVM이 읽어들인 클래스와 인터페이스 대한 런타임 상수 풀, 멤버 변수(필드), 클래스 변수(Static 변수), 상수(final), 생성자(constructor)와 메소드(method) 등을 저장하는 공간.
  • Method(Static) 영역에 있는 것은 어느곳에서나 접근 가능
  • Method(Static) 영역의 데이터는 프로그램의 시작부터 종료가 될 때까지 메모리에 남아있다. 그래서 static 메모리에 있는 데이터들은 프로그램이 종료될 때까지 어디서든 사용이 가능하다.그러나 static 데이터를 무분별하게 많이 사용할 경우 메모리 부족 현상이 일어날수 있게 된다.
image

Stack 영역

  • 메소드 내에서 정의하는 기본 자료형에 해당되는 지역변수의 데이터 값이 저장되는 공간
  • 메소드가 호출될때 스택 영역에 스택 프레임이 생기고 그안에 메소드를 호출
  • primitive 타입의 데이터(int, double, byte, long, boolean 등) 에 해당되는 지역변수, 매개 변수 데이터 값이 저장 
  • 메소드가 호출 될 때 메모리에 할당되고 종료되면 메모리에서 사라짐
    Stack 은 후입선출 LIFO(Last-In-First-Out) 의 특성을 가지며, 스코프(Scope) 의 범위를 벗어나면 스택 메모리에서 사라진다.

스택 프레임(stack frame)
하나의 메서드에 필요한 메모리 덩어리를 묶어서 스택 프레임 이라고 한다.
하나의 메서드 당 하나의 스택 프레임이 필요하며, 메서드를 호출하기 직전 스택 프레임을 자바 Stack 에 생성하고 메서드를 호출한다.
메서드의 매개 변수, 지역변수, 리턴값을 스택 프레임에 쌓는다.
메서드 호출범위가 종료되면 스택에서 제거된다.(가장 최근에 쌓였으니, 삭제하기도 편할듯)

Heap 영역

  • JVM이 관리하는 프로그램 상에서 데이터를 저장하기 위해 런타임 시 동적으로 할당하여 사용하는 영역
  • 참조형(Reference Type) 데이터 타입을 갖는 객체(인스턴스), 배열 등이 저장 되는 공간
  • 단, Heap 영역에 있는 오브젝트들을 가리키는 레퍼런스 변수는 stack에 적재
  • Heap 영역은 Stack 영역과 다르게 보관되는 메모리가 호출이 끝나더라도 삭제되지 않고 유지된다.그러다 어떤 참조 변수도 Heap 영역에 있는 인스턴스를 참조하지 않게 된다면, GC(가비지 컬렉터)에 의해 메모리에서 청소된다. => 예시는 상세하나 잘 이해되지 않음.
  • stack은 스레드 갯수마다 각각 생성되지만, heap은 몇개의 스레드가 존재하든 상관없이 단 하나의 heap 영역만 존재

Heap과 Stack의 차이점

  • 힙 메모리는 애플리케이션의 모든 부분에서 사용되며, 반면에 스택 메모리는 하나의 스레드가 실행될 때 사용. 그래서 힙 과 메서드 공간에 저장된 객체는 어디서든지 접근이 가능하지만, 스택 메모리는 다른 스레드가 접근할 수 없다.
  • 언제든지 객체가 생성되면 항상 힙 공간에 저장되며, 스택 메모리는 힙 공간에 있는 객체를 참조만 한다.즉, 스택 메모리는 primitive 타입의 지역변수와 힙 공간에 있는 객체 참조 변수만 갖고 있다.
  • 스택메모리의 생명주기는 매우 짧으며, 힙 메모리는 애플리케이션의 시작부터 끝까지 살아남는다.
  • 자바 코드를 실행할때 따로 -Xms과 -Xmx 옵션을 사용하면 힙 메모리의 초기 사이즈와 최대 사이즈를 조절할 수 있다.
  • 스택 메모리가 가득차면 자바에서는 java.lang.StackOverFlowError를 발생.힙 메모리가 가득차면 java.lang.OutOfMemoryError : Java Heap Space 에러를 발생
  • 스택 메모리 사이즈는 힙 메모리와 비교했을 때 매우 적다. 하지만 스택 메모리는 간단한 메모리 할당 방법(LIFO)를 사용하므로 힙 메모리보다 빠르다. ==> 추가 질문: 힙메모리는 어떤 메모리 할당방법을 사용할까?

출처:

private String firstName;
private String lastName;

public Customer(long id, String firstName, String lastName) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}

public long getId() {
return id;
}

public String getFirstName() {
return firstName;
}

public String getLastName() {
return lastName;
}
}
41 changes: 41 additions & 0 deletions src/main/java/jdbctest/CustomerController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package jdbctest;

import org.springframework.http.ResponseEntity;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class CustomerController {
private JdbcTemplate jdbcTemplate;

public CustomerController(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

@PostMapping("/customers")
public ResponseEntity<Void> save(@RequestBody Customer customer) {
String sql = "INSERT INTO customers(first_name, last_name) VALUES (?,?)";
jdbcTemplate.update(sql, customer.getFirstName(), customer.getLastName());
return ResponseEntity.ok().build();
}

@GetMapping("/customers")
public ResponseEntity<List<Customer>> list() {
String sql = "select id, first_name, last_name from customers";
List<Customer> customers = jdbcTemplate.query(
sql, (resultSet, rowNum) -> {
Customer customer = new Customer(
resultSet.getLong("id"),
resultSet.getString("first_name"),
resultSet.getString("last_name")
);
return customer;
});
return ResponseEntity.ok().body(customers);
}
}
35 changes: 35 additions & 0 deletions src/main/java/jdbctest/DemoApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package jdbctest;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.jdbc.core.JdbcTemplate;

@SpringBootApplication
public class DemoApplication implements CommandLineRunner {

public static void main(String args[]) {
SpringApplication.run(DemoApplication.class, args);
}

@Autowired
JdbcTemplate jdbcTemplate;

@Override
public void run(String... strings) throws Exception {

jdbcTemplate.execute("DROP TABLE customers IF EXISTS");
jdbcTemplate.execute("CREATE TABLE customers(id SERIAL, first_name VARCHAR(255), last_name VARCHAR(255))");

List<Object[]> splitUpNames = Arrays.asList("John Woo", "Jeff Dean", "Josh Bloch", "Josh Long").stream()
.map(name -> name.split(" "))
.collect(Collectors.toList());

jdbcTemplate.batchUpdate("INSERT INTO customers(first_name, last_name) VALUES (?,?)", splitUpNames);

}
}
65 changes: 65 additions & 0 deletions src/main/java/roomescape/Domains/Auth/LoginController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package roomescape.Domains.Auth;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.Map;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LoginController {

final private String secretKey = "Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E=";
private final UserRepository userRepository;

public LoginController(final UserRepository userRepository) {
this.userRepository = userRepository;
}

@PostMapping("/login")
public final ResponseEntity<String> login(@RequestBody Map<String, String> credentials) {
String password = credentials.get("password");
String email = credentials.get("email");
String name = "어드민";

this.userRepository.save(new User(name, email, password));

Claims claims = Jwts.claims().setSubject(name);

Date now = new Date();
Date expiryDate = new Date(now.getTime() + 1000L * 60 * 60); // 토큰 만료 시간: 1시간 후

String accessToken = Jwts.builder().setClaims(claims).setIssuedAt(now).setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS256, secretKey).compact();

return ResponseEntity.ok().header("Set-Cookie", "token=" + accessToken + ";").build();
}

@GetMapping("/login/check")
public final ResponseEntity<Map<String, String>> loginCheck(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
String token = getToken(cookies);
String memberName = String.valueOf(Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody().getSubject());
return ResponseEntity.ok().body(Map.of("name", memberName));
}

private static String getToken(final Cookie[] cookies) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("token")) {
return cookie.getValue();
}
}
return "";
}

}
49 changes: 49 additions & 0 deletions src/main/java/roomescape/Domains/Auth/User.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package roomescape.Domains.Auth;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import roomescape.Domains.Time.Time;

@Entity
@Table(name = "customer")
public class User {

protected User() {
}

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

private String name;
private String email;
private String password;

public User(final String name,final String email, final String password) {
this.name = name;
this.email = email;
this.password = password;
}

public Long getId() {
return id;
}

public String getName() {
return name;
}

public String getEmail() {
return email;
}

public String getPassword() {
return password;
}

}
7 changes: 7 additions & 0 deletions src/main/java/roomescape/Domains/Auth/UserRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package roomescape.Domains.Auth;

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {

}
55 changes: 55 additions & 0 deletions src/main/java/roomescape/Domains/Reservation/Reservation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package roomescape.Domains.Reservation;

import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToOne;
import roomescape.Domains.Time.Time;

@Entity
public class Reservation {

protected Reservation() {
}

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

private String name;
private String date;
@ManyToOne
@JoinColumn(name = "time_id")
private Time time;

public Reservation(final String name, final String date, final Time time) {
this.name = name;
this.date = date;
this.time = time;
}

public Long getId() {
return id;
}

public Time getTime() {
return time;
}


public String getName() {
return name;
}

public String getDate() {
return date;
}

public void setId(final Long id) {
this.id = id;
}
}
Loading