이번 글에서는 Spring Boot  + Spring Data JPA + MySQL 데모 앱을 만들겠습니다.

전형적인 MVC 패턴을 가진 구조의 코드에 CRUD 가능한 WEB API 데모를 제작해보겠습니다.

 

개발 환경 : STS 4.8.1, JDK 11, Spring Boot 2.6.2, MySQL Community 5.6, Maven 4.0.0

MySQL Community 5.6 이 미리 설치됐다는 가정하고 진행해보겠습니다.

 

들어가기전에 앞서서 간단하게 JPA와 관련된 ORM 개념을 간단하게 정리해보면 아래와 같습니다.

 

ORM(Object-Relation Mapping)은 객체와 데이터베이스 데이터를 맵핑 해주는 것으로

객체 관계를 바탕으로 SQL을 생성합니다.

 

JPA(Java Persistence API)로 JAVA에서 제공하는 API 이고 자바 어플리케이션에서

관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스입니다. 

Spring Data JPA는 Hibernate를 구현체로 사용하고 있습니다.

 

1. Spring Boot 프로젝트 생성 

Spring Stater Project 생성하고 중간 의존성 설정 단계에서 아래와 같이

MySQL Driver와 Spring Data JPA를 설정하고 생성합니다.

프로젝트, 패키지 명은 적절하게 설정해주세요.

저는 SpringbootJPADemo, com.codejcd.demo 라고 설정했습니다.

아래와 같은 구조를 가진 프로젝트가 생성된 것을 확인할수있습니다.

자동 생성된 SpringbootJPADemoApplication.java 파일을 빼고는 모두 이번 Demo에서 만들 패키지와 파일들입니다.

 

 

2. application.properties 파일 설정

1번째 라인은 테스트 시 WEB API에 접근하기 위한 포트 설정 값입니다.

3~5 번째 라인은 MySQL DB에 접근하기 위한 설정 값입니다.

자신의 환경에 맞춰서 작성하면됩니다.

참고로 실무에서는 root 유저를 사용하지 않고 따로 제한된 권한을 가진 생성된 유저를 사용하며,  

비밀번호 역시 저렇게 간단하게 설정하지 않습니다. 테스트 용이니 참고만 하시기 바랍니다.

8번째 라인은 하이버네트 자동 키 생성 전략을 OFF 했습니다.

저같은 경우 해당 데모에서는 MySQL 테이블에 AUTO_INCREMENT 설정을 했기 때문입니다.

11번째 라인은 DB에 전송하는 SQL을 보기 위한 옵션입니다.

 

3. DB 스키마 생성

MySQL에 아래와 같은 스키마를 생성합니다.

생성 SQL은 Github 소스에서 create_user_table_.sql 을 참고해서 생성해주세요.

 

4. User 생성

com.codejcd.demo.bean 패키지와 User 파일을 생성하고 아래와 같이 작성합니다.

P.K 역할을 할 id, name, phone의 간단한 객체를 생성합니다.

package com.codejcd.demo.bean;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class User {
	@Id // 해당 프로퍼티가 Primary Key
	@GeneratedValue(strategy = GenerationType.IDENTITY) // Primary Key 생성을 DB에 위임
	private Long id;
	
	@Column(length = 20, nullable = false) // 길이 20 제한, not null
	private String name;
	
	@Column(length = 20, nullable = false, unique = true) // 길이 20 제한, Not Null, Unique key
	private String phone;

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getPhone() {
		return phone;
	}

	public void setPhone(String phone) {
		this.phone = phone;
	}
}

5. UserRepository

데이터 접근을 위한 JpaRepository를 상속 받는 인터페이스를 생성합니다.

User 데이터 조회를 위한 findBy* 메소드를 구현합니다.

package com.codejcd.demo.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.codejcd.demo.bean.User;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
	public List<User> findByName(String name);
	public List<User> findByPhone(String phone);
	
}

 

JpaRepository를 상속 받으면서 CRUD 관련 아래와 같은 기능들을 사용할수있습니다.

메소드  기능
 save()  레코드 저장 (insert, update)
 findOne()  primary key로 레코드 한건 찾기
 findAll()  전체 레코드 불러오기. 정렬(sort), 페이징(pageable) 가능
 count()  레코드 갯수
 delete()  레코드 삭제

규칙에 맞는 메소드를 인터페이스 상에 정의하면 아래와 같은 기능으로도 활용 가능합니다.

자세한 활용 방법은 참고 사이트에 링크한 JPA 레퍼런스 문서를 확인해주세요.

메소드 설명 
 findBy로 시작  쿼리를 요청하는 메소드
 countBy로 시작  쿼리 결과 레코드 수를 요청하는 메소드
메소드 포함 키워드  샘플  설명
 And  findByEmailAndUserId(String email, String userId)  여러필드를 and 로 검색
 Or  findByEmailOrUserId(String email, String userId)  여러필드를 or 로 검색
 Between  findByCreatedAtBetween(Date fromDate, Date toDate)  필드의 두 값 사이에 있는 항목 검색
 LessThan  findByAgeGraterThanEqual(int age)  작은 항목 검색
 GreaterThanEqual  findByAgeGraterThanEqual(int age)  크거나 같은 항목 검색
 Like  findByNameLike(String name)  like 검색
 IsNull  findByJobIsNull()  null 인 항목 검색
 In  findByJob(String … jobs)  여러 값중에 하나인 항목 검색
 OrderBy  findByEmailOrderByNameAsc(String email)  검색 결과를 정렬하여 전달

 

6. UserService

비즈니스 로직을 구현할 클래스를 생성합니다.

CRUD를 위한 메소드를 각각 생성합니다.

package com.codejcd.demo.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.codejcd.demo.bean.User;
import com.codejcd.demo.repository.UserRepository;


@Service
@Transactional
public class UserService {
    @Autowired
    UserRepository userRepository;
    
	public List<User> findByName(String name) {
		return userRepository.findByName(name);
	}
	
	public void save(User user) {
		userRepository.save(user);
	}
	
	public List<User> findAll() {
		return userRepository.findAll();
	}
	
	public void delete(User user) {
		userRepository.delete(user);
	}
	
}

 

7. UserController

Restful 컨트롤러를 생성하기 위해 @RestController 어노테이션을 추가합니다.

@RequestMapping 어노테이션을 추가해 각각 요청을 맵핑합니다.

package com.codejcd.demo.controller;
import java.util.List;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.codejcd.demo.bean.User;
import com.codejcd.demo.service.UserService;

@RestController
public class UserController {
	
    @Autowired
    UserService userService;
    
    @RequestMapping("list")
    public List<User> userlist() {
    	return userService.findAll();
    }
    
    @RequestMapping("name/{name}")
    public List<User> userFindByName(@PathVariable String name) {
    	return userService.findByName(name);
    }
    
    @RequestMapping("insert")
    public User userInsert(@ModelAttribute User user) {
    	userService.save(user);
    	return user;
    }
    
    @RequestMapping("delete")
    public User userDelete(@ModelAttribute User user) {
    	userService.delete(user);
    	return user;
    }
    
    @RequestMapping("update")
    public User userUpdate(@ModelAttribute User user) {
    	userService.save(user);
    	return user;
    }

}

8. API 호출을 통한 CRUD 테스트 

STS Boot Dashboard에서 서버를 스타트하고 UserController에 정의한 각각의 WEB API를 호출하면서

로그와 출력 결과를 확인해봅니다.

test1 유저 등록&amp;nbsp;


test2 유저 등록&amp;nbsp;


유저 리스트 조회


test1 유저 조회


test2 유저 업데이트


test2 유저 삭제


유저 리스트 조회

 


위 로그는 2번 항목에서 설정으로 객체 관계로 생성된 SQL 이 출력되고 있습니다.

 

9. 마치며

간단하게 JPA를 사용한 CRUD Demo를 작성해보았는데요.

실제 실무 환경에서는 이보다 훨씬 복잡한 객체 관계가 요구되기때문에 복잡한 코딩이 요구될 것 같습니다.

대부분 오래전 모델링된 DB를 쓰다보니 정규화도 제대로 안되어 있고 DBA가 없는 환경에서

여러 개발자들이 거치면서 비즈니스 롤에 따라 무분별하게 컬럼들이 추가된 경우가 많다보니

실제로 JPA를 기존 시스템 환경에 도입하려면 많은 리소스와 문제점이 예상됩니다.

조금 다른 이야기를 하자면 한국 시장에서 대부분의 시스템은 Mybatis Framework를 사용한 SQL Mapper 환경입니다.

실무 환경에서는 거의 Mybatis Framework를 사용한 SQL Mapper 환경에서

일을 하고 있습니다.

자바를 사용하는 실무자 입장에서는 아무래도 객체지향적인 프로그래밍을 하는데

JPA가 좋아보이는데요.

그럼에도 불구하고 한국 시장에서는 JPA 기술 스택을 원하는 업체가 그리 많지는 않습니다.

개인적인 생각이긴 하지만 몇가지 원인을 꼽자면 이미 기존 구축된 시스템이

Mybatis 기반이다보니어쨌든 일반 회사의 의사결정자 입장에서보면

굳이 잘 돌아가는 시스템을 리소스를 넣어서 갈아엎을 이유가 없기 때문이겠죠.

그렇다고 신규 시스템에 JPA를 도입할지 생각해본다면 굳이 구하기 쉽고

상대적으로 낮은 단가를 가진 SQL Mapper에 능숙한 개발자들이 많은데

JPA 스택을 가진 개발자들을 데리고 오고 싶을까 하는 생각이듭니다.

그래도 기회가 된다면 JPA 환경에 실무에서 일해보고 싶은 개인적인 바램입니다.

Github에서 전체 소스를 확인 가능합니다.

 

※ 참고

https://www.baeldung.com/hibernate-identifiers

https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.auditing

https://www.javaguides.net/p/spring-data-jpa-tutorial.html

+ Recent posts