이번 글에서는 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

이번 글에서는 지난 Spring Boot  + Spring Data MongoDB + MongoRepository Demo 에 이어서

MongoTemplate 를 사용하여 데모 앱을 만들겠습니다.

MongoRepository 과 비교할수 있게 작성해볼 예정이고 어떤 차이가 있는 정리해보겠습니다. 

 

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

 

1. Spring Boot 프로젝트 생성 

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

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

저는 SpringbootMongoDbDemo, com.example.demo 라고 설정했습니다.

 

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

Customer 파일은 다음 과정에서 생성할 패키지와 파일들입니다.

 

2. application.properties 파일 설정

설치된 MongoDB는 테스트를 위한 기본 버전으로 username과 password 설정이 없어서 주석처리했습니다.

별도의 Mongo 설정 없이 아래의 설정만으로도 MongoDB에 액세스 가능합니다.

보통 기본 uri가 localhost를 많이 쓰고 database는 미리 test로 생성했습니다.

spring.data.mongodb.uri=mongodb://localhost:27017
spring.data.mongodb.database=test
#spring.data.mongodb.username=
#spring.data.mongodb.passwrod=

 

3. Customer 객체 생성

Spring boot에서 Mongo를 사용 가능한 방법은 스프링 가이드를 참고해보면

크게 2가지 정도인데, 간단한 CRUD 처리는 MongoRepository를 사용하면 되고

조금 복잡한 작업을 위해서는 MongoTemplate 사용하면 됩니다.

이번 글에서는 MongoRepository 로 간략한 CRUD 테스트를 진행해보겠습니다.

package com.example.demo;

import org.springframework.data.annotation.Id;

public class Customer {

	@Id
	public String id;

	public String firstName;
	public String lastName;

	public Customer() {}

	public Customer(String firstName, String lastName) {
		this.firstName = firstName;
		this.lastName = lastName;
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	@Override
	public String toString() {
		return String.format(
				"Customer[id=%s, firstName='%s', lastName='%s']",
				id, firstName, lastName);
	}
}

4. CommandLineRunner 생성 및 서버 스타트

자동 생성된 SpringbootMongoDbDemoApplication2.java 파일로 이동하여 CommandLineRunner 구현해줍니다.

Overide된 run 메소드 안에 CRUD 테스트 코드를 작성하고 Boot Dashboard에서 서버를 Start 합니다.

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

@SpringBootApplication
public class SpringbootMongoDbDemo2Application implements CommandLineRunner {

	@Autowired
	private MongoTemplate mongoTemplate;
	
	public static void main(String[] args) {
		SpringApplication.run(SpringbootMongoDbDemo2Application.class, args);
	}
	
	@Override
	public void run(String... args) throws Exception {

		// 최초 실행시 삭제
		mongoTemplate.remove(new Query(), "customer");
		
		// Customer 객체 생성
		Customer Bob = new Customer("Bob", "Smith");
		Customer John = new Customer("John", "Smith");
		Customer Alice = new Customer("Alice", "Smith");
		
		// mongoDB에 insert 수행. insert 메소드로 대체 가능하나 차이가 있음.
		mongoTemplate.save(Bob);
		mongoTemplate.save(John);
		mongoTemplate.save(Alice);
		
		// mongoDB에 Update 수행.
		Alice.setFirstName("Alice");
		Alice.setLastName("Chris");
		// upate 메소드로 대체 가능하나 차이가 있음.
		mongoTemplate.save(Alice); 
		
		// mongoDB에 조회 수행.
		Query query = new Query();
		Criteria criteria = Criteria.where("firstName").is("Bob");
		query.addCriteria(criteria);
		Customer temp = mongoTemplate.findOne(query, Customer.class);
		
		// mongoDB에 삭제 수행.
		mongoTemplate.remove(temp);
		
		System.out.println("Customers found with findAll():");
		System.out.println("-------------------------------");
		for (Customer customer : mongoTemplate.findAll(Customer.class)) {
			System.out.println(customer);
		}
		System.out.println();
		
		System.out.println("Customer found with findByFirstName('Alice'):");
		query = new Query(Criteria.where("firstName").is("Alice"));
		System.out.println(mongoTemplate.findOne(query, Customer.class));
		System.out.println("--------------------------------");

		System.out.println("Customers found with findByLastName('Smith'):");
		System.out.println("--------------------------------");
		query = new Query(Criteria.where("lastName").is("Smith"));
		for (Customer customer : mongoTemplate.find(query, Customer.class)) {
			System.out.println(customer);
		}
		
	}
}

 

이전글의 MongoRepository를 extends 하여 구현한 CustomerRepository 의 사용과는 조금 차이가 있지만 동일하게 동작합니다. 가장 큰 차이점은 Query 와 Criteria 객체를 이용하여 query를 만들어 세부적인 제어를 한다는 점인데요.

 

5. MongDB Compass 툴로 데이터 확인

해당 툴은 MongoDB Compass 시 기본 설치되는 툴입니다.

MongoDB Compass 툴로 접속하여 정보를 다시 확인해보겠습니다

 

 

5. Save, Insert, Update

그리고 삽입, 수정을 위해서 save 메소드를 사용했는데요.

삽입의 경우에는 insert 메소드, 수정의 경우에는 update 메소드로 대체 가능합니다.

다만 save 의 사용과 insert, update 사용에는 차이가 있기때문에 모르고 사용했을 경우 크게 낭패를 볼수도 있습니다.

save 메소드의 경우에는 _id 값이 존재하지 않으면 데이터를 삽입하지만 _id 값이 존재하는 경우 데이터가 수정됩니다.

그리고 값을 수정하는 경우에 특정 필드가 아닌 모든 데이터를 덮어쓰므로 만약 업데이트하는 필드 외에  다른 필드 값이 정의되어 있지 않은 객체를 save 메소드로 업데이트 하는 경우 데이터가 초기화 될수있다.

잘 이해가 가지 않는다면 아래의 참고 레퍼런스에서 차이점을 확인해보고 정확히 이해하고 사용할 필요가 있습니다.

 

6. 마치며 

MongoRepository도 @Query 어노테이션을 활용하여 어느정도 MongoTemplate 처럼 세부 제어는 가능하지만 개인적인 생각으로는 실무 환경에서 간단한 CRUD 만 처리하는 기능이 아닌 프로그램을 구현해야한다면 mongoTemplate를 사용하는 것이 좋지 않을까 생각됩니다. 비즈니스 환경에 따라서 고민이 필요한 부분 같습니다.

이번 코드도 Github에서 확인 가능하며, 참고한 아래의 사이트에서 더 자세히 확인 가능합니다.

 

※ 참고 

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

https://www.baeldung.com/spring-data-mongodb-tutorial

https://www.baeldung.com/queries-in-spring-data-mongodb

https://stackoverflow.com/questions/16209681/what-is-the-difference-between-save-and-insert-in-mongo-db

 

이번 글에서는 Spring Boot MongoDB 세팅과 어떻게 사용하는지 간략하게 파악해볼 간단한 데모 앱을 만들겠습니다.

 

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

 

1. Spring Boot 프로젝트 생성 

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

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

저는 SpringbootMongoDbDemo, com.example.demo 라고 설정했습니다.

 

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

Customer, CustomerRepository 파일은 다음 과정에서 생성할 파일들입니다.

 

 

2. application.properties 파일 설정

설치된 MongoDB는 테스트를 위한 기본 버전으로 username과 password 설정이 없어서 주석처리했습니다.

별도의 Mongo 설정 없이 아래의 설정만으로도 MongoDB에 액세스 가능합니다.

보통 기본 uri가 localhost를 많이 쓰고 database는 미리 test로 생성했습니다.

spring.data.mongodb.uri=mongodb://localhost:27017
spring.data.mongodb.database=test
#spring.data.mongodb.username=
#spring.data.mongodb.passwrod=

 

3. Customer 객체 생성

Spring boot에서 Mongo를 사용 가능한 방법은 스프링 가이드를 참고해보면

크게 2가지 정도인데, 간단한 CRUD 처리는 MongoRepository를 사용하면 되고

조금 복잡한 작업을 위해서는 MongoTemplate 사용하면 됩니다.

이번 글에서는 MongoRepository 로 간략한 CRUD 테스트를 진행해보겠습니다.

package com.example.demo;

import org.springframework.data.annotation.Id;

public class Customer {

	@Id
	public String id;

	public String firstName;
	public String lastName;

	public Customer() {}

	public Customer(String firstName, String lastName) {
		this.firstName = firstName;
		this.lastName = lastName;
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	@Override
	public String toString() {
		return String.format(
				"Customer[id=%s, firstName='%s', lastName='%s']",
				id, firstName, lastName);
	}
}

@Id는 MongoDB의 _id로 맵핑.

이름을  구성하는 아주 간단한 객체입니다.

 

4. CustomerRepository Interface 생성

package com.example.demo;

import java.util.List;

import org.springframework.data.mongodb.repository.MongoRepository;

public interface CustomerRepository extends MongoRepository<Customer, String> {
	
	public Customer findByFirstName(String firstName);
	public List<Customer> findByLastName(String lastName);
	
}

MongoRepository를 상속받는 인터페이스를 생성합니다. 

findByFirstName 메소드 성을 기준으로 Customer를 조회 가능하며,

findByLastName 메소드 이름을 기준으로 Customer 목록을 조회 가능합니다.

 

5. CommandLineRunner 생성 및 서버 스타트

자동 생성된 SpringbootMongoDbDemoApplication.java 파일로 이동하여 CommandLineRunner 구현해줍니다.

Overide된 run 메소드 안에 CRUD 테스트 코드를 작성하고 Boot Dashboard에서 서버를 Start 합니다.

 

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringbootMongoDbDemoApplication implements CommandLineRunner {

	@Autowired
	private CustomerRepository repository;
	
	public static void main(String[] args) {
		SpringApplication.run(SpringbootMongoDbDemoApplication.class, args);
	}
	
	@Override
	public void run(String... args) throws Exception {

		// 최초 실행시 삭제
		repository.deleteAll(); 
		
		// Customer 객체 생성
		Customer Bob = new Customer("Bob", "Smith");
		Customer John = new Customer("John", "Smith");
		Customer Alice = new Customer("Alice", "Smith");
		
		// mongoDB에 insert 수행.
		repository.save(Bob);
		repository.save(John);
		repository.save(Alice);
		
		// mongoDB에 Update 수행.
		Alice.setFirstName("Alice");
		Alice.setLastName("Chris");
		repository.save(Alice);
		
		// mongoDB에 조회 수행.
		Customer temp = repository.findByFirstName("Bob");
		// mongoDB에 삭제 수행.
		repository.delete(temp);
		
		System.out.println("Customers found with findAll():");
		System.out.println("-------------------------------");
		for (Customer customer : repository.findAll()) {
			System.out.println(customer);
		}
		System.out.println();

		System.out.println("Customer found with findByFirstName('Alice'):");
		System.out.println("--------------------------------");
		System.out.println(repository.findByFirstName("Alice"));

		System.out.println("Customers found with findByLastName('Smith'):");
		System.out.println("--------------------------------");
		for (Customer customer : repository.findByLastName("Smith")) {
			System.out.println(customer);
		}
		
	}
}

 

로그를 확인해보면 작성한 코드대로 CRUD가 이뤄진 것을 확인 가능합니다.

 

6. MongDB Compass 툴로 데이터 확인

해당 툴은 MongoDB Compass 시 기본 설치되는 툴입니다.

MongoDB Compass 툴로 접속하여 정보를 다시 확인해보겠습니다.

Document에서 데이터를 확인가능합니다.

해당 코드는 Github에서도 확인 가능하며, 하단의 spring 가이드를 토대로 일부 수정됐습니다.

 

7. 마치며

JPA Demo를 예전에 만들어본적이 있는데요.

블로그에는 추후에 올릴 예정이지만 JPA Repository 와 거의 유사한 패턴을 가지고 있습니다.

경험이 있는 분들이라면 아마 어렵지 않게 작성가능하지 않을까 싶습니다.

 

참고 : https://spring.io/guides/gs/accessing-data-mongodb/

+ Recent posts