본문 바로가기

카테고리 없음

스프링 입문(2)

이번 시간에는 DB접근과 그에 따른 기술들에 대해 알아본다.
항상 DB라는 것을 듣기만 하고 처음 접하는 개념이라 큰그림 그리는 느낌으로 강의를 들었다.

 

  • 스프링 DB 접근 기술
    • h2데이터베이스 사용
    • Member리포지토리를 DB로 연결시킨 후 확인
    • 스프링은 기존의 코드를 손대지 않고 애플리케이션 코드만 수정하면 DB에 접근 가능하다.
  • 메모리에서 데이터베이스로
    • memberService가 인터페이스에 의존하고 있다.
    • 이 인터페이스는 메모리와 DB가 있었다.
    • jdbc레포지토리 추가 -> 구현체 변경
    • 개방 폐쇄 원칙에 적합하다.
package hello.hello_spring.repository;

import hello.hello_spring.domain.Member;
import org.springframework.jdbc.datasource.DataSourceUtils;
import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

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(name) values(?)";

        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql,
                    Statement.RETURN_GENERATED_KEYS);
            pstmt.setString(1, member.getName());
            pstmt.executeUpdate();
            rs = pstmt.getGeneratedKeys();
            if (rs.next()) {
                member.setId(rs.getLong(1));
            } else {
                throw new SQLException("id 조회 실패");
            }
            return member;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }
    @Override
    public Optional<Member> findById(Long id) {
        String sql = "select * from member where id = ?";
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setLong(1, id);
            rs = pstmt.executeQuery();
            if(rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                return Optional.of(member);
            } else {
                return Optional.empty();
            }
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }
    @Override
    public List<Member> findAll() {
        String sql = "select * from member";
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            rs = pstmt.executeQuery();
            List<Member> members = new ArrayList<>();
            while(rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                members.add(member);
            }
            return members;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }
    @Override
    public Optional<Member> findByName(String name) {
        String sql = "select * from member where name = ?";
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, name);
            rs = pstmt.executeQuery();
            if(rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                return Optional.of(member);
            }
            return Optional.empty();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }
    private Connection getConnection() {
        return DataSourceUtils.getConnection(dataSource);
    }
    private void close(Connection conn, PreparedStatement pstmt, ResultSet rs)
    {
        try {
            if (rs != null) {
                rs.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (pstmt != null) {
                pstmt.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (conn != null) {
                close(conn);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    private void close(Connection conn) throws SQLException {
        DataSourceUtils.releaseConnection(conn, dataSource);
    }
}
  • 순수 JDBC 개발은 정말 많은 코드가 들어간다.

 

  • 스프링 통합 테스트
    • 실제로는 운영DB가 아닌 테스트용 DB나 로컬 pc에서 실행한다!
    • 트랜젝션 사용하면 테스트 후에 DB에 있는 데이터들을 롤백 == DB에 데이터가 반영되지 않는다.
    • 다음 테스트를 독립적으로 실행할 수 있다.
    • @SpringBootTest-> 스프링을 띄워서 테스트
    • @Transactional -> 테스트 시작할 때 트랜젝션 시작, 테스트 완료 후에 롤백, DB에 데이터 남지 않는다.
  • 순수한 자바로 단위 테스트를 잘 만드는 게 좋은 테스트일 가능성이 있다.

 

  • JDBC에서 JDBC 템플릿으로
    • 반복적인 코드를 JDBC 템플릿으로 사용, but 쿼리는 직접 작성한다.
  • JDBC템플릿에서 JPA(자바 퍼시스턴스 API)로
    • SQL쿼리까지 자동으로 처리할 수 있다. JPQL이라는 것을 작성할 필요가 있다.
package hello.hello_spring.repository;

import hello.hello_spring.domain.Member;
import jakarta.persistence.EntityManager;

import java.util.List;
import java.util.Optional;

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

    @Override
    public Optional<Member> findById(Long id) {
        Member member = em.find(Member.class, id);
        return Optional.ofNullable(member);
    }

    @Override
    public Optional<Member> findByName(String name) {
        List<Member> result = em.createQuery("select m from Member m where m.name = :name", Member.class)
                .setParameter("name", name)
                .getResultList();

        return result.stream().findAny();
    }

    @Override
    public List<Member> findAll() {
        return em.createQuery("select m from Member m", Member.class)
                .getResultList();
    }
}

코드가 많이 간결해졌다.

 

JPA는 인터페이스이고 구현체로 하이버네이트, 이클립스 링크 등 다양함

ORM => 객체 / 관계형 데이터베이스 / 맵핑 

 

  • 스프링부트가 entityManager라는 걸 만들어준다. entity매니저 주입 받아야함
  • 내부적으로 데이터 소스를 가지고 있어서 다 처리해준다.
  • pk(primary key)기반으로 실행되는 게 아니면 jpql이라는 쿼리 작성해줘야 한다.

 

  • JPA에서 스프링 데이터 JPA로
    • 스프링 데이터 JPA는 JPA를 도와주는 인터페이스이다.
    • 기본적으로 인터페이스를 통한 CRUD를 제공해준다. 거의 모든 메서드가 만들어져 있다.
package hello.hello_spring.repository;

import hello.hello_spring.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

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

    @Override
    Optional<Member> findByName(String name);
}

인터페이스 자체만으로도 효과를 볼 수 있고, config파일 수정도 필요없다.

 

 

  • AOP가 필요한 상황: 시간 체크하는데 유지보수가 어렵고 수정사항이 너무 많다.
    • AOP: aspect oriented programming
    • 로그를 띄우거나 보안 측정, 성능 측정 같은 부가 기능들을 모듈화해서 프로그래밍
    • 공통 관심사항 분리를 가능하게 한다. / 따로 빼서 연산하고 핵심 로직들만 남겨둔다.

파라미터 종류가 많은데 이를 통해 다양한 처리가 가능하다.
@Around 사용하여 적용 범위 설정 가능 / 이번 실습은 하위 패키지 전부에 적용

 

 

AOP를 적용시키면 가짜 스프링빈을 세워둔다. (프록시 기술)

그 후 joinPoint.proceed() 이런 거 하면서 내부적으로 작동

 


 

지금까지 스프링에 대해서 넓게 알아보고 또 얕게 공부해봤다.

처음 접해본 프레임워크라 굉장히 생소하지만 하나하나씩 깊게 이해나간다면
전반적인 스프링 개발 흐름을 더 자세히 알고 활용할 수 있을 것 같다.

다음 학습은 스프링 원리부터 다시 차근차근 쌓아갈건데
목표는 내가 만들어 두었던 RN화면에 서버를 연결시켜서 작동시켜 보는 것이 이번 방학 학습의 주된 목표이다.

파이팅!