[Spring Data JPA Tutorial] 4. JPA 및 datasource 설정하기

2021. 2. 19. 17:55Spring/Spring Data JPA Tutorial

300x250
반응형
  1. dependency 설정
    1. Spring Data JPA starter
    2. Database Connector
    3. Maven Update
  2. Spring Data JPA repository Bean 설정
  3. Entity 클래스 생성
  4. DB access를 위한 Repository 인터페이스 생성하기
  5. 서비스 단위 구성
  6. controller 정의
  7. api 테스트

1. dependency 설정

Spring Data JPA를 이용하여 데이터베이스에 접근하기 위해 Spring Data JPA 스타터와 연결할 database connector에 대한 라이브러리를 pom.xml에 추가합니다.

1.1. Spring Data JPA starter

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

1.2. Database Connector

연결하고자 하는 데이터베이스의 Connector dependency를 추가합니다.

1.2.1. MySQL Connector

연결할 database가 MySQL일 경우 아래와 같은 dependency를 추가합니다.

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <scope>runtime</scope>
</dependency>

1.2.2. MariaDB Connector

연결할 database가 MariaDB일 경우 아래와 같은 dependency를 추가합니다.

<dependency>
	<groupId>org.mariadb.jdbc</groupId>
	<artifactId>mariadb-java-client</artifactId>
	<scope>runtime</scope>
</dependency>

1.3. Maven Update

dependency를 수정했을 때에는 관련 라이브러리를 먼저 다운받아야합니다.
maven 다운로드를 위해 Maven Update를 합니다.

20

※ 이후 과정에서도 dependency를 수정하는 일은 종종 생길 것입니다. dependency를 수정한 후에는 반드시 Maven Update를 통해 변경사항을 다운로드해야함을 잊지 마세요.


의존 라이브러리를 추가한 후, 그대로 부트앱을 실행할 경우 아래와 같은 에러가 날것입니다.

21

의존 라이브러리만 추가했을 뿐인데 왜 에러가 났을까요.
에러메시지를 잘 확인해보면, DataSource url 구성 실패라고 쓰여있는것을 확인할 수 있습니다.
이 에러메시지는 spring-boot-starter-data-jpa 라이브러리를 추가한 후, db설정을 하지 않아서 발생한 문제입니다.

그럼, 이번에는 데이터베이스 설정을 하러 가봅시다.


2. Spring Data JPA repository Bean 설정

Spring Data JPA를 이용하여 데이터베이스를 access하기 위해서는 Spring Data Repository Bean 설정을 해야합니다.

Spring Data JPA Repository Bean을 설정하는 방법은 2가지가 있습니다.

  1. Spring Namespace를 이용한 설정
  2. JavaConfig를 이용한 설정

Spring Namespace는 xml을 이용해 설정하는 방법이고, JavaConfig는 Java class를 이용하여 설정하는 방법입니다.

그러나, 이 2가지 방법 외에 더 간단한 방식으로 빈을 구성할 수 있는데, 그것은 바로 프로퍼티를 설정하는 것입니다.
Spring boot에서는 예약된 프로퍼티 key에 dataSource 정보를 설정할 경우, 설정한 정보를 기반으로 datasource 빈을 생성해줍니다.

이전 시간에 설정한 프로퍼티는 아래와 같습니다.

spring:
  application:
    name: demo
  profiles:
    active: local
server:
  port: 8989

여기에 datasource와 jpa 설정을 추가해봅니다.
spring boot에서 미리 약속된 datasource 키는 spring.datasource고, jpa키는 spring.jpa입니다.

아래는 Spring Boot 2 버전의 default connection pool 인 HikariCP를 이용한 datasource 및 jpa 옵션 설정에 대한 코드입니다.

spring:
  application:
    name: demo
  profiles:
    active: local
  datasource:
    driver-class-name: org.mariadb.jdbc.Driver
    url: "jdbc:mariadb://localhost:3306/jiniworld_demo?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&tinyInt1isBit=false"
    username: test
    password: test
    hikari:
      auto-commit: false
      connection-test-query: SELECT 1
      minimum-idle: 10
      maximum-pool-size: 50
      transaction-isolation: TRANSACTION_READ_UNCOMMITTED
      pool-name: pool-jiniworld_demo
  jpa:
    database-platform: org.hibernate.dialect.MariaDB103Dialect
    properties:
      hibernate:
        format_sql: true
        hbm2ddl.auto: update
        implicit_naming_strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
        physical_naming_strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
    open-in-view: false
    show-sql: true
server:
  port: 8989

  • spring.datasource
    • DataSource 자동 구성을 설정하는 곳입니다.
    • driver-class-name
      • JDBC 드라이버 이름
    • url
      • JDBC URL
      • MySQL url prefix : jdbc:mysql://
      • MariaDB url prefix : jdbc:mariadb://
    • username
      • Database 로그인 username
    • password
      • Database 로그인 password

  • spring.datasource.hikari
    • DB Connection Pool 일종
    • 그밖에 Common DBCP, Tomcat-JDBC 가 있습니다.
    • auto-commit
      • 자동커밋 여부. (default: true)
    • connection-test-query
      • connection 유효성 검사 쿼리
    • minimum-idle
      • pool에 유지할 유휴 connection 최소 개수
    • maximum-pool-size
      • pool에 유지시킬 수 있는 최대 connection 수
    • transaction-isolation
      • 트랜잭션 격리 수준
    • pool-name
      • connection pool 이름

  • spring.jpa
    • database-platform
      • 접속할 database의 SQL Dialect 설정
      • JPA를 활용하여 동적 쿼리 생성시, database에 맞는 방언 sql을 생성합니다.
    • generate-ddl
      • 앱 시작시 @Entity로 정의한 테이블의 create 문 실행
    • properties.hibernate
      • hibernate vendor 관련 설정
      • hbm2ddl.auto
        • 스키마 변동시 취할 행동
      • format_sql
        • true로 설정할 시 sql 출력문을 보기 좋게 출력해줍니다.
        • false로 설정할 경우 SQL이 한줄로 길게 출력됩니다.
    • open-in-view
      • 템플릿 view 화면의 렌더링이 끝날 때 까지 Lazy fetch 가 가능하도록 해주는 속성
      • default는 true로, 매우 유용한 기능이지만 기능적 면에서 false로 설정해주는 것이 좋습니다.

위의 설정은 MariaDB 정보를 설정하였는데, 만일 다른 데이터베이스를 이용할거라면 3가지 정보를 변경해주면 됩니다.

  1. spring.datasource.driver-class-name
  2. spring.datasource.url
  3. spring.jpa.database-platform

MySQL 8.0을 이용하고 싶다면 driver-class-name는 com.mysql.cj.jdbc.Driver url의 prefix는 jdbc:mysql://로, 그리고 database-platform을 org.hibernate.dialect.MySQL8Dialect로 설정하면 됩니다.


spring.jpa.database-platform 은 query methods를 통해 동적 쿼리를 생성할 때 작성할 sql 방언을 설정하는 곳인데,
이곳에 설정한 클래스에 의해 각 Database에 맞는 문법으로 쿼리가 생성되어 실행됩니다.
org.hibernate.dialect 패키지 내에 정의된 클래스로 설정하면 되며, 사용하는 Database의 버전에 맞춰서 설정하면 됩니다.

프로퍼티에 datasource 설정을 완료한 후, 다시 웹 애플리케이션을 실행해봅니다.

09

datasource 설정을 추가한 후 앱을 실행하자 정상적으로 앱이 실행됩니다.
jpa 가 정상적으로 설정되어 persistenceUnitInfo name default도 출력됨을 확인할 수 있었습니다.

참고로, JavaConfig 또는 xml 설정없이 프로퍼티만으로 dataSource 빈을 구성할 경우, persistenceUnit 이름은 default로 설정됩니다.


참고로 multiple datasource 을 사용해야하는 경우, 프로퍼티만으로 설정할 수 없습니다.

이 경우, JavaConfig나 xml을 통한 dataSource 빈을 생성해야 합니다.

※ JavaConfig를 이용한 datasource 설정방법은 JavaConfig로 Datasource 설정하기를 참고해주세요.


3. Entity 클래스 생성

이제, 테이블과 매핑될 Entity 클래스를 만드는 방법을 알아봅시다.
Entity는 DB에 영속적으로 저장된 record를 Java 객체로 매핑한 것이라고 했습니다.
각 Entity 인스턴스를 구분짓기 위해서는 식별자가 필수적으로 필요하고, JPA에서는 이를 PK를 이용하여 구분합니다.
Database의 PK가 설정된 컬럼은 Entity 클래스에서 @Id 애너테이션을 설정한 컬럼과 매칭되는데. 이때문에 Entity 클래스에서는 @Id가 반드시 설정되어야 합니다.

Entity는 컬럼과 필드값을 매핑하는 것 뿐만 아니라 스키마 설정도 할 수 있습니다.
JPA Hibernate의 DDL 생성규칙 설정 조건에 따라 Entity 클래스에 설정한 정보가 직접 Database에 반영되기도 합니다.

더 자세한 설명은 아래 코드를 Entity 클래스를 참고하여 알아보도록 합시다.

@DynamicInsert @DynamicUpdate
@Getter @Setter
@Entity
@Table(name = "users", indexes = {@Index(name = "UK_USERS_EMAIL", columnList = "email", unique = true)})
public class User implements Serializable {

	private static final long serialVersionUID = -4253749884585192245L;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(updatable = false, nullable = false, columnDefinition = "INT UNSIGNED")
	private Long id;

	@ColumnDefault(value = "1")
	@Column(nullable = false, length = 1, columnDefinition = "CHAR(1)")
	private String type;


	@Column(nullable = false, length = 100)
	private String email;

	@Column(nullable = false, length = 50)
	private String name;

	@ColumnDefault(value = "1")
	@Column(nullable = false, length = 1, columnDefinition = "CHAR(1)")
	private String sex;

	@Column(nullable = false, length = 6)
	private String birthDate;

	@Column(nullable = false, length = 20)
	private String phoneNumber;

	@JsonIgnore
	@Column(nullable = false, length = 150)
	private String password;

	@Column(nullable = false, columnDefinition = "TINYINT(1) DEFAULT 1")
	private boolean active;

	@Temporal(TemporalType.TIMESTAMP)
	@Column(updatable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
	private Date createdAt;

	@Temporal(TemporalType.TIMESTAMP)
	@Column(nullable = true, columnDefinition = "TIMESTAMP DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP")
	private Date updatedAt;

	@PrePersist
	protected void onCreate() {
		createdAt = Timestamp.valueOf(LocalDateTime.now());
	}

	@PreUpdate
	protected void onUpdate() {
		updatedAt = Timestamp.valueOf(LocalDateTime.now());
	}

}
  • @Table
    • 테이블명, 인덱스 등에 관한 설정을 합니다.
  • @DynamicInsert
    • insert 되기 전에 엔티티에 설정된 컬럼 정보 중 null이 아닌 컬럼만을 이용하여 동적 insert 쿼리를 생성합니다.
    • table schema에 default 값이 설정된 컬럼의 경우, 엔티티에 값을 설정하지 않을시 default값으로 저장됩니다. (@DynamicInsert 설정이 되어있지 않을 경우라면 null이 들어갑니다.)
  • @DynamicUpdate
    • 엔티티 update 할 때, 변경된 컬럼정보만을 이용하여 동적 쿼리를 생성합니다.
  • @Id
    • PK 키임을 명시합니다. (ln 9)
    • Id 생성 규칙을 IDENTITY로 설정하면 키생성을 database에게 위임합니다.
    • 엔티티 저장시 id를 별도로 설정하지 않으면 MySQL의 AUTO_INCREMENT를 이용하여 자동으로 PK가 생성됩니다.(ln 10)
  • @Column
    • spring.jpa.generate-ddl 를 true로 설정할 경우 @Column에 정의한 행태로 테이블이 생성됩니다.
    • nullable, length, unique 등을 설정할 수 있고 DEFAULT 값 설정과 같은 상세 설정은 columnDefinition으로 설정할 수 있습니다.
    • length는 String 값에만 설정할 수 있는 기능입니다.
  • @ColumnDefault
    • 컬럼의 기본값
  • @PrePersist
    • entity의 값이 persist 영역으로 넘어가기 전에(=DB에 저장되기 전에) 행해져야할 기능을 정의합니다.
  • @PreUpdate
    • entity값이 update 되기 전에 행해져야할 기능을 정의합니다.

Entity class와 Database 상의 스키마 사이에 변동사항이 생겼을 경우, 프로퍼티에 설정한 JPA 생성 규칙에 따라 DDL이 실행됩니다.

JPA 생성규칙은 spring.jpa.generate-ddlspring.jpa.properties.hibernate.hbm2ddl.auto에 설정할 수 있는데,

generate-ddl 속성은 true(웹애플리케이션 시작시 기존 테이블을 모두 제거후 create 실행)와 false(아무동작 안함) 두가지만 설정할 수 있는것에 반해,
hbm2ddl.auto 는 아래와 같이 다양한 기능을 제공합니다.

  • spring.jpa.properties.hibernate.hbm2ddl.auto
    • none : 아무 동작 하지 않습니다.
    • create-only : 테이블이 없을 경우 create합니다.
    • drop : 테이블을 drop 합니다.
    • create : 기존에 테이블이 존재할 경우 테이블 drop후 새로 create합니다.
    • create-drop : 앱 실행시 테이블 create하고 앱 종료시 테이블을 drop합니다.
    • validate : 엔티티 설정과 기존 테이블 설정이 다를 경우 에러 발생합니다.
    • update : 테이블, 컬럼정보가 달라졌을 경우 추가됩니다.

우리는 update 로 설정을 했기 때문에, 스키마 변동사항이 있을 때 이에대한 DDL을 실행해줍니다.

아래는 프로그램 실행 중 hibernate가 자동으로 생성된 users 테이블 정보입니다.

Hibernate:

    create table users (
       id INT UNSIGNED not null auto_increment,
        active TINYINT(1) DEFAULT 1 not null,
        birth_date varchar(6) not null,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        email varchar(100) not null,
        name varchar(50) not null,
        password varchar(150) not null,
        phone_number varchar(20) not null,
        sex CHAR(1) default 1 not null,
        type CHAR(1) default 1 not null,
        updated_at TIMESTAMP DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
        primary key (id)
    ) engine=InnoDB
Hibernate:

    alter table users
       drop index if exists UK_USERS_EMAIL
Hibernate:

    alter table users
       add constraint UK_USERS_EMAIL unique (email)

조회 테스트틀 위해 레코드 하나를 미리 insert 해둡니다.

INSERT INTO `users` (`id`,`type`,`name`,`password`,`sex`,`email`,`birth_date`,`phone_number`,`active`,`created_at`) VALUES
(1,'1','지니','1','2','jini','921216','01014686004',1,'2020-08-23 17:47:37'),
(2,'2','코코','22','1','coco','930901','01012341234',0,'2020-09-09 17:04:32'),
(3,'1','솔','333','1','sol','930101','01023231231',1,'2020-11-28 16:13:09'),
(4,'1','시우','444','1','siwoo','920924','01022223333',1,'2020-12-03 05:09:37');

4. Repository 인터페이스 생성

이제, 이 Entity를 이용하여 DB 데이터를 access 하는 방법을 알아봅시다.

org.springframework.data.repository.Repository 인터페이스를 상속한 인터페이스를 만들고, 인터페이스 내에 Query Methods를 작성해봅니다.

import java.util.Optional;
import org.springframework.data.repository.Repository;
import me.jiniworld.demo.domain.entity.User;

public interface UserRepository extends Repository<User, Long> {
	Optional<User> findById(Long id);
}

Repository<T, ID> 에서 T에는 조회하고자 하는 엔티티 클래스인 User를, ID에는 PK타입인 Long을 설정합니다.

위의 Query method는 내부적으로 아래와 같은 jpql을 생성합니다.

select u from user u where id = ?1;

5. Service 클래스 생성

위에서 작성한 JpaRepository 구현인터페이스를 이용할 서비스클래스를 생성합니다.

서비스 클래스에서는 하나의 서비스 단위로 commit, rollback 되어야할 기능들을 정의하는 곳입니다.

@Transactional(readOnly = true)
@RequiredArgsConstructor
@Service
public class UserService {

	private final UserRepository userRepository;

	public Optional<User> selectById(Long id) {
		return userRepository.findById(id);
	}

}

별도의 설정을 하지 않을 경우, readOnly로 동작되도록 클래스에 @Transactional(readOnly = true)를 설정합니다.

데이터 변동이 일어나는 메서드에 명시적으로 @Transactional를 설정할 경우, 클래스에 설정한 @Transactional(readOnly = true)는 무시됩니다.


6. Controller 정의

@RestController 애너테이션으로 컨트롤러를 등록합니다.
뷰페이지를 이용하지않고 json과 같은 데이터로 응답받고자 할때 정의합니다.

@RestContoller = @Controller + @ResponseBody


@RequiredArgsConstructor
@RequestMapping(path = "/api/users")
@RestController
public class UserController {

	private final UserService userService;

	@GetMapping("/{id}")
	public Map<String, Object> selectUserById(@PathVariable("id") long id) {
		Map<String, Object> response = new HashMap<>();

		Optional<User> oUser = userService.selectById(id);
		if(oUser.isPresent()) {
			response.put("result", "SUCCESS");
			response.put("user", oUser.get());
		} else {
			response.put("result", "FAIL");
			response.put("reason", "일치하는 회원 정보가 없습니다. 사용자 id를 확인해주세요.");
		}

		return response;
	}

}

User와 관련된 api를 정의할 UserController 클래스를 만듭니다.
api prefix는 /api/users로 설정하고, selectUserById 메서드에서는 그 뒤에 /{id} 를 붙입니다.

selectUserById는 최종적으로 GET /api/users/{id} 라는 path 설정을 갖게 되는데, path에 설정된 중괄호가 설정된 값인 {id}@PathVariable를 이용하여 인자 타입을 설정할 수 있습니다.

Optional을 이용하여 Entity가 존재할 경우와 존재하지 않을 경우에 대한 분기처리를 합니다.

이전 시간에 접속 테스트를 위해 생성했던 TestController는 이제 불필요하니 제거합니다.


7. api 테스트

Entity, Repository, Service, Controller 설정을 모두 마쳤다면 앱을 Build 한 후 Boot 앱을 실행시킵니다.

id가 2인 user를 조회해봅시다.

11

위와 같이 Database에 저장된 정보가 정상적으로 조회되었습니다.

GitHub에서 demo 프로젝트(v1.0.1)를 다운받아 볼 수 있습니다.


++

  • Spring Boot + JPA + MariaDB
  • Spring Boot + JPA + MySQL
  • 프로퍼티를 통한 Spring Data JPA repository Bean 설정
  • how to set Spring Data JPA Repository bean using yml
  • how to set datasource config using property
300x250
반응형