본문 바로가기
SpringBoot

Spring Boot Project(in★ gram) 02. 데이터 모델 세팅하기

by 김마리님 2020. 8. 24.

JPA를 이용하기 위해서는 데이터 모델이 필요하다.

 

인★에 무엇이 필요한가?

유저, 이미지, 거기에 달리는 해시태그(해시태그는 영속성을 위하여 따로 분리한다.), 그리고 덧글, 좋아요.

그리고 유저들 사이의 팔로워(관계성)까지 존재한다. 그에 따라서 모델을 6개를 만든다. 그리고 그 모델에, 데이터베이스와 연결된 Repository를 만든다.

 

먼저 유저 모델을 본다.

뭐가 필요한가?

시퀀스(id)를 제외하고,

유저 이름, 비밀번호, 노출될 이름, 바이오, 주소, 휴대폰, 성별, 프로필 이미지가 필요할 것이다.

그리고 로그인할때의 로직을 처리하기 위한 유저의 권한처리용 역할을 필요로 한다.

또한, 인☆그램은 페이스북 로그인을 지원하므로, 그 로그인 처리용 provider과 그 provider의 id도 필요한다. 마지막으로, 계정 생성 날짜를 만들어준다.

 

package com.mary.instragram.domain.user;

import java.sql.Timestamp;

import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import org.hibernate.annotations.CreationTimestamp;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
	
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;
	
	private String username;
	private String password;
	private String name;
	private String website;
	private String bio;
	private String phone;
	private String gender;
	private String profileImage;
	
	@Enumerated(EnumType.STRING)
	private UserRole role;
	
	private String provider;
	private String providerId;

	@CreationTimestamp
	private Timestamp createDate;
}

 

이 때 UserRole은 일반적으로 String으로 처리하는 것 보다, Enum으로 처리하면 String의 범위를 줄일 뿐만 아니라, 이후의 getKey(이 함수의 이름은 언제든 변할 수 있다.)로 필요한 역할값을 불러오는 것도 가능하다.

Enum 을 보자.

package com.mary.instragram.domain.user;

import lombok.Getter;

@Getter
public enum UserRole {
	USER("ROLE_USER"), ADMIN("ROLE_ADMIN");

	UserRole(String key) {
		// TODO Auto-generated constructor stub
		this.key=key;
	}
	
	private String key;
	
}

 

역할 괄호 내의 값이 key값이 된다. 이후, ROLE_USER이 필요할 때마다 getKey로 호출하면 빠르게 부를 수 있다.

 

다음, 게시글인 image를 보자.

package com.mary.instragram.domain.image;

import java.sql.Timestamp;
import java.util.List;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;

import org.hibernate.annotations.CreationTimestamp;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.mary.instragram.domain.tag.Tag;
import com.mary.instragram.domain.user.User;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Image {
	
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;
	
	private String location;
	private String caption;
	private String imageUrl;
	
	
	//Image를 select를 하면 한 명의 User가 옴
	@ManyToOne(fetch=FetchType.EAGER)
	@JoinColumn(name="userId") //컬럼명
	private User user; //타입 : User옵젝트의 pk
	
	//Image를 select하면 여러 개의 TAG가 딸려옴
	//select하기 전에 불러오지 않음 => 부하가 적음
	@OneToMany(mappedBy = "image", fetch = FetchType.LAZY) //Foreign키가 아님을 선언(연관관계의 주인이 아님) //연관관계 주인의 변수명을 적는다.
	@JsonIgnoreProperties({"image"})
	private List<Tag> tags;
	
	@CreationTimestamp
	private Timestamp createDate;
	
	
}

 

 

이제부터 관계를 잘 생각해야한다. 

JPA는 자바 함수 내에서는 값을 오브젝트로 저장하는 것이 가능하다. 이 때, 데이터베이스에 들어가는건 오브젝트로 들어갈 수 없으므로, 값의 primary key가 들어가게 된다.

따라서, 작성자를 굳이 user object를 호출하고, 내부의 userId를 이용하여 힘들게 불러낼 필요 없이 object값을 호출하면 된다.

대신, 관계를 생각해야하는데, 1:n인지, n:1인지, 1:1인지 잘 생각한다.

유저는 사진을 여러장 올릴 수 있다. 그러나 사진은 한 유저에 의해서만 올라간다. 즉 유저 : 사진=1 : n의 관계가 성립된다.

따라서, 유저 오브젝트 위에 @ManyToOne 어노테이션을 걸어둔다. 이 함수를 통해 많은 사진 컬렉션 속에서 단 하나의 유저 오브젝트를 선택할 수 있게 된다. 

반대로, 해시태그는 하나는 하나의 사진에 걸리지만, 사진은 여러 해시태그를 올릴 수 있다. 즉, 해시태그 : 사진=n : 1의 관계가 성립한다. 

그렇기 때문에 태그에는 @OneToMany 어노테이션을 걸어둔다. 이로써 1:다 관계에서의 주인이 해시태그가 아닌, 이미지임을 명시한다. 유저는 여러장의 사진 중 한 장의 사진을 셀렉트 하기 전에 미리 다 올라와도, 한 사진 당 하나의 유저를 가지고 있기 때문에 부하가 적지만, 해시태그는 많기 때문에 하나의 사진이 select 되기 전에 올라오지 않도록 LAZY 프로퍼티를 걸어줌으로써 부하를 줄일 수 있다.

 

이제 해시태그를 보자.

package com.mary.instragram.domain.tag;

import java.sql.Timestamp;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

import com.mary.instragram.domain.image.Image;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Tag {
	
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;
	
	private String name;

	@ManyToOne
	@JoinColumn(name="ImageId") 
	private Image image;
	
	private Timestamp createDate;
}

 

 

간단하게 해시태그의 이름과, 해시태그의 주인 (1:n)이 이미지임을 상기하는 @ManyToOne 어노테이션을 Image 오브젝트에 걸어둔다.

 

이제 좋아요 테이블을 본다.

package com.mary.instragram.domain.like;

import java.sql.Timestamp;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

import org.hibernate.annotations.CreationTimestamp;

import com.mary.instragram.domain.image.Image;
import com.mary.instragram.domain.user.User;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Likes {
	
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;
	
	@ManyToOne
	@JoinColumn(name="userId") 
	private User user;
	
	@ManyToOne
	@JoinColumn(name="imageId") 
	private Image image;
	
	//@ManyToMany
	//두 테이블 사이의 pk만 가져와서 테이블을 생성하기 때문에 추가적 정보를 넣을 수 없음(사용빈도 낮음)
	//1:N 어노테이션 두 개를 이용해서 합해서, 중간 테이블을 만들어서 사용함
	
	
	@CreationTimestamp
	private Timestamp createDate;
}

 

한 유저는 여러 사진에 좋아요를 할 수 있고, 유저 하나의 좋아요는 하나의 사진에만 찍힌다.

또 사진은 여러개의 좋아요를 가질 수 있다. 따라서 이 좋아요 테이블을 제하고 다 대 다 관계여야 하지만, JPA 어노테이션으로 다 대 다 를 만들면 두 개의 primary key를 가지고 새로운 테이블을 만들기 때문에 다른 속성을 넣을 수 없다. 따라서 실무에선 쓰지 않는다. 그러므로 좋아요 테이블을 따로 만들어 두 테이블을 참조할 수 있는 관계 테이블을 따로 만든 것이다.

덧글도 마찬가지이다.

 

package com.mary.instragram.domain.comment;

import java.sql.Timestamp;

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

import org.hibernate.annotations.CreationTimestamp;

import com.mary.instragram.domain.image.Image;
import com.mary.instragram.domain.user.User;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Comment {
	
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;
	
	private String content;
	
	@ManyToOne
	private Image image;
	
	@ManyToOne
	private User user;
	
	@CreationTimestamp
	private Timestamp createDate;

}

 

마지막으로 유저들 사이에서도 마찬가지이기 때문에 유저를 위한 관계형 테이블을 만든다.

 

package com.mary.instragram.domain.follow;

import java.sql.Timestamp;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

import org.hibernate.annotations.CreationTimestamp;

import com.mary.instragram.domain.user.User;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Follow {
	
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;
	
	@ManyToOne
	@JoinColumn(name="fromUserId")
	private User fromUser;
	
	@ManyToOne
	@JoinColumn(name="toUserId")
	private User toUser;
	
	@CreationTimestamp //ManytoMany의 경우 이 테이블을 만들 수 없음!
	private Timestamp createDate;

}
반응형