Spring

스프링 데이터 접근 기술

dev-rootable 2023. 4. 28. 21:26

📌 순수 JDBC(Java Database Connectivity)

 

DB 에서 접근하기 위해 선언해야 할 것들이 많고, try~catch 문에서 이들을 연결해야 한다. 또한, DB 에 전달할 데이터를

바인딩하는 작업도 직접 해줘야 하며, 쿼리 또한 직접 작성해야 한다. 마지막으로 연결을 해제하는 작업도 직접 작성해야 한다.

 

@Slf4j
@RequiredArgsConstructor
public class JdbcMemberRepository implements MemberRepository {
	
    private final DataSource dataSource;

    public JdbcMemberRepository(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public Member save(Member member) {
        String sql = "insert into member(member_id, money) values(?, ?)";

        Connection con = null;
        PreparedStatement pstmt = null;

        try {
            con = getConnection();
            pstmt = con.prepareStatement(sql);
            pstmt.setString(1, member.getMemberId());
            pstmt.setInt(2, member.getMoney());
            pstmt.executeUpdate();
            return member;
        } catch (SQLException e) {
            throw new MyDbException(e);
        } finally {
            close(con, pstmt, null);
        }
    }
    
    private void close(Connection con, Statement stmt, ResultSet rs) {
        JdbcUtils.closeResultSet(rs);
        JdbcUtils.closeStatement(stmt);
        DataSourceUtils.releaseConnection(con, dataSource);
    }

    private Connection getConnection() throws SQLException {
        Connection con = DataSourceUtils.getConnection(dataSource);
        log.info("get connection={}, class={}", con, con.getClass());
        return con;
    }

}

 

이러한 불편함을 해소하기 위해 JdbcTemplate 클래스가 등장했다.

 

📌 JdbcTemplate

 

Jdbc 의 반복 코드 대부분을 제거한 라이브러리로 순수 Jdbc 와 동일한 환경 설정만 하면 된다. 하지만 SQL 은 작성해야 한다.

 

public class JdbcTemplateMemberRepository implements MemberRepository{

    private final JdbcTemplate template;

    public JdbcTemplateMemberRepository(DataSource dataSource) {
        jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Override
    public Member save(Member member) {
        String sql = "insert into member(member_id, money) values(?, ?)";
        template.update(sql, member.getMemberId(), member.getMoney());
        return member;
    }

}

 

DataSource 를 직접 Bean 으로 등록하면 JdbcTemplate 에서 이를 DI 로 주입 받기만 하면 이전에 DB 연결을 연결부터 해제까지 직접 작성했던 코드 대부분을
생략할 수 있다.

 

📌 JPA

 

🔎 패러다임 불일치

 

객체 지향 언어에서는 상속, 다형성, 캡슐화 등을 지원한다. 하지만 데이터베이스에서는 이를 지원하지 않는다. 이를 패러다임 불일치라고 한다.

 

슈퍼타입과 서브타입

 

예를 들어 데이터베이스는 상속과 비슷한 기능으로 슈퍼타입과 서브타입을 지원한다. 하지만 패러다임 불일치 상황에서 조회하거나 INSERT 할 때 차이는 크다.

 

Java의 경우, 컬렉션을 사용하면 된다. 아래와 같이 간단하게 조회와 삽입이 가능하다.

 

list.add(album);
list.add(movie);

Album album = list.get(album.getId());

 

반면 SQL은 조회는 조인을 해야 하고, 삽입은 각자 코드를 작성해야 한다. 즉, 부모는 부모 데이터만 꺼내 삽입 쿼리를 작성해야 하고, 자식은 자식 데이터만 꺼내 삽입 쿼리를 작성해야 한다. 이런 문제점이 바로 패러다임 불일치로 인한 것이다.

 

🔎 JPA의 해결책

 

JPA는 자바 컬렉션에 저장하듯이 데이터베이스의 데이터를 다룬다. 아래와 같이 저장과 조회를 수행한다.

 

em.persist(movie); //저장(자동 INSERT 쿼리 수행)

Album album = em.find(Album.class, album.getId()); //조회

 

 

JDBC와 JPA의 조인 코드를 보면 JPA는 쿼리 결과를 곧바로 자바 자료구조로서 사용하는 것을 볼 수 있다. 이처럼 패러다임 불일치를 해결한 JPA가 더 유연하게 대응하는 것을 알 수 있다.

 

//JDBC
...
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;

try {
    con = DriverManager.getConnection(...);
    String sql = "SELECT * FROM ITEM i, ALBUM a WHERE i.id = a.id"
    
    pstmt = con.prepareStatement(sql);
    rs = pstmt.executeQuery();
    
    ...
}
    ....

 

//JPA
String sql = "SELECT A.* FROM ITEM I JOIN ALBUM A ON I.ITEM_ID = A.ITEM_ID";

List<Album> resultList = em.createQuery(sql, Album.class).getResultList(); //조인 결과

 

🔎 정리

 

기존의 반복 코드는 줄이고, SQL 과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환할 수 있다. 왜냐하면 ORM 기술을 사용하여 JPA의 SQL 언어인 JPQL을 통해 관계형 DB와 자바 객체를 매핑해주기 때문에 코드 작성자가 작성한 코드를 중심으로 DB 를 설계할 수 있게 되어 객체 지향성 중심의 설계가 가능해진다.
이처럼 SQL 작성이나 객체 매핑 등 자동화를 많이 도와주기 때문에 개발 생산성에 큰 도움이 된다.

 

/*
* @Entity 를 선언하면 이것이 도메인이 된다.
* @Entity : JPA 가 관리하는 대상
* 도메인 == 테이블
* */
@Entity
public class Member {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    public void setId(Long id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}

 

public class JpaMemberRepository implements MemberRepository{

    private final EntityManager em;

    public JpaMemberRepository(EntityManager em) {
        this.em = em;
    }

    @Override
    public Member save(Member member) {
        em.persist(member);
        return member;
    }

    ...
}

 

Hibernate 에서 자동으로 insert 문을 날림

 

📌 Spring Data JPA

 

구현 클래스 없이 Interface 만으로 DB 에 접근할 수 있고, 반복 개발해온 기본 CRUD 기능도 모두 제공한다. 이처럼 반복 코드가 줄어들기 때문에 개발자는 핵심 비즈니스 로직을 개발하는데 집중할 수 있다.

 

 

JPA 를 편리하게 사용하도록 도와주는 기술

public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository {

    //JPQL select m from Member m where m.name = ?
    @Override
    Optional<Member> findByName(String name);
    
}

 

출처 : 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 by 김영한

 

위의 기본 메서드(쿼리) 들을 제공하고, 커스텀 쿼리가 필요할 경우 위 코드처럼 findByName 으로 네이밍해주면 네이밍한 이름에 맞는 쿼리를 자동으로 실행해준다.

 

Reference:

https://jgrammer.tistory.com/m/entry/JPA-%ED%8C%A8%EB%9F%AC%EB%8B%A4%EC%9E%84-%EB%B6%88%EC%9D%BC%EC%B9%98

 

[JPA] 패러다임 불일치

애플리케이션은 발전하면서 점점 복잡성이 커진다. 지속 가능한 애플리케이션을 개발하는 일은 끊임없이 증가하는 복잡성과 의 싸움이다. 복잡성을 제어하지 못하면 유지보수하기 어려운 애플

jgrammer.tistory.com

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8/dashboard

 

[무료] 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링 웹 애플리케이션 개발 전반을 빠르게 학습할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com