본문 바로가기

Project

Spring boot, react 이용한 블로그 개발 (2) - JPA + hibernate 연동

이번 포스트에서는 DB연동 부분을 알아볼 것이다. 기본적으로 이번 프로젝트에서 ORM을 사용할 것이기 때문에 자바에서 ORM을 사용하기 위한 기본인 JPA와 Hibernate를 이용한다. DB는 로컬 환경에서는 테스트가 수월한 H2 database를 이용하고 운영환경에서는 mysql를 사용한다.


1. pom.xml 설정


- pom.xml에 spring에서 jpa를 쉽게 사용하기 위한 spring-boot-starter-data-jpa 라이브러리를 추가하고 JDBC connection pool 라이브러리는 가볍고 성능이 좋은 HikariCP를 이용한다. DB 커넥트 설정을 위해 h2와 mysql 커넥터를 추가한 것을 볼 수 있다.

아래에는 위와 관련된 라이브러리를 추가한 것을 확인할 수 있다.

<!-- db -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<exclusions>
<exclusion>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>RELEASE</version>
</dependency>
<!-- end-->


2. Database 설정


- application.yml에 설정한 database 관련 정보를 기반으로 자바 config를 이용하여 Hikari Datasource를 설정하였다.

@Configuration
public class DatabaseConfig {

private final Logger log = LoggerFactory.getLogger(getClass());

@Bean(destroyMethod = "close")
public HikariDataSource dataSource(DataSourceProperties dataSourceProperties, ApplicationProperties applicationProperties) {
log.debug("Configuring Datasource");

HikariConfig config = new HikariConfig();
config.setDataSourceClassName(dataSourceProperties.getDriverClassName());

config.addDataSourceProperty("url", dataSourceProperties.getUrl());
if (dataSourceProperties.getUsername() != null) {
config.addDataSourceProperty("user", dataSourceProperties.getUsername());
} else {
config.addDataSourceProperty("user", ""); // HikariCP doesn't allow null user
}
if (dataSourceProperties.getPassword() != null) {
config.addDataSourceProperty("password", dataSourceProperties.getPassword());
} else {
config.addDataSourceProperty("password", ""); // HikariCP doesn't allow null password
}
return new HikariDataSource(config);
}
}


3. 도메인 설정


- 도메인 설정에 들어가기에 앞서 스프링 부트는 2.1버전부터 JPA 2.2, Hibernate 5.3 버전을 지원한다. 이 지원의 중요한 점은 Date type으로 자바8 부터 도입된 LocalDateTime을 지원한다는 것이다. 이 전에는 LocalDateTime 클래스를 사용하면 기본 설정으로는 binary 형태로 저장하게 되기 때문에 번거러운 컨버팅 작업등이 필요했었다.


- Spring Data JPA에서 엔티티의 기본키를 지정하기 위한 방법으로 @id 어노테이션을 이용할 수 있다. 기본키 설정 정책은 IDENTITY, SEQUENCE, TABLE, AUTO의 4가지 전략이 있는데 이 중 프로젝트에서는 IDENTITY를 사용할 것이다. GenerationType.IDENTITY 설정은 기본 키 생성을 데이터베이스에 의존하는 방법으로 mysql에서는 auto_increment를 이용하여 기본키가 생성되게 된다.


post 테이블 설정


- 게시물을 저장하기 위하여 post 테이블을 추가하겠다. @Table 어노테이션에 DB에서 사용할 DB명인 post를 지정했고 기본적으로 기본키 컬럼인 id 그리고 post의 제목과 본문 내용 필드를 위한 title, body 필드를 추가했다. 

 또한 User 필드와 Set<Comment> 필드를 확인할 수 있는데 User필드는 Post를 작성한 사용자를 연결시키기 위한 것이며 @ManyToOne 어노테이션을 이용하여 post와 user의 관계를 다대일 관계로 설정했다. 반대로 Comment 필드는 @OneToMay 어노테이션을 이용하여 일대다 관계를 표현했고 게시물이 삭제되면 코멘트도 삭제되는 관계가 논리적으로 맞기 때문에 cascade 설정을 하였다.

@Entity
@Table(name = "post")
@Getter
@Setter
public class Post {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "post_id")
private Long id;

@Column(name = "title", nullable = false)
@Length(min = 1)
private String title;

@Column(name = "body", columnDefinition = "TEXT")
private String body;

@ManyToOne
@JsonManagedReference
@JoinColumn(name = "user_id", referencedColumnName = "user_id", nullable = false)
@NotNull
private User user;

@OneToMany(mappedBy = "post", cascade = CascadeType.REMOVE)
private Set<Comment> comments;

@CreatedBy
@Column(name = "created_by", length = 50, updatable = false)
private String createdBy;

@CreatedDate
@Column(name = "created_date")
private LocalDateTime createdDate = LocalDateTime.now();

@LastModifiedBy
@Column(name = "last_modified_by", length = 50)
private String lastModifiedBy;

@LastModifiedDate
@Column(name = "last_modified_date")
private LocalDateTime lastModifiedDate = LocalDateTime.now();
}


user 테이블


- user 테이블은 기본적인 기본키, 날짜 컬럼등의 설정은 post 테이블과 같고 user 설정을 위한 emain과 password 필드가 추가되었다. 그리고 권한 관리를 위한 Authority 필드를 다대다 관계로 추가한 것과 포스트를 일대다 관계로 표현한 것을 확인할 수 있다.

@Entity
@Table(name = "user")
@Getter
@Setter
public class User extends BaseModel {

private static final long serialVersionUID = 1L;

@Id
@Column(name = "user_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Email
@Size(min = 5, max = 30)
@Column(length = 30, unique = true, nullable = false)
private String email;

@JsonIgnore
@NotNull
@Size(min = 60, max = 60)
@Column(name = "password_hash", length = 60, nullable = false)
private String password;

@JsonIgnore
@ManyToMany
@JoinTable(
name = "user_authority",
joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "user_id")},
inverseJoinColumns = {@JoinColumn(name = "authority_name", referencedColumnName = "name")})

@BatchSize(size = 20)
private Set<Authority> authorities = new HashSet<>();

@OneToMany(mappedBy = "user")
@JsonBackReference
private Set<Post> posts;

@CreatedBy
@Column(name = "created_by", length = 50, updatable = false)
@JsonIgnore
private String createdBy;

@CreatedDate
@Column(name = "created_date")
@JsonIgnore
private LocalDateTime createdDate = LocalDateTime.now();

@LastModifiedBy
@Column(name = "last_modified_by", length = 50)
@JsonIgnore
private String lastModifiedBy;

@LastModifiedDate
@Column(name = "last_modified_date")
@JsonIgnore
private LocalDateTime lastModifiedDate = LocalDateTime.now();
}


comment 테이블


- comment 테이블은 위에서 생성한 post와 user 엔티티와 연동되기 때문에 다대일 관계로 설정된 것이 볼 수 있고 실제 내용인 body 테이블이 TEXT type으로 지정된 것을 확인할 수 있다.

@Entity
@Table(name = "comment")
@Getter
@Setter
public class Comment {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "comment_id")
private Long id;

@Column(name = "body", columnDefinition = "TEXT")
private String body;

@Column(name = "create_date", nullable = false, updatable = false)
@NotNull
private LocalDateTime createDate;

@ManyToOne
@JoinColumn(name = "post_id", referencedColumnName = "post_id", nullable = false)
@NotNull
private Post post;

@ManyToOne
@JoinColumn(name = "user_id", referencedColumnName = "user_id", nullable = false)
@NotNull
private User user;
}


4. JPA Repository 설정


Spring Data JPA에서 제공하는 JpaRepository는 인터페이스를 상속하는 것만으로 직접 쿼리를 만들지 않고 몇가지 규칙을 이용하여 데이터 추출을 위한 메소드를 만들 수 있다.


UserRepository 설정


- id로 User를 조회하기 위한 findOneById 메소드와 email로 User를 조회하기 위한 findOneById 메소드를 추가하였다.

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

Optional<User> findOneById(Long id);
Optional<User> findOneByEmail(String email);
}


PostRepository 설정


- User를 기반으로 User의 post를 페이징 형태로 조회하기 위한 메소드를 추가하였고 전체 post를 페이징 형태로 조회하기 위한 메소드를 추가하였다. 위 두 메소드의 경우 OrderbyCreateDateDesc를 접미사로 붙인 것을 볼 수 있는데 post의 경우 생성된 날짜 역순으로 반환하기 위하여 설정하였다.

@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
Page<Post> findByUserOrderByCreatedDateDesc(User user, Pageable pageable);

Page<Post> findAllByOrderByCreatedDateDesc(Pageable pageable);

Optional<Post> findById(Long id);
}


NEXT 

- 다음 포스트에서는 application.yml에 사용할 프로퍼티 정보를 입력하고 ConfigurationProperties를 이용하여 자바 클래스에 바인딩 하는 작업을 진행할 것이다.


프로젝트 github

https://github.com/keumtae-kim/spring-boot-react-blog