김영환님의 강의 실전! 스프링 데이터 JPA 보면서 공부한 내용입니다.
✅ h2 데이터 연결에서 오류났을 때 jdbc:h2:~/datajpa
을 입력하면 데이터베이스 파일을 로컬에서 만들면서 나오게된다. 이후 JDBC URL을 다시 jdbc:h2:tcp://localhost/~/datajpa
로 연결하면 정상 연결 된다.
// 인터페이스
public interface MemberRepository extends JpaRepository<Member, Long> {
}
// 테스트
@SpringBootTest
@Transactional
@Rollback(value = false)
class MemberRepositoryTest {
@Autowired MemberRepository memberRepository;
@Test
public void testMember(){
// given
Member member = new Member("memberA");
Member savedMember = memberRepository.save(member);
// when
Member findMember = memberRepository.findById(member.getId()).get();
// then
assertThat(findMember.getId()).isEqualTo(findMember.getId());
assertThat(findMember.getUsername()).isEqualTo(findMember.getUsername());
assertThat(findMember).isEqualTo(findMember);
}
}
// Member
@Entity
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of={"id", "username", "age"}) // 객체를 찍을 때 바로 출력이 됨
// 연관관계 필드{team}은 무한루프가 생성될 수 있기 때문에 자제하는 것이 좋음
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
private String username;
private int age;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="team_id")
private Team team;
public Member(String username) {
this.username = username;
}
public Member(String username, int age, Team team) {
this.username = username;
this.age = age;
if(team != null){
changeTeam(team);
}
}
public void changeTeam(Team team){
this.team = team;
team.getMembers().add(this);
}
}
// Team
@Entity
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of={"id","name"})
public class Team {
@Id @GeneratedValue
@Column(name="team_id")
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
public Team(String name) {
this.name = name;
}
}
// Test
@SpringBootTest
@Transactional
@Rollback(value = false)
class MemberTest {
@PersistenceContext
private EntityManager em;
@Test
public void testEntity(){
Team teamA = new Team("teamA");
Team teamB = new Team("teamB");
em.persist(teamA);
em.persist(teamB);
Member member1 = new Member("member1", 10, teamA);
Member member2 = new Member("member2", 20, teamB);
Member member3 = new Member("member3", 30, teamA);
Member member4 = new Member("member4", 40, teamB);
em.persist(member1);
em.persist(member2);
em.persist(member3);
em.persist(member4);
// 초기화
em.flush(); // 강제로 db에 insert 쿼리를 날림
em.clear(); // db와 영속성컨텍스트에 있는 캐쉬를 날려버림
// 확인
List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
for (Member member : members) {
System.out.println("member = " + member);
System.out.println("-> member.team = " + member.getTeam());
}
}
}
📝 기본 CRUD
💡 jpa에서 수정은 변경감기 기능을 사용하면 된다.
트랜잭션 안에서 엔티티를 조회한 다음에 데이터를 변경하면, 트랜잭션 종료 시점에 변경 감지 기능이 작동해서 변경된 엔티티를 감지하고 update sql을 실행한다.
// MemberRepository
// team도 이하 동일
@Repository
public class MemberJpaRepository {
@PersistenceContext
private EntityManager em;
public Member save(Member member) {
em.persist(member);
return member;
}
public void delete(Member member) {
em.remove(member);
}
// 단건 조회
public Member find(Long id) {
return em.find(Member.class, id);
}
// 전체 조회
public List<Member> findAll() {
// 전체 및 필터링 조회는 jpa에서 제공하는 jpql이라는 기술을 사용한다
// 해당 기술에서 Member는 엔티티를 의미한다. 즉, 테이블을 대상으로 하는 것이 아닌 객체를 대상으로 하는 것이다
// 모양은 sql과 똑같다
return em.createQuery("select m from Member m", Member.class)
.getResultList();
}
public Optional<Member> findById(Long id) {
Member member = em.find(Member.class, id);
return Optional.ofNullable(member); // member가 null일수도 아닐수도 있다
}
public long count() {
return em.createQuery("select count(m) from Member m", Long.class).getSingleResult();
}
}
📝 설정
public interface TeamRepository extends JpaRepository<Team, Long> {
// JpaRepository와 타입(team)만 잘 적으면 Spring Data JPA가 보고 구현체를 알아서 꽂아버림
}
public interface MemberRepository extends JpaRepository<Member, Long> {
}
✅ @Repository 애노테이션 생략 가능
- 컴포넌트 스캔을 스프링 데이터 JPA가 자동으로 처리
- JPA 예외를 스프링 예외로 변환하는 과정도 자동으로 처리
✅ 구현체가 아닌 공통 인터페이스가 있음
📝 분석
- JpaRepository 인터페이스: 공통 CRUD 제공
- 제네릭은 <엔티티 타입, 식별자 타입> 설정
✅ T findOne(ID) → Optional findById(ID) 변경
✅ boolean exists(ID) → boolean existsById(ID) 변경
📝 제네릭 타입
📝 주요 메서드
💡 JpaRepository 는 대부분의 공통 메서드를 제공한다.
public interface MemberRepository extends JpaRepository<Member, Long> {
// Spring Data Jpa에서 제공하는 기능이 아닌 새로운 기능을 만들고자 할 때
// MemberRepository에 있는 기능들을 다 구현해야되는 문제 발생
// => 방법을 해결하는 "커스텀 기능"이 있음
// List<Member> findByUsername(String username){}
// 구현하지 않아도 동작함 => "쿼리 메소드 기능"으로 가능
}