이번 시간에는 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화면에 서버를 연결시켜서 작동시켜 보는 것이 이번 방학 학습의 주된 목표이다.
파이팅!