ORM - JPA

2023. 3. 27. 23:35WEB개발/TIL

반응형

ORM

Object Relational Mapping

  • Object: 객체 지향 언어의 객체
  • Relational: 관계형 데이터베이스의 관계
  • Mapping: 객체 지향 언어의 객체와 관계형 데이터를 서로 변환

 

객체와 관계형 데이터베이스의 테이블을 매핑하여 데이퍼를 객체화하는 기술

Persistence Framework의 일종

SQL문을 직접 작성하지 않음

ex) JPA, Hibernate 등

 

JPA

Java Persistence API

JPA가 개발자 대신 적합한 SQL을 생성하고 DB에 전달하며, 객체를 자동으로 Mapping 해주기 때문에 SQL을 직접 작성할 필요가 없음

ex) Hibernate(JPA를 구현한 대표적 오픈소스)

 

JPA의 장단점

장점

1. 생산성이 뛰어나며 유지보수가 용이함

2. DBMS에 대한 종속성이 줄어듬

단점

1. JPA의 장점을 살려 잘 사용하기 위해서는 학습 비용이 높으며, 복잡한 쿼리를 사용할 때 불리함

2. 잘못 사용할 경우, SQL을 직접 사용하는 것보다 성능이 떨어질 수 있음

 

JPA 설정하기

1. build.gradle 파일의 dependencies에 코드 추가

* JPA를 사용하기 위해 dependency(라이브러리)를 추가히는 작업

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.mysql:mysql-connector-j'

 

2. application.properties 파일에 내용 추가

* MySQL 설정: DB명, DB사용자명, DB비밀번호는 각자의 정보로 변경하여 입력

* ddl-auto 옵션

    1. create: DROP -> CREATE (시작 시점에서 테이블 DROP)

    2. create-drop: CREATE -> DROP (종료 시점에서 테이블 DROP)

    3. validate: Entity와 테이블이 정상 매핑되었는지 확인(다르면 예외 발생)

    4. none: not create(아무 일도 안 일어남)

    5. update: Entity에 따라 DB 변경

 

(참고) ddl-auto 옵션 설정

    * 개발 초기: create or update

    * 테스트 서버: update or validate

    * 운영 서버: validate or none

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/DB명?useUnicode=yes&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Seoul
spring.datasource.username=DB_USER명
spring.datasource.password=DB_PW

#mysql connect
spring.jpa.database=mysql
spring.jap.database-platform=org.hibernate.dialect.MyQOL5InnoDBDialect

#DB function use
# sql문 로그 출력 여부 설정
spring.jpa.show-sql=true
# 정의된대로 table이 새로 만들어짐
spring.jpa.hibernate.ddl-auto=create

# 로그 출력 범위 설정
## info: 정보성 로그 출력
logging.leve.org.hibernate=info

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

 

사용하기(전체 조회 및 필터링 조회)

1. 폴더 구조 설정

Controller: 요청과 응답 처리

클라이언트의 요청을 받았을 때, 그 요청에 대한 실제 업무를 수행하는 Service를 호출

 

Domain: DB의 테이블 역할을 하는 클래스

즉, DB의 테이블과 매핑되며 Entity class라고도 불림

 

Dto: 데이터 저장 담당 클래스

Controller-Service와 같이 계층 간의 데이터 교환을 위해 사용

사용하는 이유? 로직의 효율성을 위해(데이터 전송 직렬화->캡슐화)

이미지1 참고

 

Repository: Entity에 의해 생성된 DB에 접근하기 위한 인터페이스

* JPA에서 Repository 인터페이스 생성 시, 그에 관한 구현 클래스(JpaRepository)를 자동으로 생성함.

 

Service: 각 도메인에서 요구되는 비지니스 로직을 처리하는 곳

 

이미지1

사진 출처: https://velog.io/@dasole1234/dTo-Controller-Service-Repository-%EB%9E%80

 

 

2. 코드 작성

Entity

DB(데이터베이스)에서 쓰일 필드와 여러 Entity 간의 관계를 설정하는 것.

JPA에 정의된 필드를 바탕으로 DB에 테이블을 생성함.

@Entity를 사용하여 해당 클래스가 Entity임을 알림.

// Domain/UserEntity.java

@Entity
@Table(name="user")
@Getter
@Setter
public class UserEntity {
    @Id
    @GeneratedValue
    private int id;

    @Column(length=5, nullable = false)
    private  String name;

    @Column(length=10, nullable = false)
    private String nickname;
}

@Entity: 객체를 DB의 테이블과 매핑하는 것임을 명시

@Table: 매핑된 테이블명 명시

@Id: primary key를 의미

@GeneratedValue: primary key의 생성 전략

@Column: 테이블의 컬럼을 의미

 

Repository

Entity에 의해 생성된 DB에 접근하기 위한 인터페이스

* CRUD 작업을 수행하는 메서드를 포함(SQL 쿼리를 직접 작성하지 않음)

* JpaRepository를 상속받으면 기본적인 DB 접근 메서드를 사용할 수 있음

   JpaRepository: Repository 인터페이스를 구현한 클래스이며 Spring Data JPA가 자동으로 생성함

메서드 종류: findAll(), findBy컬럼명(), save() 등

// Repository/UserRepository.java
@Repository
public interface UserRepository extends JpaRepository<UserEntity, Integer> {
    Optional<UserEntity> findByName(String name);
    // SELECT * FROM user WHERE name = name;
}

JpaRepository<T,ID>

  • T: 엔티티 타입 입력
  • ID: 해당 엔티티의 ID 타입 입력

Optional: Null일 수도 있는 개체를 감싸는 Wrapper 클래스

default) Optional<T> option

* NullPointerException 예외를 처리하기 위해 사용됨

 

참고) Controller & Service

1. Controller

@Controller
public class UserController {

    @Autowired
    UserService userService;

    // 사용자 조회
    @GetMapping("/")
    public String getUsers(Model model) {
        ArrayList<UserDTO> userList = (ArrayList<UserDTO>) userService.getUserList();
        model.addAttribute("list", userList);
        return "user";
    }
    
    // 사용자 검색
    @GetMapping("/users")
    public String searchUsers(@RequestParam String name, Model model) {
        ArrayList<UserDTO> userList = (ArrayList<UserDTO>) userService.searchUsers(name);
        model.addAttribute("list", userList);
        return "search";
    }
}

2. Service

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    // 사용자 전체 조회
    public List<UserDTO> getUserList() {
        List<UserEntity> result = userRepository.findAll(); // DB에서 값 받아옴
        List<UserDTO> users = new ArrayList<>();  // UserArrayList 생성

        for(int i=0 ; i < result.size() ; i++) {  // DB값을 UserArrayList에 매핑
            UserDTO user = new UserDTO();
            user.setId(result.get(i).getId());
            user.setName(result.get(i).getName());
            user.setNickname(result.get(i).getNickname());
            user.setNo(i+1);

            users.add(user);
        }
        return users;  // UserArrayList 리턴
    }
    
    // 사용자 검색
    public Object searchUsers(String name) {
        List<UserEntity> result = userRepository.findByName(name);  // DB에서 값 받아옴
        List<UserDTO> users = new ArrayList<>();  // UserArrayList 생성
        try { // NullPointerException 예외 처리
            for(int i=0 ; i < result.size() ; i++) {  // DB값을 UserArrayList에 매핑
                UserDTO user = new UserDTO();
                user.setId(result.get(i).getId());
                user.setName(result.get(i).getName());
                user.setNickname(result.get(i).getNickname());
                user.setNo(i+1);

                users.add(user);
            }
        } catch (NullPointerException e) {
            System.out.println("error");
        }
        return users;  // UserArrayList 리턴
    }   
}

 

▼ 프론트 코드

더보기

1. 첫 화면

// resources/templates/user.html

<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <h2>사용자 정보</h2>
    <table>
        <tr>
            <th>번호</th>
            <th>ID</th>
            <th>이름</th>
            <th>닉네임</th>
        </tr>
        <tr th:each="user:${list}">
            <td th:text="${user.getNo()}">번호</td>
            <td th:text="${user.getId()}">ID</td>
            <td th:text="${user.getName()}">이름</td>
            <td th:text="${user.getNickname()}">닉네임</td>
        </tr>
    </table>
    <hr>
    <h2>찾아보기</h2>
    <form action="/users" method="get">
        <input type="text" name="name" placeholder="이름 검색">
        <button type="submit">검색</button>
    </form>
    <table id="success"></table>
</body>
</html>

2. 검색 결과 화면

// resource/templates/search.html

<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <table>
        <tr>
            <th>번호</th>
            <th>ID</th>
            <th>이름</th>
            <th>닉네임</th>
        </tr>
        <tr th:each="user:${list}">
            <td th:text="${user.getNo()}">번호</td>
            <td th:text="${user.getId()}">ID</td>
            <td th:text="${user.getName()}">이름</td>
            <td th:text="${user.getNickname()}">닉네임</td>
        </tr>
</table>
</body>
</html>

 

반응형

'WEB개발 > TIL' 카테고리의 다른 글

프로그램, 프로세스, 스레드  (0) 2023.05.08
NestJS 애플리케이션 Docker로 배포하기  (0) 2023.05.01
Lombok  (0) 2023.03.24
Persistence Framework와 Mybatis  (0) 2023.03.24
Spring에서 REST API  (0) 2023.03.22