JPA

[JPA] SQL을 직접 다룰 때 발생하는 문제점

제리 . 2020. 11. 6. 20:29

SQL을 직접 다룰 때 발생하는 문제를 확인하기위해 아래 예시를 참고해보자.

 

회원 테이블은 이미 만들어졋다고 가정하고 회원을 CRUD하는 기능을 개발해보겠다. 먼저 회원 객체를 만든다.

public class Member {

    private String memberId;
    private String name;
    
    ...
    
    
 }

 

다음은 데이터베이스에 연결할 회원용 DAO를 만든다.

public class MemberDAO {

    public Member find(String memberId) { ... }
    
}

 

이제 MemberDAO의 find() 메소드를 만들어 회원을 조회하는 기능을 개발해보자. 일반적인 순서는 아래와 같다.

 

1. 회원 조회용 SQL작성

 

SELECT MEMBER_ID, NAME FROM MEMBER M WHERE MEMBER_ID = ?

 

2. JDBC API를 사용해 SQL실행

ResultSet rs = stmt.excuteQuery(sql);

3. 조회 결과를 Member 객체로 매핑한다.

String memberId = rs.getString("MEMBER_ID");
String name = rs.getString("Name");

Member member = new Member();
member.setMemberId(memberId);
member.setName(name);

회원 조회기능이 완성되었다. 이번에는 회원 등록 기능을 만들어보자.

public class MemberDAO {

    public Member find(String memberId) { ... }
    public void save(Member member) { ... } //추가
}

순서는 아래와 같다.

 

1. 회원 등록용 SQL을 작성

String sql = "INSERT INTO MEMBER(MEMBER_ID, NAME) VALUES(?,?)";

2. 회원 객체의 값을 꺼내 등록 SQL에 전달

pstmt.setString(1, member.getMemberId());
pstmt.setString(2, member.getName());

3. JDBC API를 사용해서 SQL실행

pstmt.executeUpdate(sql);

수정, 삭제 기능도 위와 비슷하게 반복될 것이다. 지루한 반복의 과정이다. 

 

만약, DB가 아닌 자바 컬렉션에 저장한다하면 아래와 같이될 것이다.

list.add(member);

문제는 데이터베이스는 객체 구조와 다른 데이터 중심의 구조로 객체를 직접 저장하거나 조회할 수 없다. 매번 개발자가 데이터베이스와 객체관계를 매핑하는 과정이 필요하다. 데이터베이스 테이블이 많으면 무수히 많은 SQL문을 작성하고 위와같은 과정을 반복해야된다.

 

이번에는 개발도중 스키마가 변경되어 새로운 컬럼이 추가되는 경우를 생각해보자. 수정하는 과정은 아래와 같다.

 

1. 필드 추가

public class Member {

    private String memberId;
    private String name;
    private String tel; //추가
    ...
    
    
 }

2. SQL문 수정

String sql = "INSERT INTO MEMBER(MEMBER_ID, NAME, TEL) VALUES(?,?, ?)"; //SQL문 수정

3. 파라미터 전달

pstmt.setString(3, member.getTel()); //추가

4. 조회 SQL문 수정

SELECT MEMBER_ID, NAME, TEL FROM MEMBER M WHERE MEMBER_ID = ?

 

5. 쿼리 결과 객체 전달

...
String tel = rs.getString("tel");  //추가
Member member = new Member();
member.setTel(tel);  //추가
...

 

새로운 컬럼이 추가됨에따라 등록과 조회 기능이 수정되는 과정이다. 여기서 회원 정보 수정과 삭제등 다른 기능이 위와같이 전부 수정되어야한다. 유지보수에 굉장히 불편하다.

 

반면, 객체를 수정한다고 해보자.

list.add(member); //등록
Member member = list.get(xxx); //조회
member.setTel("xxx"); //수정

객체의 수정은 단순하다.

 

이번에는 연관된 객체에 대해 알아보자. 회원은 어떤 한 팀에 소속되어야한다는 요구사항이 추가되었다.

public class Member {

    private String memberId;
    private String name;
    private String tel;
    private String team; //추가

    ...
    
    
 }

다음 코드를 추가해서 화면에 팀의 이름을 출력했다.

이름 : member.getName();
소속 팀 : member.getTeam().getTeamName()//추가

코드를 실행해보니 member.getTeam()의 값이 항상 null이였다.  데이터베이스에서 실제 확인해본결과 team값은 잘 들어갔다. 문제를 찾다가 MemberDAO에 findWithTeam()이라는 새로운 메소가 추가된 것을 확인했다.

public class MemberDAO {

    public Member find(String memberId) { ... }
    public Member findWithTeam(String memberId) { ... } //추가
}

MemberDAO코드를 확인해보니 회원을 출력할 때 사용되는 find()메소드는 새로운 요구사항이 반영되지않고 회원 정보만 조회하는 SQL이 유지되고있었다. 결국 문제는 find()를 사용하던 기존 코드를 findWithTeam()메소드로 변경해서 해결됐다.

 

Member객체가 Team객체를 사용하는지는 전적으로 SQL이 어떤식인지에 달렸다. 이런 상황에서 가장 큰 문제는 DAO를 사용해서 SQL을 숨겨도 어쩔수 없이 SQL문을 확인해봐야한다.

 

Member나 Team처럼 비즈니스 요구사항을 모델링한 객체를 엔티티라 하는데, 지금처럼 SQL에 모든 것이 의존되는 상황에서는 개발자들이 엔티티를 신뢰하고 사용할 수 없다. DAO에서 직접 SQL을 확인해서 어떤 SQL문이 실행되고 어떤 객체가 함께 조회되는지 확인해야한다. 이렇게 강한 의존관계를 띄고있어 객체가 수정될때 DAO의 CRUD코드와 SQL대부분을 변경해야하는 문제가 발생한다.

 

요약하자면 

1. 진정한 의미의 계층 분할이 어렵다.

2. 엔티티를 신뢰할 수 없다.

3. SQL에 의존적인 개발을 피하기 어렵다.

 

 

JPA에서는 이런 문제들을 해결한다.

 

JPA는 개발자대신 적절한 SQL을 생성해서 데이터베이스에 전달한다.

 

1. 저장기능

jpa.persist(member); //저장

persist()메소드는 객체를 데이터베이스에 저장한다. 이 메소드를 호출하면 JPA가 객체와의 매핑정보를 보고 적절한 INSERT SQL문을 생성해서 데이터베이스에 전달한다. 

 

2. 조회기능

String memberId = "helloId";
Member member = jpa.find(Member.class, memberId); //조회

find()메소드는 객체 하나를 데이터베이스에서 조회한다. JPA는 객체와 매핑정보를 보고 적절한 SELECT문을 만들어 데이터베이스에 전달하고 그 결과를 Member객체로 생성해서 반환한다.

 

3. 수정기능

Member member = jpa.find(Member.class, memberId);
member.setName("이름변경") //수정

JPA는 별도의 수정 메소드를 제공하지않는다. 대신 객체를 조회해 값을 변경하면 트랜잭션을 커밋할때 적절한 UPDATE문이 전달된다.

 

4. 연관된 객체 조회

Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam(); //연관된 객체조회

JPA에서는 연관된 객체를 사용하는 시점에 적절한 SELECT문을 실행한다.