spring boot 에서 DB 연계를 위해서는 JPA를 통해 진행해야 한다.
- build.gradle 에 다음을 추가
- implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
- appication.yml 설정 추가 (예시)
spring:
datasource:
url: ${DB_URL}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update # 운영 환경에서는 validate 또는 none 권장
show-sql: true # 실행되는 SQL 출력. product 에서는 false
properties:
hibernate:
format_sql: true # SQL 보기 좋게 정렬
highlight_sql: true # (Hibernate 5.5+) ANSI 색상 강조
use_sql_comments: true # JPQL -> SQL 변환 시 주석 추가
# 사용할 데이터베이스 방언(Dialect) 지정
dialect: org.hibernate.dialect.H2Dialect
database-platform: org.hibernate.dialect.MySQL8Dialect
open-in-view: false # OSIV 비활성화 (서비스 계층에서만 영속성 사용)
sql:
init:
mode: never # schema.sql, data.sql 실행 여부 (never, embedded, always)
# defer-datasource-initialization: true
# ↑ JPA가 스키마 생성 후 data.sql 실행하도록 보장 (Spring Boot 2.5+)
logging:
level:
org.hibernate.SQL: DEBUG # 실행되는 SQL 로그
org.hibernate.type.descriptor.sql.BasicBinder: TRACE # 바인딩 파라미터 로그
org.springframework.orm.jpa: INFO
org.springframework.transaction: DEBUG
- 실제 DB 접속 정보는 env 파일에 별도로 두고 해당 값을 읽어온다. env 파일은 git에 올리면 안된다.
- https://dean83.tistory.com/322 여기 하단부에 명시되어 있다.
- open-in-view
- true 설정시 EntityManager 를 웹 요청이 끝날때까지 사용가능하도록 하여 Persistance를 유지한다. 즉, Controller 에서도 사용가능하다.
- false 설정시 트랜잭션을 관리하는 Service가 끝나게 되면, 사용할 수 없다.
- lazy initialization 오류를 방지할 수 있는 옵션으로, 서비스에선 true로, 개발시에는 false로 두어 오류를 잡는것이 좋다.
- sql.init.mode
- 이 옵션을 쓸 일은 잘 없고, 보통 flyway를 써서 관리한다고 한다.
- src/main/resources/data/sql 쿼리문이 있을 경우, 해당 쿼리문을 자동실행함 (임시 데이터 삽입 등)
- never : 실행안함
- embedded :내장 DB(H2, HSQL, Derby 등)**일 때만 실행. MySQL, PostgreSQL 같은 외부 DB에서는 실행 안 됨. 개발/테스트 기본값
- always :DB 종류와 상관없이 항상 실행. 운영/개발 구분 없이 schema.sql 및 data.sql을 실행함
이 중, ddl-auto 옵션을 설명하면,
spring:
jpa:
hibernate:
ddl-auto: update
- update : 엔티티 변경부분을 db에 반영
- none : 엔티티가 변경되어도 아무것도 안함
- validate : 실제 DB와 엔티티에 차이점이 있는지만 검증
- create : 서버가 시작될때 DB를 새로 생성
- create-drop : 서버가 종료될때 DB를 삭제
- 개발환경에서는 update를 사용하고, 실제 운용에선 none 이나 validate 만 사용한다고 한다.
- 엔티티
- DB 와 소스코드를 매핑한 자바클래스. 클래스 조작을 통해 DB에 반영 가능
- DB 구조와 동일한 Java Class 를 생성
- 테이블 매핑을 위해서 @Table(name="테이블명") 어노테이션을 쓰는게 좋다.
- DB 에서 생성된 key 맴버변수인 경우에는 @Id 와 @GeneratedValue(strategy=GenerationType.IDENTITY) 를 적어준다
- 만일 개발자가 생성한 키값을 이용한 경우에는 @Id 어노테이션만 붙여준다
- 예 : id (키값), 이름, 연락처, 주소 DB 가 있다고 한다면, 다음과 같다
- column 어노테이션은 쓰지 않아도 되긴 하다.
- name 으로 컬럼명을 지정할 수 있고, length 등으로 옵션 설정을 할 수 있다
- @Transient 어노테이션을 이용하면, 해당 맴버변수는 매핑에서 제외한다.
- @Enumerated
- 맴버변수와 컬럼 매핑시 Enum으로 매핑할때 사용하는 어노테이션
- @Enumerated(EnumType.STRING) 을 주로 사용하여, DB에는 문자열로 저장한다.
- DB의 시간값과 매핑하기 위해 LocalDateTime (타임존 없을 경우) 혹은 OffsetDateTime(타임존 있는 경우) 자료형을 이용해 맴버변수를 정의할 수 있다.
- 접근 할때에는 getter 를 사용중이므로 getter (예 : getname, getphoneNumber ,,,)를 통해 가져올 수 있다.
- setter 를 통해 직접 값을 설정할 수도 있으나, getter만 설정하고 값 설정은 별도의 함수로 처리하는것이 좋다.
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
@Entity
@Getter
@Setter
@Table(name="addresses")
public class AddressTable
{
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
Long idx;
@Column(length=200)
String name;
@Column(length=11)
String phoneNumber;
@Column(columnDefinition = "TEXT")
String address;
}
- 1:N, N:1 관계의 Entity 생성
- N:1 로 표현을 하고, N이 주도권을 갖는다. 양방향으로 설정하여 1에 해당하는 엔티티에서 데이터를 활용할 수 있다.
- 만일, 위의 클래스를 맴버변수로 갖는 엔티티 클래스가 있다면(N에 해당), foreign key로 묶이게 되는데, 이때에는 관계를 명시 해주어야 한다.
- n:1 관계이고, n이 주도권을 갖고 있다.
- 이유. 1:N 관계가 주도권을 갖게 되면, N 에 해당하는 엔티티의 외래키가 null 이나 이상한 값으로 설정이 되고, 이후 1 에 해당하는 엔티티가 DB에 작성될때 다시 N 에 해당하는 엔티티의 외래키가 수정된다. 따라서 불필요한 쿼리가 많아지고, 외래키 설정이 not null 일 경우 문제가 된다.
- @ManyToOne
- 대부분은 이에 해당한다. n 쪽이 주인이 된다 (DB에서 외래키 필드를 갖고 있으므로)
- (하위개념이 주인이 된다고 보면 된다)
- 이 예에서 보자면, 1개의 Address에 다수의 추가 정보가 있는 관계가 된다.
- n:1 관계이고, n이 주도권을 갖고 있다.
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
@Entity
@Getter
@Setter
@table(name="addtionals")
public class AdditonalClass
{
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
Long idx;
...
@ManyToOne
@JoinColumn(name=외래키 이름)
AddressTable address;
}
- 1에 해당하는 엔티티의 양방향성 설정
- @OneToMany 를 이용하고, 변수를 list 로 두어야 한다.
- 1:N 관계이나, 위의 N이 주인이 되어 매핑을 하였고, 1의 입장에서도 N 을 쓰고 싶을때 쓴다. (ManyToOne의 양방향 관계)
- 항상 mappedBy를 써야 한다
- mappedBy 에는 N에 해당하는 클래스(주인역할)의 맴버변수 중 이 클래스 자료형을 갖는 맴버변수명
- 예에서는 AdditionalClass의 address 맴버변수
- Cascade
- foreign key 로 묶인 관계를 나타내는 설정이다. AddressTable 의 항목에 변경이 이뤄졌을때 AdditionalClass 의 항목이 어떻게 동작해야 하는지 설정한다.
- 데이터 무결성 문제가 있을 수 있으니 잘 고려해야 한다.
- 하나의 부모만이 자식 항목들을 소유하고 있을때 사용해야 한다. 만일 자식을 공유한다면, 데이터 무결성 문제가 발생할 수 있다.
- ALL : 모든 속성 부여
- PERSIST : 연관된 엔티티가 영구 보관될때 같이 영구 보관
- MERGE : 연관된 엔티티가 병합될때 같이 병합
- REMOVE : 연관된 엔티티가 삭제될때 같이 삭제
- REFRESH : 연관된 엔티티가 새로고침 될때 같이 새로고침
- DETACH : 연관된 엔티티가 영구 보관에서 분리될 경우 같이 분리
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
@Entity
@Getter
@Setter
public class AddressTable
{
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
Long idx;
...
@OneToMany(mappedBy=AdditionalClass의맴버변수명, cascade=CascadeType.REMOVE)
List<AddtionalClass> aditionals;
}
- 하위개념, 즉 주인이 되는 엔티티를 추가 할때 에는 양방향 모두 값을 추가 해줘야 한다
- AdditionalClass에 값이 추가될 경우, AddressTable 클래스에 값도 추가해야 한다.
//Address 서비스 코드에서 추가한다.
public void addAdditionalInfo(AdditionalClass item) {
this.additionals.add(item);
item.setAddress(this); //
}
- 1:1 관계에서의 Entity
- 외래키가 있는 쪽이 주인이 된다. (하위개념이 주인이 된다고 보면 된다)
- @OneToOne 어노테이션을 사용한다.
- 만일 양방향으로 구성할 경우에는,
- 주인 쪽에서는, @OneToOne 과 @JoinColumn을 사용한다.
- 주인이 아닌 쪽에서는 @OneToOne 과 mappedBy를 사용한다.
- 마찬가지로 하위개념 항목이 추가될 경우에는 양쪽 다 추가를 해주어야 한다.
- 만일 양방향으로 구성할 경우에는,
- Spring boot Validation
- Entity에 국한된것은 아니나, 변수값의 validation을 설정 할 수 있다.
- null 이 되면 안되는 값, 특정 size 가 요구되는 값 등
- Entity, DTO 에서 주로 사용 (컨트롤러 인자값 확인등)
- 설치
- build.gradle 에서 다음을 추가
- implementation 'org.springframework.boot:spring-boot-starter-validation'
- build.gradle 에서 다음을 추가
- import 추가
- import jakarta.validation.constraints.*
- * 부분은 사용되는 validation 어노테이션에 따라 다르다. notnull, size 등등
- import jakarta.validation.constraints.*
- 사용
- 어노테이션을 이용해 변수 앞에 적어주면 된다.
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
@Getter
@Setter
@NoArgsCoinstrucor
public class Test {
@NotNull(message = "value1은 null이면 안됩니다")
public String value1;
@Size(min = 2, max = 2, message = "value2는 2글자여야 합니다")
public String value2;
}
...
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;
@RestController
@RequestMapping("/api/test")
@Validated
public class TestController {
@PostMapping
public ResponseEntity<String> createTest(@Valid @RequestBody Test request) {
// 검증 통과 시 로직 수행
return ResponseEntity.ok("Validation Passed: value1=" + request.getValue1() +
", value2=" + request.getValue2());
}
}
어노테이션대상주요 속성설명은 아래와 같다.
| @NotNull | 모든 객체 | message | null이면 안 됨 |
| @NotEmpty | 컬렉션, 문자열 | message | null이 아니고 비어있으면 안 됨 |
| @NotBlank | 문자열 | message | null이 아니고, 공백 문자만으로 구성되면 안 됨 |
| @Size | 문자열, 컬렉션, 배열, Map | min, max, message | 길이/크기 제한 |
| @Min | 숫자(Long, Integer 등) | value, message | 최소값 지정 |
| @Max | 숫자 | value, message | 최대값 지정 |
| @DecimalMin | BigDecimal, String | value, inclusive, message | 최소값 지정 (소수 가능) |
| @DecimalMax | BigDecimal, String | value, inclusive, message | 최대값 지정 (소수 가능) |
| @Positive | 숫자 | message | 0보다 큰 값 |
| @PositiveOrZero | 숫자 | message | 0 이상 |
| @Negative | 숫자 | message | 0보다 작은 값 |
| @NegativeOrZero | 숫자 | message | 0 이하 |
| 문자열 | message, regexp | 이메일 형식 검사 | |
| @Pattern | 문자열 | regexp, flags, message | 정규식 패턴 검증 |
| @Past | Date, LocalDate 등 | message | 과거 날짜여야 함 |
| @PastOrPresent | Date, LocalDate 등 | message | 과거 또는 현재 날짜 |
| @Future | Date, LocalDate 등 | message | 미래 날짜여야 함 |
| @FutureOrPresent | Date, LocalDate 등 | message | 미래 또는 현재 날짜 |
| @AssertTrue | boolean | message | 값이 true여야 함 |
| @AssertFalse | boolean | message | 값이 false여야 함 |
'Backend > SpringBoot' 카테고리의 다른 글
| 의존성 주입 (List, Map 포함) (0) | 2024.10.23 |
|---|---|
| [기본구조3] Repository with JPA (DB CRUD동작,Interface, findby) (0) | 2024.10.23 |
| 프로젝트 설정파일 application.properties (또는 application.yml) (0) | 2024.10.22 |
| [VSCode] 코틀린 + 자바 섞어서 쓰기 (0) | 2024.10.21 |
| [기본 구조1] 컨트롤러 (@controller) 및 각종 매핑 (0) | 2024.10.21 |