JPA의 정의

JPA(Java Persistence API)는 자바 객체를 관계형 데이터베이스와 매핑(ORM, Object-Relational Mapping)하기 위한 표준 API
즉, DB의 데이터들을 객체 다루듯이 다룰 수 있도록 해줍니다.

ORM 매핑

프로젝트의 객체와 이 객체에 해당하는 DB의 테이블을 매핑할 수 있습니다. 아래는 그 예시입니다.

@Data
@Entity
public class Member {
	
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long memberId;
	
	@Column(name = "money")
	private Integer money
	
	pubic Member() {
	
	}
	
	public Member(Integer money) {
		this.money = money
	}
	
}

변경된 점

  • @Entity : 해당 애노테이션이 있으면 JPA가 해당 클래스를 엔티티라고 인식합니다.
  • @Id : 테이블의 PK와 매핑하는 필드라는 뜻입니다.
  • @GeneratedValue : PK생성값을 자동으로 정의된 값으로 사용한다는 뜻입니다.
  • @Column : DB의 컬럼과 매핑되는 필드라는 뜻입니다. 생략 가능합니다.
  • JPA는 public Member() {} 와 같은 기본 생성자가 필수입니다.

Repository 구현

EntityManager를 주입받아서 제공하는 기능을 사용하며 구현이 가능합니다.
그리고 JPA에서 데이터의 변경이 일어나는 메서드는 꼭 @Transactional을 추가해줘야 합니다.

1. 저장

@Repository
@Transactional
@RequiredArgsConstructor
public class Repository {
 
	private final EntityManager em;
	
	public Member save(Member member) {
		em.persist(member);
		return member;
	}
}

저장은 EntityManager가 제공하는 persist()를 사용합니다.
해당 기능을 통해 자동으로 생성된 PK값까지 알아서 처리되어 객체에 적용됩니다.

2. 조회

2-1. 일반 조건 조회

@Repository
@Transactional
@RequiredArgsConstructor
public class Repository {
 
	private final EntityManager em;
	
	public Optional<Member> findById(Long id) {
		Member member = em.find(Member.class, id);
		return Optional.ofNullable(member);
	}
}

조회는 EntityManager가 제공하는 find()를 사용합니다.
처음에 return될 객체 클래스를 넣어주고, 그 다음에 조회할 조건이 될 id값을 넣어주면 됩니다.

2-2. 특수 조건 조회

@Repository
@Transactional
@RequiredArgsConstructor
public class Repository {
 
	private final EntityManager em;
	
	public List<Member> findAll() {
		String jpql = "select i from Item i";
		TypedQuery<Item> query = em.createQuery(jpql, Member.class);
		return query.getResultList();
	}
}

특수한 조건으로 쿼리를 작성하여 조회하고 싶다면 JPQL을 작성하면 됩니다.
이는 일반적인 SQL과는 작성법이 다르고, 해당 쿼리와 받아올 결과값 형태 클래스를 넣어서 조회할 수 있습니다.

3. 수정

@Repository
@Transactional
@RequiredArgsConstructor
public class Repository {
 
	private final EntityManager em;
	
	public void update(Long id, MemberUpdateDto updateParam) {
		Member member = em.find(Member.class, id);
		member.setMoney(updateParam.getMoney());
	}
}

수정은 특이한데, EntityManager가 제공하는 기능을 사용하는 것이 아니고, 객체의 Setter메서드를 사용합니다.
객체가 수정된 후 DB에 특정 기능을 통해 변경사항을 적용해줘야할 것 같지만 JPA는 객체에 수정만 진행해도 DB에 바로 적용이 알아서 됩니다.

예외 변환

JPA에서 발생하는 예외 상황을 위한 Exception 클래스들이 여러가지 있습니다.
대표적으로 PersistenceException와 그 하위 예외들, IllegalStateException, IllegalArgumentException이 있습니다.

이와같은 특정 데이터 접근 기술의 예외에 종속되는 것을 막기 위해 예외 누수를 해결하는 과정이 필요했습니다.
하지만 JPA는 해당 과정을 자동으로 해결해줍니다. 이는 @Repository를 추가했기 때문입니다.

[@Repository가 없는 경우]

[@Repository를 추가한 경우]

@Repository가 붙은 클래스는 Component Scan의 대상이 됩니다.
그리고 @Repository가 붙은 클래스는 JPA가 인식하여 알아서 예외 변환 AOP를 적용시켜서 JPA만의 Exception이 아닌, 스프링이 제공하는 예외로 변환됩니다.

스프링 데이터 JPA

스프링에서 JPA를 편리하게 사용할 수 있도록 공통 인터페이스를 구현해주고 쿼리 메서드를 지원해주는 라이브러리

1. 대표 기능

1-1. 공통 인터페이스 기능

일반적으로 Repository에서 구현하는 CRUD 기능들을 JpaRepository인터페이스를 통해 제공하기 때문에 코드가 간결해집니다.

[Before]

@Repository
@Transactional
@RequiredArgsConstructor
public class Repository {
 
	private final EntityManager em;
	
	public void update(Long id, MemberUpdateDto updateParam) {
		Member member = em.find(Member.class, id);
		member.setMoney(updateParam.getMoney());
	}
}

[After]

public interface Repository extends JpaRepository<Member, Long> {
}

위와같이 스프링 데이터 JPA가 구현 클래스를 보이지 않는 곳에서 자동으로 생성하여 Bean으로 등록합니다.

JpaRepository를 extends하여 사용할 수 있는데, JpaRepository<엔티티 클래스, PK값 타입>으로 넣어주면 됩니다.

1-2. 쿼리 메서드 기능

스프링 데이터 JPA에서 기본적으로 지원하는 메서드 외에 더 복잡한 메서드를 만들고자 한다면, 복잡한 쿼리나 코드 작성 없이 메서드의 이름에서 무슨 작업을 할 것인지 정의해주면 구현 가능합니다.

[Before]

public List<Member> findByUsernameAndAgeGreaterThan(String username, int age) {
	return em.createQuery("select m from Member m where m.username = :username and m.age > :age")
		.setParameter("username", username)
		.setParameter("age", age)
		.getResultList()
}

[After]

public interface Repository extends JpaRepository<Member, Long> {
	List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
}

규칙

  • 조회: find…By ,read…By , query…By , get…By
    • 예:) findHelloBy 처럼 …에 식별하기 위한 내용(설명)이 들어가도 된다.
  • COUNT: count…By 반환타입 long
  • EXISTS: exists…By 반환타입 boolean
  • 삭제: delete…By , remove…By 반환타입 long
  • DISTINCT: findDistinct , findMemberDistinctBy
  • LIMIT: findFirst3 , findFirst , findTop , findTop3

물론 JPQL을 통한 구현도 가능합니다.

public interface Repository extends JpaRepository<Member, Long> {
	@Query("select m from member m where m.username = :username and i.age > :age")
	List<Member> findUsers(@Param("username") String username, @Param("age") int age);
}