학습할 내용
- REST API 핵심 기능
- URI의 Resource에 대해 알아보기
(ex :/users,/users/{id}/posts.. etc )
- 상호작용하는
Http Request Method에 대해 알아보기
(ex :GET, POST, PUT, DELETE.. etc)
JSONformat의Request와Response의 구조 정의 학습하기
。Http Body와Http Header구조 학습
- Request 시 알맞은
Http Status Code을 설정하여 Client에 Status Code 반환하기
(ex : 200, 404, 500 ... etc)
Security , Validation, Exception Handling학습하기.
- REST API 고급 기능
Internationalization:
。Accept-Language Header를 통한 Message의 국제화(i18n, internationalization ) 기능을 제공하면서 다국어 처리하는 방법을 정의.
HATEOAS( Hypermedia as the Engine of Application State )
。API의 Response에서 Resource에 관한 link를 포함하여 Client가 동적으로 API를 탐색할 수 있도록 하는 개념.
▶ REST API를 이용하는 Client가 Server와 동적 상호작용이 가능하도록 하는것.
Versioning:
。API가breaking change하더라도 Client의 사용성을 유지하도록 기존Version을 사용하거나 새로운 유형의 API가 추가될 경우 Client가 원할 때 새 버전의 API로 변경할 수 있는 유연성을 확보.
Documentation:
。REST API를 문서화하여 개발자가 쉽게 이해하고 테스트할 수 있도록함. (swagger,OAS)
Content Negotiation:
。Client와 Server 간HTTP Transaction에서 Client가header를 설정하여 원하는 Language 또는 Data format(JSON,XML,HTML등 )을 협상하여 Server가 적절한Response를 제공하는 과정.
Static Filtering
Filtering: Spring Bean의 Field 중Http Response Body에서 선택된 Field만 반환하도록 Customizing
。동일한 Spring Bean에 대해 각각의 다른 REST API 에 대해서도 동일한 filtering을 적용.
Dynamic Filtering
。특정 REST API의 Spring Bean에 대해서 Filtering을 Customizing.
Monitoring
。Spring Boot Actuator을 활용하여 API를 Monitoring.
- REST API를 DB에 연결하는 방법
JPA와Hibernate를 활용하여 H2 , MySQL DB 연결하기
- 번외
Spring Security로Authentication구현
JPA와 Hibernate를 활용하여 DB에 연결하기.
。H2 DB(In-Memory DB)로 선행적으로 CRUD를 수행 후 DB로 전환하기.
DB관련 학습내용 , H2-DB 초기설정
。dependency의 경우 Spring Project를 생성 시 이미 정의했으나 필요한 경우h2,jpa를 정의하기.
▶JPA와Hibernate를 생성할 경우 필요.<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <!--jar파일의 일부에 h2 db가 포함되지 않도록 scope를 설정.--> <scope>runtime</scope> </dependency>。이후
h2-console을 사용 설정 및 동적 JDBC URL을 정적 JDBC URL로 변경spring.h2.console.enabled=true spring.datasource.url=jdbc:h2:mem:testdb
- JPA를 통해 Bean을 DB Table에 Mapping
@Entity(name="DBTable명"): JPA 관련 학습내용
。Spring JPA에 의해 해당 Annotation이 선언된 Spring Bean을 특정DB Table과 Mapping
。Spring BootAuto Configuration에 의해@Entity가 발견 될 경우, 자동으로 H2 DB에 Table을 생성.
。JPA의@Entity는 반드시 기본 생성자(no-arg constructor)를 지녀야한다.@Entity(name="user_details") public class User { @Id // Pk값을 자동 할당. @GeneratedValue private int id; // JPA 정의를 위해 생성한 기본 생성자 public User(){} @Size(min=2, message = "Name should have at least 2 characters.") @JsonProperty("user_name") private String name; @Past(message = "Birth Date should be in the past.") @JsonProperty("birth_date") private LocalDate birthDate; public User(int id, String name, LocalDate birthDate) { this.id = id; this.name = name; this.birthDate = birthDate; }
user_details이름의 table이H2 DB상에서 생성됨.
@JsonProperty("JSON속성이름"):
。Jackson라이브러리에서 제공하는 Annotation으로서JSON과 Java 객체(field) 간 Mapping을 Control.
▶EntityClass의 field에 선언 시JSON Format으로 직렬화(Serialization)하거나,JSON Format을 field로 역직렬화(Deserialization)를 수행할 때@JsonProperty("JSON속성이름")을 통해 field의JSON속성명(key)을 임의로 설정.
。Spring Bean에 정의된@JsonProperty에 의해 Field명이 변경될 경우 변경된 Field명으로Http Request Body로 전송.
@JsonIgnore
。Jackson라이브러리에서 제공하는 Annotation으로서JSON의 직렬화/ 역직렬화 시 특정 field를 제외하는 Annotation.
▶ Response할JSON data에서 선언한 데이터를HTTP Response에서 제외.
src/main/resources에 초기화 데이터가 작성된data.sql을 작성.
。data.sql:
DB Table에 삽입할 초기 데이터를 정의하는 파일
。schema.sql:
DB Schema를 정의하는 파일.insert into user_details(id,name,birth_date) values(1,'wjdtn1',CURRENT_DATE()), (2,'wjdtn2',CURRENT_DATE()), (3,'wjdtn3',CURRENT_DATE()), (4,'wjdtn4',CURRENT_DATE());。
UserClass의birthDatefield는 DB상에서 대,소문자 구분을 위해 임의로birth_date로 명명되어 Table에 정의됨.
。이때DataSource의 초기화를 지연하면서 JPA의Auto-Configuration을 통해 Table이 생성된 이후(hibernate초기화 이후 )data.sql이 실행되도록 변경하기위해 다음 구문을 선언spring.jpa.defer-datasource-initialization=true
。h2-console에서@Entity을 선언함으로써 JPA의Auto-Configuration에 의해 DB상에서 자동 생성된 Table에data.sql에서 정의한 데이터가 반영됨을 확인 가능.
JpaRepository를 상속하는UserRepository생성. Spring JPA
。JpaRepository<DBEntity class, id data Type>를 상속하는 Interface를 생성.import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository<User,Integer> {}
- 기존의 Static Todo List가 아닌
Spring JPA에 의해DB Table과 Mapping된 Controller Class 구축하기.
。Controller Class의 기존의 Static List가 정의된 Class( =UserDaoService) 객체를UserRepository객체로 대체한 후 코드 수정.
。findById()method 사용 시JpaRepository<>interface의 객체로 변경함에 따라 해당 data type을Optional<User>로 설정해야한다.
▶ 또는findById().get()으로UserClass의 instance로 가져올 수 있다.
JpaRepository의findById()사용 시Optional<T>로 return하는 이유?
▶ 조회한DB Entity가 존재하지 않을 수 있으므로 (null방지용 )import jakarta.validation.Valid; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import java.net.URI; import java.util.List; import java.util.Optional; @RestController public class UserResource { UserRepository userRepository; // @Autowired Spring Context가 생성자 기반 Denpendency Injection을 수행. public UserResource(UserRepository userRepository) { this.userRepository = userRepository; } // GET Method 구현 // GET /users : 모든 사용자 조회 @GetMapping(path="/jpa/users") public List<User> ListAllUsers(){ List<User> users = userRepository.findAll(); return users; } // GET /users/{id} : 특정 사용자 조회 @GetMapping(path="/jpa/users/{id}") public EntityModel<User> GetUserById(@PathVariable int id){ // JpaRepository객체.findById() method 사용 시 // return 되는 data type을 Optional<Bean>로 설정. Optional<User> user = userRepository.findById(id); // User user = userRepoistory.findById(id).get(); if(user.isEmpty()){ // 해당 id가 존재하지 않는 경우 발생. throw new UserNotFoundException("id :" + id); } // HATEOAS EntityModel<User> entityModel = EntityModel.of(user.get()); WebMvcLinkBuilder link = linkTo(methodOn(this.getClass()).ListAllUsers()); entityModel.add(link.withRel("all-users")); return entityModel; } // POST Method 구현 // POST /users : 새로운 사용자 생성 @PostMapping(path="/jpa/users") public ResponseEntity<User> createUser(@Valid @RequestBody User user) { // @RequestBody : HttpRequest의 Body(=JSON Format)를 Bean으로 변환하여 저장. User savedUser = userRepository.save(user); //REST API에서 알맞은 Response Status를 Client에게 반환하는 코드 URI locationHeader = ServletUriComponentsBuilder.fromCurrentRequest() .path("/{id}").buildAndExpand(savedUser.getId()).toUri(); // 201 : created status에 해당하는 ResponseEntity<User> 객체를 생성하여 반환. return ResponseEntity.created(locationHeader).build(); } @DeleteMapping(path="/jpa/users/{id}") public void DeleteUserById(@PathVariable int id){ userRepository.deleteById(id); } }
REST API의 Test는API Tester를 이용.
。GETRequest의 경우 브라우저를 통해 Mapping된 URL에 접속이 가능하지만GET이외의 경우API Tester를 이용.
JpaRepository<>interface가 제공하는 Method : Spring JPA
Optional<T>:
。null값을 직접 다루는 대신 값이 존재할 수 있고 없을 수 있는 상황을 명확히 표현하는데 사용하는 Class
。Repository객체.findById(id)를 통해Optional<해당Bean>생성이 가능.
▶Optional객체.get()을 통해 해당 Spring Bean을 획득 가능.
optional객체.isEmpty():
。해당optional객체가null인지 여부를 알려주는 역할을 수행하는 method.Optional<User> user = userRepository.findById(id); if (user.isEmpty()){ throw new UserNotFoundException("id :" + id); }else{ return user.get().getPosts(); }▶ 보통 해당 method를 활용하기위해 `Repository객체.findById(id).get()`으로 Spring Bean을 직접 가져오기보다는 `Repository객체.findById(id)`를 통해 `Optional<T>` type으로 가져오는걸 선호.
POSTMethod로Http Request Body를 전송하여 Spring Bean instance를JpaRepository<>를 통해 Spring Bean과 Mapping된 DB Table에 추가
。Content Negotiation을 통해 Client이 요구하는XMLtype으로 Data를 전달 시XML dataformat을 Application에서 정의하기 위해jackson-dataformat-xmldependency를 추가
。Client가 필요한 데이터를 Request하기 위해Http Request Body를@RequestBody에 의해Controller Method의 매개변수로 Spring Bean instance로서JSON Format으로 전송해야하므로,Content-Type Header를application/json로 설정하여 Request URL로 전송.
。Spring Bean에 정의된@JsonProperty에 의해 Field명이 변경될 경우 변경된 Field명으로Http Request Body로 전송.{ "user_name" : "fa50", "birth_date" : "1998-03-04" }
。"id"field 는 Spring Bean Class field에 선언된@GeneratedValue를 통해 첫번째 URL로 Request를 보낼 경우id=1로서 자동 생성되며, 만약id=1로서 기존 Spring Bean과 겹치는 경우Http status code:500(웹 애플리케이션 코드의 버그:Spring JPA의 SQL오류)이 반환되며 한번 더 API 전송 시 다음 숫자인id=2로 인가되어 전달.
▶ URL을 통해 Request를 전달 시 이미 Table 상에 존재하는id=[1,4]에 대해서는500을 반환하고 그 이후는201을 반환.
。H2-console로 조회 시 성공적으로 값이 들어가있음을 확인 가능.Header
Accept과Content-Type의 차이점 Content-Negotiation
。둘 다 데이터타입(MIME)을 다루는 헤더.
Accept:
。Client가 서버에게AcceptHeader로 특정 data type을 설정하여 전송 시 Client가 보낸 Header의 특정 data type으로만 response 해야하는 규약 설정.Content-Type:
。현재 전송하는 data가 어떤 type인지만 설명.
- 전 단계의 REST API 기능 :
。DB에서 모든 / 단일User조회
。User생성 / 삭제
- 현 단계의 목표 :
REST API를 구축하여 특정 사용자의 모든 게시물을
검색 (GET/users/{id}/posts) ,
생성 (POST/users/{id}/posts),
삭제 (DELETE/users/{id}/posts)
연관관계 Mapping :
。JPA에서DB Entity간의 연관관계를 설정.
。연관관계의 자식은FK( 외래키 )가 있는쪽으로 설정해야한다.
- 연관관계 방향 :
객체지향 모델링에서는 구현하고자 하는 서비스에 따라서 단방향 / 양방향 관계인지 설정해야한다.
。단방향 관계 : 한 쪽의DB Entity만 참조
ex: Member에서 Team의 정보를 가져올 수 있지만, 반대는 불가능.
。양방향 관계 : 양 쪽의DB Entity가 서로 참조
ex: Member와 Team 각 table에서 상대 table의 정보를 가져올 수 있음.
。데이터베이스(RDBMS)는 관계만 설정하면 자동으로 양방향 관계가 되므로 어느 Table이든 Join을 수행하면 원하는 Data를 가져오지만,JPA는 양방향 관계를 맺어야 가져올 수 있음!
JPA의DB Entity간 연관관계 Mapping의 Annotation 종류 :
。@Entity를 선언하여DB Entity를 정의한 Spring Bean에서 사용.
。DB Entity간 연관관계를 Mapping시 부모 Entity를 기준으로 Mapping을 수행.
。연관관계의 자식 Entity는 FK가 있는쪽으로 설정해야한다.
@JoinColumn(name = "Mapping 할 Entity의 외래키 Field명"):
。JPA에서Entity간 연관관계 Mapping 시 자식 Entity의FK를 지정하는 Annotation.
。1:N양방향 관계 설정 시 부모 Entity에서 선언.
@JoinColumn예시@Entity public class Order { @Id @GeneratedValue private Long id; @ManyToOne @JoinColumn(name = "user_id") // 외래 키 이름 지정 private User user; // Getter, Setter 생략 }。다음의 경우
OrderEntity의userfield와UserEntity의user_idfield가N:1연관관계를 가진다.
▶UserEntity의user_id가FK로 활용됨.
@OneToMany
。DB Entity간 양방향 관계 설정 시 부모 Entity에서1:N관계를 Mapping할 때 자식 Entiy에 의해pk로서 참조되는fkfield에서 선언하는JPAAnnotation
@OneToMany(mappedBy="자식 Entity의 Mapping된 FK Field명"):
。연관관계의 주인을 지정하여 외래키를 어느 Entity에서 관리할지 결정.
▶ 기본적으로 자식테이블에 외래키 Column이 생성되므로,mappedBy속성은 부모 Entity에서@OneToMany의 속성으로서 선언됨.
。양방향 Mapping 시 양쪽 Entity에서 FK Column을 중복생성하지 않도록 방지하는 역할 수행.
@ManyToOne
。DB Entity간 양방향 관계 설정 시 자식 Entity에서1:N관계를 Mapping할 때 부모 Entity의 외래키를 참조하는 field에 선언하는JPAAnnotation
@ManyToOne(fetch = FetchType.LAZY):
。연관관계의lazily loaded(지연로딩),eagerly fetched(즉시로딩 ,1:N에서 Default)의 여부를 결정.
FetchType.EAGER로 설정 시 동일한 query에서 특정 Entity를 참조하면 Mapping된 Entity도 같이 참조하는 기능 수행.
FetchType.LAZY의 경우 동일 query에서 특정 Entity만 참조가능하고 Mapping된 다른DBEntity를 참조하지 못함.
@OneToOne1:1
@ManyToManyN:M:
。실무에서는 사용하지 않는다.
▶1:NorN:1을 통해 중간에Mapping Table을 구현하여 처리.
- 게시물 역할의 Entity 생성
。연관관계의 자식은 FK가 있는쪽으로 설정해야한다.
- DB의 Table과 Mapping 하도록
@Entity를 정의한 게시물 Entity 생성.
。User와PostEntity 간에 연관관계 Mapping을 부여하며 서로1:N관계설정.
▶ 자식 Entity (Post) instance에@ManyToOne(fetch=FetchType.LAZY)를 정의하여UserEntity를 참조 시 Mapping된 자식Post의 Detail을 가져오지 않도록 설정.
。JPA의Auto-Configuration을 통해 자식 (Post) Entity를 DB Table로 자동 생성 시 부모 (User) Entity의PK(Id)를 참조하는FK(user_id) column이 자동 생성import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; // 자식 DB Entity @Entity public class Post { @Id @GeneratedValue private int id; private String description; @ManyToOne(fetch= FetchType.LAZY) @JsonIgnore // Response할 `JSON data`에서 선언한 데이터를 `HTTP Response`에서 제외 private User user; public int getId() {return id;} public void setId(int id) {this.id = id;} public String getDescription() {return description;} public void setDescription(String description) {this.description = description;} }
UserEntity에는Postinstance들을 저장하는List를 정의.
。UserEntity 기준 자식 EntityPost와1:N관계이므로@OneToMany(mappedBy="자식 Entity에서 FK로서 mapping되는 Field명")을 field에 선언.
▶ 이를 Mapping하는 자식 Entity(Post)의userfield에는 이와 반대인@ManyToOne을 선언.
。User의 Spring Bean에서post를 가져오기위해UserClass의posts field에 대해서 getter와 setter를 정의!
。각DB Entity는 각각 서로REST API의HTTP Response의 일부로 사용.
▶UserClass에서 해당 API를GET Request를 할 경우postfield가HTTP JSON Response에 포함하면 안되며 마찬가지로PostClass의Userinstance를HTTP JSON Response에 포함하면 안되므로@JsonIgnore를 선언.
@JsonIgnore: Response할JSON data에서 선언한 데이터를HTTP Response에서 제외.@OneToMany(mappedBy="user") @JsonIgnore private List<Post> posts; public List<Post> getPosts() {return posts;} public void setPosts(List<Post> posts) {this.posts = posts;} }。이때 다음 구문을
application.properties에 입력 시 JPA의 Background 상 SQL query를 콘솔에서 확인 가능.
JpaRepository method(save, findById 등) 사용 시, Background에서 어떤 query가 생성되는지 확인spring.jpa.show-sql=true
。다음처럼 JPA를 통해 2개의 table이 생성된 sql query를 확인 가능.
。posttable에서는UserEntity의 PK인Id를 참조하는 FKuser_idcolumn이 자동생성되있음.
=> 해당user_id는 게시물과 특정 사용자를 연결 시 활용됨.
data.sql을 통해PostDB Table에 초기값 data 정의.insert into post(id,user_id,description) values(20001,1,'I want to learn AWS'), (20002,2,'I want to learn DevOps'), (20003,3,'I want to learn Certificate'), (20004,4,'I want to learn MultiCloud');。
user_id는User의id를 참조하기위해JPA에 의해 자동생성된 외래키이므로 해당 id의 domain의 값 범위에 있어야한다.
。초기 Data가 성공적으로 들어가있으며 외래키로서도 정의되어 사용이 가능.
특정
User의 모든Post와 상호작용하는 API를 Controller Method로 구현
。기존의UserSpring Bean 관련 API를Spring Data JPA를 이용해 관리하는 Controller Class( =UserResource)에PostSpring Bean 관련 API를 생성하여 함께 관리되도록 설정.
- 특정
Userinstance에 Mapping된 모든Postinstance를 가져오는 API를 Controller Method로 구현
。GET /users/{id}/posts
。HTTP Request를 통해 Mapping된 URL에User의 id를 포함하여 Application으로 전송 시@PathVariable을 통해 매개변수에Userinstance의Id를 가져온 후 해당Userinstance와 Mapping된List<Post> postsinstance를 return 하여PostDetail을 가져오기.// 특정 User와 Mapping된 Post 가져오기 @GetMapping(path="/jpa/users/{id}/posts") public List<Post> GetPostsByUserId(@PathVariable int id){ Optional<User> user = userRepository.findById(id); if (user.isEmpty()){ throw new UserNotFoundException("id :" + id); }else{ // 해당 User instance에 Mapping된 List<Post> posts instance를 return. return user.get().getPosts(); } }。
Optional객체.isEmpty()를 활용하기 위해Repository객체.findById(id).get()으로 Spring Bean을 직접 가져오는게 아닌userRepository.findById(id)으로Optional객체를 return.
。Id = 1에 해당하는UserSpring Bean에List<Post>객체를 return하여FK로서 Mapping된 모든Postinstance를 가져오게됨.
▶ 해당List<Post>객체가 포함하는Postinstance는 연관관계 Mapping이 설정되어FKuser_id=1인Postinstance를 포함.
。해당List<Post>객체는UserSpring Bean을HTTP Response할때@JsonIgnore에 의해 포함되지않는다.
- 특정
User의 특정Post를 가져오는 API를 Controller Method로 구현
GET /users/{userid}/posts/{postid}
。HTTP Request를 통해 Mapping된 URL에 특정User의 id와Post의 ID를 포함하여 Application으로 전송 시@PathVariable으로 매개변수로 가져온 후 해당 ID에 해당하는User의Post불러오기.// 특정 User와 Mapping된 특정 Post 가져오기 @GetMapping(path="/jpa/users/{userid}/posts/{postid}") public List<Post> GetPostsByUserIdAndPostId(@PathVariable int userid,@PathVariable int postid){ Optional<User> user = userRepository.findById(userid); if (user.isEmpty()){ throw new UserNotFoundException("id :" + userid); }else{ List<Post> posts = user.get().getPosts(); posts.removeIf(post -> post.getId() != postid); return posts; } }
。localhost:8080/jpa/users/1/posts/20001입력 시 다음처럼 정상적으로id=1User에 속하는id=20001의Post가 도출됨.
- 특정 User의 Post를 생성하는 API 생성하기
。POST /users/{id}/posts
JpaRepository<Entity,ID Field>를 상속하는 Interface 구축
。Post을 저장하기위한 Repository를 구축하기위한 용도
▶User관리를 위한UserRepositoryInterface와 동일한 용도.import org.springframework.data.jpa.repository.JpaRepository; public interface PostRepository extends JpaRepository<Post, Integer> { }
POST를 통해Post를 생성하는 Controller method 구현
。PostRepositoryinstance를 생성 및 생성자 의존성주입.UserRepository userRepository; PostRepository postRepository; // @Autowired Spring Context가 생성자 기반 Denpendency Injection을 수행. public UserResource(UserRepository userRepository, PostRepository postRepository) { this.userRepository = userRepository; this.postRepository = postRepository; }。
PostSpring Bean의User userinstance를 참조하기 위해서 Getter , Setter 를 정의.@ManyToOne(fetch= FetchType.LAZY) @JsonIgnore private User user; public User getUser() { return user; } public void setUser(User user) { this.user = user; }。특정 id의
Userinstance를 찾은 후 생성할PostSpring Bean의User user에 해당Userinstance를 설정.
@Valid:POST HTTP Request Body와 Data Binding 전 유효성검증
@RequestBody:POST HTTP Request Body의 Data를 Data Binding하여 매개변수의 Java 객체로 변환하여 저장.
@PathVariable: URL의PathVariable을 매개변수의 변수로 생성.
。ResponseEntity 관련// 특정 User에 Mapping된 Post 생성 @PostMapping(path="/jpa/users/{id}/posts") public ResponseEntity<Post> CreatePost(@PathVariable int id, @Valid @RequestBody Post post){ // 특정 User instance 조회 Optional<User> user = userRepository.findById(id); if (user.isEmpty()){ throw new UserNotFoundException("id :" + id); }else{ // HTTP Request Body와 Data Binding된 Post instance의 User field를 // 상단에서 불러온 User instance로 설정. post.setUser(user.get()); // 이후 해당 Post instance를 DB Table에 저장. Post savedPost = postRepository.save(post); // ResponseEntity를 이용한 REST API에서 알맞은 Response Status 반환하기 URI locationHeader = ServletUriComponentsBuilder.fromCurrentRequest() .path("/{id}").buildAndExpand(savedPost.getId()).toUri(); return ResponseEntity.created(locationHeader).build(); } }。URL에 포함된 ID에 해당하는
Userinstance를 DB Table에서 조회.
。이후 Client가POST HTTP Request Body를 전송 시@RequestBody를 통해Postinstance로 DataBinding한 후
해당 instance의User userfield를 사전에 조회한Userinstance를 setter로 설정 후JpaRepository.save(post객체)로 DB Table에 저장.
▶ 최종적으로 해당Userinstance를 가지는Postinstance가 생성됨.
▶ Client는 POST Method를 통해JSON Format으로 data를 입력하여 해당 API를 통해HTTP Request Message를 전달.
。Location Header,ResponseEntity를 활용해 Application에서 Spring Bean(Post) Instance 생성되었는지 알려주는 기능 추가.
。API Tester를 이용해HTTP Request Body를 작성하여 해당 API로POST전.
▶Location Header를 통해 Client에게Post가 해당 id로 정상적으로 생성되었음을 지시.
。201: Created :POST로 성공적으로 새로운 Resource가 생성됨.
。최종적으로 DB의PostTable에 해당 URL의PathVariable를 통해 얻은Userinstance의 id를foreign key로서 생성된user_id열로 가지는Postinstance가 생성되며 POST의primary key인 id는@GeneratedValue에 의해 자동 생성.
。GET으로http://loca lhost:8080/jpa/users/10001/posts검색시에도 해당 post의 대한 정보가 추가되어있음.
JpaRepositorymethod를 통해 Background에서 발생하는 SQL Query 분석하기
。spring.jpa.show-sql=true를 통해JpaRepository객체.save()같은 method에 해당하는 SQL을 Console에서 확인이 가능.
。Client가 Application에게HTTP Request Method를 포함하여HTTP Request Message를 전송 시 URL과HTTP Request Method에 Mapping된 Controller Method를 실행 및 해당 Method 내부의JpaRepositorymethod의 SQL 구문을 확인.
。console에 나온 log로 분석하면,@PathVariable int id를 활용하여select문을 통해 특정 user 객체가 생성됨.Hibernate: select u1_0.id,u1_0.birth_date,u1_0.name from user_details u1_0 where u1_0.id=? Optional<User> user = userRepository.findById(id);。
@RequestBody Post post를 통해 생성된 post객체의 user field를 위 과정에서 생성된 user 객체로 설정 후insert문을 통해 post에 저장.Hibernate: insert into post (description,user_id,id) values (?,?,?) postRepository.save(post);。
postRepository는JpaRepository<Post,Integer>를 상속하므로, 해당 Post Bean의 인자를 통해@Entity로 생성된 DB의 post table에insert를 통해 저장됨.
- 특정 Post를 삭제하는 메소드 구현하기.
@DeleteMapping(path="/jpa/posts/{id}") public void DeletePostById(@PathVariable int id){ postRepository.deleteById(id); }
。영속성 DB와 연결하는것이므로 H2와 같은 In-Memory DB처럼 Application을 재시작하면 Data가 초기화되는것이 아니기 때문에 초기값 설정용 data.sql , schema.sql을 정의 안해도된다.
▶ DB 사용 시 해당 .sql파일을 만들 필요는 없다.
。H2 DB 등 Spring JPA를 통해 구현한 DB Entity의 구현내용을 재활용하여 PostgreSQL DB에도 적용이 가능하다.
▶ 。Spring JDBC , Spring JPA를 사용할 경우 PostgreSQL DB , MySQL DB , H2-DB 등에 관계없이 구축된 DB Entity을 매우 쉽게 적용할 수 있다.
▶ Spring JDBC의 JdbcTemplate Class를 이용하여 SQL을 하드코딩하여 PostgreSQL DB와 상호작용을 수행할 수 있다.
- Spring Boot에
PostgreSQL DB연결 시 필요한 dependency
Maven
。pom.xml에 정의。
Spring Web<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>。
Spring JDBC<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jdbc</artifactId> </dependency>。
Spring Data JPA<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>。
PostgreSQL Driver<dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency>
Gradle
。build.gradle에 정의implementation 'org.springframework.boot:spring-boot-starter-web' // Spring Web implementation 'org.springframework.boot:spring-boot-starter-jdbc' // Spring JDBC implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // Spring Data JPA implementation group: 'org.postgresql' , name: 'postgresql' , version: '42.2.23' // PostgreSQL Driver runtimeOnly 'org.postgresql:postgresql'
JDBC를 통해application.properties에PostgreSQL DBDataSource정의
。Spring Boot가 기본적으로 자동으로 연결할PostgreSQL DB의DataSource를 설정하기위해application.properties에postgresql에 관한JDBC를 정의.
▶@Bean을 선언하여 직접DataSourceinstance를 생성하는 방법은 아래에서 소개.
▶JdbcUserDetailsManager(DataSource객체)에서 사용됨.
。Spring Boot가 자동으로 연결할DataSource를 설정 시application.properties에 기존에 정의된h2-db설정을 삭제해야한다.spring.datasource.url=jdbc:postgresql://주소:포트번호/DB이름 spring.datasource.username=DB계정이름 spring.datasource.password=DB계정비밀번호 spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=JDBC URL:
。 특정 DB의JDBC URL
▶ Local에서 실행되고 있는PostgreSQL DB의 URL을 정의.
。H2-DB에서 동적 URL을 고정하기 위한 정적 JDBC URL 설정 시에도 사용됨.
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.username,spring.datasource.password:
。DB에 접근하기 위한 사용자 이름과 비밀번호
spring.jpa.database-platform:
。Hibernate가 사용할PostgreSQL DB의 Dialect
spring.jpa.show-sql:
。SQL Query를 Console에 출력할지의 여부
spring.jpa.hibernate.ddl-auto:
。Application 실행 시Hibernate가 DB Schema를 자동 Update할지의 여부를 설정.
▶spring.jpa.hibernate.ddl-auto=update로 설정할 경우,EntityClass와DB Table Schema를 동기화.
Hibernate
。JPA를 구현한 대표적인 오픈소스.
。Hibernate JAR을 class path에 추가해서 Hibernate를 JPA 구현체로 사용.
Dialect:
。JPA또는Hibernate에서 사용하는 특정 DB에 해당하는 SQL문법과 기능을 정의한 설정.
▶ 각 DB(MySQL,PostgreSQL,Oracle)등은 공통적으로 SQL 표준을 따르지만, 각각 조금씩 차이가 발생하는 SQL 문법을 가지므로Hibernate는 해당 문법/기능적 차이를Dialect를 통해 자동으로 조정.주요
Dialect
org.hibernate.dialect.MySQLDialect:MySQLorg.hibernate.dialect.MariaDBDialect:MariaDBorg.hibernate.dialect.PostgreSQLDialect:PostgresDBorg.hibernate.dialect.H2Dialect:H2DB
Spring JDBC의PostgreSQL DBDataSourceinstance 생성하기
。PostgreSQL에서JdbcUserDetailsManager(DataSource객체)을 통한 DB에 사용자 자격증명을 추가할때의 용도로 활용할DataSourceinstance 정의하기. JdbcUserDetailsManager 설정
▶ 사용자 자격증명 저장이 필요없는 경우, 설정안해도application.properties에서Spring Boot에 자동연결설정된DataSource를 활용하여 기본적으로CRUD를 수행 가능.
DataSource생성하는@Bean Method정의
。@Configuration이 선언된Configuration Class에서DataSourceinstance를 반환하는@Bean Method를 생성.@Bean public DataSource pgdataSource() { HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:postgresql://localhost:5432/GeoDB"); config.setUsername("postgres"); config.setPassword("wjd747"); return new HikariDataSource(config); }。
postgresql DataSource를 정의하기위해application.properties에서 작성된JDBC내용과 유사.
DataSource:javax.sql.DataSource
。JDBC에서 특정DB의DB Connection을 관리하는 Interface.
▶Application이DB에 접근 시DataSource를 통해 Connection을 관리.
。최적화된DB Connection Pool을 제공하고,Spring Boot에서Auto-Configuration이 가능.
HikariCP:
。Spring Boot에서 기본적으로 활용하는 고성능JDBC Connection Pool을 관리하는DataSourceInterface를 구현한 Class.
▶new HikariConfig()를 통해DB Connection이 구현된DataSourceinstance 생성.
。JDBC Connection Pool을 통해DriverManagerDataSource와 달리DB Connection을 재사용.
Connection Pool:
。DB Connection을 매번 생성 시 성능 저하 발생하는 단점을 보완.
▶Connection Pool을 사용 시 일정 수의DB Connection을 사전에 생성 및 재사용 가능하여 불필요한DB Connection생성을 방지 가능.
DriverManagerDataSource:
。Spring에서JDBC Connection을 위한DataSourceInterface를 구현한 Class.
▶new DriverManagerDataSource()를 통해DB Connection이 구현된DataSourceinstance 생성.
。 매번 새로운DB Connection을 생성하며 연결을 재사용하지 않고 사용하므로HikariCP에 대체됨.
Spring JPA기능을 활용하여PostgreSQL과 연동
。Spring JPA를 활용해REST API를 통해PostgreSQL DB와 상호작용 수행 시 활용.
。PostgreSQL DB의 Table과 Mapping된JPAEntityClass 생성
。H2 DB등Spring JPA를 통해 구현한DB Entity의 구현내용을 재활용하여PostgreSQL DB에도 적용이 가능하다.
- DB Entity 생성
。PostgreSQL DB와 Data Binding되어 상호작용을 수행하는EntityClass 생성
。@Entity를 선언하여EntityClass 생성 및@Id와@GeneratedValue를primary key field에 선언
▶ 기존H2-DB로 실습한EntityClass를 그대로 활용.// PostgresUser.java import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.persistence.*; import jakarta.validation.constraints.Past; import jakarta.validation.constraints.Size; import java.time.LocalDate; import java.util.List; @Entity public class PostgresUser { @Id @GeneratedValue Integer id; @JsonProperty("username") @Size(min=2, message = "Name should have at least 2 characters.") String name; @JsonProperty("birth_date") @Past(message="Birth Date should be in the past.") LocalDate birthDate; public List<PostgresPost> getPosts() { return posts; } public void setPosts(List<PostgresPost> posts) { this.posts = posts; } @OneToMany(mappedBy="user") @JsonIgnore private List<PostgresPost> posts; public PostgresUser(){} public PostgresUser(Integer id, String name, LocalDate birthDate) { this.id = id; this.name = name; this.birthDate = birthDate; } public Integer getId() { return id; } public String getName() { return name; } public LocalDate getBirthDate() { return birthDate; } public void setId(Integer id) { this.id = id; } public void setName(String name) { this.name = name; } public void setBirthDate(LocalDate birthDate) { this.birthDate = birthDate; } }。
idfield의 경우Wrapper Class의Integertype으로 설정.
▶int와 달리Wrapper Class객체 이므로Null값을 저장할 수 있다.
▶DB에서pk가 없거나 아직 할당되지 않은 상태를 지시하기 위해NULL을 사용할 수 있게 설정.
Integer vs int
int와Integer차이
。Java에서 둘다 정수를 다루지만, 기본 자료형(primitive type)과wrapper class라는 차이가 존재.
int
。기본자료형 (primitive type)
。null값을 가질 수 없다.
Integer
。java.lang.IntegerClass 객체
▶int를 객체로 감싼Wrapper Class객체
▶new키워드로 선언하여 객체 생성.
。null값을 저장 가능.
▶DB Entity의primary key로 활용.
JPA의CRUD를 수행하기위한JpaRepository<Entity,ID Field>Interface 정의import org.springframework.data.jpa.repository.JpaRepository; public interface PgRepository extends JpaRepository<PostgresUser,Integer> { }.
JPA의Business Logic을 구현하기위한Service Class정의
。Business Logic구현을 위한 Spring Bean이므로@Service선언.
▶@Entity에서 명시적 구체화.
。JpaRepository<>interface의 instance 생성 및 생성자주입을 수행하여CRUDmethod를 구현import java.util.List; import java.util.Optional; import org.springframework.stereotype.Service; @Service public class pgService { // JpaRepository instance PgRepository pgRepository; // @Autowired 생략 가능한 Constructor Based Dependency Injection public pgService(PgRepository pgRepository) { this.pgRepository = pgRepository; } // Create public PostgresUser CreatePGUser(PostgresUser user) { return pgRepository.save(user); } // Update public PostgresUser UpdatePGUser(PostgresUser user) { return pgRepository.save(user); } // Read public Optional<PostgresUser> GetPGUser(Integer id) { return pgRepository.findById(id); } public List<PostgresUser> GetAllPGUsers() { return pgRepository.findAll(); } // Delete public void DeletePGUser(Integer id) { pgRepository.deleteById(id); } }。
JpaRepository.save(DB Entity)는CREATE와UPDATE기능을 수행.
▶ 기존Entity를 사용 시UPDATE, 새로운Entity를 사용 시INSERT
CRUD기능을 포함한REST API를 구현할 Controller Class 생성
。JPA CRUD를 사전에 정의한pgServiceSpring Bean instance를 생성하여 의존성 주입하여 수행.import jakarta.validation.Valid; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import java.net.URI; import java.util.List; import java.util.Optional; @RestController public class PgController { // JPA Business Logic을 사전에 정의한 Spring Bean pgService pgservice; // @Autowired 생략 가능한 Constructor Based Dependency Injection public PgController(pgService pgservice) { this.pgservice = pgservice; } // GET Method 구현 // GET /users : 모든 사용자 조회 @GetMapping(path="/pg/jpa/users") public List<PostgresUser> ListAllUsers(){ // JpaRepository를 구현한 Service Class의 JpaRepository객체.findAll() Method 활용. List<PostgresUser> users = pgservice.GetAllPGUsers(); return users; } // GET /users/{id} : 특정 사용자 조회 @GetMapping(path="/pg/jpa/users/{id}") public Optional<PostgresUser> GetUserById(@PathVariable int id){ // JpaRepository를 구현한 Service Class의 JpaRepository객체.findById() Method 활용. // 가져온 데이터는 Optional<PostgresUser>로 return. Optional<PostgresUser> user = pgservice.GetPGUser(id); if(user.isEmpty()){ // 해당 id가 존재하지 않는 경우 발생. throw new UserNotFoundException("id :" + id); } return user; } // POST Method 구현 // POST /users : 새로운 사용자 생성 @PostMapping(path="/pg/jpa/users") public ResponseEntity<PostgresUser> createUser(@Valid @RequestBody PostgresUser user) { // @RequestBody : HttpRequest의 Body(=JSON Format)를 Bean으로 변환하여 저장. // JpaRepository를 구현한 Service Class의 JpaRepository객체.save() Method 활용. PostgresUser savedUser = pgservice.SavePGUser(user); //REST API에서 알맞은 Response Status를 Client에게 반환하는 코드 URI locationHeader = ServletUriComponentsBuilder.fromCurrentRequest() .path("/{id}").buildAndExpand(savedUser.getId()).toUri(); // 201 : created status에 해당하는 ResponseEntity<User> 객체를 생성하여 반환. return ResponseEntity.created(locationHeader).build(); } // Post의 Update를 수행하고 Save한 PostgresPost instance를 return. // PUT /users/{id} @PutMapping(path="/pg/jpa/users/{id}") public PostgresUser UpdateUserById(@PathVariable int id, @RequestBody PostgresUser user){ // HTTP Request Body가 @RequestBody를 통해 변수에 Mapping되어 Update를 수행. pgservice.UpdatePGUser(user); return user; } @DeleteMapping(path="/pg/jpa/users/{id}") public void DeleteUserById(@PathVariable int id){ // JpaRepository를 구현한 Service Class의 JpaRepository객체.deleteById() Method 활용. pgservice.DeletePGUser(id); } }
。PostgreSQL DB에 해당EntityClass의 Table 명이 없을 경우Spring JPA에 의해 자동생성됨.
Spring JDBC기능을 활용하여PostgreSQL과 연동 Spring JDBC
。SQL을 조작하여PostgreSQL DB과 상호작용시 사용.
Spring JDBC를 통해 data의 삽입을 용이하게 하는 Spring Bean Class 생성
。해당 Class를 통해Spring Beaninstance를 생성하여Spring JDBC를 통해 DB에 Insert를 수행.import java.time.LocalDate; public class PostgresUserforJDBC { int id; String name; LocalDate birth_date; public PostgresUserforJDBC(int id, String name, LocalDate birth_date) { this.id = id; this.name = name; this.birth_date = birth_date; } public int getId() {return id; } public void setId(int id) {this.id = id;} public String getName() {return name;} public void setName(String name) {this.name = name;} public LocalDate getBirth_date() {return birth_date;} public void setBirth_date(LocalDate birth_date) {this.birth_date = birth_date; } }
Spring JDBC을 활용하여DAO를 구현하는 Class 생성
。Spring JDBC를 통해 하드코딩한 SQL를 DB에 반영하는 Class객체에@Repository를 선언하여 Spring Bean으로 구체화.
。특정 SQL문을JdbcTemplateClass의 instance를 활용하여JdbcTemplate객체.update(SQL구문)로 DB에 SQL을 반영하는 Logic을 구현.
。DAO(Data Access Obejct : DB에 접근하는 역할을 수행하는 객체)import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository public class pgJdbcRepository { private JdbcTemplate jdbcTemplate; // @Autowired : 생성자기반 의존성주입 public pgJdbcRepository(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } // text-block을 활용한 sql 문자열 private static String sqlinsert = """ insert into postgres_user(id,birth_date,name) values(?,?,?); """; public void insert(PostgresUserforJDBC user){ jdbcTemplate.update(sqlinsert,user.getId(),user.getBirth_date(),user.getName()); } }
CommandLineRunner상속 Class 생성
。CommandLineRunner를 상속한 Class를 정의하여run()을 통해 application 구동 시 DB와 상호작용하는 기능을 구현한Spring Bean( =pgJdbcRepositor) instance의 method를 작동.
▶JdbcTemplate의update(sql)method로 DB와 상호작용을 수행하는 method를 구현.
。Query문입력 시text-block내에 SQL문을 입력.import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; import java.time.LocalDate; @Component public class pgCodeLineRunnerClass implements CommandLineRunner { private pgJdbcRepository jdbcRepository; // @Autowired : 생성자기반 의존성주입 public pgCodeLineRunnerClass(pgJdbcRepository jdbcRepository) { this.jdbcRepository = jdbcRepository; } @Override public void run(String... args) throws Exception { jdbcRepository.insert(new PostgresUserforJDBC(2, "Lee",LocalDate.now())); } }。
@SpringBootApplicationClass의Component Scan에 의해@Component가 선언된 해당CommandLineRunnerClass를 식별하여run() method를 실행하여DAO에서 구현된JdbcTemplate와SQL로Spring JDBC기능을 실행.
。다음처럼 성공적으로insert into postgres_user(id,birth_date,name) values(?,?,?);SQL 구문을 통해 Data Insert가 완료된것을 확인 가능.
JDBC/Spring JDBC/JPA/Spring Data JPA차이
JDBC:
。SQL을 많이 작성하면서 Java 코드가 많다.
▶ 하드코딩이 많음.
Spring JDBC:
。SQL을 많이 작성
。JdbcTemplateinstance를 통해 Java코드는 적게 사용.
JPA:
。SQL을 작성하지않음
。EntityManager을 통해DB Entity를 DB Table로 Mapping 하는 과정 필요.
Spring JPA:
。SQL,EntityManager을 사용하지 않는다.
▶EntityManager대신JpaRepository<Entity class, ID>Interface를 상속한 Interface를 Instance로 생성하여 활용.
。Spring JDBC,Spring JPA를 사용할 경우PostgreSQL DB,MySQL DB,H2-DB등에 관계없이 구축된DB Entity을 매우 쉽게 적용할 수 있다.
。Spring JDBC의JdbcTemplate객체의 경우 직접SQL을 작성하여JdbcTemplate객체.update(SQL)로 상호작용한다면JpaRepository는 자동으로Query가 생성되는 Method를 제공.
DAO( Data Access Object ) :
。DB와 직접 상호작용을 수행하는Object로서 개발자가SQL을 직접 작성하여CRUD를 수행.
▶Spring에서는 보통@RepositoryAnnotation으로 선언하여Spring Beaninstance으로 생성.
。JdbcTemplate같은 Spring JDBC를 활용하여 DB와 상호작용하는 method를 제공하는 Class를 instance로 생성하여 활용.
ORM( Object-Relational Mapping )
。Application의DB Entity Classinstance를 RDBMS의DB Table에 자동으로 영속화 하는것을 의미.
▶DB Entityinstance와RDBMS DB Table간 자동 Mapping.
。SQL을 직접 사용하지 않고,DB Entityinstance를 중심으로DB의 Data 조작을 수행.
▶RDBMS(H2-DB,mySQL,postgreSQL) 의 변경에도 유연한 적용이 가능.
。Spring Security Framework는 생성한 모든 API에 대해 전부 보안을 적용할 수 있다.
。
REST API에 보안 ( 인증:Authentication / 권한:Authorization 등 ) 기능을 담당하는Spring Security Framework활용하기.
▶HTTP Transaction에서 Server의 특정 Resource 접근 시 자격증명을 전달하여 접근하도록 설정.
。Spring Security 공부내용
- Spring Security Dependency 추가하기.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>。 dependency를 추가 후 spring application을 재실행 시 console의 log창에 개발용 자격증명의
PW가 생성되어 표시.
。 Spring Security Framework의 dependency를 추가하는 경우 Spring application의Controller가 mapping하는 모든 url로 연결 시 Spring 로그인 창이 도출.
。API Tester로REST API로GET HTTP Request를 전송하여UserSpring Bean instance를 생성 시Http Status Code:401( Resource에 유효한 자격 증명이 없기 때문에HTTP Request가 적용되지 않았음 )이 Response됨.
▶ 자격증명을 전달하여 API가 동작하도록 설정
Spring Security의 개발용 자격증명
Spring Security는 해당 Framework의 Dependency만 정의하더라도 Controller에 의해 Mapping된 URL 접속 시 Spring Security의 기본인증폼을 전송하며, Auto-Configuration에 의해 다음 개발용 자격증명을 자동 생성.
。Console에 표시된 PW는 Application이 재실행될때마다 초기화
。ID :user
。PW :console에 표시된 PW
- 개발용 자격증명의 Username과 Password 변경방법
。application.properties에 다음 구문을 추가.spring.security.user.name=user123 spring.security.user.password=pw456。해당 구문 추가 후 로그인 시 변경된 개발용 자격증명에 로그인이 가능.
Authorization Header를 추가하여REST API를 활용하여GET HTTP Request전송하기
。Authorization Header에 Username과 Password를 입력하여 API를 전송.
。Spring Security에 의해 Application과 Mapping된 모든Resource URL이 보호되었으나,Authorization Header를HTTP Request에 첨부하여 전송 시 성공적으로 Resource가 반환됨.
HTTP Authorization Header:
。Client가 Server에 인증정보를 전달하는HTTP Header.
。API를 통해HTTP Request전달 시 권한(token등 )을 포함하는 역할을 수행.
▶ Server는Authorization Header를 확인하여HTTP Request의 허용여부를 결정.
API를 통해POST HTTP Request에Authorization Header를 첨부하여 전송하여 DB에 객체를 생성하기
。Authorization Header에 자격증명을 첨부하여 API에POST Http Request로 전송 시HttpStatusCode:403(서버에 요청이 전달되었지만, 권한 때문에 거절)이 반환.
▶Spring Security에서 Default로 설정된CSRF Protection에 의해PUT,POST,DELETERequest로 부터 DB를 보호하는 기능이 작동되었으므로.
。CSRF 관련 공부내용Filter Chain Customizing
。승인되지 않은 요청에 대해서는 로그인 양식이 표현하는 기능 구현
。CSRF를 해제해서POST, PUT, DELETERequest를 보내는 기능 구현.
。Spring Security는 기존의 filter chain을 Override할 경우 Chain 전체를 재정의해야함.
▶HttpSecurity를 통해 기본기능과 추가기능을 모두 구현
- Spring Security 설정 Spring Security 설정 예제
。SpringFilterChain에 관한 전반적인 Configuration Class를 구현.
▶ 기본기능과 추가기능을 모두 구현해야함.
。@Configuration을 선언한 class를 생성 및HttpSecurityinstance를 매개변수로하여SecurityFilterChaintype의Spring Beaninstance를 생성 및 return하는@Bean method를 구현.
▶@Bean method에서는HttpSecurityinstance를 이용하여SecurityFilterChain의 기본기능( Default )과 추가기능( Additional )을 모두 구현 후HttpSecurityinstance를 반환.import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.web.SecurityFilterChain; @Configuration public class pgSecurityConfiguration { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // 인증된 Http Request에 대해 승인처리 설정. http.authorizeHttpRequests( auth->auth.anyRequest().authenticated() ); // 인증되지않은 Http Request에 대해 로그인 양식 도출. http.httpBasic(Customizer.withDefaults()); // CSRF 비활성화 http.csrf(csrf->csrf.disable()); // SecurityFilterChain instance를 Spring bean으로 반환 return http.build(); } }。기본기능 : Authentication 되지 않은 HttpRequest에 대해서 login form 표현
HttpRequest객체.httpBasic(withDefaults())。
Authorization Header에 자격증명을 담아HTTP Request를 전달해야하므로,formLogin이 아닌,httpBasic사용.
▶httpBasic()에 관련된 기본값인withDefaults()을 적용하여 기본 로그인 페이지로 이동하도록 설정.
HttpSecurity객체.httpBasic(Customizer.withDefaults())
。Spring Security에서HTTP Basic인증을 설정하는 method.
▶ 사용자의 ID와 PW를HTTP Request의HTTP Authorization Header에 포함하여 인증하는 방식.
。HTTP Basic: ID와 PW를Base64로 인코딩하여HTTP Authorization Header에 포함하여 Server로 전송.
。Authorization Header에 자격증명을 입력 한 후,JSON Format으로 data를 정의하여 API로POST HTTP Request를 전달 시,CSRF Protection에 의한 제약 없이 정상적으로 DB에 전달되었음을 확인 가능.
▶POST, GET, PUT, DELETE모두 사용 가능.Spring Security의 인증방식
。HttpSecurity객체를 이용하여Spring Security를 설정
formLogin()
HttpSecurity객체.formLogin(): 로그인 기능 활성화
。Spring Security에서 제공하는 인증방식.
。 Application에서UI를 이용해Web Login Form으로 로그인 시 활용하며Session기반 인증.
▶ Application에서 해당 User의 Session 상태가 유효한지 판단하여 처리.
。Custom Login Page를 사용 가능.
。쉽게Log-out가능.
。CSRF Protection,HTTP Session으로 보안을 유지.
Form Login특징
Client가 Server에 Mapping된 resource(= URL)을 요청 시 해당 URL이 인증이 필요한 경우 Server는Login page를 반환
。Client는 ID와 PW를 입력 후 로그인 요청 시POSTmethod로 data가 Server에 전송.
。Server는 해당 data를 확인 후 사용자 정보와 Matching할 경우Session과Token을 생성하여 저장.
httpBasic()
。http 프로토콜에서 정의한 기본인증방식.
。REST API에서HTTP Request로 Application의 Resource에 접근 시Authorization Header를 요구.
▶REST API에서 매HTTP Request마다Authorization Header에 ID와 PW를 포함하여 전송하여 인증하는 방식.
▶ 접근인증방식은 브라우저에서 Client가 Username과 Password를Authorization Header를 통해 전송하면 됨.
。웹브라우저의 기본 팝업으로Authorization Header의ID/PW입력 기능 제공.
。MySQL DB를 Docker Container로 실행하여 Application을 MySQL DB로 연결.
。H2-DB 관련 dependency 삭제 및
application.properties에서 관련내용 지우기.
。JPA & Hibernate 사용 시 사용중인 DB의 교체가 매우 편리
전단계 Docker 관련 공부내용
- Docker를 이용해서 MySQL 실행
。Docker를 실행한 후PowerShell을 실행하여 다음 구문을 입력하여 MySQL에 속한 Docker Container 생성.docker run --detach --env MYSQL_ROOT_PASSWORD=dummypassword --env MYSQL_USER=social-media-user --env MYSQL_PASSWORD=dummypassword --env MYSQL_DATABASE=social-media-database --name mysql --publish 3306:3306 mysql:8-oracle。생성이 될 경우 다음 구문으로 실행중인 Container 목록을 확인 가능.
。application.properties에 다음 설정 정의 및H2-DB관련 설정 삭제.
Property 정보spring.jpa.hibernate.ddl-auto=update spring.datasource.url=jdbc:mysql://localhost:3306/social-media-database spring.datasource.username=social-media-user spring.datasource.password=dummypassword spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
- MySQL dependency 추가
。H2-DB관련 dependency는 삭제하기.<dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> </dependency>
- 이때 , 브라우저에
localhost:8080/jpa/users를 검색 시 Response에서 return되는List<User Bean>들이 포함되지 않는다.
。이는data.sql내 정의한 초기값 data는 In-Memory DB(H2-DB)에 연결시만 실행되지 실제 DB(MySQL)로 연결 시 실행되지 않기 때문.
- API Tester을 이용해 POST Method로 User Bean 생성 후 조회하기.
。POST Method로 HttpRequestBody에 생성할 Bean에 맞는 field 내용을 입력 후 API를 전송하여 각각 User과 해당 User의 Post Bean을 생성.
。이는 Docker를 통해 MySQL에 저장되므로 Application을 재시작하고, GET Method로 조회 시에도 정상적으로 생성한 Bean data가 response됨!
- Application과 MySQL이 연동되었는지 확인하기위해
MySQLShell를 이용하여 MySQL DB에 생성된 데이터 조회하기
Windows PowerShell을 관리자권한으로 실행후mysqlsh을 입력.\connect DB사용자이름@localhost:포트번호입력.
。DB사용자 이름을 통해 3306 포트로 실행되는 DB로 연결.
ex)\connect social-media-user@localhost:3306- 해당 DB의 MySQL Password를 입력.
ex)dummypassword\use <schema>으로 해당 DB schema 사용 선언.
ex)\use social-media-database\sql을 입력 후 SQL Query를 입력하여 데이터 조회.
ex)\sql select * from post;
。다음처럼 생성된 데이터가 정상적으로 조회됨!
- DB에 생성된 데이터는
In-Memory DB와 다르게 영속성을 가지게되므로, Application을 재실행해도 동일하게 조회가 가능하다!