IntelliJ + SpringBoot + Maven + Jpa 프로젝트

배포시스템 테스트를 위한 간단한 게시판 화면을 만들 계획입니다.
IntelliJ Tool을 사용하고 SpringBoot 기반의 Maven을 사용하고 DB는 JPA를 이용한 게시판 프로젝트를 구성할 계획입니다.

침고URL

프로젝트 생성하기

1. New Project 클릭 후 아래 사진과 같이 설정

img1

2. NEXT 버튼 클릭 후 세부 라이브러리 설정

img2

  • Lombook : Java 라이브러리로 반복되는 getter, setter, toString 등의 메서드 작성 코드를 줄여주는 코드 다이어트 라이브러리입니다
  • Spring Web : 기본 스프링 Framwork를 담고있는 라이브러리입니다.
  • Spring Data JPA : JPA 관련 라이브러리입니다.

3. 기본 폴더 구조

img3

  • config : 환경설정 파일이 담겨진 패키지
  • controller : 컨트롤러 파일이 담겨진 패키지
  • doamain : 모델설정관련 파일이 담겨진 패키지
  • dao :
  • service : 실제 로직이 담겨진 패키지

main Class 설정 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.spring.algorithm;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AlgorithmApplication {

public static void main(String[] args) {
SpringApplication.run(AlgorithmApplication.class, args);
}

}

@SpringBootApplication

  • 프로젝트의 메인 클래스입니다.
  • @SpringBootApplication으로 인해 스프링 부트의 자동 설정, 스프링 Bean 읽기와 생성을 모두 자동으로 설정됩니다.
  • @SpringBootApplication이 있는 위치부터 설정을 읽기 때문에 이 클래스는 항상 프로젝트 최상단에 위치해야 합니다.
  • SpringApplication.run로 내장 WAS를 실행합니다. (외장 WAS를 둘 수 있지만 스프링 부트에서는 내장 WAS를 권장합니다.)

Domain 설정 (Model 구조 설정)

User.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.spring.algorithm.domain.user;

import lombok.*;

import javax.persistence.*;

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; //sequence, auto_increment

@Column(nullable = false, length = 30)
private String username; //아이디

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

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

@Column(nullable = false, length = 50)
private String nickname; //닉네임

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Role role;
}

@NoArgsConstructor : Lombok 어노테이션으로 빈 생성자를 만들어줍니다.
@Entity : 해당 클래스가 엔티티를 위한 클래스이며, 해당 클래스의 인스턴스들이 JPA로 관리되는 엔티티 객체라는 것을 의미합니다. 즉, 테이블을 의미합니다.
디폴트값으로 클래스의 카멜케이스 이름을 언더스코어 네이밍(_)으로 테이블 이름을 매칭합니다.
@Id : 테이블의 Primary Key(PK)
@GeneratedValue(strategy = GenerationType.IDENTITY) : PK를 자동으로 생성하고자 할 때 사용합니다. 즉, auto_increment를 말합니다. 여기서는 JPA의 넘버링 전략이 아닌 이 전략을 사용합니다. (전에 application.yml 설정에서 use-new-id-generate-mappings: false로 한 것이 있습니다.)
@Column : 해당 필드가 컬럼이라는 것을 말하고, @Column에는 다양한 속성을 지정할 수 있습니다. (nullable = false: null값이 되면 안된다!, length = 50: 길이 제한 등등)
@Enumerated(EnumType.STRING) : JPA로 DB에 저장할 때 Enum 값을 어떤 형태로 저장할지를 결정합니다.
기본적으로는 int로 저장하지만 int로 저장하면 무슨 의미인지 알 수가 없기 때문에 문자열로 저장될 수 있도록 설정합니다.
User 클래스 Setter가 없는 이유는 이 setter를 무작정 생성하게 되면 해당 클래스의 인스턴스가 언제 어디서 변해야하는지 코드상으로는 명확하게 알 수가 없어 나중에는 변경시에 매우 복잡해집니다.
Builder를 사용하는 이유는 어느 필드에 어떤 값을 채워야하는지 명확하게 알 수 있기 때문에 실수가 나지 않습니다.

Role.enum

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.spring.algorithm.domain.user;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum Role {
USER("ROLE_USER", "사용자"),
ADMIN("ROLE_ADMIN", "관리자");

private final String key;
private final String title;
}

enum?
보통 도메인을 설계할 때 사용하는 인스턴스의 수가 정해져 있고 관련된어 처리할 수 있는 상수값이 여러개 존재할 때 Enum을 사용합니다.
스프링 시큐리티에서는 권한 코드에 항상 ROLE_이 앞에 있어야 합니다. 따라서 키 값을 ROLE_USER, ROLE_ADMIN로 지정했습니다.

Controller 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.spring.algorithm.controller;

import com.spring.algorithm.domain.user.Role;
import com.spring.algorithm.domain.user.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.persistence.Column;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;

@RestController
public class LoginController {

@GetMapping("/hello")
public String hello() {
return "hello Spring Boot!";
}

@GetMapping("/hello/dto")
public User helloResponseDto(@RequestParam("id") Long id, @RequestParam("name") String name, @RequestParam("nickname") String nickname , @RequestParam("pw") String pw, @RequestParam("email") String email, @RequestParam("role") Role role) {
return new User(id ,name, nickname, pw, email, role);
}
}

@RequestParam : 외부에서 API로 넘긴 파라미터(@RequestParam(“name”), @RequestParam(“nickname”), , @RequestParam(“pw]”) … )를 가져와 String name, String nickname, String pw에 각각 저장
return new User(id ,name, nickname, pw, email, role); 파라미터로 가져온 변수 기준으로 User 생성자 return

세부파일 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
server.address=127.0.0.1
server.port=8090
spring.datasource.url=jdbc:mysql://localhost:3306?allowPublicKeyRetrieval=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=1234
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# mysql 사용
spring.jpa.database = mysql
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect

# 로깅 레벨
logging.level.org.hibernate=info

# 하이버네이트가 실행한 모든 SQL문을 콘솔로 출력
spring.jpa.properties.hibernate.show_sql=true

# SQL문을 가독성 있게 표현
spring.jpa.properties.hibernate.format_sql=true

# 디버깅 정보 출력
spring.jpa.properties.hibernate.use_sql_comments=true

application.properties
이 파일은 스프링부트가 애플리케이션을 구동할 때 자동으로 로딩하는 파일입니다.
key - value 형식으로 값을 정의하면 애플리케이션에서 참조하여 사용할 수 있습니다.
값을 참조할 때는 여러가지 방법이 있다. @Value 어노테이션으로 값을 받아올 수도 있습니다.


JPA(Java Persistence API)
자바 진영의 ORM 기술 표준으로, 인터페이스의 모음입니다.
즉, 실제로 동작하는 것이 아닙니다.
JPA 인터페이스를 구현한 대표적인 오픈소스가 Hibernate입니다.
ORM(Object Relational Mapping)이란, 객체는 객체대로, 관계형 DB는 관계정 DB대로 설계하여 ORM 프레임워크가 중간에서 매핑시켜줍니다. 즉, ORM은 객체와 RDB 두 기둥 위에 있는 기술입니다.

CF) mybatis 설정 시 설정

Dependency 추가

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.2</version>
</dependency>

DBConfiguration 지정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// /config/DBConfiguration
package com.spring.algorithm.config;

import javax.sql.DataSource;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

@Configuration
@PropertySource("classpath:/db.properties")
@MapperScan
public class DBConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public HikariConfig hikariConfig() {

return new HikariConfig();
}

@Bean
public DataSource dataSource() {
DataSource dataSource = new HikariDataSource(hikariConfig());

return dataSource;
}

@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/static/mapper/**/*.xml"));
sessionFactoryBean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:/static/config/mybatis-config.xml"));
return sessionFactoryBean.getObject();

}

@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}

@Bean
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}

세부 경로 작성

1
2
3
4
5
6
#spring.datasource.hikari.driver-class-name=com.mysql.jdbc.Driver
#spring.datasource.hikari.jdbc-url=jdbc:mysql://localhost:3306/
spring.datasource.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
spring.datasource.hikari.jdbc-url=jdbc:log4jdbc:mysql://localhost:3306/SID?characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.hikari.username=root
spring.datasource.hikari.password=1234

docker-compose.yml 작성 (어플리케이션)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

version: "3"
services:
test_web:
image: nginx
ports:
- 80:80
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
depends_on:
- test_application

test_database:
image: mysql:5.7
environment:
MYSQL_DATABASE: test_db
MYSQL_ROOT_PASSWORD: root
MYSQL_ROOT_HOST: '%'
ports:
- 3306:3306

test_application:
build: .
expose:
- 8080
depends_on:
- test_database

/etc/nginx/conf.d 에 해당 설정파일 작성

1
2
3
4
5
6
7
8
9
10
11
server {
listen 80;
access_log off;

location / {
proxy_pass http://test_application:8080;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}