Spring Boot 5월 1주차

김용진·2025년 5월 7일
0

※기본적으로 MVC패턴을 모두 인지했다는 가정하에 진행되는 리뷰이므로, 전에 배운 공통적인 부분은 넘어갈 예정이다.

프로젝트 기본설정

sts4 설치경로

Boot 기본설정

기본 설정

Spring Legacy 개발환경에서 Boot로 넘어가기위해 다음과 같은 설정을 하도록 하자

application.properties설정

해당가린 부분은 각자 데이터베이스의 유저 id와 pw를 적도록하자

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.1.3'
	id 'io.spring.dependency-management' version '1.1.7'
}

group = 'org.kosa'
version = '0.0.1'

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(17)
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
    // devtools 추가
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
    // lombok
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	//타임리프 설정
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'
	//JPA설정
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	
	// 마리아 DB 설정
	implementation 'org.mariadb.jdbc:mariadb-java-client:3.0.11'
	
	//validate 설정
	implementation 'org.springframework.boot:spring-boot-starter-validation' 
}

tasks.named('test') {
	useJUnitPlatform()
}

build.gradle설정

주석처리는 다음과 같으며, 현재 진행과제에 맞게 의존성을 추가했다. 기존에 추가되지 않은 의존성을 추가할때마다 프로젝트 우클릭 -> gradle -> Refresh Gradle Project를 잊지않아야 한다.

DevTools

타임리프(thymeleaf)

JPA

validate

lagacy Project와는 다르게 lombok을 이용한 생성자지정을 다음과 같이한다.

package org.kosa.sbb.question;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;

@Getter
@Setter
public class LombokEx {
	private final String hello;
	private final int lombok;
}

public LombokEx(String hello, int lombok) {
		super();
		this.hello = hello;
		this.lombok = lombok;
	}

@RequiredArgsConstructor를 지정하지 않을 경우 생성자 지정을 별도의 코드를 통해 작성해야한다. 그러나 어노테이션을 붙일 경우 생성자 지정 코드를 삭제가능하다.

package org.kosa.sbb.question;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@RequiredArgsConstructor
public class LombokEx {
	private final String hello;
	private final int lombok;
}

현재 디렉터리에서 몇가지를 언급하고 넘어가고자 한다.

templates 디렉터리

src/main/resource 디렉터리 하위 templates에는 자바코드를 삽입할 수 있는 html파일을 저장하는 장소이다.

static 디렉터리

static 디렉터리에서는 스타일시트(.css), 자바스크립트(.js), 이미지파일(.jpeg, .png)를 저장하는 장소이다.

application.properties 파일

프로젝트 환경을 설정하는 파일로, 환경변수, 데이터베이스 설정을 하는 장소이다.

src/test/java

서버를 실행하지 않은 상태에서 프로젝트의 기능을 테스트하는 코드를 작성하여 저장하는 공간, JUnit이나 기타 스프링 부트의 테스트 도구를 이용하여 테스트가 가능하다

build.gradle

환경파일을 그래들로 사용하는걸 의미하며, Ant, Maven과 비슷하다고 보면된다. 프로젝트에 필요한 플러그인과 라이브러리를 설치하기 위해 사용되며, 기존 legacy에서는 Maven을 이용하여 진행했던것을 인지하자.

JPA로 데이터 베이스 사용하기

Legacy는 Mybatis로 DAO에 sql.xml을 매핑하여 DB에 명령문을 날리도록 했다. 이번 boot에서는 ORM과 JPA를 이용하여 DB에 sql문을 직접 사용하지 않고 관리하려 한다.

ORM(Object-Relational Mapping)이란?

ORM은 객체와 관계형 데이터베이스 자동매핑을 의미하며 SQL을 사용하지 않고 데이터베이스를 관리할 수 있는 도구이다. 자바 클래스로 테이블을 만들어 관리 할 수 있으며, 쿼리문 역시 보낼 수 있다.

		Question q1 = new Question();
		q1.setSubject("sbb가 무엇인가요?");
		q1.setContent("sbb에 대해서 알고 싶습니다.");
		q1.setCreateDate(LocalDateTime.now());
		this.questionRepository.save(q1);

		Question q2 = new Question();
		q2.setSubject("스프링 부트 모델 질문입니다.");
		q2.setContent("id는 자동으로 생성되나요?");
		q2.setCreateDate(LocalDateTime.now());
		this.questionRepository.save(q2);

다음 명령문은 SQL로 insert하는 명령문을 ORM으로 구현한 모습니다.

위와같이 SQL문을 ORM으로 대체하면 SQL문을 모르더라도 DB관리가 가능한것을 알 수 있다. 또 다른 장점으로는 MariaDB와 Oracle은 SQL이 조금씩 다르지만 ORM은 자동으로 SQL을 만들기 때문에 별도의 차이를 둘 필요가 없다.

여기서 Question은 자바클래스로 데이터베이스의 테이블과 매핑되는 역할을 하는데 ORM에서는 엔티티라고 부른다

JPA(Java Persistence API)란?

ORM 표준 기술로서, SQL을 자동으로 매핑해주는 인터페이스 모음이다. 여기서 JPA는 전부 사용하는것이 아닌 일부분을 사용하는것을 인지하자.

spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.show_sql=true

예를 들어 다음과 같은 하이버네이트를 사용하는 문구를 확인해보자.

  • spring.jpa.hibernate.ddl-auto

하이버네이트의 데이터정의어로

다음과 같은 속성이 적용된다. 개발환경에서는 update를 주로 사용하고, 운영 환경에서는 none 또는 validate를 주로 사용한다.

spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.show_sql=true

두가지의 구문은 프로그램 실행시 콘솔에서 sql을확인하게 해주는 구문이다 설정값을 주게되면 다음과 같은 sql문을 확인 할 수 있다.

엔티티 속성 부여

코드 색상때문에 이미지로 붙였다...

마리아DB에서 미리 테이블을 만들었고, Boot에서 두개의 엔티티를 다음과 같이 정의하였다. 각 엔티티의 속성에 부여된 어노테이션은 다음과 같다.

  • @ID
    기본키로 지정한다는 어노테이션, 데이터베이스의 PK제약과 같다고 보면된다.

  • @GeneratedValue
    새로운 값이 저장될 때마다 1씩 증가하여 저장하도록 지정하는 어노테이션, 데이터베이스의 시퀀스와 같다고 보면된다. GenerattionType.IDENTITY는 해당 속성만 별도로 번호가 늘어나도록 만든 명령문이며, strategy를 통해 해당 속성에 추가되는 값에만 번호를 부여하게 만든다.

  • @Column
    엔티티의 속성은 DB테이블의 컬럼이름과 같은데, 이때 세부설정을 주기 위하여 주는 어노테이션이다. 예시와 같이 length를 통해 열의 길이를 제한하거나, columnDefinition을 통해 TEXT속성을 부여하기도 한다.

💡엔티티의 속성이름과 테이블 열의 이름이 다른것이 있다 바로 Question에 createDate와 데이터베이스의 create_date인데 카멜 케이스형식의 이름은 소문자로 변경되고 단어가 언더바로 구분되기 때문에 같은 이름이된다. 만약 이것이 싫다면 어노테이션에 name을 별도로 지정도 가능하다.

@Column(name = "create_date")
private LocalDateTime createDate;

엔티티의 속성을 테이블의 열로 인식시키고 싶지 않다면 @Transient 어노테이션을 적용하면 클래스의 속성으로만 적용가능하다.

  • @ManyToOne
    다대일관계를 만드는 어노테이션으로, 서로의 엔티티를 묶어주는 DB의 외래키설정 방식이다. 반대의 방식인 @OneToMany역시 가능하다. 이때 하나의 값에 여러 값이 묶여 오므로 List형태로 받아오며, 매핑되는 엔티티 명을 지정해주는 것을 알 수있다.

💡여기서 cascade는 Answer와 연관된 Question에도 동작을 반영하겠다는 의미이며, CascadeType.REMOVE는 부모(Question)가 삭제될 경우 자식인 Answer의 값도 자동으로 삭제하는것을 의미한다.

Repository

우리는 Legacy에서 CRUD를 명령문을 DAO+sql.xml+mapper 로 설정하였으나, boot에서는 리포지터리하나로 관리가 가능하다.

package org.kosa.sbb.answer;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

public interface AnswerRepository extends JpaRepository<Answer, Integer>{

	List<Answer> findByQuestionId(Integer id);
}

JpaRepository에는 JPA에서 제공하는 CRUD를 처리하는 메서드가 이미 내장되어있다. 우리는 여기서 추가적으로 받아올 매개변수와 함수를 설정 가능하다.

대표적으로 몇개의 명령문만 알아보자.

  • findAll()
		List<Question> all = this.questionRepository.findAll();
		assertEquals(34, all.size()); // 현재 데이터 테이블의 데이터가 34개 들어있음

위 코드는 데이터베이스에 Question 테이블에 34개의 데이터가 있는지 확인하는 Junit 테스트 코드이다.

Run as -> Junit test를 사용하면 위와같은 결과가 나오는것을 알 수 있다.

💡assert는 JUnit 테스트에서 기대하는 결과와 실제 결과가 일치하는지를 검증하기 위한 핵심 도구이다. 여기서 assert는 말하는 건 정확히는 JUnit의 Assertions 클래스에서 제공하는 다양한 검증 메서드인것을 인지하자.

메서드설명
assertEquals(expected, actual)기대 값과 실제 값이 같은지
assertNotEquals(unexpected, actual)같지 않은지
assertTrue(condition)조건이 true인지
assertFalse(condition)조건이 false인지
assertNull(object)객체가 null인지
assertNotNull(object)객체가 null이 아닌지
assertThrows(Exception.class, () -> ...)예외가 발생하는지 검증
  • findBy속성명()
	Optional<Question> oq = questionRepository.findById(32); // 아이디 번호가 32인걸 찾기
		if (oq.isPresent()) {
			Question q1 = oq.get();
			assertEquals("sbb가 무엇인가요?", q1.getSubject());
		}
		
		// 제목이 "sbb가 무엇인가요?"인걸 찾기, 대신에 무조건 1개만 찾아야 오류가 안뜬다.
		Question q = questionRepository.findBySubject("sbb가 무엇인가요?");
		if (q != null) {
			assertEquals(32, q.getId());
			assertEquals("sbb가 무엇인가요?", q.getSubject());
		}

findById 또는 Subject를사용하여 값을 받아오는 함수

💡Optional<T>를 사용하는것을 볼 수 있는데. 이렇게 하면 데이터가 null일때도 NullPointException이 발생하지 않는다.

메서드설명
Optional.of(value)절대 null 아님. null이면 예외 발생
Optional.ofNullable(value)null 허용
Optional.empty()비어 있는 Optional 객체 반환
isPresent()값이 있는지 여부 (true/false)
ifPresent(consumer)값이 있으면 실행
orElse(default)값이 없으면 기본값 반환
orElseGet(Supplier)지연 계산을 통해 기본값 반환
orElseThrow()값이 없으면 예외 발생
  • findBySubjectAndContent()
    위 코드 예시처럼 id와 subject 두개를 이용해 where절을 사용하고싶으면 다음위와같이 사용할 수 있다.

아래 표는 AND구문 외에 다른 구문도 보여주고 있다

profile
메모리폼

0개의 댓글