Spring boot는 요구하는 기능에 따른 dependency module들을 한번에 불러올 수 있는 initializr 기능을 가지고 있다. 필요한 Dependency를 검색하여 추가해주었다. web, jpa, mysql, thymeleaf, validation, oauth2, spring security, dev tool 등이었다.
필요한 모듈을 처음부터 한 번에 불러올 필요 없이 새로운 의존성이 필요할 때마다 위의 start spring 사이트나 maven repository에서 모듈을 검색한 후 build.gradle에 추가해줄 수 있다.
maven repository 사이트:
1. RDS (Relational Database Service)
보통 spring boot 프로젝트의 개발 단계에서 사용하는 DB는 h2 in-memory DB이다. 실행 중인 프로그렘의 메모리에 DB 공간을 올려놓는 것이다. 별다른 연결 설정을 할 필요 없이 간단하게 데이터베이스 구성이 되지만 휘발성이 있는 RAM 특성상 프로그램이 reload 될때마다 데이터가 전부 초기화된다는 단점이 있다.
MySQL 서버를 원격으로 설정하기 위해 접근성이 높고 비교적 설정이 간단한 RDS를 사용했다. RDS는 AWS에서 제공하는 관계형 데이터베이스 서비스이다. DB 서버를 외부에 둘 수 있고 몇가지 설정을 통해 로컬이나 다른 환경 서버에서 원격으로 접속할 수 있다.
AWS 콘솔 로그인을 한 뒤, RDS 서비스에 들어가 데이터베이스 생성 버튼을 클릭한다. 그 뒤 MySQL Community Edition 엔진을 선택한다. 대부분의 설정은 손쉬운 생성 기능에서 제공한 default 값을 이용했다. 작은 프로젝트이므로 Free Tier 규모를 택해도 절대 모자라지 않다.
DB 인스턴스 식별자는 말 그대로 생성할 데이터베이스의 이름이다. 프로젝트와 관련한 이름으로 설정했다. 마스터 사용자 이름과 암호는 해당 DB에 admin user로서 접근하기 위한 ID와 PW이다. 이 정보는 DB 연결에 꼭 필요한 설정이므로 보관해 놓는 것이 좋다.
아래 화면은 DB 생성 후 상세정보에 들어가면 볼 수 있는 화면이다. 엔드포인트는 DNS로 설정된 DB 서버 주소이고, 포트번호 3306은 MySQL이 사용하는 기본 포트번호이다.
DB가 만들어졌으면 해당 DB 접근을 위한 보안 그룹의 인바운드 규칙을 편집해주어야 한다. '인바운드 규칙'이란 외부에서 DB 내부로 통신할 수 있는 IP를 지정하는 과정이다. 한마디로 해당 DB에 CRUD 쿼리를 날릴 수 있는 IP를 추가하는 것이다. 방금 만든 DB에 자동 생성된 default VPC 보안 그룹을 클릭한 뒤 인바운드 규칙에 들어가 <인바운드 규칙 편집> 버튼을 클릭하면 아래와 같은 설정 창이 뜬다.
개발 단계에선 편의상 MySQL을 이용한 프로토콜은 전부 허용할 생각이다.(참고로 MySQL의 포트 번호는 3306이 기본값이다.) 유형(프로토콜)에 MySQL/Auror를을 선택하고 IPv4 및 IPv6를 통해 들어올 수 있는 모든 IP 요청에 대한 접근을 허용한 뒤 규칙을 저장한다.
이 뒤 MySQL이 설치된 로컬 컴퓨터의 커맨드 창에 아래 명령어를 입력한다.
> mysql -h {URL} -u {ID} -p
URL은 디비의 엔드포인트, ID는 마스터 사용자 이름 설정시에 설정한 ID이다. 나의 경우는 admin이었다. 인바운드 규칙 설정이 잘 되었다면 password 입력 창이 나올텐데, 마스터 암호 설정시에 등록했던 비밀번호를 등록하면 완료이다.
꼭 커맨드창을 이용하지 않더라도 mysql workbench, sql electron, heidiSQL 등의 워크벤치를 이용해서 DB에 접근할 수도 있다.
그리고 프로젝트의 application.properties에 아래와 같은 DB configuraton code를 추가한다. {} 내의 항목들은 직접 추가해야한다.
# spring 에게 내가 사용할 DB가 mysql임을 명시
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
# 사용할 DB config
spring.datasource.url=jdbc:mysql://{URL}/{DEFAULT_DATABASE}?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
spring.datasource.username={ID}
spring.datasource.password={PW}
# 개발 단계에서 hibernate(ORM)가 어떤 쿼리를 날리는지 log로 SQL문을 확인하기 위함
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
# ddl-auto option : DB의 테이블과 프로젝트의 object
spring.jpa.hibernate.ddl-auto=update
ddl은 Data Definition Language의 약자로, DB 테이블의 생성/수정/변경/삭제를 담당하는 명령어이다. Spring의 ORM인 hibernated에선 연결한 DB 테이블과 Entity Object를 어떻게 맞출 것인지 설정하는 ddl-auto option이 존재한다. 나는 DB에 존재하는 기존 테이블들의 수정을 최소화하고자 update 옵션을 이용했고, option 종류는 아래와 같다.
create : 프로젝트 run때마다 테이블 전부를 삭제 후 재생성
create-drop : 어플리케이션 종료 후 table drop
update : 테이블과 엔티티 객체의 차이를 비교 후 테이블 업데이트
validate : 테이블과 엔티티 객체의 차이 비교 후 차이 존재시에 어플리케이션 실행 X
none : 테이블 자동 생성 기능 사용 X
2. Entity Object 설정
JPA 환경에서 테이블의 역할을 하는 @Entity 어노테이션이 붙은 class들은 entity package에 모아뒀다.
User 엔티티를 설정하기 전에 createdAt, updatedAt이 필요한 Entity들에게 상속할 BaseEntity를 추가했다.
@Getter
@Setter
@EntityListeners(AuditingEntityListener.class) // 필수 !!
@MappedSuperclass // BaseEntity 상속한 class들이 이 field를 가질 수 있도록.
public class BaseEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(updatable = false)
private LocalDateTime updatedAt;
}
@EntityListeners 어노테이션을 붙여 DB 서버 시각에 맞는 @CreatedDate, @LastModifiedDate가 할당되는 필드를 만들었다. 원격으로 연결한 DB 서버의 시간에 맞게 해당 데이터가 만들어진 시간과 마지막으로 수정된 시간 필드가 BaseEntity를 상속한 Entity에 추가될 것이다.
한가지 알아둬야 할 것은, @EntityListeners 사용을 위해선 Configuration Bean을 하나 추가해 @EnableJpaAuditing 어노테이션을 붙여줘야 한다는 것이다. JPA와 연결된 DB 서버의 상태를 확인하기 위함이다.
@Configuration
@EnableJpaAuditing
public class JpaAuditingConfiguration {
}
위의 설정 빈에서 얻은 JPA의 DB서버 auditing 정보가 @EntityListeners가 붙은 entity에 올라가는 원리이다.
이제 위 BaseEntity를 상속받는 실제 User class를 작성했다.
@Entity
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class User extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
@Size(max = 10, message = "아이디는 4자 이상 10자 이하여야 합니다!")
private String userId;
@NotBlank
@Size(min = 1, max = 15, message = "닉네임이 공란이거나 15자 이상이어선 안됩니다!")
private String name; // nickname
@Email
@NotBlank
private String email;
// 자기소개
@Size(max = 150, message = "자기소개 항목은 150자 이하의 길이여야 합니다.")
private String bio;
// OAuth2
private String registrationId;
private String oAuth2Id;
@Enumerated(EnumType.STRING)
private Role role;
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
private List<FarmLog> farmLog;
// 이 유저가 팔로우하고 있는 사람
@OneToMany(mappedBy = "following", cascade = CascadeType.ALL)
private List<Follow> following;
// 이 유저가 팔로우 당하고 있는 사람.. (팔로워!!)
@OneToMany(mappedBy = "followed", cascade = CascadeType.ALL)
private List<Follow> followed;
@OneToMany(mappedBy = "liker", cascade = CascadeType.ALL)
private List<Good> likeList;
public String getRoleKey() {
return role.getKey();
}
public User editByDto(UserProfileDto userProfileDto) {
this.userId = userProfileDto.getUserId();
this.name = userProfileDto.getName();
this.bio = userProfileDto.getBio();
return this;
}
public int getFollowingNum() {
return this.following.size();
}
public int getFollowerNum() {
return this.followed.size();
}
}
1) @Id : JPA 환경에서는 Entity class 작성 시에 해당 테이블의 PK가 되는 @Id 필드를 추가하는 것이 필수적이다. @GeneratedValue({STRATEGY})와 함께 쓰여 데이터베이스가 자동으로 id를 할당하게 할 수 있다. 나는 strategy로 GenerationType.IDENTITY를 택해 데이터베이스의 AUTO_INCREMENT 설정으로 pk를 할당할 수 있도록 했다.
2) @NotBlank, @Size, @Email, @Pattern 등 : jakarta.validation에서 제공하는 유효성 검사 annotation이다.
3) @OneToMany : 순환 참조가 발생할 수 있기 때문에 양방향 매핑은 최소화 해야하지만 User가 필요한 곳에선 대부분 DTO를 쓸 것이고 일단 개발 단계에서 가독성과 이해도를 높이기 위해 표시할 수 있는 relation은 대부분 표시했다. 자세한 연관 관계는 다음 포스팅에..
3. UserRepository 작성
OAuth2 서비스에서 제공한 유저 고유의 이메일과, 농장 서비스에서 이용하는 유저의 userId를 통해 유저를 찾는 derived query method를 작성했다.
package com.buchu.greenfarm.repository;
import com.buchu.greenfarm.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
public Optional<User> findByEmail(String email);
public Optional<User> findByUserId(String userId);
}
4. Run SpringBoot
설정한 것은 User Entity밖에 없지만.. 이대로 spring boot 프로젝트를 시작하면 ddl 명령어로 rds 서버에 테이블이 만들어 질 것이고 mysql 커맨드 창이나 workbench 등의 GUI 환경에서 RDS 서버 접속 후 desc user; SQL문을 입력하면 생성한 엔티가 잘 테이블로 저장되어 있음을 확인할 수 있다.