Validation
Spring에서는 null 확인 뿐 아니라 문자의 길이 측정과 같은 다른 검증 과정도 쉽게 처리할 수
있도록 Bean Validation을 제공하고 있다.
- 간편하게 사용할 수 있는 여러 애너테이션을 제공해 준다.
@NotNull - null 불가
@NotEmpty - null, "" 불가
@NotBlank - null, "", " " 불가
@Size - 문자 길이 측정
@Max - 최댓값
@Min - 최솟값
@Positive - 양수
@Negative - 음수
@Email - E-mail 형식
@Pattern - 정규 표현식
implementation 'org.springframework.boot:spring-boot-starter-validation'
위 코드를 build.gradle에 추가하여 사용할 수 있다.
RestTemplate
Spring에서 지원하는 객체로 간편하게 Rest방식 API를 호출할 수 있는 Spring 내장 클래스
- 지금까지는 Cline 즉, 브라우저부터 요청을 받는 서버의 입장에서 개발을 진행했다. 서비스 개발을 진행하다 보면 라이브러리 사용만으로는 구현이 힘든 기능들이 많다. 예를 들어 우리의 서비스에서 회원가입을 진행할 때 사용자의 주소를 받아야 한다면, 이것을 직접 구현하게 되면 많은 시간과 비용이 들어갈 것이다. 이때 카카오에서 만든 주소 검색 API를 사용한다면 간편하게 구현할 수 있을 것이다.
RestTemplate의 Get요청
private final RestTemplate restTemplate;
// RestTemplateBuilder의 build()를 사용하여 RestTemplate을 생성한다.
public RestTemplateService(RestTemplateBuilder builder) {
this.restTemplate = builder.build();
}
- 먼저 위와 같이 RestTemplate을 주입받는다.
public ItemDto getCallObject(String query) {
// 요청 URL 만들기
URI uri = UriComponentsBuilder
.fromUriString("http://localhost:7070")
.path("/api/server/get-call-obj")
.queryParam("query", query)
.encode()
.build()
.toUri();
log.info("uri = " + uri);
ResponseEntity<ItemDto> responseEntity = restTemplate.getForEntity(uri, ItemDto.class);
log.info("statusCode = " + responseEntity.getStatusCode());
return responseEntity.getBody();
}
- Spring의 UriComponentsBuilder를 사용해서 URI를 손쉽게 만들 수 있다.
- RestTemplate의 getForEntity는 Get 방식으로 해당 URI의 서버에 요청을 진행한다.
- 요청의 결괏값에 대해 직접 JSON TO Object를 구현할 필요 없이 RestTemplate을 사용하면 자동처리하여 준다.
RestTemplate의 Post요청
public ItemDto postCall(String query) {
// 요청 URL 만들기
URI uri = UriComponentsBuilder
.fromUriString("http://localhost:7070")
.path("/api/server/post-call/{query}")
.encode()
.build()
.expand(query)
.toUri();
log.info("uri = " + uri);
User user = new User("Robbie", "1234");
ResponseEntity<ItemDto> responseEntity = restTemplate.postForEntity(uri, user, ItemDto.class);
log.info("statusCode = " + responseEntity.getStatusCode());
return responseEntity.getBody();
}
- 요청받은 검색어를 Query String 방식으로 서버로 RestTemplate을 사용하여 요청한다.
- UriComponentsBuilder의 expend를 사용하여 {query} 안의 값을 동적으로 처리할 수 있다.
- RestTemplate의 postForEntity는 Post방식으로 해당 URI의 서버에 요청을 진행한다.
- 첫 번째 피라미터에는 URI, 두 번째 피라미터에는 HTTP Body에 넣어줄 데이터를 넣는다. 세 번째 피라미터에는 전달받은 데이터와 매핑하여 인스턴스화할 클래스의 타입을 넣어주면 된다.
RestTemplate의 exchange
public List<ItemDto> exchangeCall(String token) {
// 요청 URL 만들기
URI uri = UriComponentsBuilder
.fromUriString("http://localhost:7070")
.path("/api/server/exchange-call")
.encode()
.build()
.toUri();
log.info("uri = " + uri);
User user = new User("Robbie", "1234");
RequestEntity<User> requestEntity = RequestEntity
.post(uri)
.header("X-Authorization", token)
.body(user);
ResponseEntity<String> responseEntity = restTemplate.exchange(requestEntity, String.class);
return fromJSONtoItems(responseEntity.getBody());
}
요청 Header에 정보를 추가하려면 어떻게 해야 할까?
- exchange 메서드의 첫 번째 피라미터에 RequestEntity 객체를 만들어 전달해 주면 uri, header, body의 정보를 한 번에 전달할 수 있다.
DB table 간의 방향
- 방향은 크게 단방향과 양방향을 생각할 수 있다.
테이블이 users, food 두 개의 테이블이 있다고 가정을 했을 때, 단방향은 users테이블에서만 food테이블을 참조할 수 있을 때를 의미한다. 양방향은 users테이블과 food테이블이 서로를 참조할 수 있을 때를 의미한다. 그러면 DB 테이블가느이 방향이 있는 게 맞을까? DB에서는 어떤 테이블을 기준으로 하든 원하는 정보를 JOIN 하여 조회할 수 있다. 이처럼 DB 테이블 간의 관계에는 방향의 개념이 없다.
Entity 간의 연관 관계
- DB테이블에서는 테이블 사이의 연관관계를 FK(외래 키)로 맺을 수 있고, 방향 상관없이 조회가 가능하다.
- Entiy에서는 상대 Entity를 참조하여 Entity사이의 연관관계를 맺을 수 있다.
- 상대 Entity를 참조하고 있지 않다면 상대 Entity를 조회할 수 있는 방법은 없다.
- Entity에서는 DB테이블에는 없는 방향의 개념이 존재한다.
1대 1 단방향 관계
- 외래키의 주인 정하기 : Entity에서 외래 키의 주인은 일반적으로 N(다)의 관계는 Entity지만 1대 1 관계에서는 외래 키의 주인을 직접 지정해야 한다.
- 외래키 주인만이 외래키를 등록, 수정, 삭제할 수 있으며 주인이 아닌 쪽은 오직 외래키를 읽기만 가능하다.
- @JoinColumm()은 외래키의 주인이 화용 하는 애너테이션이다. (칼럼명, null여부, unique 여부등을 지정할 수 있다.)
// 음식 (외래키의 주인)
@Entity
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@OneToOne
@JoinColumn(name = "user_id")
private User user;
}
// 고객
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
1대 1 양방향 관계
- 양방향 관계에서 외래키의 주인을 지정해 줄 때 mappedBy 옵션을 사용한다.
- mappedBy의 속성값은 외래키의 주인인 상대 Entity의 필드명을 의미한다.
- 외래키의 주인은 상대 Entity타입의 필드를 가지면서 @JoinColumn()을 활용하여 외래키의 속성을 설정해 주면 된다.
// 음식 (외래키의 주인)
@Entity
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@OneToOne
@JoinColumn(name = "user_id")
private User user;
}
// 고객
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToOne(mappedBy = "user")
private Food food;
}
N대 1 단방향 관계
// 음식 (외래키의 주인)
@Entity
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
}
// 고객
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
- @ManyToOne 애너테이션은 N대 1 관계를 맺어주는 역할을 한다.
N대 1 양방향 관계
// 음식 (외래키의 주인)
@Entity
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
}
// 고객
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "user")
private List<Food> foodList = new ArrayList<>();
}
- 양방향 참조를 위해 고객 Entity에서 Java컬렉션을 사용하여 음식 Entity를 참조하는 코드이다.
1대 N 단방향 관계
// 음식 (외래키의 주인)
@Entity
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@OneToMany
@JoinColumn(name = "food_id") // users 테이블에 food_id 컬럼
private List<User> userList = new ArrayList<>();
}
// 고객
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
- @OneToMany 애너테이션은 1대 N관계를 맺어주는 역할을 한다.
- 위코드에서 외래키를 관리하는 주인은 음식 Entity이지만 실제 외래키는 고객 Entity가 가지고 있다.
- 1:N에서 N관계의 테이블이 외래키를 가질 수 있기 때문에 외래키는 N의 관계인 users테이블에 키 칼럼을 만들어 추가하지만 외래키의 주인인 음식 Entity를 통해 관리한다.
- 외래키를 음식 Entity가 직접 가질 수 있다면 INSERT발생 시 한 번에 처리할 수 있지만, 실제 DB에서 외래키를 고객 테이블이 가지고 있기 때문에 추가적인 UPDATE가 발생된다는 단점이 존재한다.
1대 N 양방향 관계
- 1대 N 관계에서는 일반적으로 양방향 관계가 존재하지 않는다.
- 1대N 관계에서 양방향 관계를 맺으려면 음식 Entity를 외래키의 주인으로 정해주기 위해 고객 Entity에서 mappedBy옵션을 사용해야지만 @ManyToOne 애너테이션은 mappedBy속성을 제공하지 않는다.
N대 M 단방향 관계
// 음식 (외래키의 주인)
@Entity
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@ManyToMany
@JoinTable(name = "orders", // 중간 테이블 생성
joinColumns = @JoinColumn(name = "food_id"), // 현재 위치인 Food Entity 에서 중간 테이블로 조인할 컬럼 설정
inverseJoinColumns = @JoinColumn(name = "user_id")) // 반대 위치인 User Entity 에서 중간 테이블로 조인할 컬럼 설정
private List<User> userList = new ArrayList<>();
}
// 고객
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
- @ManyToMany 애너테이션은 N대 M 관계를 맺어주는 역할을 한다.
- 생성되는 중간테이블을 컨트롤하기 어렵기 때문에 추후에 중간 테이블의 변경이 발생할 경우 문제가 발생할 가능성이 높다.
N대 M 양방향 관계
// 음식 (외래키의 주인)
@Entity
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@ManyToMany
@JoinTable(name = "orders", // 중간 테이블 생성
joinColumns = @JoinColumn(name = "food_id"), // 현재 위치인 Food Entity 에서 중간 테이블로 조인할 컬럼 설정
inverseJoinColumns = @JoinColumn(name = "user_id")) // 반대 위치인 User Entity 에서 중간 테이블로 조인할 컬럼 설정
private List<User> userList = new ArrayList<>();
}
// 고객
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany(mappedBy = "userList")
private List<Food> foodList = new ArrayList<>();
}
- 반대 방향인 고객 Entity에 @ManyToMany로 음식 Entity를 연결하고 mappedBy 옵션을 설정하여 외래 키의 주인을 설정하면 양방향 관계 맺음이 가능하다.
정리
Validation을 사용하면 받아올 입력값의 범위를 개발자가 지정한 범위 내에서 받아올 수 있다는 것을 알게 되었다. 하나씩 지정해줘야 하는 줄 알았는데 내가 몰랐던 애너테이션이 존재했었다. 앞으로 많이 활용될 것 같다. 또한 RestTemplate을 사용하여 간편하게 Rest방식 API를 호출해 코딩에 사용할 수 있다는 것을 알았다. 1대 1, 1대 N, N대 1, N대 M 관계에 대해서는 생각보다 개념이 헷갈렸다. 1대 1, N대 M방식은 어느 정도 이해가 되었고, 1대 N과 N대 1 상황에서의 애너테이션 종류와 요구되는 입력값을 제대로 이해해야 할 필요성을 느꼈다. 오늘의 공부는 코딩에 직접적으로 도움이 되는 공부들을 하게 됐던 것 같다. 자바스프링은 편한 기능이 많이 탑재되어 있는 것 같다.
'Back-End > Spring (SpartaCC)' 카테고리의 다른 글
[Spring] JPA - ORM의 배경, Raw JPA 기능 (0) | 2023.07.27 |
---|---|
[Spring] 스파르타코딩클럽 Spring공부(6) - OAuth, 단위테스트, 통합테스트, 예외처리관리 (1) | 2023.07.10 |
[Spring] 스파르타코딩클럽 Spring공부(4) - 인증/인가, 쿠키/세션, 필터 (0) | 2023.06.20 |
[Spring] 스파르타코딩클럽 Spring공부(3) - 3 Layer Architecture, JPA (0) | 2023.06.20 |
[Spring] 스파르타코딩클럽 Spring공부(2) - 데이터의 전달방식과 SQL (0) | 2023.06.14 |
남건욱's 공부기록