6 데이터베이스 연동
애플리케이션은 데이터(리소스)를 주고받는 것이 주 목적입니다.
엔터프라이즈급 애플리케이션에서 정상적으로 로직이 동작하기 위해서는 데이터베이스가 꼭 필요합니다.
6.1 마리아DB 설치
Download MariaDB Server - MariaDB.org
REST API Release Schedule Reporting Bugs … Continue reading "Download MariaDB Server"
mariadb.org
위의 링크에 들어가서 자신에게 맞는 운영체제 버전의 Maria DB를 다운바아서 설치합니다.
설치 과정 중 위의 단계에서는 설정한 비밀번호를 틀리면 안됩니다.
또한 한글 데이터가 입력됐을 때, 깨지는 것을 방지하기 위해서 UTF8 인코딩을 활성화합니다.
여기에서는 서비스의 이름과 port 번호를 명시해야합니다. 설정한 값들을 까먹지 않아야합니다.
이런한 과정을 모두 마쳤다면 설치를 마칩니다. MariaDB를 이러한 방식으로 설치하면 서드파티 도구로 HeidiSQL이 함께 설치됩니다.
HeidiSQL은 데이터베이스에 접속해서 관리하는 GUI 도구로 앞으로 계속 사용합니다.
이러한 화면을 통해서 사용자가 사용하고자하는 서비스의 IP, port, username, password를 작성하면 접속할 수 있습니다.
위와 같이 정보를 입력하고 열기 버튼을 누르면 우리가 작성한 데이터베이스에 접속할 수 있습니다.
6.2 ORM
ORM은 Object Relational Mapper의 약자로, 객체 관계 매핑을 의미합니다. 자바와 같은 객체지향 언어에서 의미하는 객체와 RDB의 테이블을 자동으로 매핑하는 방법입니다. 자바에서 선언한 클래스는 데이터베이스 테이블과 매핑되어서 코드적으로 하나의 객체처럼 다뤄집니다.
ORM의 장점은 다음과 같습니다.
- ORM을 사용하면서 데이터베이스 쿼리를 객체 지향적으로 조작할 수 있습니다.
- 쿼리문을 작성하는 양이 현저히 줄어 개발 비용을 최소화할 수 있습니다.
- 객체지향적으로 데이터베이스에 접근할 수 있어 코드의 가독성을 높입니다.
- 재사용 및 유지보수가 용이해집니다.
- ORM을 통해 매핑된 객체는 모두 독립적으로 작성되어 있어 재사용이 용이합니다.
- 객체들은 각 클래스로 나뉘어 있어 유지보수가 수월합니다.
- 데이터베이스에 대한 종속성이 줄업듭니다.
- ORM을 통햇 자동 생성된 SQL문은 객체를 기반으로 데이터베이스 테이블을 관리하기 때문에 데이터베이스에 종속적이지 않습니다.
- 데이터베이스를 교체하는 상황에서도 비교적 적은 리스크를 부담합니다.
ORM의 단점을 아래와 같습니다.
- ORM만으로는 온전한 서비스를 구현하기에는 한계가 있습니다.
- 복잡한 서비스의 경우 직접 쿼리를 구현하지 않고 코드로 구현하기 어렵습니다.
- 복잡한 쿼리를 정확한 설계 없이 ORM만으로 구성하게 되면 속도 저하 등의 성능 문제가 발생할 수도 있습니다.
6.3 JPA
JPA는 Java Persitence API의 약자로 자바 진영의 ORM 기술 표준으로 채택된 인터페이스의 모음입니다. ORM이 큰 개념이라면 JPA는 더 구체화된 스펙을 포함합니다. 즉 JPA는 실제로 동작하는 것이 아니고 어떻게 동작해야하는지 메커니즘을 정리한 표준 명세라고 생각하면 됩니다.
JPA 메커니즘을 살펴보면 내부적으로 JDBC를 사용합니다. 개발자가 직접 JDBC를 구현하면 SQL에 의존하게 되는 문제등이 있어 개발의 효율성이 떨어지는 데, JPA는 이 같은 문제점을 보완해서 개발자 대신 적절한 SQL을 생성하고 데이터베이스를 조작해서 객체를 자동 매핑하는 역할을 수행합니다.
6.4 Hibernate
HIbernate는 자바 ORM 프레임워크로 JPA가 정의하는 인터페이스를 구현하고있는 JPA 구현체 중 하나입니다.
6.4.1 Spring Data JPA
Spring Data JPA는 JPA를 편리하게 사용할 수 있도록 지원하는 스프링 하위 프로젝트 중 하나입니다.
Spring Data JPASMS CRUD 처리에 필요한 인터페이스를 지원하며, 하이버네이트의 엔티티 매니저를 직접 다루지 않고 리포지토리를 정의해 사용함으로써 스프링이 적합한 쿼리를 동적으로 생성하는 방식으로 데이터를 조작합니다.
6.5 영속성 컨텍스트
영속성 컨텍스트(Persistence Context)란 애플리케이션과 데이터베이스 사이에서 엔티티와 레코드의 괴리를 해소하는 기능과 객체를 보관하는 기능을 수행합니다. 엔티티 객체가 영속성 컨텍스트에 들어오면 JPA는 엔티티 객체의 매핑 정보를 데이터베이스에 반영하는 작업을 수행합니다. 이처럼 엔티티 객체가 영속성 컨텍스트에 들어와 JPA의 관리 대상이 되는 시점부터 해당 객체를 영속 객체(Persistence Object)라고 합니다.
영속성 컨텍스트는 세션 단위의 생명주기를 가집니다. 데이터베이스에 접근하기 위한 세션이 생성되면 영속성 컨텍스트가 생성되고, 세션이 종료되면, 영속성 컨텍스트도 없어집니다. 엔티티 매니저는 이러한 일련의 과정에서 영속성 컨텍스트에 접근하기 위한 수단으로 사용됩니다.
6.5.1 엔티티 매니저
엔티티 매니저는 이름 그대로 엔티티를 관리하는 객체입니다. 엔티티 매니저는 데이터베이스에 접근해서 CRUD 작업을 수행합니다.
엔티티 매니저는 엔티티 매니저 팩토리가 만듭니다. 엔티티 매니저 팩토리는 데이터베이스에 대응하는 객체로서 스프링 부트에서는 자동 설정 기능이 있기 때문에 application.properties에서 작성한 최소한의 설정만으로도 동작하지만, Hibernate에서는 persistence.xml이라는 설정파일을 구성하고 사용해야합니다.
6.5.2 엔티티의 생명주기
- 비영속(New)
- 영속성 컨텍스트에 추가되지 않은 엔티티 객체의 상태
- 영속(Managed)
- 영속성 컨텍스트에 의해 엔티티 객체가 관리되는 상태
- 준영속(Detached)
- 영속성 컨텍스트와 엔티티 객체가 분리된 상태
- 삭제(Remove)
- 데이터베이스에서 레코드를 삭제하기 위해 영속성 컨텍스트에 삭제 요청을 한 상태
6.6 데이터베이스 연동
6.6.1 프로젝트 생성
Springboot version = 2.5.6
groupId = com.springboot
name = jpa
artifactId = jpa
Dependency = Lombok, Web, JPA, MariaDB Driver
추가적으로 5장에서 실습한 Swagger 설정 파일과 logback-spring.xml을 추가
또한, application.properties 파일에 MariaDB와 연결하기 위한 설정값을 작성합니다.
6.7 엔티티 설계
Spring Data JPA를 사용하면 데이터베이스에 테이블을 생성하기 위해 직접 쿼리를 작성할 필요가 없습니다.
@Entity라는 어노테이션을 붙이면 Spring Container가 실행되면서 루트 패키지 하위에 존재하는 @Entity 어노테이션이 붙은 클래스들을 전부 스캔합니다. 이를 통해서 자동으로 SQL문을 만들고 데이터베이스에 테이블을 생성합니다.
6.7.1 엔티티 관련 기본 어노테이션
@Entity
해당 클래스가 엔티티임을 명시하기 위한 어노테이션이다. 클래스 자체는 테이블과 1대1로 매칭되지만, 해당 클래스의 인스턴스는 데이터베이스에 있는 테이블에서 하나의 레코드를 의미합니다.
@Table
엔티티 클래스는 테이블과 매핑되므로 일반적으로는 Table 어노테이션을 사용하지 않아도 됩니다. 하지만 User와 같은 클래스의 경우, 데이터베이스의 기본 데이터베이스와 중복이 될 여지가 있습니다. 또는 예약어인 경우 데이터베이스 접속은 되지만 데이터를 확인할 수 없는 경우도 있습니다. 이러한 경우 @Table(name=member)와 같은 방식으로 테이블의 이름을 명시적으로 변환할 수 있습니다.
@Id
테이블의 기본값 역할로 사용됩니다. 모든 엔티티에는 @Id가 필요합니다.
@GeneratedValue
일반적으로 @Id와 함께 사용합니다. 이 어노테이션은 해당 필드의 값을 어떤 방식으로 자동으로 생성할지 결정할때 사용됩니다.
- AUTO : 기본 값
- IDENTITY : AUTO_INCREMENT를 사용
- SEQUENCE : 식별자 생성기를 설정하고 이를 통해 값을 자동 주입
- TABLE : 어떤 DBMS를 사용하더라도 동일하게 동작하기 위해 사용
@Column
해당 어노테이션이 붙어있는 필드가 테이블의 컬럼으로 매핑이 됩니다. 별다른 설정이 없는 경우, 어노테이션을 제거해도 됩니다.
6.8 리포지토리 인터페이스 설계
Spring Data JPA는 JpaRepository를 기반으로 더욱 쉽게 데이터베이스를 사용할 수 있는 아키텍쳐를 제공합니다.
6.8.1 리포지토리 인터페이스 생성
@Repository
public interface CompanyRepository extends JpaRepository<CompanyEntity,Long> {
}
위와 같이 JpaRepository를 상속하고 꺽쇠 <> 안에는 해당 리포지토리가 어떤 객체를 테이블로 매핑했는지와 ID 컬럼의 타입을 제공합니다.
6.8.2 리포지토리 메서드의 생성 규칙
일반적으로 CRUD에서 따로 생성해서 사용하는 메서드는 대부분 READ에 해당하는 SELECT 쿼리밖에 없습니다. 에티티를 저장하거나 갱신 또는 삭제할 때는 별도의 규칙이 필요하지 않기때문입니다.
JPA에서는 이름의 규칙에 따라 그에 맞는 쿼리를 생성합니다. 규칙은 첫 단어릴 제외한 이후 단어들의 첫 글자를 대문자로 설정해서 정상적으로 인식합니다.
기능으로는 아래와 같은 키워드가 있습니다.
6.9 DAO 설계
DAO는 Data Access Object의 약자로 데이터베이스에 접근하기 위한 로직을 관리하기 위한 객체입니다.
비즈니스 로직의 동작 과정에서 데이터를 조작하는 기능은 DAO 객체가 수행합니다. 다만 스프링 데이터 JPA에서는 DAO의 개념은 리포지토리가 대체하고 있습니다.
규모가 작은 서비스에서는 DAO를 별도로 설계하지 않고 바로 서비스 레이어에서 데이터베이스에 접근해서 구현하기도 하지만, DAO를 서비스 레이어와 리포지토리의 중간 계층을 구성하는 역할로 사용할 것입니다.
6.9.1 DAO 클래스 생성
public interface TestDAO {
Company insertCompany(Company company);
Company selectCompany(Long number);
Company updateCompany(Company company);
void deleteCompany(Long number);
}
위의 코드와 같이 리턴 값으로 데이터 객체를 전달합니다. 이떄 데이터 객체를 엔티티 객체로 전달할지, DTO 객체로 전달할지에 대해서는 개발자마다 다르다.
이제 구현체를 만든다.
@Component
public class TestDAOImpl implements TestDAO{
@Override
public Company insertCompany(Company company) {
return null;
}
@Override
public Company selectCompany(Long number) {
return null;
}
@Override
public Company updateCompany(Company company) {
return null;
}
@Override
public void deleteCompany(Long number) {
}
}
위와 같은 식으로 TestDAO를 Implement해서 인터페이스 내부에 작성되어 있는 메서드를 Override한다.
현재 위에는 return null; 으로 내부 메서드가 정의되어 있지 않지만, 실제로 코드를 작성할 때는 함수를 구현해야합니다.
6.10 DAO 연동을 위한 컨트롤러와 서비스 설계
앞에서 만든 DAO와 DAOImpl을 통해서 서비스와 컨트롤러를 만들어서 테스트한다.
6.10.1 서비스 클래스 만들기
서비스 레이어에서는 도메인 모델을 활용해 애플리케이션에서 제공하는 핵심 기능을 제공합니다. 여기서 말하는 핵심 기능을 구현하려면 세부 기능을 정의해야합니다. 세부 기능이 모여 핵심 기능을 구현하기 떄문입니다. 이러한 모든 로직을 서비스 레이어에서 포함하기란 쉽지 않은 일입니다. 이 같은 아키텍쳐의 한계를 극복하기 위해 아키텍처를 서비스 로직과 비즈니스 로직으로 분리하기도 합니다. 도메인을 활용한 세부 기능들을 비즈니스 레이어의 로직에서 구현하고 서비스 레이어에서는 기능들을 종합해서 핵심 기능을 전달하도록 구성하는 경우가 대표적입니다.
6. 10.2 컨트롤러 생성
@RestController
@RequestMapping("/product" )
public class ProductController {
private final ProductService productService;
@Autowired
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping()
public ResponseEntity<ProductResponseDto> getProduct (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(Long number) throws Exception {
productService.deleteProduct(number);
return ResponseEntity.status(Httpstatus.OK).body("정상적으로 삭제되었습니다."):
}
}
6.10.3 Swagger AP를 통한 동작 확인
6.11 [한걸음 데] 반복되는 코드의 작성을 생략하는 방법 Lombok
모델을 만들때 자주 사용하는 getter, setter와 같은 메서드를 어노테이션으로 대체할 수 있다.
어노테이션 기반으로 코드를 자동 생성하므로 생산성이 높아지고, 반복되는 코드를 생략할 수 있어 가독성이 좋아집니다.
'책' 카테고리의 다른 글
[Book] 스프링 부트 핵심 가이드 Chap.9 (1) | 2024.07.21 |
---|---|
[Book] 스프링 부트 핵심 가이드 Chap.8 (0) | 2024.07.14 |
[Book] 스프링 부트 핵심 가이드 Chap.5 (1) | 2024.07.02 |
[Book] 스프링 부트 핵심 가이드 Chap.4 (0) | 2024.06.25 |
[Book] 스프링 부트 핵심 가이드 Chap.3 (0) | 2024.06.23 |