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

2jjong·2024년 5월 20일

자바 애플리케이션을 개발할 때, 대부분의 개발자들은 관계형 데이터베이스를 사용한다. 그러나 SQL을 직접 작성하여 데이터베이스에 데이터를 저장, 수정, 삭제하는 과정은 지루하고 비생산적일 수 있다. 이번 포스트에서는 직접 SQL을 다룰 때 발생하는 문제점과 이를 JPA(Java Persistence API)를 사용하여 어떻게 해결할 수 있는지 알아보자.

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

관계형 데이터베이스는 가장 대중적이고 신뢰할 만한 안전한 데이터 저장소다. 그래서 자바로 개발하는 애플리케이션은 대부분 관계형 데이터베이스를 저장소로 사용한다.

이러한 데이터베이스에 데이터를 관리하려면 SQL을 사용해야 하는데 자바로 개발하는 애플리케이션은 JDBC API를 사용해서 SQL을 데이터베이스로 전달한다.

하지만 직접 SQL을 사용해서 데이터베이스를 관리하게 되면 많은 문제점이 발생한다. 이러한 문제점들과 이를 해결해준 JPA에 대해 알아보자.

끊임없는 반복

SQL을 직접 다룰 때의 문제점을 알아보기 위해 자바와 관계형 데이터베이스를 사용해서 회원 관리 기능을 구현해보았다.

회원을 CRUD하는 기능을 개발하려면 먼저 회원 객체가 필요하고 데이터베이스 관리 목적으로 회원 DAO가 필요하다.

public class Member {
	private String memberId;
	private String name;
}
public calss MemberDAO {
	
	public Member find(String memberId){...}
}

회원 조회 기능

MemberDAO의 find() 메소드를 완성해서 회원을 조회하는 기능을 개발하려면 다음 순서로 진행된다.

  1. 회원 조회용 SQL을 작성한다.

    String sql = "SELECT MEMBER_ID, NAME FROM MEMBER M WHERE MEMBER_ID = ?";
  2. JDBC API를 사용해서 SQL을 실행한다.

    ResultSet rs = stmt.executeQuery(sql);
  3. 조회 결과를 Member 객체로 매핑한다.

    String memberId = rs.getString("MEMBER_ID");
    String name = rs.getString("NAME");
    
    Member member = new Member();
    member.setMemberId(memberId);
    member.setName(name);

회원 등록 기능

MemberDAO에 회원을 등록하는 save() 메소드를 추가하여 만들어보자.

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

MemberDAO의 save() 메소드를 완성해서 회원을 조회하는 기능을 개발하려면 다음 순서로 진행된다.

  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);

직접 SQL을 사용해서 회원을 조회하는 기능과 등록하는 기능을 만들어 보았다. 회원을 수정하고 삭제하는 기능도 이와 비슷하게 SQL을 작성하고 JDBC API를 사용하는 일을 반복해야 할 것이다.

그러나 만약 회원 객체를 데이터베이스가 아닌 자바 컬렉션에 보관한다면 다음 한 줄로 객체를 저장할 수 있다.

list.add(member);

하지만 데이터베이스는 객체 구조와는 다른 데이터 중심의 구조를 가지므로 객체를 데이터베이스에 직접 저장하거나 조회할 수는 없다. 따라서 개발자가 객체지향 애플리케이션과 데이터베이스 중간에서 SQL과 JDBC API를 사용해서 변환 작업을 직접 해주어야 한다.

문제는 객체를 데이터베이스에 CRUD하려면 너무 많은 SQL과 JDBC API를 코드로 작성해야 한다. 이런 과정을 테이블마다 해주어야 하니 개발자는 무수히 많은 SQL을 작성해야 하고 반복해야 한다.

SQL에 의존적인 개발

앞에서 만든 회원 객체를 관리하는 MemberDAO를 완성하고 애플리케이션의 나머지 개발도 완료했다. 그런데 갑자기 회원의 연락처도 함께 저장해달라는 요구사항이 추가되었다. 개발자는 요구사항에 맞춰 코드를 수정해야 한다.

먼저 회원의 연락처를 추가하려고 예제 1.4와 같이 회원 객체에 연락처 필드를 추가하였다.

public class Member {
	private String memberId;
	private String name;
	private String tel;
}

다음은 연락처를 저장할 수 있도록 MemberDAO를 수정해야 한다. 회원 등록 기능만 수정해보자.

  1. 연락처를 저장할 수 있도록 INSET SQL을 수정한다.

    String sql = "INSERT INTO MEMBER(MEMBER_ID, NAME, TEL) VALUES(?, ?, ?)";
  2. 회원 객체의 연락처 값을 꺼내서 등록 SQL에 전달한다.

    pstmt.setString(1, member.getMemberId());
    pstmt.setString(2, member.getName());
    pstmt.setString(3, member.getTel()); //추가

다른 기능들도 이와 같이 SQL과 JDBC API를 수정하고 연락처가 잘 저장되는지 테스트해야 한다.

회원 객체에 연락처를 추가하여 개발을 완료하였다. 그런데 다음 예제와 같이 회원은 어떤 한 팀에 필수로 소속되어야 한다는 요구사항이 추가되었다.

public class Member {
	private String memberId;
	private String name;
	private String tel;
	private Team team;
}

public class Team {
	private String teamName;
}

연관된 팀을 추가한 Member 객체를 조회하면 팀이 제대로 조회되지 않을 것이다. MemberDAO의 find() 메소드의 SQL은 팀을 조회하는 부분이 없기 때문이다. 멤버와 팀을 같이 조회하는 SQL을 사용하는 새로운 메소드를 작성해야 한다.

이와 같이 엔티티의 내용이 변경될 때마다 개발자는 DAO를 열어서 어떤 SQL이 실행되고 어떤 객체들이 함께 조회되는지 일일이 확인해야 한다. 이것은 진정한 의미의 계층 분할이 아니다. 물리적으로 SQL과 JDBC API를 데이터 접근 계층에 숨기는 데 성공했을지는 몰라도 논리적으로는 엔티티와 아주 강한 의존관계를 갖고 있다. 이런 강한 의존관계 때문에 회원을 조회할 때는 물론이고 회원 객체에 필드를 하나 추가할 때도 DAO의 CRUD 코드와 SQL 대부분을 변경해야 하는 문제가 발생한다.

JPA와 문제 해결

JPA를 사용하면 객체를 데이터베이스에 저장하고 관리할 때, 개발자가 직접 SQL을 작성하는 것이 아니라 JPA가 제공하는 API를 사용하면 된다. 그러면 JPA가 개발자 대신에 적절한 SQL을 생성해서 데이터베이스에 전달한다.

JPA가 제공하는 CRUD API를 간단히 알아보자.

저장 기능

jpa.persist(member); // 저장

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

조회 기능

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

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

수정 기능

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

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

연관된 객체 조회

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

JPA는 연관된 객체를 사용하는 시점에 적절한 SELECT SQL을 실행한다. 따라서 JPA를 사용하면 연관된 객체를 마음껏 조회할 수 있다.

정리

JPA를 사용하면 직접 SQL을 작성하는 것보다 훨씬 간단하고 편리하게 객체를 데이터베이스에 저장하고 관리할 수 있다. 반복 작업을 줄이고 유지보수를 용이하게 할 수 있으며, 객체지향적인 코드 작성이 가능하다. JPA는 개발자가 SQL에 의존하지 않고도 데이터베이스와의 상호작용을 효율적으로 처리할 수 있도록 도와준다.

profile
student

0개의 댓글