[내일배움캠프 Spring 4기] 67일차 TIL - Spring Data 구조 및 JpaRepository 원리

서예진·2024년 3월 12일
0

오늘의 학습 키워드

오늘의 코드카타
Spring Data 구조 및 JpaRepository 원리


오늘의 코드카타

2024년 3월 8일 - [프로그래머스 - 자바(JAVA)] 35 : 숫자의 표현


SpringData 구조 및 JpaRepository 원리

Spring Data 구조

  • 여기서는 스프링 데이터 Common 과 스프링 데이터 JPA에 대해서 다루고자 한다.

SpringData 기능 목록

  • 강력한 리포지토리 및 사용자 지정 객체 매핑 추상화 → ORM
  • 리포지토리 메서드 이름에서 동적 쿼리 파생
  • 기본 속성을 제공하는 구현 도메인 기본 클래스
  • 명료한 추적기능 지원(생성일시, 마지막 변경일시, 생성자, 마지막 변경자)
  • 사용자 지정 리포지토리 코드 통합 가능성
  • JavaConfig 및 사용자 지정 XML 네임스페이스를 통한 간편한 Spring 통합
  • Spring MVC 컨트롤러와의 고급 통합
  • 교차 스토어 지속성에 대한 실험적 지원

SpringData Jpa 와 JpaRepository 원리

  • Repository 는 MarkerInterface 로 특별한 기능은 없음

  • Repository ~ JpaRepository 까지는 @NotRepositoryBean 이 붙어있는 인터페이스이다. → 기능을 확장하기 위한 인터페이스라는 것을 명시

    • JpaRepository<Entity,ID> 붙이면 알맞은 프로그래밍 된 SimpleJpaReository 구현체 빈이 등록된다.
      - 어떻게? @SpringBootApplication 을 통해 자동으로 붙여지는 @EnableJpaRepositoriesJpaRepositoriesRegistrar 를 통해서 등록된다.
      - JpaRepositoriesRegistrar 는 ImportBeanDefinitionRegistrar 의 구현체이다
      - ImportBeanDefinitionRegistrar 는 프로그래밍을 통해 빈을 주입해준다.

      기존 Repository vs 새로운 JpaRepository

      • 기존 Repository
        • **@Repository** 을 클래스에 붙인다.
        • 앞서배운 RawJPA의 Repository 기능만 가진 구현체가 생성된다. (DB별 예외처리 등)
      • 새로운 JpaRepository
        • JpaRepository<Entity,ID> 인터페이스를 인터페이스에 extends 붙인다.
        • @NotRepositoryBean 된 **상위 인터페이스들의 기능을 포함한 구현체가 프로그래밍된다. (@NotRepositoryBean** = 빈생성 막음)
        • SpringDataJpa 에 의해 엔티티의 CRUD, 페이징, 정렬 기능 메소드들을 가진 빈이 등록된다. (상위 인터페이스들의 기능)
    • Repository 를 JpaRepository 로 간단하게 바꾸기!
      // 변경 전
      @Repository
      public class UserRepository {
      
        @PersistenceContext
        EntityManager entityManager;
      
        public User insertUser(User user) {
          entityManager.persist(user);
          return user;
        }
      
        public User selectUser(Long id) {
          return entityManager.find(User.class, id);
        }
      }
      
      // 변경 후
      public interface UserRepository extends JpaRepository<User, Long> {
        
      }

▼ Repository 기능을 제한하기

  • JpaRepository 에서 사용할 메소드 제한하기
    • JpaRepository 는 기본적으로 모든 기능을 제공하기 때문에 리스크가 있을 수 있다.
    • 따라서, 아래와 같은 방법으로 원하는 기능 메소드만 구현하도록 제한할 수 있다.

1. **@RepositoryDefinition** 을 인터페이스에 붙이는법 (가장 많이 쓰임)

  • 어노테이션을 붙이면 BeanDefinition 에 직접 접근하여 프로그래밍으로 주입받을 구현체 메소드들을 지정해서 요청할 수 있다.
@RepositoryDefinition(domainClass = Comment.class, idClass = Long.class)
public interface CommentRepository {

    Comment save(Comment comment);

    List<Comment> findAll();
    
}

2. **@NoRepositoryBean** 인터페이스로 한번더 감싸는법

  • 상위 인터페이스 개념을 하나 더 만들어서 열어줄 메소드만 선언해준다.
@NoRepositoryBean
public interface MyRepository<T, ID extends Serializable> extends Repository<T, ID> {

    <E extends T> E save(E entity);

    List<T> findAll();

}

▼ Repository 에 기능 추가하기

💡 delete() 메소드의 내부 기능 확인하기

@Transactional
    public void delete(T entity) {
        Assert.notNull(entity, "Entity must not be null");
        if (!this.entityInformation.isNew(entity)) {
            Class<?> type = ProxyUtils.getUserClass(entity);
            T existing = this.em.find(type, this.entityInformation.getId(entity));
            if (existing != null) {
                this.em.remove(this.em.contains(entity) ? entity : this.em.merge(entity));
            }
        }
    }
  • delete 호출시 영속성 상태인지 확인한다.
    • 영속성 컨텍스트에 없다면(!em.contains(entity)) 엔티티를 조회해서 영속성 상태로 바꾼다.
    • 어차피 삭제할건데 굳이라는 생각이 들기도 하지만,
      • Cascade, orphanRemoval (삭제 이벤트를 전파시켜주는) 에 의한 자식도 삭제가 누락되지 않도록 해준다.
  • JpaRepository 의 delete() 는 해당 엔티티를 바로 삭제하지 않는다.
    • remove() 메소드를 통해 remove 상태로 바꾼다.
  • 자식 엔티티도 삭제해야하는 경우 어떻게 해야하는가?→ 영속성 업테이트도 해줘야 한다. ⇒ 영속성 업데이트를 위해 조회를 해줘야 한다.
  • 우선, 우리가 기능을 추가할 빈 Repository 인터페이스를 만들자
    public interface MyRepository {
    	...여기다가 추가할 메소드 선언...
    }

1. delete 쿼리가 바로 날아가도록 기능을 개선해보자 - 영속성 전이 상관없이 delete만 날려도 되는 경우

  • MyRepository
public interface MyRepository<T> {

	void delete(T entity);

}
  • MyUserRepositoryImpl
@Repository
@Transactional
public class MyUserRepositoryImpl implements MyRepository<User> {

	@Autowired
	EntityManager entityManager;

	@Override
	public void delete(User user) {
		System.out.println("delete Force");
		entityManager.remove(user);
	}
}
  • UserRepository
public interface UserRepository extends JpaRepository<User, Long> {
	
}
  • MyUserRepositoryTest
@SpringBootTest
@Rollback(value = false)
public class MyUserRepositoryTest {

	@Autowired
	UserRepository userRepository;

	@Test
	void myUserRepositoryDeleteTest() {
		// given
		User newUser = User.builder().username("name").password("pass").build();
		userRepository.save(newUser);

		// when
		userRepository.delete(newUser);
	}
}

  • delete 쿼리가 날라간 것으르 확인할 수 있다.
  • 근데 왜 deleteForce가 안찍히는지 모르겠다.

2. findAll 할때 이름만 가져오도록 기능을 추가해보자

  • MyRepository

    public interface MyRepository<T> {
    
    	void delete(T entity);
    
    	List<String> findNameAll();
    }
  • MyRepositoryImpl

    @Repository
    @Transactional
    public class MyRepositoryImpl implements MyRepository {
    
        @Autowired
        EntityManager entityManager;
    
        @Override
        public List<String> findNameAll() {
        return entityManager.createQuery("SELECT u.username FROM User AS u", String.class).getResultList();
      }
    
    }

영속성 컨텍스트 정리


  • @Transactional 을 적용하면 트랜직션이 생성이 되면 트랜직션의 범위는 1차캐시와 DB Transaction
    • 1차 영속성 캐시에서는 어플리케이션 단에서의 최적화가 발생 → 쓰기지연 쿼리를 계속해서 최적화(생성했다가 삭제하면 삭제를 못하게 한다던가)
    • DB Transaction에서는 쿼리가 이미 날라간 상태, 쿼리가 이미 날라갔지만 DB에는 반영시키지 않은 상태 → commit()이 발생했을 때 반영
  • Rollback
    • 1차 캐시단에서의 Rollback이 발생할 수 있음
    • DB Transaction에서 commit()을 하지 않으면 자동으로 반영되지 않으니 Rollback 형태가 됨
    • 둘의 Rollback은 다르다.
profile
안녕하세요

0개의 댓글