JPA란
- JAVA Persistance API 의 줄임말로 자바 프로그램에서 관계형 데이터베이스에 접근하는 방식을 명세화한 인터페이스
- JPA는 자바애플리케이션과 JDBC에서 작동하며 구현체는 Hibernate 라이브러리를 사용
EntityManagerFactory emf = Persistence.createEntityManagerFactory("my-persistence-unit");
EntityManager em = emf.createEntityManager();
// 엔터티 매니저를 사용하여 영속성 컨텍스트와 상호 작용
// ...
em.close();
emf.close();
애플리케이션 전체의 라이프사이클 동안 하나의 엔터티 매니저 팩토리가 생성되며,
애플리케이션 내의 여러 엔터티 매니저를 생성할 수 있음
Entity Mapping
@Entity
도메인 객체를 관계형 데이터 베이스 테이블로 맵핑할 때 사용
@Table
테이블옵션 설정가능(name, scheme, catalog)
@Access
데이터 필드 접근 / 프로퍼티 접근 방식(getter, setter) 설정가능
기본키 맵핑
@Id
기본키를 직접할당하는 경우
@GeneratedValue
기본 값을 자동생성
엔티티 설계
OneToOne
- 일대일 관계
- 주 테이블에 외래키 설정
@Entity
public class Person {
@OneToOne
private Address address;
// ...
}
@Entity
public class Address {
@OneToOne(mappedBy = "address")
private Person person;
// ...
}
OneToMany
- 일대다 관계
- 외래키 설정
- 연관관계의 주인 : 외래키가 있는 테이블
- 반대테이블은 mappedBy 속성을 사용하여 연관된 엔터티의 필드를 지정
@Entity
public class Team {
@OneToMany(mappedBy = "team")
private List<Member> members;
// ...
}
@Entity
public class Member {
@ManyToOne
private Team team;
// ...
}
ManyToOne
- 다대일 관계
- @JoinColumn 어노테이션을 사용하여 외래 키를 지정
@Entity
public class Member {
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
// ...
}
@Entity
public class Team {
// ...
}
ManyToMany
- 다대다 관계
- 연관된 엔터티 간의 관계를 표현하는 연결 테이블을 자동으로 생성
- 실무 사용 X
@Entity
public class Student {
@ManyToMany
@JoinTable(
name = "STUDENT_COURSE",
joinColumns = @JoinColumn(name = "STUDENT_ID"),
inverseJoinColumns = @JoinColumn(name = "COURSE_ID")
)
private List<Course> courses;
// ...
}
@Entity
public class Course {
@ManyToMany(mappedBy = "courses")
private List<Student> students;
// ...
}
- 외래키 설정은 필수 아님
Cascade
부모 엔티티 변경이 자식 엔티티에 자동으로 전파되도록 함
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();
엔티티 설계 - 도메인 모델 패턴
- 엔티티가 비즈니스 로직 가지고 객체 지향의 특성을 활용 하는 방법
//==생성 메서드==//
public static OrderItem createOrderItem(Item item, int orderPrice, int count) {
OrderItem orderItem = new OrderItem();
orderItem.setItem(item);
orderItem.setOrderPrice(orderPrice);
orderItem.setCount(count);
item.removeStock(count);
return orderItem;
}
엔티티 설계 주의점
- 가급적 setter를 사용 X
- 변경 포인트가 많아 유지보수가 어렵다
- 모든 연관관계는 지연로딩으로 설정 (LAZY)
- 관련된 모든 연관관계를 가져올 수 있음
- 특히 JPQL을 실행할 때 N + 1 문제가 자주 발생
- 연관된 엔티티를 함께 DB에서 조회해야하면 fetch join 을 이용
- x to one → 기본이 EAGER로 되어있음
- 컬렉션은 필드에서 초기화, 가급적으로 바꾸지 x
더티 체킹(dirty checking)
영속성 컨텍스트(persistence context)가 엔티티의 상태 변화를 감지하여 데이터베이스에 자동으로 변경을 반영하는 기능
Repository 설계
@PersistanceContext 어노테이션으로 EntityManater 변수를 생성
→ 스프링이 영속성 컨텍스트를 주입해 줌
→ 주입된 엔티티는 트랜잭션 커밋될 때 까지 변경되지 않음
Service 설계
@Transactional
서비스 단에서 트랜잭션을 관리하는 어노테이션 필요
사용하면, 트랜잭션을 시작하고 커밋하거나 롤백하는 코드를 작성할 필요가 없어짐
읽기전용인 경우 @Transactional(readOnly = true) 의 조건을 달아줌 → 성능 최적화됨
repostiory 필드 주입 vs 생성자 주입
필드주입
public class MemberService {
@Autowired
MemberRepository memberRepository;
...
}
생성자 주입
public class MemberService {
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
...
}
- 생성자 주입 방식을 권장
- 변경 불가능한 안전한 객체 생성 가능
- 생성자가 하나면, @Autowired 를 생략할 수 있다.
- final 키워드를 추가하면 컴파일 시점에 memberRepository 를 설정하지 않는 오류를 체크할 수 있다.(보통 기본생성자를 추가할 때 발견)
@RequiredArgsConstructor : 파이널이 있는 필드만 생성자 주입
Controller
주의사항
api에 엔티티를 전달해서 노출하면 안됨
테스트 작성
ctrl + shift + T를 통해 테스트 클래스 만듬
JUNIT 주입
@RunWith(SpringRunner.class) @SpringBootTest @Transactional(실패시 롤백) 의 어노테이션 적용
given, when , then 방식으로 구현
Autowired로 서비스와 레파지토리 주입
assertequals 를 통한 데이터 검증
@Autowired MemberService memberService;
@Autowired
MemberRepository memberRepository;
@Autowired
EntityManager em;
@Test
@Rollback(false) //쿼리 찍히는 것 보기
public void 회원가입() throws Exception{
//given
Member member = new Member();
member.setName("kim");
//when
Long savedId = memberService.join(member);
//then
em.flush(); //쿼리 나가는 것 보려면
assertEquals(member, memberRepository.findOne(savedId));
}
//어노테이션으로 예외처리
@Test(expected = IllegalStateException.class)
public void 중복회원예외() throws Exception{
//given
Member member1 = new Member();
member1.setName("kim");
Member member2 = new Member();
member2.setName("kim"); //중복 상황
//when
memberService.join(member1);
memberService.join(member2);
//then
fail("실패");
}
QueryDSL
동적 쿼리, 조인 및 복잡한 쿼리를 작성할 때 사용하는 프레임 워크
변경감지와 병합
준영속 엔티티 : 영속성 컨텍스트가 관리하지 않는 객체
직접 new 를 통해 만든 객체는 JPA가 관리하지 않음
- 변경감지(dirty checking) : em.find()
@Transactional
void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티
Item findItem = em.find(Item.class, itemParam.getId()); //같은 엔티티를 조회한다.
findItem.setPrice(itemParam.getPrice()); //데이터를 수정한다.
}
- 병합 : em.merge()
@Transactional
void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티
Item mergeItem = em.merge(itemParam);
}
변경 감지 기능을 사용하면 원하는 속성만 선택해서 변경할 수 있지만, 병합을 사용하면 모든 속성이 변경된다.
병합시 값이 없으면 null 로 업데이트 할 위험도 있다. (병합은 모든 필드를 교체한다.)
변경감지를 사용하는 것이 좋다
'SPRINGBOOT' 카테고리의 다른 글
IntelliJ - finished with non-zero exit value 1 해결 방법 (0) | 2024.01.24 |
---|---|
springboot, thymeleaf 'cannot be found on null' 해결방법 (0) | 2023.06.01 |
Spring Boot Validation 적용하는 법(2.3.version 이상일 때) (0) | 2023.05.31 |
spring boot - dao, dto, service, mapper 개념 (0) | 2023.05.07 |
springboot - model 생성, controller 연결하기 (0) | 2023.04.01 |