entity
@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
@Data
@Builder
@Entity
public class User {
@Id
@GeneratedValue
private Long id;
@NonNull
private String name;
@NonNull
private String email;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
repository
public interface UserRepository extends JpaRepository<User, Long> {
}
data.sql(/resources)
call next value for hibernate_sequence;
→ insert를 하는 경우 id를 자동 생성하는데 그것이 충돌나지 않기 위하여 id값을 증가
저 구문을 없애고 추가할려하면 기존에 id 1인 레코드가 존재하기에 충돌 일어남
call next value for hibernate_sequence;
insert into user(`id`, `name`, `email`, `created_At`, `updated_At`) values (1, 'martin', 'martin@fastcampus.com', now(), now());
call next value for hibernate_sequence;
insert into user(`id`, `name`, `email`, `created_At`, `updated_At`) values (2, 'dennis', 'dennis@fastcampus.com', now(), now());
call next value for hibernate_sequence;
insert into user(`id`, `name`, `email`, `created_At`, `updated_At`) values (3, 'sophia', 'sophia@slowcampus.com', now(), now());
call next value for hibernate_sequence;
insert into user(`id`, `name`, `email`, `created_At`, `updated_At`) values (4, 'james', 'james@slowcampus.com', now(), now());
call next value for hibernate_sequence;
insert into user(`id`, `name`, `email`, `created_At`, `updated_At`) values (5, 'martin', 'martin@another.com', now(), now());
여기서 칼럼명에서 '가 아닌 ₩(백틱)임!
Repository의 여러 동작 테스트
getOne
User user = userRepository.getOne(1L);
// LAZY 패치 지원하므로 @Transactional 필요
System.out.println(user);
Lazy 방식 -> 우선 레퍼런스만 가지고 있음
@Override
public T getOne(ID id) {
Assert.notNull(id, ID_MUST_NOT_BE_NULL);
return em.getReference(getDomainClass(), id);
}
findbyId
User user = userRepository.findById(1L).orElseThrow(null);
// orElseThrow 처리시 Optional 붙일 필요 X
System.out.println(user);
save
@Test
void crud(){ //save, read, update, delete
// Flush는 쿼리를 조절하는 것이 아니라 DB 반영 시점 조절 -> 로그 상 변화 X
userRepository.saveAndFlush(new User("new martin", "newmartin@abcd.com"));
userRepository.findAll().forEach(System.out::println);
}
count(갯수 출력)
@Test
void crud() { //save, read, update, delete
long count = userRepository.count();
System.out.println(count);
}
existBy(id)
-> 아이디에 해당 하는 값이 있는지 없는지 boolean으로 리턴
boolean exists = userRepository.existsById(1L);
Hibernate:
select
count(*) as col_0_0_
from
user user0_
where
user0_.id=?
쿼리에 카운트가 포함되어있음
delete(deletebyId)(All)(Batch)
userRepository.delete(userRepository.findById(1L).orElseThrow(RuntimeException::new));
userRepository.deleteById(1L);
userRepository.deleteAll();
// 각각을 delete 해서 성능이슈(개수 많을 시)
userRepository.deleteAll(userRepository.findAllById(Lists.newArrayList(1L, 3L)));
// 위는 Iterable을 통해 갯수당 delete 한번씩 수행
//delete 한번 수행
userRepository.deleteInBatch(userRepository.findAllById(Lists.newArrayList(1L, 3L)))
// 처음부터 delete 바로
userRepository.deleteAllInBatch();
실행전에 인덱스에 해당하는 레코드가 있는지 조회(select) 후 삭제하는 것을 쿼리를 통해 볼 수 있다
페이징
Page<User> users = userRepository.findAll(PageRequest.of(0,3));
System.out.println("page:" + users);
System.out.println("totalElements: " + users.getTotalElements());
System.out.println("totalPages: " + users.getTotalPages());
System.out.println("numberOfElements: " + users.getNumberOfElements());
System.out.println("sort : " + users.getSort());
System.out.println("size : " + users.getSize()); // 페이징 할때 나누는 크기
users.getContent().forEach(System.out::println);
page:Page 1 of 2 containing com.example.bookmanager2.domain.User instances
totalElements: 5
totalPages: 2
numberOfElements: 3
sort : UNSORTED
size : 3
User(id=1, name=martin, email=martin@fastcampus.com, createdAt=2022-11-17T19:31:50.567268, updatedAt=2022-11-17T19:31:50.567268)
User(id=2, name=dennis, email=dennis@fastcampus.com, createdAt=2022-11-17T19:31:50.570374, updatedAt=2022-11-17T19:31:50.570374)
User(id=3, name=sophia, email=sophia@slowcampus.com, createdAt=2022-11-17T19:31:50.570543, updatedAt=2022-11-17T19:31:50.570543)
Request.of(1,3)으로 할 시 id = 4,5에 해당하는 페이지가 출력됨
QueryByExample
ExampleMatcher matcher = ExampleMatcher.matching()
.withIgnorePaths("name")
.withMatcher("email", endsWith());
// Example.of 의 인자인 Probe는 일종의 가짜 객체 의미
// IgnorePath 뺀다면 이름에 해당되는 ma 까지 매칭에 사용
Example<User> example = Example.of(new User("ma", "fastcampus.com"), matcher);
userRepository.findAll(example).forEach(System.out::println);
User(id=1, name=martin, email=martin@fastcampus.com, createdAt=2022-11-17T19:38:59.734576, updatedAt=2022-11-17T19:38:59.734576)
User(id=2, name=dennis, email=dennis@fastcampus.com, createdAt=2022-11-17T19:38:59.738029, updatedAt=2022-11-17T19:38:59.738029)
매처가 없는 이 경우엔 두 파라미터에 해당되는 레코드만 조회
Example<User> example = Example.of(new User("martin", "martin@fastcampus.com"));
User user = new User();
user.setEmail("slow");
ExampleMatcher matcher = ExampleMatcher.matching().withMatcher("email", contains());
Example<User> example = Example.of(user, matcher);
userRepository.findAll(example).forEach(System.out::println);
User(id=3, name=sophia, email=sophia@slowcampus.com, createdAt=2022-11-17T19:47:50.714419, updatedAt=2022-11-17T19:47:50.714419)
User(id=4, name=james, email=james@slowcampus.com, createdAt=2022-11-17T19:47:50.714594, updatedAt=2022-11-17T19:47:50.714594)
create : insert
update는 ?
userRepository.save(new User("david", "david@fastcampus.com"));
User user = userRepository.findById(1L).orElseThrow(RuntimeException::new);
user.setEmail("martin-updated@fastcampus.com");
userRepository.save(user);
Hibernate:
insert
into
user
(created_at, email, name, updated_at, id)
values
(?, ?, ?, ?, ?)
...
Hibernate:
update
user
set
created_at=?,
email=?,
name=?,
updated_at=?
where
id=?
경우에 따라 save가 insert또는 save로 동작하는 것을 볼 수 있다
SimpleJpaRepository
에서는 JpaRepositoryImplementation
구현
JpaRepositoryImplementation
는 JpaRepository
상속
-> 보통 우리가 JpaRepository에서 사용하는 메서드는 SimpleJpaRepository에서 구현체를 제공
save 메서드
@Transactional이 붙은 메서드는 메서드가 포함하고 있는 작업 중에 하나라도 실패할 경우 전체 작업을 취소
@Transactional
@Override
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null.");
if (entityInformation.isNew(entity)) {
em.persist(entity); // 엔티티 매니저
return entity;
} else {
return em.merge(entity);
}
}
persist -> insert , merge -> update
isNew
@Transient // DATAJPA-622
@Override
public boolean isNew() {
return null == getId();
}
@Id에 해당하는 값이 Null이 아니라면 update
SaveAll(앞서 사용 시 각각에 대해 insert 쿼리문 생성하였음)
@Transactional
@Override
public <S extends T> List<S> saveAll(Iterable<S> entities) {
Assert.notNull(entities, "Entities must not be null!");
List<S> result = new ArrayList<S>();
for (S entity : entities) {
result.add(save(entity));
}
return result;
}
for로 save를 실행하는 것을 볼 수 있다
다른 메서드들도 구현 내용을 보면서 왜 이러한 쿼리가 실행되었는지 이해해보자!