1. 커넥션 연결: 애플리케이션 서버가 db에 주로 TCP/IP를 사용해서 커넥션을 연결
2. SQL 전달: 애플리케이션 서버는 DB가 이해할 수 있는 SQL을 연결된 커넥션을 통해 DB에 전달
3. 결과 응답: DB는 전달된 SQL을 수행하고 그 결과를 응답하고 애플리케이션 서버는 응답 결과를 활용
문제는 각각의 데이터베이스마다 커넥션을 연결하는 방법, SQL을 전달하는 방법, 그리고 결과를 응답 받는 방법이 모두 다름!!
1. db를 다른 종류의 db로 변경하면 애플리케이션 서버에 개발된 데이터베이스 사용 코드도 함께 변경해야됨
2. 개발자가 각각의 데이터베이스마다 커넥션 연결, SQL 전달과 그 결과를 응답 받는 방법을 새로 학습해야됨
그래서 JDBC라는 자바 표준이 등장!!
[JDBC 표준 인터페이스] 자바랑 db를 연결하는~
java.sql.Connection - 연결
java.sql.Statement - SQL을 담은 내용
java.sql.ResultSet - SQL 요청 응답
자바는 이제 표준 인터페이스를 정의해두는데
JDBC 드라이버라이버 : 그런데 인터페이스만 있다고해서 기능이 동작하지는 않는다. 이 JDBC 인터페이스를 각각의 DB 벤더 (회사)에서 자신의 DB에 맞도록 구현해서 라이브러리로 제공
JDBC의 등장으로 다음 2가지 문제가 해결.
1. 데이터베이스를 다른 종류의 데이터베이스로 변경하면 애플리케이션 서버의 데이터베이스 사용 코드도 함께 변경해야하는 문제 애플리케이션 로직은 이제 JDBC 표준 인터페이스에만 의존한다. 따라서 데이터베이스를 다른 종류의 데이터베이스로 변경하고 싶으면 JDBC 구현 라이브러리만 변경하면 된다. 따라서 다른 종류의 데이터베이스로 변경해도 애플리케이션 서버의 사용 코드를 그대로 유지.
2. 개발자가 각각의 데이터베이스마다 커넥션 연결, SQL 전달, 그리고 그 결과를 응답 받는 방법을 새로 학습해야하는 문제 개발자는 JDBC 표준 인터페이스 사용법만 학습하면 된다. 한번 배워두면 수십개의 데이터베이스에 모두 동일하게 적용
[JDBC 접근 기술]
0.jdbc 직접 사용 : 애플리케이션 로직에서 jdbc를 통해서 sql전달하면 됨
1.sql Mapper : sql전달하면 jdbcTemplate나 mybatis를 사용. 근데 SQL을 직접 작성해야하는 단점.
2.ORM : 직접 쿼리를 전달하는게 아니라 회원 객체를 jpa에 전달해.여기서 객체의 매핑정보를 보고 insert쿼리를 직접 만들어내. jpa가 알아서 jdbc를 통해서 db에 전달해줘.
[데이터베이스 연결]
<ConnectionConst> 데이터베이스에 접속하는데 필요한 기본 정보를 편리하게 사용할 수 있도록 상수로
//상수니까 객체 생성안하도록
public abstract class ConnectionConst {
public static final String URL = "jdbc:h2:tcp://localhost/~/test";
public static final String USERNAME = "sa";
public static final String PASSWORD = "";
}
<DBConnectionUtil> JDBC를 사용해서 실제 데이터베이스에 연결
@Slf4j
public class DBConnectionUtil {
public static Connection getConnection(){
try {
Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
log.info("getConnection={}, class={}", connection, connection.getClass()); //커넥션과 클래스 정보 출력
return connection;
} catch (SQLException e) {
throw new IllegalStateException(e);
}
}
}
데이터베이스에 연결하려면 JDBC가 제공하는 DriverManager.getConnection(..) 를 사용
<DBConnectionUtilTest>
@Slf4j
class DBConnectionUtilTest {
@Test
void connection() {
Connection connection = DBConnectionUtil.getConnection(); //가져오고
assertThat(connection).isNotNull();
}
}
커넥션 인터페이스 구현체를 제공
h2커넥션이 자바의 표준 커넥션을 구현. h2 데이터베이스 드라이버는 JDBC Connection 인터페이스를 구현한 org.h2.jdbc.JdbcConnection 구현체를 제공
1. 애플리케이션 로직에서 커넥션이 필요하면 DriverManager.getConnection() 을 호출한다.
2. DriverManager 는 라이브러리에 등록된 드라이버 목록을 자동으로 인식한다. 이 드라이버들에게 순서대로 다음 정보를 넘겨서 커넥션을 획득할 수 있는지 확인한다.
URL: 예) jdbc:h2:tcp://localhost/~/test
이름, 비밀번호 등 접속에 필요한 추가 정보
여기서 각각의 드라이버는 URL 정보를 체크해서 본인이 처리할 수 있는 요청인지 확인한다. 예를 들어서 URL이 jdbc:h2 로 시작하면 이것은 h2 데이터베이스에 접근하기 위한 규칙이다. 따라서 H2 드라이버는 본인이 처리할 수 있으므로 실제 데이터베이스에 연결해서 커넥션을 획득하고 이 커넥션을 클라이언트에 반환한다. 반면에 URL이 jdbc:h2 로 시작했는데 MySQL 드라이버가 먼저 실행되면 이 경우 본인이 처리할 수 없다는 결과를 반환하게 되고, 다음 드라이버에게 순서가 넘어간다.
3. 이렇게 찾은 커넥션 구현체가 클라이언트에 반환된다. 우리는 H2 데이터베이스 드라이버만 라이브러리에 등록했기 때문에 H2 드라이버가 제공하는 H2 커넥션을 제공받는다. 물론 이 H2 커넥션은 JDBC가 제공하는 java.sql.Connection 인터페이스를 구현하고 있다.
[JDBC개발 - 등록/조회/수정/삭제]
<Member>
@Data
public class Member {
private String memberId;
private int money;
public Member(){
}
public Member(String memberId, int money) {
this.memberId = memberId;
this.money = money;
}
<MemberRepositoryV0>jdbc를 통해서 회원객체를 db에 저장
/**
* JDBC - DriverManager 사용
*/
@Slf4j
public class MemberRepositoryV0 {
public Member save(Member member) throws SQLException {
String sql = "insert into member(member_id, money) values(?, ?)";
Connection con = null; //연결
PreparedStatement pstmt = null; //db에 쿼리 날려
try {
con = getConnection(); //드라이버 매니저로 커넥션 획득
pstmt = con.prepareStatement(sql); //sql넘기기, sql예외는 try-catch로
pstmt.setString(1, member.getMemberId()); //파라미너 바인딩
pstmt.setInt(2, member.getMoney());//파라미터 바인딩
pstmt.executeUpdate();//
return member;
} catch (SQLException e) {
log.error("db error", e);
throw e;
} finally {
close(con, pstmt, null); //연결 닫기
}
}
public Member findById(String memberId) throws SQLException {
String sql = "select * from member where member_id = ?";
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = getConnection(); //가져오고
pstmt = con.prepareStatement(sql); //sql넣어주고
pstmt.setString(1, memberId);
rs = pstmt.executeQuery();
if (rs.next()) { //한번은 호출
Member member = new Member();
member.setMemberId(rs.getString("member_id"));
member.setMoney(rs.getInt("money"));
return member;
} else { //data가 없다
throw new NoSuchElementException("member not found memberId=" +
memberId);
}
} catch (SQLException e) {
log.error("db error", e);
throw e;
} finally {
close(con, pstmt, rs);
}
}
public void update(String memberId, int money) throws SQLException {
String sql = "update member set money=? where member_id=?";
Connection con = null;
PreparedStatement pstmt = null;
try {
con = getConnection();
pstmt = con.prepareStatement(sql);
pstmt.setInt(1, money);
pstmt.setString(2, memberId);
int resultSize = pstmt.executeUpdate();
log.info("resultSize={}", resultSize);
} catch (SQLException e) {
log.error("db error", e);
throw e;
} finally {
close(con, pstmt, null);
}
}
public void delete(String memberId) throws SQLException {
String sql = "delete from member where member_id=?";
Connection con = null;
PreparedStatement pstmt = null;
try {
con = getConnection();
pstmt = con.prepareStatement(sql);
pstmt.setString(1, memberId);
pstmt.executeUpdate();
} catch (SQLException e) {
log.error("db error", e);
throw e;
} finally {
close(con, pstmt, null);
}
}
private void close(Connection con, Statement stmt, ResultSet rs) {
if (rs != null) { //닫는데 예외
try {
rs.close();
} catch (SQLException e) {
log.info("error", e);
}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
log.info("error", e);
}
}
if (con != null) {
try {
con.close();
} catch (SQLException e) {
log.info("error", e);
}
}
}
private Connection getConnection() {
return DBConnectionUtil.getConnection(); //커넥션 가져오기
}
}
<MemberRepositoryV0Test> - 회원 등록/조회/수정/삭제
@Slf4j
class MemberRepositoryV0Test {
MemberRepositoryV0 repository = new MemberRepositoryV0();
@Test
void crud() throws Exception{
//save
Member member = new Member("memberV0", 10000);
repository.save(member);
//findById
Member findMember = repository.findById(member.getMemberId());
log.info("findMember={}", findMember);//@Data에서 toString
assertThat(findMember).isEqualTo(member);
//update: money: 10000 -> 20000
repository.update(member.getMemberId(), 20000);
Member updatedMember = repository.findById(member.getMemberId());
assertThat(updatedMember.getMoney()).isEqualTo(20000);
//delete
repository.delete(member.getMemberId());
assertThatThrownBy(() -> repository.findById(member.getMemberId()))
.isInstanceOf(NoSuchElementException.class);
}
}
'Spring 강의 > DB' 카테고리의 다른 글
[스프링 DB 2편] - (1) 데이터 접근 기술 (0) | 2022.08.28 |
---|---|
[스프링 DB 1편] - (3) 트랜잭션 (0) | 2022.04.20 |
[스프링 DB 1편] - (2) 커넥션풀과 데이터소스 (0) | 2022.04.20 |