*책 내용과 다르게, 다음과 같은 환경에서 프로젝트 생성
- Windows11(윈도우 11) 환경
- 자바 JDK 17 버전 설치 https://yungenie.tistory.com/11
[Java] 차근차근 Java 설치하기 (JDK17, Window 11)
자바 개발 도구 설치 방법에 대해서 알아보겠습니다. Java17은 LTS(Long Term Support : 장기 지원) 릴리즈로 1년 후까지 기술 지원 및 버그를 개선한 서비스를 제공받을 수 있습니다. 업데이트 버전을 꾸
yungenie.tistory.com
- 스프링 부트 4.31.0 사용 - STS(Spring Tool Suite) 설치(Spring Tools for Eclipse - https://spring.io/tools)
=> https://priming.tistory.com/147 참고
[Windows] Spring Tool Suite 4(STS 4) 다운로드 및 설치
STS란?Spring Tool Suite(STS)는 스프링 프로젝트를 생성하고, 개발할 수 있게 해주는 도구입니다. STS 설치 과정에 대해 설명드리겠습니다. 설치 파일 다운로드STS 공식 사이트에서 설치 파일을 다운로
priming.tistory.com
- MySQL Community Server 8.0.42 설치 https://dev.mysql.com/downloads/mysql/
MySQL :: Download MySQL Community Server
Select Version: 9.3.0 Innovation 8.4.5 LTS 8.0.42 Select Operating System: Select Operating System… Microsoft Windows Ubuntu Linux Debian Linux SUSE Linux Enterprise Server Red Hat Enterprise Linux / Oracle Linux Fedora Linux - Generic Oracle Solaris mac
dev.mysql.com
- Gradle
**STS에서 Gradle 프로젝트 생성한 과정
*** 함께 보면 좋은 글
스프링 부트 핵심 가이드(장정우 지음) - 스프링 부트 개요
1. 스프링 프레임워크자바(Java) 기반 애플리케이션 프레임워크로, 엔터프라이즈급(기업 환경 대상 개발) 애플리케이션을 위한 다양한 기능 제공-> 오픈소스 경량급 애플리케이션 프레임워크로
keep-programming-study.tistory.com
스프링 부트 핵심 가이드(장정우 지음) - 개발에 앞서 알면 좋은 기초 지식
1. 서버 간 통신마이크로서비스 아키텍처에서 한 서버가 다른 서버에 통신을 요청하는 것을 의미-> 한 대는 서버/다른 한 대는 클라이언트가 됨 가장 많이 사용되는 방식은 HTTP/HTTPS 방식(TCP/IP, SOA
keep-programming-study.tistory.com
1. 연관관계 매핑 개요
- 연관관계 매핑: JPA(Java Persistence API)를 통해 객체와 관계형 데이터베이스 간의 관계를 설정하는 핵심 개념
-> 이를 통해 엔티티 간의 관계를 선언적으로 표현하고, 복잡한 SQL 없이 객체 간 연동을 쉽게 처리 - RDBMS를 사용할 때는 테이블 하나만 사용해서 애플리케이션의 모든 기능을 구현하는 것은 불가능하며,
각 도메인에 맞는 테이블을 설계하고 연관관계를 설정해서 조인(Join) 등의 기능을 활용 - JPA를 사용하는 애플리케이션의 경우 테이블 간 연관관계를 엔티티 간 연관관계로 표현할 수는 있지만,
객체와 테이블의 성질이 달라 정확히 표현하기는 어려움
=> 이 제약을 보완하면서 연관관계를 매핑하고 사용해야 함
2. 연관관계 매핑 종류와 방향
1) 연관관계를 맺는 두 엔티티 간 생성 가능한 연관관계의 종류(4가지)
- One To One: 일대일(1:1)
- One To Many: 일대다(1:N)
- Many To One: 다대일(N:1)
- Many To Many: 다대다(N:M)
2) 연관관계 매핑의 방향
- 단방향: 두 엔티티의 관계에서 한 쪽의 엔티티만 참조
- 양방향: 두 엔티티의 관계에서 각 엔티티가 서로의 엔티티를 참조
3) 연관관계 설정 시 생기는 키
- 한 테이블에서 다른 테이블의 기본값을 외래키로 갖게 되며 이런 관계에서 주인(Owner)이라는 개념 사용
- 외래키를 가진 테이블이 그 관계의 주인이 되는데, 주인은 외래키를 사용할 수 있으나 상대 엔티티는 읽는 작업만 수행 가능
3. 일대일 매핑 예시
1) 일대일 단방향 매핑(entity 패키지 안에 상품정보 엔티티 작성)
(1) 기존 상품 엔티티와 BaseEntity.java 사용
// Product.java
package com.example.demo.jpa.data.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
//클래스의 모든 필드에 대해 Getter 메서드를 자동 생성
@Getter
//클래스의 모든 필드에 대해 Setter 메서드를 자동 생성
@Setter
//파라미터가 없는 기본 생성자를 자동 생성
@NoArgsConstructor
//모든 필드를 파라미터로 받는 생성자를 자동 생성
@AllArgsConstructor
//이 클래스는 JPA 엔티티로 데이터베이스 테이블과 매핑
@Entity
// 객체 생성 시 빌더 패턴을 사용할 수 있게 해주는 어노테이션
@Builder
// toString() 메서드를 자동 생성하되, name 필드는 출력에서 제외
@ToString(exclude="name")
//엔티티가 매핑될 테이블 이름을 명시적으로 지정 (기본값은 클래스 이름)
@Table(name="product")
// BaseEntity를 상속하여, LocalDateTime.now() 메서드로 시간을 주입하지 않아도 자동으로 생성 일시/수정 일시의 값 생성
public class Product extends BaseEntity {
// 기본 키(PK)로 지정된 필드. 데이터베이스의 고유 식별자 역할
@Id
// ID 값을 자동 생성함. GenerationType.IDENTITY는 DB가 직접 AUTO_INCREMENT 방식으로 생성하도록 함(insert 후에 pk 자동생성)
// -> IDENTITY외에도 AUTO(기본값, 사용하는 데이터베이스에 맞게 기본값 자동 생성),
// SEQUENCE(Oracle, PostgreSQL 등 시퀀스를 지원하는 DB에서 별도 시퀀스 객체를 생성해 PK값 증가),
// TABLE(키 값을 관리하는 별도 테이블을 생성해 PK값 증가-성능 낮음)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long number;
// 엔티티 클래스의 필드는 자동으로 테이블 칼럼으로 매핑되므로, 별다른 설정을 하지 않을 경우 @Column을 생략해도 됨
// 상품 이름 (Null 허용하지 않음)
@Column(nullable = false)
private String name;
// 가격 (Null 허용하지 않음)
@Column(nullable = false)
private int price;
// 재고 수량 (Null 허용하지 않음)
@Column(nullable = false)
private int stock;
// 상품 생성 일시와 상품 수정 일시 자동 설정
}
package com.example.demo.jpa.data.entity;
import java.time.LocalDateTime;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter // 모든 필드에 대한 getter 메서드 자동 생성
@Setter // 모든 필드에 대한 setter 메서드 자동 생성
@ToString // toString 메서드 자동 생성
@MappedSuperclass // 이 클래스를 상속받는 엔티티 클래스에 필드가 매핑되도록 지정
@EntityListeners(AuditingEntityListener.class) // 생성일-수정일 자동 설정을 위해 Auditing 기능 활성화
public class BaseEntity {
@CreatedDate // 엔티티가 처음 저장될 때 자동으로 현재 시간이 설정됨
@Column(updatable = false) // 생성일은 수정되지 않도록 설정
private LocalDateTime createdAt; // 생성일시
@LastModifiedDate // 엔티티가 수정될 때 자동으로 현재 시간이 설정됨
private LocalDateTime updatedAt; // 최종 수정일시
}
(2) 상품정보 엔티티
// 상품 정보 엔티티 작성
package com.example.demo.jpa.data.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Entity
@Table(name="product_detail")
@Getter
@Setter
@NoArgsConstructor
// 부모 클래스의 필드까지 포함해서 toString() 메서드를 생성
@ToString(callSuper=true)
// 부모 클래스의 필드까지 포함해서 equals()와 hashCode()
@EqualsAndHashCode(callSuper=true)
public class ProductDetail extends BaseEntity {
// 기본 키(PK)로 자동 생성되는 ID
// GenerationType.IDENTITY 전략을 사용하여 DB에서 자동 증가
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 제품에 대한 설명을 담는 필드
private String description;
// Product 엔티티와의 일대일 관계를 나타냄
// -> 외래키 컬럼 이름은 'product_number'이며, ProductDetail이 관계의 주인
@OneToOne(optional = false) // product가 null인 값을 허용하지 않음
@JoinColumn(name = "product_number")
private Product product;
}
(3) 상품정보 엔티티 객체 사용을 위한 리포지토리 인터페이스 생성
package com.example.demo.jpa.data.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.example.demo.jpa.data.entity.ProductDetail;
//이 인터페이스는 ProductDetail 엔티티에 대한 리포지토리 역할을 수행
public interface ProductDetailRepository extends JpaRepository<ProductDetail, Long> {
// 이 인터페이스를 통해 다음 기능들을 사용할 수 있음:
// - save(), findById(), delete() 등 기본적인 JPA 메서드
// - findAll(Predicate predicate) 형태의 QueryDSL 조건 검색
// - findByName(String name) 등 커스텀 구현한 메서드 호출 가능
}
(4) 리포지토리 인터페이스에 대한 테스트 코드 작성
package com.example.demo.jpa.data.repository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.example.demo.jpa.data.entity.Product;
import com.example.demo.jpa.data.entity.ProductDetail;
@SpringBootTest // Spring Boot 테스트 환경을 설정하여 실제 애플리케이션 컨텍스트에서 테스트를 실행
public class ProductDetailRepositoryTest {
// ProductDetail 엔티티를 위한 JPA 리포지토리 주입
@Autowired
ProductDetailRepository productDetailRepository;
// Product 엔티티를 위한 JPA 리포지토리 주입
@Autowired
ProductRepository productRepository;
@Test
public void saveAndReadTest1() {
// Product 객체 생성 및 값 설정
Product product = new Product();
product.setName("스프링 부트 JPA"); // 상품 이름 설정
product.setPrice(5000); // 상품 가격 설정
product.setStock(500); // 상품 재고 설정
// Product 객체 저장
productRepository.save(product);
// ProductDetail 객체 생성 및 Product와 연관 설정
ProductDetail productDetail = new ProductDetail();
productDetail.setProduct(product); // 연관된 Product 설정
productDetail.setDescription("스프링 부트와 JPA를 함께 볼 수 있는 책"); // 상세 설명 설정
// ProductDetail 객체 저장
productDetailRepository.save(productDetail);
// 저장된 ProductDetail의 ID를 기반으로 Product 조회 및 출력
System.out.println("savedProduct: " + productDetailRepository.findById(
productDetail.getId()).get().getProduct());
// 저장된 ProductDetail 전체 정보 출력
System.out.println("savedProductDetail: " + productDetailRepository.findById(
productDetail.getId()).get());
}
}
(5) 테스트 코드 실행 시 Hibernate 쿼리 로그
Hibernate:
select
productdetai0_.id as id1_2_0_,
productdetai0_.created_at as created_2_2_0_,
productdetai0_.updated_at as updated_3_2_0_,
productdetai0_.description as descript4_2_0_,
productdetai0_.product_number as product_5_2_0_,
product1_.id as id1_1_1_,
product1_.created_at as created_2_1_1_,
product1_.updated_at as updated_3_1_1_,
product1_.name as name4_1_1_,
product1_.price as price5_1_1_,
product1_.stock as stock6_1_1_
from
product_detail productdetai0_
left outer join
product product1_
on
productdetai0_.product_number=product1_.id
where
productdetai0_.id=?