∞. 기술 면접/4. 데이터베이스

13. 기술면접 - 데이터베이스 - ORM (Object Relation Mapping)

THE HEYDAZE 2021. 10. 18. 11:27
공부목적으로 다른 블로그의 글을 그대로 따라치면서 작성되었습니다. 저작권 문제 시, 비공개 처리하겠습니다

ORM 이란

- 객체와 관계형 데이터베이스의 데이터를 자동으로 매핑(연결) 해주는 것을 말한다

 

영속성 (Persistance)

- 데이터를 생성한 프로그램의 실행이 종료되더라도 사라지지 않는 데이터의 특성

- 영속성을 갖지 않는 데이터는 단지 메모리에서만 존재하기 때문에 프로그램을 종료하면 모두 잃어버리게 된다

- 때문에 파일 시스템, 관계형 데이터베이스 혹은 객체 데이터베이스 등을 활용하여 데이터를 영구하게 저장하여 영속성
  을 부여한다

 

영속성 레이어 (Persistence Layer)

- 프로그램의 아키텍처에서, 데이터에 영속성을 부여해주는 계층
- JDBC 를 이용하여 직접 구현할 수 있지만 Persistance Framework 를 이용한 개발이 많이 이루어진다

 

Persistence Framework (Mybatis, Hibernate, JPA)

- JDBC 프로그래밍의 복잡함이나 번거로움 없이 간단한 작업만으로 데이터베이스와 연동되는 시스템을 빠르게 개발
  할 수 있으며, 안정적인 구동을 보장한다 

- 종류
    - SQL 문장으로 직접 데이터베이스 데이터를 다루는 SQL 맵퍼
       - Myabatis 등
    - 객체를 통해 간접적으로 데이터베이스 데이터를 다루는 객체(Object) 관게 맵퍼(ORM)
       - Hibernate 등
        (기존의 Hibernate 를 통해 만든 것이 JPA (Java Persistence API) 이며, Spring 에서는 Spring Data JPA를 사용한다)

TIP JDBC API 기반으로 만들어진 프레임워크들이다

 

■ 예시 (JDBC vs JPA)

- JDBC example

public String getPersonName(long personId) throws SQLException {
    PreparedStatement st = null;
    try {
        st = connection.prepareStatement (
            "SELECT name FROM people WHERE id = ?");
            st.setLong(1, personId);
            ResultSet rs = st.excuteQuery();
            if (ts.next()) {
                String result = rs.getString("name");
                assert !rs.next();
                return result;
            } else {
                return null;
            }
    } finally {
        if (st != null) {
            st.close();
        }
    }
}

 

- JPA example

public String getPersonName(long personId) {
    Person p = em.find(Person.class, personId);
    return p.getName();
}

 

장점 - 왜 사용하는 지

- 객체 지향적인 코드로 인해 더 직관적이고 비즈니스 로직에 더 집중할 수 있게 도와준다
    - 선언문, 할당, 종료 같은 부수적인 코드가 없거나 급격히 줄어든다
        - JDBC API 로 하기에는 Connection 객체와 Statement 객체를 선언하는 것과 오류 발생 시 try catch 
          문 등 들어가게 되어 코드가 복잡해보인다 (가독성이 떨어지게 된다)
    - 각 종 객체에 대한 코드를 별도로 작성하기 때문에 코드의 가독성을 올려준다
    - SQL 의 절차적이고, 순차적인 접근이 아닌 객체 지향적인 접근으로 인해 생산성이 증가한다

- 재사용 및 유지보수의 편리성이 증가한다
    - ORM 은 독립적으로 작성되어있고, 해당 객체들을 재활용 할 수 있다.
    - 때문에 모델에서 가공된 데이터를 컨트롤러에 의해 뷰와 합쳐지는 형태로 디자인 패턴을 견고하게 다지는데 유리하다
    - 매핑정보가 명확하여, ERD 를 보는 것에 대한 의존도를 낮출 수 있다.

- DBMS 에 대한 종속성이 줄어든다
    - 대부분 ORM 솔루션은 DB에 종속적이지 않다
    - 종속적이지 않다는 것은 구현 방법 뿐만 아니라 많은 솔루션에서 자료형 타입까지 유효하다
    - 프로그래머는 Object 에 집중함으로 극단적으로 DBMS를 교체하는 거대한 작업에도 비교적 적은 리스크와 시간이
      소요된다.
    - 또 한 자바에서 가공할 경우 equals, hash code 의 오버라이드 같은 자바의 기능을 이용할 수 있고, 간결하고 빠른
      가공이 가능하다

 

단점

- 완벽한 ORM 으로만 서비스를 구현하기가 어렵다
    - 사용하기는 편하지만 설계는 매우 신중하게 해야한다
    - 프로젝트의 복잡성이 커질 경우 난이도 또한 올라갈 수 있다
    - 잘못 구현 된 경우에 속도 저하 및 심각할 경우 일관성이 무너지는 문제점이 생길 수 있다.
    - 일부 자주 사용되는 대형 쿼리는 속도를 위해 SP(Stored Procedure, 저장 프로시저)를
      쓰는 별도의 튜닝이 필요한 경우가 있다
    - DBMS의 고유 기능을 이용하기 어렵다

- 프로시저가 많은 시스템에선 ORM 의 객체 지향적인 장점을 활용하기 어렵다
    - 이미 프로시저가 많은 시스템에선 다시 객체로 바꿔야하며, 그 과정에서 생산성 저하나 리스크가 많이 발생 할 수
      있다


아래 글 내용은 공부목적으로 망나니 개발자님의 블로그 글을 따라쳤습니다

 

ORM (Object Relation Mapping) 이란?

ORM이란 객체(Object)와 DB의 테이블을 Mapping 시켜 RDB 테이블을 객체지향적으로 사용하게 해주는 기술이다.
RDB 테이블은 객체지향적 특성(상속, 다형성, 레퍼런스) 등이 없어서 Java 와 같은 객체지향적 언어로 접근하는 것이
쉽지 않다, 추가적으로 자바에서는 Collection 을 이용해 Join 된 데이터를 갖고 있어야 하는 반면에 테이블은 분리되어있다 - JPA 의 양방향 매핑이 안되는 이유이다 - 단방향&단방향  (패러다임 불일치) 이러한 상황에서 ORM 을 사용하면 보다 객체지향적으로 RDB(관계형 데이터베이스)를 사용할 수 있다. Java 에서 사용하는 대표적인 ROM 으로는 JPA 와
그의 구현체 Hibernate 가 있다

JPA(Java Peresistence API) 가 등장하기 이전에는 MyBatis 라는 SQL Mapper 기술이용하였는데. Java 코드와 직접 작성한 SQL 코드를 Mapping 시켜주어야 했다. 반면 JPA 와 같은 ORM 기술은 객체가 DB에 연결되기 때문에, SQL을 직접 작성하지 않고 표준 인터페이스 기반으로 처리한다는 점에서 차이가 있다. Google Trend 조사에 따르면 전 세계적으로 Mybatis 보다 Hibernate 를 많이 사용하는 추세이다. 하지만 우리나라에서는 아직까지도 MyBatis 를 사용하는 곳이 상당히 많다

MyBatis VS Hibernate 비교

- 우리나라의 시장은 대부분 SI 또는 금융이기 때문에 비즈니스가 매우 복잡하다. 또한 안정속과 속도를 중요시하기
  떄문에 직접 작성하는 Mybatis 를 사용하는 것이 나을 수 있다

- JPA 를 사용하면 통계 같은 복잡한 쿼리를 처리하는 것이 어려우므로 우리나라에서는 Hibernate 가 뜨지 못했다

- 하지만 MyBatis 는 쿼리를 직접 작성해야 하기 때문에 Hibernate 에 능숙해진다면 생산성을 상당히 높힐 수 있다

- MyBatis 와 Hibernate 모두 각각의 특징을 갖고 있기 때문에 상황에 맞는 적합한 ORM 을 사용하는 것이 중요하다

 

ORM 활용 예제
JDBC 를 통한 코드 작성 ( ORM 방식 아님 )
ORM을 사용하지 않은 코드는 아래와 같다. 아래의 코드는 사용자 데이트를 추가하는 코드인데, Java Object와 RDB가 Mapping 되지 않기 때문에 각각의 쿼리 파리미터에 사용자 데이터를 직접 Set 해서 DB 에 저장하고 있다.
이러한 코드는 상당히 가독성이 떨어지며 작업이 불편한데, ORM 을 사용하면 보다 편리하게 작업 할 수 있다
public void insertUser(UserVO userVO) {
    String query = "INSERT INTO user(email, name, pw) VALUES(?, ?, ?)";
    
    PreparedStatement pmt = conn.prepareStatement(query);
    pmt.setString(1, userVO.getEmail());
    pmt.setString(2, userVO.getName());
    pmt.setString(3, userVO.getPW());
    
    pmt.execute();
}

 

MyBatis 를 통한 코드 작성 ( SQL Mapper 방식 )
최근에는 다양한 방식이 등장하긴 했지만, MyBatis 는 기본적으로 Database 처리를 위한 DAO 클래스와 Mapper 파일을 사용한다 DAO 에는 DB쪽 연결을 도와주는 SqlSession의 인스턴스를 가지고 있다. sqlSession 을 통해 insert 를 요청하면 해당 namespace 를 갖는 mapper 의 쿼리를 실행시키는데, 이 때 파라미터로 UserVO 객체를 넘겨주고 있다.
MyBatis를 활용하면 이렇게 객체의 값을 DB에 넘겨줄 수 있다.
public class UserDAO {
    
    @Autowried
    private SqlSession sql;
    
    private final String namespace = "mang.blog.user.userMapper";
    
    public int insertUser(UserVO userVO) {
         return sql.insert(namespace + "insertUSer", userVO);
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 
<mapper namespace="mang.blog.user.userMapper">
 
    <insert id="insertUser" parameterType = "userVO">
        INSERT USER(
            EMAIL, USER_NAME, USER_PW, 
        )VALUES(
            #{userEmail}, #{userName}, #{userPw}
        )
    </insert>
 
</mapper>

 

JPA 를 통한 코드 작성 ( ORM 방식 )
JPA 는 기본적으로 Repository 단계에서 구현된다. 아래의 예제에는 Service 클래스가 있는 데, 사용자를 id 값으로
조회하고 갱신을 해주는 데, 별도의 Update 쿼리를 해주고 있지 않다. 이러한 이유는 JPA 라는 ORM 기술에 의해 DB에서 조회한 데이터들이 객체로 연결되어 있고, 객체의 값을 수정하는 것은 DB의 값을 수정하는 것이기 때문이다 (해당 메소드가 종료될 때 Update 쿼리가 JPA의 Dirty Checking 이후에 나가게 된다) 아래의 예제를 통해 JPA는 MyBatis 와 달리 쿼리를 직접 작성지 않음을 확인할 수 있다
@Service
public class UserService {
     private UserRepository userRepository;

    @Transactional
     public UserVO findUserAndUpdateName(Long id) {
         UserVO user = userRepository.findById(id);
         user.setName("변경된 이름");
     }
}