지난 번까지는 각 테이블 간의 연관관계를 하나도 지정해주지 않고 기본적인 작동 방식만 알아보았다. 이번에는 PK-FK로 연관된 테이블의 관계를 엔티티에서 역시 맺어보고자 한다.
이 프로젝트의 테이블들의 상관관계는 다음과 같다.
상품과 관련된 prod 테이블이 있고, 상품을 조회하면 글/이미지 쌍으로 디테일이 있는 prod_detail 테이블이 있다. 두 테이블은 1(prod) : N(prod_detail) 관계이다. prod 테이블의 no 컬럼이 PK이며, prod_detail의 prod_no 컬럼이 prod 테이블을 참조하는 컬럼이다. 상품이 삭제되면 디테일도 삭제되어야 하기 때문에 양방향 관계를 가진다.
이 방향이라는 것은, 양방향/ 단방향이 있다.
테이블 개념에서 볼 때 PK-FK로 연관관계를 맺고 있으면 방향이랄 것이 없다.
하지만 JPA 환경에서 엔티티들은 기본적으로 단방향이다. Prod 엔티티에 @OneToMany 어노테이션으로 ProdDetail을 명시해주면 Prod->ProdDetail의 방향이 된다. 테이블의 양방향 상태처럼 만들어주려면 ProdDetail 엔티티에서도 @ManyToOne 설정을 해 주어야 한다.
@Getter
@Setter
@ToString
@Table(name = "prod")
@Entity
public class Prod {
@Id
@GeneratedValue
@Column(name = "no")
private Long no;
private String name;
private String thumbnailUrl;
private Long originPrice;
private Long discPrice;
private String description;
private LocalDateTime createdAt;
@Transient
private boolean inBasket;
@PrePersist
public void createdAt() {
this.createdAt = LocalDateTime.now();
}
@OneToMany(mappedBy = "prod", fetch = FetchType.EAGER)
private List<ProdDetail> detailList = new ArrayList<ProdDetail>();
}
이렇게 간단한 인터페이스만으로도 데이터 입력이 잘 되는지 보기 위해 간단히 컨트롤러와 서비스를 작성한다.
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/signup")
public User signUp(@RequestBody User user) {
User userResult = userService.signUp(user);
return userResult;
}
}
@Service
public class UserService {
@Autowired
UserRepository userRepo;
public User signUp(User user) {
User userResult = userRepo.save(user);
return userResult;
}
}
userRepo에 save라는 메소드를 생성하지 않았지만 CrudRepository를 상속하였기 때문에 기본적인 CRUD의 메소드를 바로 사용할 수 있다. ㄹㅇ 편리 ㅎㅎ
CREATE TABLE 테이블이름(
…
열이름 데이터형식,
…,
);
CREATE FULLTEXT INDEX 인덱스이름
ON 테이블이름 (열이름);
2. 전체 텍스트 인덱스 삭제
ALTER TABLE 테이블이름
DROP INDEX FULLTEXT (열이름);
3. 중지 단어(stopwords)
실제로 검색할 때 무시할 만한 단어들은 아예 전체 텍스트 인덱스로 생성하지 않는 편이 좋다.
이번
선거는
아주
중요한
행사이므로
모두
꼭
참여
바랍니다
‘이번’, ‘아주’, ‘모두’, ‘꼭’ 등과 같은 단어는 검색할 이유가 없으므로 제외한다. 이것이 중지 단어이다.
MySQL 5.7은 INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD 테이블에 약 36개의 중지 단어를 가지고 있다.
4. 전체 텍스트 검색을 위한 쿼리
전체 텍스트 인덱스를 생성한 후에 이를 사용하기 위한 쿼리는 일반 SELECT문의 WHERE절에 MATCH() AGAINST()를 사용하면 된다.
MATCH (col1, col2, ...) AGAINST (expr [search_modifier])
search_modifier:
{
IN NATURAL LANGUAGE MODE
| IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION
| IN BOOLEAN MODE
| WITH QUERY EXPANSION
}
-자연어 검색
특별히 옵션을 지정하지 않거나 IN NATURAL LANGUAGE MODE를 붙이면 자연어 검색을 한다. 자연어 검색은 단어가 정확한 것을 검색해 준다.
newspaper이라는 테이블의 article이라는 열에 전체 텍스트 인덱스가 생성되어 있다고 가정한다.
‘영화’라는 단어가 들어간 기사를 찾으려면 다음과 같이 사용한다.
SELECT * FROM newspaper
WHERE MATCH(article) AGAINST('영화');
‘영화는’, ‘영화가’ 등 검색 불가.
‘영화’ 또는 ‘배우’ 두 단어 중 하나가 포함된 기사 검색.
SELECT * FROM newspaper
WHERE MATCH(article) AGAINST('영화 배우');
-BOOLEAN MODE 검색
단어나 문장이 정확히 일치하지 않는 것도 검색하는 것을 의미한다.
+
검색 필수
SELECT * FROM newspaper
WHEREMATCH(article) AGAINST('영화+액션' IN BOOLEAN MODE);
-
검색 제외
SELECT * FROM newspaper
WHEREMATCH(article) AGAINST('영화-액션' IN BOOLEAN MODE);
~
검색 부정(-보다 부드러운 방식)
SELECT * FROM newspaper
WHEREMATCH(article) AGAINST('영화~액션' IN BOOLEAN MODE);
‘영화’를 찾되 ‘액션’이 없는 열보다 ‘액션’이 있는 열이 아래 순위
*
부분 검색
SELECT * FROM newspaper
WHEREMATCH(article) AGAINST('영화*' IN BOOLEAN MODE);
‘영화를’, ‘영화가’, ‘영화는’ 등
“
“”안에 있는 구문과 정확히 동일한 철자의 구문
부분 검색
SELECT * FROM newspaper
WHEREMATCH(article) AGAINST("재밌는영화" IN BOOLEAN MODE);
“재밌는 영화”, “재밌는 영화가” 등
“재밌는 한국 영화”, “재밌는 할리우드 영화” 불가
5. 실습
MySQL은 기본적으로 3글자 이상만 전체 텍스트 인덱스로 생성한다. 이러한 설정을 2글자까지 전체 텍스트 인덱스가 생성되도록 시스템 변숫값을 변경한다.