merge()
는 준영속 상태에서 영속상태로 전환해준다
1차 캐싱은 하나의 트랜잭션 단위에서만 유효하다
@Enumerated(EnumType.STRING) 은 실무에서 권장되지 않는다
JPA GenerationType.IDENTITY
은 쓰기 지연을 지원하지 않는다
한 서비스 트랜잭션 내에서 IDENTITY 전략으로 save() 를 수행 롤백되는 경우 어떻게 되는지
Date
나 Calendar
의 경우 @Temporal
이 필요하다
ZonedDateTime
또는 OffsetDateTime
을 사용하는 것이 좋다.fetch type 은 항상 LAZY 로
연관관계는 왠만하면 단방향으로 둔다
@ManyToOne
을 사용하자cascadeType 의 경우
BaseEntity 의 경우
@Id
와 createdDate
updatedDate
등의 공통적인 값들을
@MappedSuperclass
public abstract class BaseEntity {
@Id @GeneratedValue
private Long id;
private String name;
}
@Entity
public class Member extends BaseEntity {
private String email; // 이메일 속성 추가
}
@Entity
public class Seller extends BaseEntity {
private String shopName; // 상점 이름 속성 추가
}
AttributeOverrides
를 통해 공통밸류에 대한 재정의가 가능함@Entity
@AttributeOverrides({
@AttributeOverride(name = "id", column = @Column(name = "MEMBER_ID")),
@AttributeOverride(name = "name", column = @Column(name = "MEMBER_NAME"))
})
public class Member extends BaseEntity {
private String email;
}
@MappedSuperClass
의 경우 테이블에 매핑되는 값이 아님.비식별과 식별 관계
MapsId
가 사용됨.엔티티 조회와 DTO 조회
JPA 의 경우 페치타입을 EAGER 로 설정하는 경우
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID", nullable = false) // 내부 조인 사용
private Team team;
CascadeType.REMOVE 와 고아객체 제거의 차이점
부모가 자식을 더 이상 참조하지 않는 경우 → 컬렉션에서 자식을 제거하는 경우 자식이 자동으로 삭제됨.
@Entity
public class Parent {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy="parent", orphanRemoval = true)
private List<Child> children = new ArrayList<>();
}
에서
Parent parent1 = em.find(Parent.class, id);
parent1.getChildren().remove(0); // 자식 엔티티를 컬렉션에서 제거 -> 자식 자동 삭제 쿼리가 수행
값 타입의 경우 객체의 참조값을 그대로 사용하는 오류를 범하지 않아야 한다
public class Address {
private String city;
public Address(String city) {
this.city = city;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
public class Member {
private Address homeAddress;
public Member(Address homeAddress) {
this.homeAddress = homeAddress;
}
public Address getHomeAddress() {
return homeAddress;
}
public void setHomeAddress(Address homeAddress) {
this.homeAddress = homeAddress;
}
}
public class Main {
public static void main(String[] args) {
// 회원 1 생성
Member member1 = new Member(new Address("OldCity"));
// 회원 1의 주소 객체 참조
Address address = member1.getHomeAddress();
// 회원 2에 동일한 주소 객체를 할당 (참조만 전달됨)
Member member2 = new Member(address);
// 회원 2의 주소값 변경
member2.getHomeAddress().setCity("NewCity");
// 회원 1의 주소값도 변경되었음을 확인
System.out.println(member1.getHomeAddress().getCity()); // 출력: NewCity
}
}
// 회원 1의 주소 객체를 복사하여 새로운 객체 생성
Address newAddress = member1.getHomeAddress().clone();
값 타입의 경우
값 타입을 변경하거나 삭제하는 경우
값 타입은 엔티티에서의 식별자가 없기에
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@ElementCollection
@CollectionTable(name = "ADDRESS_HISTORY", joinColumns = @JoinColumn(name = "MEMBER_ID"))
private List<Address> addressHistory = new ArrayList<>();
}
@Embeddable
public class Address {
private String city;
private String street;
private String zipcode;
// equals, hashCode 메서드 정의 (필수)
}
Member member = em.find(Member.class, 1L);
List<Address> addressHistory = member.getAddressHistory();
// 강남의 주소를 변경하고 싶다면 기존 주소를 삭제하고 새로운 주소를 추가해야 함
addressHistory.remove(new Address("서울", "강남", "123-456")); // 기존 주소 삭제
addressHistory.add(new Address("새로운 도시", "신도시", "111-222")); // 새로운 주소 추가
em.persist(member);
DELETE FROM ADDRESS_HISTORY WHERE MEMBER_ID = 1;
INSERT INTO ADDRESS_HISTORY (MEMBER_ID, CITY, STREET, ZIPCODE) VALUES (1, '서울', '강북', '789-101');
INSERT INTO ADDRESS_HISTORY (MEMBER_ID, CITY, STREET, ZIPCODE) VALUES (1, '새로운 도시', '신도시', '111-222');
@ElementCollection
라서 새 테이블을 생성한다고 하네요..@Entity
public class AddressEntity {
@Id
@GeneratedValue
private Long id; // 식별자 추가
@Embedded
private Address address;
}
AddressEntity
안의 id 로 수정이나 삭제가 가능해짐Member member = em.find(Member.class, 1L);
// AddressEntity 수정
AddressEntity addressEntity = member.getAddressHistory().get(0); // 특정 주소를 찾아서 수정
addressEntity.setAddress(new Address("새로운 도시", "신도시", "111-222"));
em.persist(member);
---
UPDATE ADDRESS_HISTORY SET CITY = '새로운 도시', STREET = '신도시', ZIPCODE = '111-222'
WHERE ID = 1; -- 특정 ID에 대한 업데이트
spring data jpa 에서의 save()
@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);
}
}
getOne 과 findOne( findById.. )
JPQL 의 경우 update 와 delete 의 경우 @Modifying
이 필수적이다.
Spring Data JPA 에서는 update 나 delete 쿼리의 경우 별도의 쿼리 힌트를 어플리케이션단에서 제공하지 않는다면
해당 쿼리를 SELECT 쿼리로 간주해버린다. → 예외가 발생한다.
javax.persistence.TransactionRequiredException: Executing an update/delete query
해당 어노테이션은 JPQL 이 DB 상태를 변경하는 쿼리임을 알려주는 역할을 하게됨.
Modifying
의 경우 내부적으로 2가지 옵션이 있다.
Pageable의 경우 페이지를 1부터 시작하고 싶은 경우
PageableHandlerMethodArgumentResolver 를 빈으로 등록하고 setOneIndexedParameters(true)
를 설정하면 페이지 인덱스가 1부터 시작함.
JPA 는 락 경합의 관점에서 이득이 크다.
JPA 에서 락 사용
쿼리 캐싱