스프링 데이터 JPA

대영·2023년 11월 26일
2

Spring

목록 보기
4/15

🙏내용에 대한 피드백은 언제나 환영입니다!!🙏

앞선 글에서 JPA에 대해서 알아보았다.
스프링 데이터 JPA를 알기 위해서는 JPA에 대해서 알아야한다.
이유는 스프링 데이터 JPA는 JPA를 기반으로 한 것이기 때문이다.

📌스프링 데이터 JPA

👉JPA에 대해 다시 되새기며

이 글(JPA)에서 작성한 내용을 다시 상기해보자.
JPA를 사용하려면 먼저, persistence.xml에 JPA 설정 정보(데이터베이스 연결을 위한 정보, 엔티티 클래스 등) 작성하여 영속성 유닛을 설정한다. 그리고, 영속성 유닛을 생성하는 팩토리인 EntityManagerFactory에 연결시켜 준다. 그 후, EntityManager에서 객체의 인스턴스를 받아 트랜잭션 과정을 통해 진행된다.

👉달라진 점

스프링 데이터 JPA에서는 EntityManagerFactory가 자동 설정된다.
그리고, 스프링에서는 @Transactional을 이용하여 Transactional과의 연동을 지원하고, EntityManager과의 연동도 지원한다.
즉, JPA에서 작성하던 코드를 적지 않아도 된다.

👉사용방법

  1. spring-boot-starter-data-jpa 의존.
  2. 스프링 부트 설정. (이 후에 스프링 부트에 대한 내용을 작성하겠다.)
  3. 엔티티 단위 Repository 인터페이스를 상속 받은 인터페이스 생성.
  4. 지정한 규칙에 맞게 메서드 추가.
  5. 필요한 곳에 해당 인터페이스 타입을 주입해서 사용.

❕Repository 인터페이스 상속 받은 인터페이스

/*JpaRepository 상속*/
public interface CommentRepository extends JpaRepository<Comment, Long> {
}

/*Repository 상속*/
public interface CommentRepository extends Repository<Comment, Long> {
}

위 코드에서 보면 Repository<Comment, Long> 가 적혀있다.
여기서, 첫 번째 매개변수에는 엔티티 클래스, 두 번째 매개변수에는 엔티티의 주요 식별자(@Id가 지정된 필드)를 작성한다.

그리고, Repository와 JpaRepository는 같은 역할을 하지만, 차이점이 있다.
그 이유는 이 글에서 확인할 수 있다.
또한, 빈의 자동주입을 위한 @Reposiroty 어노테이션도 사용하지 않는데, 그 이유는 이 글에서 알 수 있다.

📌스프링 데이터 JPA를 이용한 CRUD구조

우선 바로 위에 Repository 인터페이스를 상속 받은 인터페이스를 작성한다.
그리고 이 인터페이스 의존성을 주입(DI) 받아 사용하면 된다.

	/* CommentService 코드의 일부 */
    
	private CommentRepository commentRepository;

    @Autowired
    public CommentService(CommentRepository commentRepository) {
        this.commentRepository = commentRepository;
    }

아래의 모든 과정은 @Transactional을 통해 Transactional이 진행된다.

👉Create(저장)

 @Transactional
    public void save(Comment comment) {
        commentRepository.save(comment);
    }

commentRepository이 상속한 Repositoty를 통해서 save() 사용한다. 그리고, commit하면 저장이 된다.

그렇다면 save()는 어떻게 동작할까? 아래의 코드는 save 메소드를 가지고 있는 SimpleRepository.java의 코드이다.

@Transactional
	@Override
	public <S extends T> S save(S entity) {

		if (entityInformation.isNew(entity)) {
			entityManager.persist(entity);
			return entity;
		} else {
			return entityManager.merge(entity);
		}
	}

이것을 본다면, 매개변수의 정보가 새로운 것인지, 새로운 것이 아닌지를 확인한다.
그 정보는 @Id가 붙은 주요 식별자를 확인 하는 것이다.
isNew를 통해서 존재하지 않는다면 persist를 이용하여 create 쿼리를 날리고,
존재한다면 merge를 이용하여 update 쿼리를 날린다.

👉Read(조회)

여기서는 getById와 findById를 보겠다.

	@Transactional
    public Comment getById(Long id) {    // read (by id)
        return commentRepository.getById(id);
    }

위는 getById를 이용하였다.
getById는 @Deprecated 어노테이션이 달려 있어 더이상 사용하지 않는다고 getReferenceById에 연결하여 getReferenceById메소드를 가지고 왔다.

	@Override
	public T getReferenceById(ID id) {

		Assert.notNull(id, ID_MUST_NOT_BE_NULL);
		return entityManager.getReference(getDomainClass(), id);
	}

이것은 getReference을 통해서 엔티티를 직접 반환하는게 아니라 프록시만 반환한다.
이게 무슨 말이냐면, 실제로 사용하기 전까지는 데이터베이스에서 접근하지 않는다는 것이다. 즉, 지연 로딩이 적용되고 있다.
만약, 객체가 존재하지 않는다면 EntityNotFoundException을 발생한다.


다음으로는 findById를 보겠다.

	@Transactional
    public Optional<Comment> findById(Long id) {    // read (by id)
        return commentRepository.findById(id);
    }

아래에서 findById 메소드를 자세히 보겠다.

	@Override
	public Optional<T> findById(ID id) {

		Assert.notNull(id, ID_MUST_NOT_BE_NULL);

		Class<T> domainType = getDomainClass();

		if (metadata == null) {
			return Optional.ofNullable(entityManager.find(domainType, id));
		}

		LockModeType type = metadata.getLockModeType();
		Map<String, Object> hints = getHints();

		return Optional.ofNullable(type == null ? entityManager.find(domainType, id, hints) : entityManager.find(domainType, id, type, hints));
	}

findById는 실제 DB 에 요청해서 엔티티를 가지고 온다.
일단, 요청하기 전에 영속성 컨텍스트의 1차 캐시를 먼저 확인하고 데이터가 없으면 데이터베이스에 데이터가 있는지 확인하고 가지고 온다.
그리고, 가지고 올 데이터가 없으면 null을 직접다루는 것이 아닌 Optional을 통해 반환한다. 그래서, NullPointerException이 발생하는 경우는 없다.

만약 객체의 id정보만 필요하다면 getById를 사용하여 성능을 높일 수 있다.
전체적인 정보가 필요하다면 findById를 사용하는 것이 적절하다.


이외에도, Id정보가 아닌 다른 정보를 통해서(name, title 등 필드의 정보를 통해) 정보를 가지고 오고 싶다면 Repository가 상속된 Repository에 가서 아래와 선언 후 사용하면 된다.
이렇게 선언된 메소드들은 사용하면 스프링 데이터 JPA에서 내부적으로 JPQL 쿼리를 생성하여 실행한다.

	/* 이렇게 선언 후 해당 메소드를 사용하면 됨.*/
    public interface CommentRepository extends JpaRepository<Comment, Long> {
        // content를 통해 객체 가지고 오기.
		Comment findByContent(String content)}

이름 작성 규칙

find(or delete) + By + 메서드 이름 + (선택)검색조건연산자
ex) findByTitle , findByTitleContaining
deleteByTitle , deleteByTitleContaining

검색조건연산자

Equal : 지정된 필드가 주어진 값과 정확하게 일치하는 엔터티를 검색.
Like : 지정된 필드가 주어진 값과 부분적으로 일치하는 엔터티를 검색.
Containing : 지정된 필드가 주어진 값을 포함하는 엔터티를 검색.(문자열 일부도 검색)
StartingWith : 지정된 필드가 주어진 값으로 시작하는 엔터티를 검색.
EndingWith : 지정된 필드가 주어진 값으로 끝나는 엔터티를 검색.
IsNull : 지정된 필드가 null인 엔터티를 검색.
IsNotNull : 지정된 필드가 null이 아닌 엔터티를 검색.
OrderBy : 결과를 정렬할 때 사용. Desc는 내림차순, Asc는 오름차순.
And : 여러 조건을 조합하여 검색.
Or : 여러 조건 중 하나라도 일치하는 엔터티를 검색.

👉Update(수정)

수정은 find를 통해서 할 수 있다.
find를 통해 영속성 컨텍스트에 있는 1차 캐시에 저장하여 엔티티 매니저가 관리하게 한 후, 내용을 수정하여 commit을 하면 update쿼리를 날려 수정이 된다.

    @Entity
    public class Comment {
    	public void update(String content) {
     	   this.content = content;
    	}
    }
    
    @Transactional
    public void update(Long id, String content) {
        Comment comment = commentRepository.findById(id);
        comment.update(content);
    }

👉Delete(삭제)

	@Transactional
    public void delete(Long id) {
        commentRepository.deleteById(id);
    }

Id를 통해서 삭제하는 deleteById를 보겠다.

	@Transactional
	@Override
	public void deleteById(ID id) {

		Assert.notNull(id, ID_MUST_NOT_BE_NULL);

		findById(id).ifPresent(this::delete);
	}

findById를 통해서 데이터를 영속성 컨텍스트의 1차 캐시에 가지고 온 후, isPresent를 통해서 존재한다면 삭제하는 과정을 거친다.

💡느낀점

스프링 데이터 JPA를 코드 작성에 편리해 질 수 있다.
깔끔한 코드로 순수 JDBC를 공부할 때 생각해보면 아주 간단히 표현할 수 있게 되었다. 그래서, 가독성은 물론이고 생산성 또한 JdbcTemplate를 공부할 때보다 더욱 증가한다는 것을 알게 되었다.

profile
Better than yesterday.

0개의 댓글