Spring-data-jpa, Entity Manager 함께 쓰기


개요

Spring-data-jpa를 사용하면 Entity Manager 를 사용하지 않고 쉽게 구현 가능하지만 가끔 Entity Manager로 직접 영속성 컨텍스트를 컨트롤 해야할 필요가 생긴다. 어떤식으로 둘을 함께 사용할 수 있는지 헤딩한 내용을 기록해본다.


Spring-data-jpa 와 jpa의 차이


우선 JPA와 EntityManager가 무엇인지 이전글 jpa란 무엇인가?에서 정리해봤다.

그럼 Spring-data-jpa란 무엇일까? Spring-data-jpaJPA를 추상화 시켜둔 개념으로, 개발자가 EntityManager로 직접 접근 없이도 쉽게 개발 할 수 있도록 도와주는 모듈이다. Spring-data-jpa를 구성하는 내부에 Entity Manager가 구현되어 있어 사용자는 Repository Interface를 정의하는 것 만으로 JPA를 손쉽게 사용할 수 있다.

spring-data-jpa Repository


흔히 사용하는 spring-data-jpa Repository 예시

@Repository
public interface workRepository {
	int insertNtisNotice(FeedMessage feedMessage) throws Exception;
	List<FeedMessage> findByMessageLikeOrderByIDDesc(String message) throws Exception;
	int insertNtisNoticeAuto(FeedMessage feedMessage) throws Exception;
	int deleteNtisNoticeAutoCheck() throws Exception;
	List<AccountInfoDTO> selectAccountInfo(AccountInfoDTO accountInfoDTO) throws Exception;
	AccountInfoDTO findByManagerByID(int id) throws Exception;
}

interface 만으로 쿼리에 접근하도록 돕는다. 이것만으로도 반복에 가까운 자바 코드가 줄어든다.

하지만 뭔가 부족하다. 어쩌다 한 두번씩 EntityManager를 사용해야 할 때가 온다.. 그럼 그냥 함께 사용해보자.


함께 사용해보자


SpringBoot 프로젝트로 간단하게 구현해보자.

1. 프로젝트 생성하기

스프링부트 프로젝트 만들기 에서 프로젝트를 만들거나, 또는 하단 예제처럼 이클립스에서 New Spring Starter Project 로 프로젝트를 만든다.

프로젝트 생성1 설정은 크게 상관없다. Gradle 이나 자바 버전을 8로 선택한 건 단지 내가 익숙해서다.

프로젝트 생성2 간단하게 구현 테스트만 해 볼 것이기 때문에 메모리타입 DB를 지원하는 H2 DB와 편의성이 엄청난 Lombok, 당연히 Spring Data Jpa 와 웹서비스를 구현하는 것이므로 Spring Web도 선택한다.

자주 사용하는 항목에 저게 없으면 하단 검색창에서 검색하여 선택하면 된다.

사실 제대로 선택 안해도 상관없다. 프로젝트가 생성된 후 gradle 파일을 직접 수정하고 Refresh gradle project 해주면 제대로 반영된다.

finish 하면 프로젝트가 생성된다.

2. build.gradle 파일

plugins {
	id 'java'
	id 'org.springframework.boot' version '2.4.5'
	id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
	maven { url 'https://repo.spring.io/milestone' }
	maven { url 'https://repo.spring.io/snapshot' }
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'com.h2database:h2'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
	useJUnitPlatform()
}

예제를 따라 했다면 위 내용은 버젼만 좀 다르고 거의 같을 것이다.

3. H2 DB 설정하기

spring:
  datasource: 
    url: jdbc:h2:mem:demo #메모리 타입DB로 demo라는 DB에 접근한다는 뜻.
    username: sa          #사용자는 필수 입력사항.
    password:             #패스워드는 선택사항.
    driver-class-name: org.h2.Driver
  h2:
    console:
      enabled: true # H2 DB의 웹 콘솔 사용여부 설정. (http://localhost:8080/h2-console 로 접근)
  jpa:
    database: H2
    show-sql: true # jpa 에서 쿼리 출력여부 설정
    properties:
      hibernate:
        format_sql: true # 쿼리 출력시 정렬 여부 설정 

src/main/resource 경로에 application.yml 파일을 생성하고 위 내용을 입력한다. DB 콘솔 접근법은 하단에서 설명하겠다.

4. 패키지 생성, 소스 작성

프로젝트 생성3

위와 같이 패키지 혹은 폴더를 생성하고 다음 파일들을 작성한다. 핵심 내용만 보면

  • Member.java
    public class Member {
      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      @Column(name="ID")
      private long id;
    
      private String name;
    
      @Column(nullable=true)
      private Integer age;
    }
    

    간단하게 id와 이름, 나이 컬럼을 사용하려 한다. 실행시 테이블 자동생성을 위해 id에 GeneratedValue 어노테이션과 strategy를 설정해 준다.


  • MemberController.java

public class MemberController {
	@Autowired
    private MemberService memberService;

    // 멤버 등록 (spring-data-jpa)
    @PostMapping(value = "/member")
    public String createMember(@RequestBody Member member){
        return memberService.createMember(member);
    }

    // 멤버 등록 (jpa EntityManager)
    @PostMapping(value = "/memberjpa")
    public String createMemberJpa(@RequestBody Member member){
        return memberService.createMemberJpa(member);
    }
	
	// 멤버 조회
    @GetMapping(value = "/member")
    public List<Member> getMember(@RequestParam(required = false, defaultValue = "") String name){
        return memberService.getMemberList(name);
    }
}

두 가지 방식의 멤버 등록을 위한 메써드를 준비하고, 등록이 잘 되었는지 확인할 조회 메서드를 하나 만든다.


  • MemberService.java
public class MemberService {

	@Autowired
    private MemberRepository memberRepository;

    // spring-data-jpa
    public String createMember(Member member){
    	memberRepository.save(member); // 사용자 등록 처리
        return "success";
    }

    // jpa
    public String createMemberJpa(Member member){
    	log.info("member name : " + member.getName());
    	memberRepository.createUserAuto(member);
    	return "jpa success";
	}

    public List<Member> getMemberList(String name){
        if(name.isEmpty()) {
            return memberRepository.findAll();
        } else {
        	return memberRepository.findByNameLikeOrderByName(name);
        }
    }
}

컨트롤러와 같은 목적으로 생성한다.


  • MemberRepository.java

Repository는 우선 spring-data-jpa를 위해 기본적인 JpaRepository를 상속하는 interface를 만들어준다.

@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
	List<Member> findByNameLikeOrderByName(String name);
}

이제 JPA를 만들어본다. JpaRepository는 EntityManager에 직접 접근할 수 없으므로 우회로를 만들어주자.

추후 MemberRepository에 상속하기 위한 interface repository를 만드는데 service에서 jpa용으로 만들어 둔 함수를 사용한다.

public interface MemberCustomReposity {
	String createUserAuto(Member member);
}

그리고 해당 인터페이스를 구현할 MemberCustomReposityImpl 파일도 만든다.

@Transactional(readOnly = true)
public class MemberCustomReposityImpl implements MemberCustomReposity {
	@PersistenceContext	// 영속성 컨택스트를 spring-data-jpa 에서 사용하기 위한 어노테이션
	EntityManager em;

	@Override
	@Transactional
	public String createUserAuto(Member member){
		log.info(member.getName());
		em.persist(member);

		Member member2 = em.find(Member.class, member.getId());
		log.info(member2.getName() + " / " + member2.getId());

		return "실행 완료";
	}
}

아주 단순하게 넘어온 멤버객체 이름을 출력하고, 등록된 값의 id로 데이터를 찾아 정보를 출력해 보았다.

@PersistenceContext를 통해 EntityManager를 가져다 사용하고, 클래스 단의 트랜잭션은 readonly 상태로, 트랜잭선 처리가 필요한 메서드는 @Transactional 어노테이션을 추가해야 EntityManager가 1차캐쉬를 플러쉬 하면서 처리할 수 있다.

여기까지 작성한 내용을 MemberRepository에 추가해 주자.

@Repository
public interface MemberRepository extends JpaRepository<Member, Long>, MemberCustomReposity {
	List<Member> findByNameLikeOrderByName(String name);
}

말은 길었지만 정작 코드는 참 쉽다. 이제 서버를 구동하고 테스트를 해보자.


5. 테스트

컨트롤러에 작성한 접근 방식을 이용하여 하나씩 테스트해보자.
먼저 spring-data-jpa 로 등록. 프로젝트 생성6 success 응답이 왔다.
다음 JPA EntityManager로 등록. 프로젝트 생성7
콘솔 로그도 정상 확인. 프로젝트 생성8
제대로 등록 되었는지 조회해보자. 프로젝트 생성10

제대로 등록되어 있다.

DB에 직접 붙어서 확인하고 싶다면,

http://localhost:8080/h2-console 로 접속한다.

프로젝트 생성4 이와같은 화면을 볼 수 있는데 이것이 h2 DB console 이다. 다른건 그대로 두고, JDBC URL 만 본인 설정에 맞게 변경한다.

연결 테스트 해보고 접속해보자.

프로젝트 생성9 그럼 SQL을 직접 작성해 볼 수 있는 창이 보인다. 마음껏 쿼리를 날려보자.


정리


  • spring-data-jpa를 기반으로 EntityManager를 위한 repository 를 따로 구현한다.
  • EntityManager를 사용하기 위해 @PersistenceContext 어노테이션을 이용한다.
  • JPA는 트랜잭션 기반으로 동작하기 때문에 EntityManager 설정 시 트랜잭션 설정을 해준다. 정도가 되겠다.

해당 내용의 소스 파일은 Github 에 올려두었다.

하나하나 헤딩하는 과정이 힘들지만 모레알처럼 작은 지식들 하나하나가 쌓여가고 있다고 행복회로를 돌려본다.