상세 컨텐츠

본문 제목

[스프링3] 6장. 데이터베이스 연동 - 2

23-24/Spring 3

by recoday 2023. 11. 10. 10:00

본문

728x90

 

데이터베이스 연동

프로젝트 생성

  • 버전: 2.5.6
  • groupId: com.springboot
  • name: jpa
  • artifactId:jpa

-라이브러리 설정

  • Developer Tools: Lombok, Spring Configuration Processor
  • Web: Spring Web
  • SQL: Spring Data JPA, MariaDB Driver

-위와 같은 과정이 끝나면 Swagger 의존성을 pom.xml에 추가

 

 

 

 

작성해놓았던 소스코드(config/SwaggerConfiguration.java)와 리소스 파일(logback-spring.xml)을 가져옴

 

*SwaggerConfiguration 파일을 그대로 복사하면 api() 메서드 내의 basePackage가 일치하지 않아 오류가 남

->아래와 같이 코드를 부분적으로 수정

 

@Configuration
@EnableSwagger2
public class SwaggerConfiguration {

    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.springboot.jpa"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Spring Boot Open API Test with Swagger")
                .description("설명 부분")
                .version("1.0.0")
                .build();
    }
}

 

바뀐 부분: com.springboot.api  ->  com.springboot.jpa

 

 

 

  • 애플리케이션이 정상적으로 실행될 수 있도록 연동할 데이터베이스의 정보를 application.properties에 작성
  • application.properties에 데이터베이스 관련 설정을 추가
spring.datasource.driverClassName=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://localhost:3306/springboot
spring.datasource.username=root
spring.datasource.password=password

spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
  • spring.datasource.driverClassName: 연동하려는 데이터베이스의 드라이버를 정의
  • spring.datasource.url: 마리아DB의 경로임을 명시하기 위해 경로와 데이터베이스명을 입력
  • 3~4번 줄: 마리아DB를 설치할 때 설정한 계정 정보 기입. 프로퍼티 파일에 패스워드가 그대로 들어가면 보안상 위험하기에 패스워드를 암호화해서 사용하지만 여기서는 간편화를 위해 암호화하지 않고 진행 
  • 6~8번 줄: 하이버네이트를 사용할 때 활성화할 수 있는 선택사항. ddl-auto는 데이터베이스를 자동으로 조작하는 옵션
  • show-sql: 로그에 하이버네이트가 생성한 쿼리문을 출력하는 옵션

 

-ddl-auto에서 사용할 수 있는 옵션&설명

  • create: 애플리케이션이 가동되고 SessionFactory가 실행될 때 기존 테이블을 지우고 새로 생성함. 
  • create-drop: create와 동일한 기능을 수행하지만 create와의 차이점은 애플리케이션을 종료하는 시점에 테이블을 지움
  • update: SessionFactory가 실행될 때 객체를 검사하여 변경된 스키마 갱신
  • validate: update와 같이 객체를 검사하지만 스키마는 건드리지 않음. 검사 과정에서 데이터베이스의 테이블 정보와 객체의 정보가 다르면 에러 발생
  • none: ddl-auto 기능을 사용하지 않음

 

운영환경에서 create, create-drop, update 기능은 사용하지 않음

    -> 데이터베이스의 데이터 삭제 가능성과 실수로 객체의 정보가 변경되었을 때 운영 환경의 데이터베이스 정보까지 변          경될 수 있는 위험이 있기 때문

 

  • 운영환경에서는 validate, none을 주로 사용
  • 개발환경에서는 create, update를 주로 사용

 

 

 

엔티티 설계

 

-엔티티

: JPA에서 데이터베이스의 테이블에 대응하는 클래스

  • Spring Data JPA를 사용하여 데이터베이스에 테이블을 생성하기 위해 직접 쿼리를 작성할 필요없게 해주는 기능을 함
  • 데이터베이스에 쓰일 테이블과 칼럼 정의

 

 

 

-데이터베이스 테이블

 

 

 

-Product.java의 엔티티 클래스

package com.springboot.jpa.data.entity;

import javax.persistence.*;
import java.time.LocalDataTime;

@Entity
@Table(name="product")
public class Product{

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long number;
    
    @Column(nullable=false)
    private String name;
    
    @Column(nullable=false)
    private Integer price;
    
    private LocalDataTime createdAt;
    
    private LocalDataTime updatedAt;
    
    }

 

 

 

 

 

엔티티 관련 기본 어노테이션

@Entity

  • 해당 클래스가 엔티티임을 명시하기 위한 어노테이션
  • 테이블과 일대일로 매칭
  • 해당 클래스의 인스턴스는 매핑되는 테이블에서 하나의 레코드를 의미

@Table

  • @Table을 따로 명시하지 않으면 테이블 이름과 클래스 이름이 동일하다는 의미
  • 다른 이름을 쓰려면 @Table(name=값) 형태로 테이블명을 명시

@Id

  • @Id 어노테이션이 선언된 필드는 테이블의 기본값 역할로 사용됨
  • 모든 엔티티는 @Id 어노테이션을 필요로 함

@GeneratedValue

  • 주로 @Id 어노테이션과 함께 사용됨
  • 해당 필드의 값을 어떤 방식으로 자동으로 생성할지 결정할 때 사용

 

-@GeneratedValue에서 값 생성 방식

1. GeneratedValue를 사용하지 않는 방식

  • 애플리케이션에서 자체적으로 고유한 기본값을 생성할 경우 사용하는 방식
  • 내부에 정해진 규칙에 의해 기본값을 생성하고 식별자로 사용

2. AUTO

  • @GeneratedValue의 기본 설정값
  • 기본값을 사용하는 데이터베이스에 맞게 자동 생성함

3. IDENTITY

  • 기본값 생성을 데이터베이스에 위임하는 방식
  • AUTO_INCREMENT를 이용하여 기본값을 생성함

4. SEQUENCE

  • @SequenceGenerator 어노테이션으로 식별자 생성기를 설정하고 이를 통해 값을 자동으로 주입받는 방식
  • name, sequenceName, allocationSize를 이용하여 SequenceGenerator를 정의함

5. TABLE

  • 어떤 DBMS를 사용하더라도 동일하게 동작하기를 원할 경우 사용
  • 식별자로 사용할 숫자의 보관 테이블을 별도로 생성하여 엔티티를 생성할 때마다 값을 갱신함
  • @TableGenerator 어노테이션으로 테이블 정보를 설정함

 

@Column

  • 특별한 경우가 아니면 @Column 어노테이션은 명시하지 않아도 됨
  • name, nullable, length, unique 등 필드에 특정한 설정을 더할 때 사용되는 어노테이션

@Transient

  • 엔티티 클래스에는 선언되어 있지만 데이터베이스에서는 필요없는 필드일 때 @Transient 어노테이션을 이용하여 데이터베이스에서 이용하지 않게 할 수 있음

 

 

 

리포지토리 인터페이스 설계

리포지토리 인터페이스 생성

-리포지토리: Spring Data JPA가 제공하는 인터페이스

 

리포지토리 인터페이스 생성 경로

 

  • 리포지토리 생성
public interface ProductRepository extends JpaRepository<Product, Long>{

}

-엔티티에 대한 인터페이스를 생성, JpaRepository를 상속받음

 

 

 

  • 대상 엔티티를 Product로 설정하고 해당 엔티티의 @Id 필드 타입 Long을 설정
@NoRepositoryBean
public interface JpaRepository<T, ID>extends PagingAndSortingRepository<T, ID>,   //T는 엔티티 유형, ID는 엔티티의 기본 키 유형
QueryByExampleExecutor<T>{
        List<T> findAll();
        
        List<T> findAll(Sort sort);
        List<T> findAllById(Iterable<ID> ids);
        
        <S extends T> List<S> saveAll(Iterable<S> entities);
        
        void flush();
        
        <S extends T> S saveAndFlush(S entity);
        <S extends T> List<S> saveAllAndFlush(Iterable<S> entities);
        
        @Deprecated
        default void deleteInBatch(Iterable<T> entities){
            this.deleteAllInBatch(entities);
        }
        
        void deleteAllInBatch(Iterable<T> entities);
        void deleteAllByIdBatch(Iterable<ID> ids);
        void deleteAllInBatch();
        
        @Deprecated
        T getOne(ID id);
        T getById(ID id);
        
        <S extends T> List<S> findAll(Example<S> example);
        
        <S extends T> List<S> findAll(Example<S> example, Sort sort);
}

 

 

 

 

리포지토리 메서드 생성 규칙

  • 리포지토리에서 기본적으로 제공하는 조회 메서드는 기본값으로 단일 조회하거나 전체 엔티티를 조회하는 것만 지원하기 때문에 서로 다른 조회 메서드가 필요함
  • 메서드에 이름을 붙일 때는 첫단어를 제외하고 그 뒤에 붙는 단어들의 첫 글자는 대문자로 설정해야함

-조회 메서드(find)에 조건으로 붙일 수 있는 기능들

  • FindBy : SQL문의 where 절 역할을 수행
  • AND,OR 
  • Like/NotLike : 특정 문자를 포함하는지 여부를 조건으로 추가
  • StartsWith/StartingWith : 특정 키워드로 시작하는 문자열 조건을 설정
  • EndsWith/EndingWith : 특정 키워드로 끝나는 문자열 조건을 설정
  • IsNull/IsNotNull
  • True/False
  • Before/After : 시간을 기준으로 값을 검색
  • LessThan/GreaterThan
  • Between 
  • OrderBy : SQL 문에서 order by와 동일한 역할. ex) List<Product> findByNameOrderByPriceAsc(String name);             ->가격순으로 이름을 조회
  • countBy : 결괏값의 개수를 count 함

 

 

 

 

DAO 설계

  • DAO(Data Access Object): 데이터베이스에 접근하기 위한 로직을 관리하기 위한 객체
  • 비즈니스 로직 동작 과정에서 데이터를 조작하는 기능을 수행
  • JPA에서 DAO의 역할은 리포지토리가 대체함

 

DAO와 리포지토리는 역할이 비슷함.
리포지토리는 Spring Data JPA에서 제공하는 기능이기 때문에 리포지토리라는 개념을 사용하지 않고 DAO 객체로 데이터베이스에 접근함.

 

 

DAO 클래스 생성

=> data.dao.impl 구조로 패키지 생성 후 ProductDAO 인터페이스와  ProductDAOImpl 클래스를 각각 생성

 

 

 

-ProductDAO 인터페이스 작성

package com.springboot.jpa.data.dao;

import com.springboot.jpa.data.data.entity.Product;

public interface ProductDAO{

    Product insertProduct(Product product);
    Product selectProduct(Long number);
    Product updateProductName(Long number, String name) throws Exception;
    
    void deleteProduct(Long number) throws Exception;
    
}
  • 인터페이스에 메서드 정의
  • 일반적인 설계 원칙에서 엔티티 객체는 데이터베이스에 접근하는 계층에서만 사용하도록 정의
  • 다른 계층으로 전달할 때는 DTO 객체 사용

 

 

-ProductDAOImpl 인터페이스 구현체 클래스 작성

  • ProductDAOImpl 클래스를 스프링이 관리하는 빈으로 등록하려면 @Component 또는 @Service 어노테이션을 지정해야 함
  • 리포지토리 정의, 생성자를 통해 의존성 주입을 받음
  • insertProduct() 메서드를 구현하여 Product 엔티티를 데이터베이스에 저장
@Component
public class ProductDAOImpl implements ProductDAO{
    private final ProductRepository productRepository;
    
    @Autowired
    public ProductDAOImpl(ProductRepository productRepository){
        this.productRepository=productRepository;
    }
    
    @Override
    public Product insertProduct(Product product){
        return null;
    }
    
    @Override
    public Product selectProduct(Long number){
        return null;
    }
    
    @Override
    public Product updateProductName(Long number, String name) throws Exception{
        return null;
    }
    
    @Override
    public void deleteProduct(Long number) throws Exception{
    
    }
}

 

 

-insertProduct() 메서드 작성

@Override
public Product insertProduct(Product product){
    Product savedProduct=productRepository.save(product);
    
    return savedProduct;
}

 

 

 

-조회메서드 selectProduct() 메서드 작성

@Override
public Product selectProduct(Long number){
    Product selectedProduct=productRepository.getById(number);
    
    return selectedProduct;
}

 

 

 

getById()

  • EntityManager의 getReference()메서드 호출
  • getReference() 호출하면 프락시 객체를 리턴
  • 데이터가 존재하지 않으면 EntityNotFoundException 발생

findById()

  • 내부적으로 EntityManager의 find()메서드 호출
  • 영속성 컨텍스트의 캐시에서 값을 조회
  • 영속성 컨텍스트에 값이 존재하지 않는 경우엔 실제데이터베이스에서 조회
  • 리턴 값으로 Optional 객체를 전달

 

-업데이트 메서드 작성

  • JPA에서 값을 변경할 때 다른 메서드와의 차이점은 update라는 키워드를 사용하지 않음
  • 영속성 컨텍스트를 활용해 값을 갱신함
@Override
public Product updateProductName(Long number, String name) throws Exception{
    Optional<Product> selectedProduct=productRepository.findById(number);
    
    Product updatedProduct;
    if (selectedProduct.isPresent()){
        Product product=selectedProduct.get();
        
        product.setName(name);
        product.setUpdatedAt(LocalDateTime.now());
        
        updatedProduct=productRepository.save(product);
    }else{
       throw new Exception();
    }
    return updatedProduct;
}

 

 

-삭제 메서드 작성

  • findById()메서드를 통해 객체를 가져옴
  • delete()메서드를 통해 해당 객체를 삭체하도록 삭제요청을 함
@Override
public void deleteProduct(Long number) throws Exception{
    Optional<Product> selectedProduct=productRepository.findById(number);
    
    if (selectedProduct.isPresent()){
        Product product=selectedProduct.get();
        
        productRepository.delete(product);
   }else{
      throw new Exception();
   }
}

 

 

 

DAO 연동을 위한 컨트롤러와 서비스 설계

서비스 클래스 만들기

  • 서비스 인터페이스 작성 전 필요한 DTO 클래스 생성
  • data패키지> dto 안에 ProductDto와 ProductResponseDto 클래스 생성

-ProductDto 클래스 작성

public class ProductDto{

    private String name;
    private int price;
    private int stock;
    
    public ProductDto(String name, int price, int stock){
        this.name=name;
        this.price=price;
        this.stock=stock;
    }
    
    public String getName(){
        return name;
    }
    
    public void setName(String name){
        this.name=name;
    }
    
    public int getPrice(){
        return price;
    }
    
    public void setPrice(int price){
        this.price=price;
    }
    
    public int getStock(){
        return stock;
    }
    
    public void setStock(int stock){
        this.stock=stock;
    }
}

 

 

-ProductResponseDto 클래스 작성

public class ProductResponseDto{

    private Long number;
    private String name;
    private int price;
    private int stock;
    
    public ProductResponseDto() {}
    
    public ProductResponseDto(Long number, String name, int price, int stock){
        this.number=number;
        this.name=name;
        this.price=price;
        this.stock=stock;
    }
    
    public Long getNumber(){
        return number;
    }
    
    public void setNumber(Long number){
        this.number=number;
    }
    
    public String getName(){
        return name;
    }
    
    public void setName(String name){
        this.name=name;
    }
    
    public int getPrice(){
        return price;
    }
    
    public void setPrice(int price){
        this.price=price;
    }
    
    public int setStock(){
        return stock;
    }
    
    public void setStock(int stock){
        this.stock=stock;
    }
}

 

 

-ProductService 인터페이스 설계

  • 서비스 인터페이스 작성
  • CRUD 기능을 호출하기 위해 간단한 메서드 정의
public interface ProductService{

    ProductResponseDto getProduct(Long number);
    
    ProductResponseDto saveProduct(ProductDto productDto);
    
    ProductResponseDto changeProductName(Long number, String name) throws Exception;
    
    void deleteProduct(Long number) throws Exception;
}

 

 

 

-서비스 인터페이스 구현체 클래스

  • DAO 인터페이스 선언
  • @Autowired를 지정한 생성자를 통해 의존성을 주입받음
  • 인터페이스에서 정의한 메서드를 오버라이딩 함
@Service
    public class ProductServiceImpl implements ProductService{
        private final ProductDAO productDAO;
        
        @Autowired
        public ProductServiceImpl(ProductDAO productDAO){
            this.productDAO=productDAO;
        }
        
        @Override
        public ProductResponseDto getProduct(Long number){
            return null;
        }
        
        @Override
        public ProductResponseDto saveProduct(ProductDto productDto){
            return null;
        }
        
        @Override
        public ProductResponseDto changeProductName(Long number, String name) throws Exception{
            return null;
        }
        
        @Override
        public void deleteProduct(Long number) throws Exception{
            
        }
    }

 

 

-getProduct() 메서드 구현

        @Override
        public ProductResponseDto getProduct(Long number){
            Product product=productDAO.selectProduct(number);
            
            ProductResponseDto productResponseDto=new ProductResponseDto();
            productResponseDto.setNumber(product.getNumber());
            productResponseDto.setName(product.getName());
            productResponseDto.setPrice(product.getPrice());
            productResponseDto.setStock(product.getStock());
            
            return productResponseDto;
        }

 

 

-saveProduct() 메서드 작성

  • 전달받은 DTO 객체를 통해 엔티티 객체를 생성하여 초기화한 후 DAO 객체로 전달
  • saveProduct() 메서드는 상품 정보를 전달하고 애플리케이션을 거쳐 데이터베이스에 저장하는 역할
        @Override
        public ProductResponseDto saveProduct(ProductDto productDto){
            Product product=new Product();
            product.setName(productDto.getName());
            product.setName(product.getName());
            product.setPrice(product.getPrice());
            product.setStock(product.getStock());
            product.setCreatedAt(LocalDateTime.now());
            product.setUpdatedAt(LocalDateTime.now());

            Product savedProduct=productDAO.insertProduct(product);

            ProductResponseDto productResponseDto=new ProductResponseDto();
            productResponseDto.setNumber(savedProduct.getNumber());
            productResponseDto.setName(savedProduct.getName());
            productResponseDto.setPrice(savedProduct.getPrice());
            productResponseDto.setStock(savedProduct.getStock());

            return productResponseDto;
        }

 

 

-changeProductName() 메서드 구현

  • changeProductName() 메서드는 상품정보 중 이름을 변경하는 기능
        @Override
        public ProductResponseDto changeProductName(Long number, String name) throws Exception{
            Product changedProduct=productDAO.updateProductName(number, name);

            ProductResponseDto productResponseDto=new ProductResponseDto();
            productResponseDto.setNumber(changedProduct.getNumber());
            productResponseDto.setName(changedProduct.getName());
            productResponseDto.setName(changedProduct.getPrice());
            productResponseDto.setName(changedProduct.getStock());

            return productResponseDto;
        }

 

 

-deleteProduct() 메서드 구현

  • 리턴받는 타입이 지정되어 있지 않으므로 리턴 타입을 void로 지정
@Override
public void deleteProduct(Long number) throws Exception{
    productDAO.deleteProduct(number);
}

 

 

 

컨트롤러 생성

컨트롤러 생성 위치

 

  • 요청과 응답을 전달하는 역할
  • 컨트롤러는 클라이언트로부터 요청을 받고 해당 요청에 대해 서비스 레이어에 구현된 적절한 메서들를 호출, 결괏값을 받음

-ProductController 클래스 작성

@RestController
@RequestMapping("/product")
public class ProductController {
    
    private final ProductService productService;
    
    @Autowired
    public ProductController(ProductService productService){
        this.productService=productService;
    }
    
    @GetMapping()
    public ResponseEntity<ProductResponseDto> getProduct(@RequestParam Long number){
        ProductResponseDto productResponseDto=productService.getProduct(number);
        
        return ResponseEntity.status(HttpStatus.OK).body(productResponseDto);
    }
    
    @PostMapping()
    public ResponseEntity<ProductResponseDto> createProduct(@RequestBody ProductDto productDto){
        ProductResponseDto productResponseDto=productService.saveProduct(productDto);
        
        return ResponseEntity.status(HttpStatus.OK).body(productResponseDto);
    }
    
    @PutMapping()
    public ResponseEntity<ProductResponseDto> changeProductName(
            @RequestBody ChangeProductNameDto changeProductNameDto) throws Exception{
        ProductResponseDto productResponseDto=productService.changeProductName(
                changeProductNameDto.getNumber(),
                changeProductNameDto.getName());
        
        return ResponseEntity.status(HttpStatus.OK).body(productResponseDto);
    }
    
    @DeleteMapping()
    public ResponseEntity<String> deleteProduct(@RequestParam Long number) throws Exception{
        productService.deleteProduct(number);
        
        return ResponseEntity.status(HttpStatus.OK).body("정상적으로 삭제되었습니다.");
    }
}

 

 

-ChangeProductNameDto 클래스

package com.springboot.jpa.data.dto;

public class ChangeProductNameDto {
    
    private Long number;
    private String name;
    
    public ChangeProductNameDto(Long number, String name){
        this.number=number;
        this.name=name;
    }
    
    public ChangeProductNameDto(){
        
    }
    
    public String getNumber(){
        return this.name;
    }
    
    public void setName(String name){
        this.name=name;
    }
}

 

 

현재까지의 프로젝트 구조


 

Swagger API 를 통한 동작 확인

 

 

 

  • 상품정보 저장-POST 메서드를 사용하는 createProduct() 메서드 사용
  • POST API 클릭->[Try it out] 클릭
  • 상품명:연필, 가격: 1000, 재고수량:100 입력->[Execute] 버튼 클릭

 

 

  • 하이버네이트 로그 출력
  • insert 쿼리 생성
Hibernate:
    insert
    into
        product
        (create_at, name, price, stock, updated_at)
        
     values
         (?, ?, ?, ?, ?)

 

 

=>HeidiSQL 데이터베이스에 데이터가 추가됨

 

 

 

  • GET 메서드의 파라미터 값 입력->[Execute] 클릭

 

 

  • 응답 Body에 값이 담긴 결과 확인

 

 

  • intellij IDEA에서 로그에서 select 쿼리가 실행된 것을 확인
Hibernate:
    select
        product0_.number as number1_0_0_,
        product0_.created_at as created_2_0_0),
        product0_.name as name3_0_0_,
        product0_.price as price4_0_0_,
        product0_.stock as stock5_0_0_,
        product0_.updated_at as updated_6_0_0_
    from
        product product0_
    where
        product0_.number=?

 

 

 

  • updateProductName() 메서드를 이용해 상품 이름 변경
  • PUT 메서드의 Body 값 입력

 

 

 

  • 이름이 변경된 것을 확인할 수 있음

 

 

 

  • intellij IDEA에서 로그를 확인하면 위와 같은 쿼리가 실행됨을 알 수 있음
Hibernate:
    select
        product0_.number as number1_0_0_,
        product0_.created_at as created_2_0_0),
        product0_.name as name3_0_0_,
        product0_.price as price4_0_0_,
        product0_.stock as stock5_0_0_,
        product0_.updated_at as updated_6_0_0_
    from
        product product0_
    where
        product0_.number=?
 
Hibernate:
    update
        product
    set
        created_at=?,
        name=?,
        price=?,
        stock=?,
        updated_at=?
    where
        number=?

 

 

 

-상품정보 삭제

  • number 값을 파라미터에 입력

 

 

 

  • 정상적으로 삭제가 완료되면 '정상적으로 삭제되었습니다'라는 문자열을 Response Body 부분에서 확인할 수 있음

 

 

 

  • select 쿼리 통해 데이터를 영속성 컨텍스트로 가져오고, 해당 객체를 삭제 요청하여 comit 단계에서 정상적으로 삭제하는 동작이 수행됨
Hibernate:
    select
        product0_.number as number1_0_0_,
        product0_.created_at as created_2_0_0),
        product0_.name as name3_0_0_,
        product0_.price as price4_0_0_,
        product0_.stock as stock5_0_0_,
        product0_.updated_at as updated_6_0_0_
    from
        product product0_
    where
        product0_.number=?
 
Hibernate:
    delete
    from
        product
    where
        number=?

 

 

 

 

롬복-반복되는 코드 작성을 생략하는 법

  • 롬복: 데이터 클래스를 생성할 때 반복적으로 사용하는 getter/setter 같은 메서드를 어노테이션으로 대체하는 기능을 제공하는 라이브러리

-롬복의 장점

  • 어노테이션 기반으로 코드를 자동 생성 -> 생산성 향상
  • 반복되는 코드 생략 -> 가독성이 높아짐
  • 롬복을 안다면 간단하게 코드 유추 가능 -> 유지보수에 용이

 

롬복 설치

 

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <optional>true</optional>
</dependency>
  • pom.xml 파일에서 롬복이 위의 코드처럼 의존성이 추가되어있는지 확인

 

  • [File]->[Settings]->[Plugins]->'lombok' 검색하여 설치

 

  • [Building,Execution,Deployment]->[Compiler]->[Annotation Processors] 선택한 후 [Enable annotation processing] 항목을 체크하고 OK  버튼 누르면 롬복 사용을 위한 설정이 끝이 남

 

 

롬복 적용

 

-Product 엔티티 클래스에 롬복 적용

package com.springboot.jpa.data.entity;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Table(name="product")
public class Product {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long number;

    @Column(nullable=false)
    private String name;

    @Column(nullable=false)
    private Integer price;
    
    @Column(nullable=false)
    private Integer stock;

    private LocalDataTime createdAt;

    private LocalDataTime updatedAt;
}

 

  • Product 클래스에 마우스를 대고 우클릭->[Refactor]->[Delombok]->[All Lombok annotations] 순으로 선택하면 롬복의 어노테이션이 실제 코드로 리팩토링 됨

 

 

롬복의 주요 어노테이션

 

-@Getter/@Setter

public String getName(){
    return this.name;
}

public Integer getPrice(){
    return this.price;
}

public Integer getStock(){
    return this.stock;
}

public LocalDateTime getCreatedAt(){
    return this.createdAt;
}

public LocalDateTime getUpdatedAt(){
    return this.updatedAt;
}

public void setNumber(Long number){
    this.number=number;
}

public void setName(Long name){
    this.name=name;
}

public void setPrice(Long Price){
    this.price=price;
}

public void setStock(Long stock){
    this.stock=stock;
}

public void setCreatedAt(LocalDateTime createdAt){
    this.createdAt=createdAt;
}

public void setUpdatedAt(LocalDateTime updatedAt){
    this.updatedAt=updatedAt;
}
  • 코드 라인 수를 줄이는 데에 효과적

 

-생성자 자동 생성 어노테이션

  • NoArgsConstructor : 매개변수가 없는 생성자를 자동 생성
  • AllArgsConstructor : 모든 필드를 매개변수로 갖는 생성자를 자동 생성
  • RequiredArgsConstructor : 필드 중 final이나 @NotNull이 설정된 변수를 매개변수로 갖는 생성자를 자동 생성

 

-@ToString

  • 필드의 값을 문자열로 조합하여 리턴
  • 숨겨야 할 정보가 있다면 @ToString 어노테이션이 제공하는 exclude 속성을 이용해 특정 필드를 자동생성에서 제외할 수 있음
@ToString(exclude="name")
@Table(name="product")
public class Product{
}

=>exclude 속성 활용한 코드

 

 

-@EqualsAndHashCode

  • 객체의 동등성과 동일성을 비교하는 연산 메서드를 생성
  • equals: 두 객체의 내용의 동등성을 비교
  • hashCode: 두 객체가 같은 객체인지 동일성을 비교
  • 부모 클래스에게 상속을 받을 경우 @EqualsAndHashCode에서 제공하는 callSuper 속성을 설정하여 부모 클래스의 필드를 비교 대상에 포함할 수 있음
@Entity
@EqualsAndHashCode(callSuper=true)
@Table(name="product")
public class Product extends BaseEntity{
}

=>callSuper 속성 활용한 코드

 

 

-@Data

  • @Getter/@Setter, @RequiredArgsConstructor, @ToString, @EqualAndHashCode 모두를 포괄하는 어노테이션

 

더보기

Quiz.

 

1. 엔티티 관련 어노테이션으로 해당 클래스가 엔티티임을 명시하기 위한 어노테이션은?

 

답: @Entity

 

2. 리포지토리 메서드의 생성 규칙에서 (  countBy  ) 는 결괏값의 개수를 추출하는 기능을 수행한다. 

 

3.데이터베이스에 접근하기 위한 로직을 관리하기 위한 객체는?

답: DAO

 

4. 앞서 ProductServiceImpl에서 구현했던 저장 메서드에 해당하는 메서드는?

답: saveProduct()

 

5. 데이터 클래스를 생성할 때 반복적으로 사용하는 getter/setter 같은 메서드를 어노테이션으로 대체하는 기능을 제공하는 라이브러리는?

답: 롬복(lombok)

 

6. 롬복에서 생성자 자동 생성 어노테이션 중 매개변수가 없는 생성자를 자동 생성하는 어노테이션은?

답: NoArgsConstructor

 

7. 롬복의 어노테이션 중 @Getter/@Setter, @RequiredArgsConstructor, @ToString, @EqualsAndHashCode를 모두 포괄하는 어노테이션은?

답: @Data

 

8. 다음 코드는 ProductController 클래스의 일부이다. 빈칸에 들어갈 코드는?

public class ProductController {
    private final ProductService productService;
    
    @Autowired
    public ProductController(ProductService productService){
        this.productService=productService;
    }
    @GetMapping()
    public ResponseEntity<ProductResponseDto> getProduct(@RequestParam Long number){
        ProductResponseDto productResponseDto=productService.getProduct(number);
        return ResponseEntity.status(HttpStatus.OK).body(productResponseDto);
    }
    @PostMapping()
    public ResponseEntity<ProductResponseDto> createProduct(@RequestBody ProductDto productDto){
        ProductResponseDto productResponseDto=productService.saveProduct(productDto);
        return ResponseEntity.status(HttpStatus.OK).body(productResponseDto);
    }
}

 

9. 다음은 pom.xml 파일의 롬복 의존성을 주입하는 코드의 일부이다. 빈칸에 들어갈 코드는?

 

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <optional>true</optional>
</dependency>

 

 

 

[출처] 장정우, 『스프링 부트 핵심가이드 스프링 부트를 활용한 애플리케이션 개발 실무』, 위키북스(2022), p104-158.

 

ⓒjiwon

728x90

관련글 더보기