본문 바로가기
[IT] Spring Boot (JAVA)/[JPA] Spring Data JPA

[JPA] JPA란? Spring Data JPA로 간단 예제 프로젝트 구현

by 오리엔탈킴 2022. 1. 27.

 

안녕하세요, 저는 주로 Mybatis로 쿼리를 직접 작성하여 DB 데이터를 가져와 처리하던 전통적인 자바 프로젝트를 많이 다뤘었는데요, 예전에 JPA를 잠깐 접해볼 일이 생겼는데 정말 신세계를 경험한 느낌을 받았었습니다. 그러나 업무에선 다룰 일이 많이 없어서 공부를 깊게 하지는 못하였습니다. 그래서 JPA에 대해 공부를 하기 위해 오늘은 JPA의 개념과 그중에서 Spring Data JPA를 이용하여 아주 간단한 프로젝트 샘플 예제를 구현해보도록 하겠습니다.

JPA란?

JPA란 Java Persistence API의 약자이며 자바의 ORM을 위한 표준 기술로 Hibernate, Spring JPA, EcliplseLink 등 과 같은 구현체가 있고 이것의 표준 인터페이스가 JPA 입니다.

ORM(Object-Relational Mapping)이란 자바의 객체와 관계형 DB를 맵핑하는 것으로 DB의 특정 테이블이 자바의 객체로 맵핑되어 SQL문을 일일이 작성하지 않고 객체로 구현할 수 있도록 하는 프레임워크입니다.

JPA의 장점으로, SQL 위주의 Mybatis 프로젝트와 비교하여 쿼리를 하나하나 작성할 필요도 없어 코드량이 엄청나게 줄어듭니다. 또한 객체 위주로 코드가 작성되다 보니 가독성도 좋고, 여러 가지 요구사항으로 기능 수정이 발생해도 DB부터 더 간편하게 수정이 가능합니다. 또한 Oracle, MySQL 등 DB 벤더에 따라 조금씩 다른 SQL 문법 때문에 애플리케이션이 DB에 종속될 수밖에 없었는데, JPA는 직접 쿼리를 작성하는 것이 아니라서 DB 벤더에 독립적으로 개발이 가능합니다.

Spring Data JPA 샘플 예제 구현

Spring Data JPA는 JPA를 더 쉽게 사용하기 위한 Spring Data 프레임워크의 한 파트로 JPA를 이용한 구현체를 더 추상화시켜 더 쉽고 간편하게 JPA를 이용한 프로젝트를 개발할 수 있게 해 주는 Spring 모듈입니다.

그럼 아주 간단한 Spring Data JPA를 이용하여 H2 DB에 Access 하는 Spring Boot 샘플 프로젝트를 만들어보도록 하겠습니다. 아래와 같이 Spring Boot 프로젝트를 생성합니다. 이때 Dependency는 Spring Web, Spring Data JPA, H2 Database, Lombok을 선택합니다. (참고 포스팅 : Spring Tool Suite(STS) IDE (Eclipse) 설치 및 JAVA Spring Boot 프로젝트 생성 , [VS Code] VSCode에 Spring Boot 개발 환경 세팅 및 샘플 프로젝트 생성, 실행 )

STS에서 스프링부트 프로젝트 생성
프로젝트 생성 with STS

 

위의 예시는 STS에서 직접 프로젝트를 생성한 것이고, Spring Boot 프로젝트 생성 후 Maven 프로젝트 pom.xml이나 Gradle 프로젝트 build.gradle 파일에 아래와 같이 직접 Dependency를 수동을 작성하셔도 상관없습니다.

  • Maven 프로젝트 pom.xml
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>           
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

 

  • Gradle 프로젝트 build.gradle
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'
}

 

프로젝트 구조는 아주 간략하게 아래와 같이 기본 MVC 프로젝트 구조로 파일과 패키지를 생성합니다.

  • controller - UserController.java
  • entity - Users.java
  • repository - UserRepository.java
  • service - UserService.java
  • application.yml

프로젝트 구조
프로젝트 구조

 

프로퍼티 설정 - application.yml

DB는 따로 설치된 DB에 직접 테이블을 만들고 접속해서 사용해도 되지만, 간단한 프로젝트를 위해서 H2 In-Memory DB 모드로 사용을 하도록 하겠습니다. H2 DB 정보를 application.yml 프로퍼티 파일에 작성하고, jpa:show-sql 세팅 등도 콘솔 창에서 실제 수행되는 쿼리를 확인할 수 있어 개발 중 유용하여 추가하였습니다.

spring:
  datasource: # H2 DB 설정
    url: jdbc:h2:mem:JPATest
    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 수행 시 SQL문 로그 출력
    properties:
      hibernate:
        format_sql: true # 쿼리 출력시 포맷 정렬되도록 설정

 

Entity 작성 - Users.java

Lombok이 설정되었다는 가정하에 아래와 같이 Entity를 작성합니다. (참고 포스팅 : lombok(롬복) Eclipse(이클립스), STS(Spring Tool)에 설치하기 )

SpringBoot jpa.hibernate.ddl-auto 설정의 기본값이 create-drop이라서 In-Memory DB를 사용하는 경우 Entity를 기준으로 애플리케이션 실행 시, 테이블을 Drop 하고 신규로 생성하게 되어, @Entity 어노테이션을 필수로 작성합니다. 아래와 같이 작성을 하면, 자동으로 Users라는 테이블이 생성이 되게 됩니다.

Users 테이블에 칼럼은, Key가 될 ID란 칼럼에 @Id 어노테이션을 붙여야 하며, 해당 칼럼은 직접 정의하지 않아도 자동으로 작성이 되는 AUTO_INCREMENT 기능을 위해 @GeneratedValue 어노테이션을 추가합니다. 그리고 추가로 username이라는 칼럼도 생성을 할 수 있도록 추가해줍니다.

 

@Entity
@Getter
@Setter
public class Users {
	
	@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
	private long ID;

	private String username;

}

 

Repository 작성 - UserRepository.java

실제 DB에 Access 하여 쿼리를 수행하는 등의 역할을 하는 Repository Interface를 생성합니다. @Repository 어노테이션을 추가하고, JpaRepository를 상속합니다. JpaRepository는 Spring Data JPA에서 제공하는 JPA 구현을 위한 인터페이스로 간단하게 상속하여 사전에 정의된 여러 메서드를 통해 간단히 DB에 Create/Read/Update/Delete 쿼리를 수행할 수 있습니다. 또한 정해진 규칙과 단어를 조합한 이름으로 메서드명을 작성하는 것만으로 다양한 쿼리를 생성할 수 있습니다. 우선 예시로 Name을 Like 검색한 값을 ID Desc로 Order by 하여 2개만 가져오는 메서드를 생성해보겠습니다.

@Repository
public interface UserRepository extends JpaRepository<Users, Long> {
	
	List<Users> findFirst2ByUsernameLikeOrderByIDDesc(String name);

}

 

Repository Test 파일 작성 및 수행

이렇게 간단하게 Spring JPA 구현이 완료가 되었고, Test 수행을 통해 실제로 잘 동작하는지 테스트 데이터를 Insert 후 Select 쿼리를 날려서 결과를 로그를 찍어보도록 하겠습니다. 이를 위해 Repository Test 파일도 아래와 같이 간단하게 작성하였습니다.

@DataJpaTest
@Slf4j
class JpaPjtApplicationTests {
	
@Autowired
	UserRepository userRepository;

	@BeforeEach
	void insertTestData() {
		Users user = new Users();
		user.setUsername("kim ori");
		userRepository.save(user);
		
		user = new Users();
		user.setUsername("lee ori");
		userRepository.save(user);
		
		user = new Users();
		user.setUsername("kim ental");
		userRepository.save(user);
		
		user = new Users();
		user.setUsername("lee ental");
		userRepository.save(user);
		
		user = new Users();
		user.setUsername("kim samuel");
		userRepository.save(user);
	}

	@Test
	void findAllTest() { // 저장된 데이터 모두를 Spring JPA에 미리 구현된 findAll 명령을 통해 불러온다
		List<Users> userList = userRepository.findAll();
		for(Users u : userList) log.info("[FindAll]: " + u.getID() + " | " +u.getUsername());
	}

	@Test
	void find2ByNameTest() { // Like 검색으로 2개만 값을 가져오는 내가 작성한 명령을 실행해본다
		List<Users> userList = userRepository.findFirst2ByUsernameLikeOrderByIDDesc("kim%");
		for(Users u : userList) log.info("[FindSome]: "  + u.getID() + " | " +u.getUsername());
	}

}

 

위와 같이 작성된 TEST 파일을 돌려보면 정상적으로 DB에 Insert가 되어 아래와 같이 Select 결과가 콘솔 로그에 남는 것을 확인할 수 있습니다.

select() 결과
findAll() 결과

 

select2() 결과
findFirst2ByUsernameLikeOrderByIDDesc() 결과

 

Service 작성 - UserService.java

위와 같이 Spring Data JPA를 통해 DB에 접속하여 쿼리를 날리는 테스트가 완료가 되었으면, 간단하게 CRUD 기능 중 Read, Create API 구현을 위해 Service와 Controller도 추가로 작성을 하도록 하겠습니다.

name 파라미터가 Null이면 JpaRepository에 기본으로 구현된 findAll 쿼리를 수행하여 전체 user를 가져오고, name이 Null이 아니면, 위의 Repository에 작성한 메서드 쿼리를 수행하도록 하는 getUsers라는 임의의 서비스를 작성하였습니다.

또한 Users 객체를 받아 JpaRepository의 save 메서드를 통해 Insert 쿼리를 날리는 createUsers 서비스도 아래와 같이 작성하였습니다.

@Service
@RequiredArgsConstructor
public class UserService {
    
    private final UserRepository userRepository;

    public List<Users> getUsersService(String name){
        if(name.isBlank()) // name 파라미터가 Null이면 전체 user를 리턴
            return userRepository.findAll();
        else  // name 이 존재를 하면, Like 쿼리로 2개만 리턴
            return userRepository.findFirst2ByUsernameLikeOrderByIDDesc(name);
    }

    public String createUserService(Users user){
         userRepository.save(user); // User Insert 쿼리 수행
         return "등록 완료";
    }
}

 

Controller 작성 - UserController.java

마지막으로 위에서 작성한 Repository, Service를 수행하고 API 응답을 리턴하기 위한 컨트롤러도 아래와 같이 간단하게 작성을 해줍니다. Get 매소드에 name 파라미터를 추가하여 응답을 받아 존재 유무에 따라 다른 Select 문을 날려 uesr의 리스트를 리턴하는 API와 user를 저장하기 위한 Post 매소드까지 컨트롤러에 작성을 합니다.

@RestController
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;
    
    @GetMapping(value = "/users")
    public List<Users> getUsers( @RequestParam(required = false, defaultValue = "") String name ){
        return userService.getUsersService( name );
    }

    @PostMapping(value = "/user")
    public String createUser(@RequestBody Users user){
        return userService.createUserService(user);
    }

}

 

Spring Data JPA - Rest API 테스트

그럼 마지막으로 애플리케이션을 실행하고 실제 API를 전송하는 테스트를 해보도록 하겠습니다.

먼저, PostMan 등의 API 테스팅 툴을 활용하여, "/user" 주소로 Post 요청을 전송합니다. 이때 Body에 username을 아래와 같이 포함을 하는데, ID는 엔티티에 GeneratedValue 설정을 하였으므로 username만 입력을 해도 ID는 자동 생성되어 Insert 되게 됩니다.

http://localhost:8080/user
{ "username" : "kim ori" }
 

Post 테스트 결과
Post 테스트

 

다음으로 "/users" 주소로 Get 요청을 전송을 하면, 위에서 저장된 데이터가 장상적으로 리턴되는 것을 확인할 수 있습니다. 이때 name 파라미터는 생략을 하면 전체 users의 리스트가 리턴되고, name을 포함하면 Like 쿼리로 조회된 2개의 User만 리턴되는 것을 확인할 수 있습니다.

http://localhost:8080/users?name=kim%

Get 테스트 결과
Get 테스트

 

 

반응형

댓글