JPA 사용법
목차
1. Entity
@Id
- primary key를 가지는 변수를 선언하는 것을 뜻한다.
@GeneratedValue
- 어노테이션은 해당 Id 값을 어떻게 자동으로 생성할지 전략을 선택할 수 있다.
@Table
- 별도의 이름을 가진 데이터베이스 테이블과 매핑한다. 기본적으로 @Entity로 선언된 클래스의 이름은 실제 데이터베이스의 테이블 명과 일치하는 것을 매핑한다. 따라서 @Entity의 클래스명과 데이터베이스의 테이블명이 다를 경우에 @Table(name=" ")과 같은 형식을 사용해서 매핑이 가능하다.
@Column
- @Column 선언이 꼭 필요한 것은 아니다. 하지만 @Column에서 지정한 변수명과 데이터베이스의 컬럼명을 서로 다르게 주고 싶다면 @Column(name=" ") 같은 형식으로 작성하면 된다. 그렇지 않은 경우에는 기본적으로 멤버 변수명과 일치하는 데이터베이스 컬럼을 매핑한다.
2. Repository
public interface CrudRepository<T, ID extends Serializable>
extends Repository<T, ID> {
<S extends T> S save(S entity);
T findOne(ID primaryKey);
Iterable<T> findAll();
Long count();
void delete(T entity);
boolean exists(ID primaryKey);
// … more functionality omitted.
}
save
findOne
findAll
- 전체 레코드 불러오기. 정렬(sort), 페이징(pageable) 가능
count
delete
exists
3. 쿼리 작성 방법
샘플소스
public interface UserRepository extends Repository<User, Long> {
List<User> findByEmailAddressAndLastname(String emailAddress, String lastname);
}
동작 SQL
select u from User u where u.emailAddress = ?1 and u.lastname = ?2
쿼리 만드는 방법
- 리턴타입 {접두어}{도입부}By{프로퍼티 표현식}(조건식)[(And|Or){프로퍼티 표현식}(조건식)]{정렬 조건} (매개변수)
각부분 |
매소드명 |
접두어 |
Find, Get, Query, Count, ... |
도입부 |
Distinct, First(N), Top(N) |
프로퍼티 표현식 |
Person.Address.ZipCode => find(Person)ByAddress_ZipCode(...) |
조건식 |
IgnoreCase, Between, LessThan, GreaterThan, Like, Contains, ... |
정렬 조건 |
OrderBy{프로퍼티}Asc |
리턴 타입 |
E, Optional, List, Page, Slice, Stream |
매개변수 |
Pageable, Sort |
Keyword |
Sample |
JPQL snippet |
And |
findByLastnameAndFirstname |
… where x.lastname = ?1 and x.firstname = ?2 |
Or |
findByLastnameOrFirstname |
… where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals |
findByFirstname,findByFirstnameIs,findByFirstnameEquals |
… where x.firstname = ?1 |
Between |
findByStartDateBetween |
… where x.startDate between ?1 and ?2 |
LessThan |
findByAgeLessThan |
… where x.age < ?1 |
LessThanEqual |
findByAgeLessThanEqual |
… where x.age ⇐ ?1 |
GreaterThan |
findByAgeGreaterThan |
… where x.age > ?1 |
GreaterThanEqual |
findByAgeGreaterThanEqual |
… where x.age >= ?1 |
After |
findByStartDateAfter |
… where x.startDate > ?1 |
Before |
findByStartDateBefore |
… where x.startDate < ?1 |
IsNull |
findByAgeIsNull |
… where x.age is null |
IsNotNull,NotNull |
findByAge(Is)NotNull |
… where x.age not null |
Like |
findByFirstnameLike |
… where x.firstname like ?1 |
NotLike |
findByFirstnameNotLike |
… where x.firstname not like ?1 |
StartingWith |
findByFirstnameStartingWith |
… where x.firstname like ?1 (parameter bound with appended %) |
EndingWith |
findByFirstnameEndingWith |
… where x.firstname like ?1 (parameter bound with prepended %) |
Containing |
findByFirstnameContaining |
… where x.firstname like ?1 (parameter bound wrapped in %) |
OrderBy |
findByAgeOrderByLastnameDesc |
… where x.age = ?1 order by x.lastname desc |
Not |
findByLastnameNot |
… where x.lastname <> ?1 |
In |
findByAgeIn(Collection ages) |
… where x.age in ?1 |
NotIn |
findByAgeNotIn(Collection age) |
… where x.age not in ?1 |
TRUE |
findByActiveTrue() |
… where x.active = true |
FALSE |
findByActiveFalse() |
… where x.active = false |
IgnoreCase |
findByFirstnameIgnoreCase |
… where UPPER(x.firstame) = UPPER(?1) |
기본예제
//기본 예제
List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);
// distinct
List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);
// ignoring case
List<Person> findByLastnameIgnoreCase(String lastname);
// ignoring case
List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);
//정렬
List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
//페이징
Page<User> findByLastname(String lastname, Pageable pageable);
Slice<User> findByLastname(String lastname, Pageable pageable);
List<User> findByLastname(String lastname, Sort sort);
List<User> findByLastname(String lastname, Pageable pageable);
4. Pageable
호출 샘플소스
@Controller
@RequestMapping("/users")
public class UserController {
@Autowired UserRepository repository;
@RequestMapping
public String showUsers(Model model, Pageable pageable) {
model.addAttribute("users", repository.findAll(pageable));
return "users";
}
}
- 위와 같이 작성된 Pageable에서는 다음과 같은 파라미터를 자동 수집한다.
parameter |
Header Two |
page |
페이지 인지를 전달(0부터 시작) |
size |
한 페이지 사이즈(기본값 20) |
sort |
정렬정보를 전달. 예:sort=createdAt,desc&sort=userId,asc |
5. Entitiy간의 방향성
- A클래스는 B클래스를 사용하게 되고 이런 경우에 방향성을 갖습니다. 반대로 B클래스가 A클래스를 사용하게 되면 역시 방향성을 갖습니다.
- 가능한 단방향만으로 구현을 해보고 양방향이 필요한 경우에 성능과 편의성을 고려하여서 양방향을 추가하는 방식이 좋습니다.
6. 양방향 연관관계의 주인
- 두 개의 Entity 클래스가 양방향으로 서로 참조를 하고 있으면, '연관관계의 주인'을 지정해줘야 합니다.
- 연관관계의 주인만이 외래키를 관리하여 갱신(등록, 수정, 삭제)를 할 수 있고, 반대편은 읽기만 가능하도록 만들기 위함입니다. 연관관계의 주인은 FK(외래키)가 명시된 Entity클래스로 설정합니다. (쉽게말해 mappedBy라는 옵션을 사용하는 쪽은 주인이 아닙니다).
7. ManyToOne(N:1) 설정
- 다대일 설정시의 FK(외래키)는 N(Many)쪽에 둡니다.
8. OneToMany(1:N) 설정
- 다대일 설정시의 FK(외래키)는 N(Many)쪽에 둡니다. 하지만 N(Many)쪽에서 단방향 참조를 하고 있지 않으면 FK(외래키)설정할 필드값이 존재하지 않습니다. 그러므로 FK(외래키) 설정은 1(One)쪽에서 설정합니다. 양방향 설정을 하기 위해서는 ManyToOne의 양방향 구현방식을 사용합니다.
9. OneToOne(1:1) 설정
- 일대일 관계에서는 '주(主)테이블'을 정해야 합니다. 주테이블이라는 것은 단독적으로도 사용가능한 테이블을 말합니다. (혹은 기획상의 확장성을 고려해서 주테이블을 결정하기도 합니다.) 아래표에서는 왼쪽열이 주테이블, 오른쪽열이 대상테이블 입니다. 참고로 대상테이블에 외래키가 있는 단방향은 구현할 수 없습니다.
10. JPA 프로그래밍: Fetch
- @OneToMany의 기본값은 Lazy
- @ManyToOne의 기본값은 Eager