[STUDY13] 01.12수업 내용 총정리
원래 노션에만 정리하는데 진짜진짜진짜진짜모르겠어서 ...하나하나 정리를 하려고합니다.
오늘은 jdbc / dao / mvc패턴 / 예외처리 / preParedStatement / mybatis / jpa
에 대해 배웠어요. 레전드 모르겠단 말이죠...
그래서 지삐띠니와 함께 정리했습니다.
⁉️ ABOUT JDBC
- ABOUT Connection
- ABOUT ResultSet
- ABOUT Statement
⁉️ ABOUT JDBC 정석구조!
⁉️ ABOUT MVC Layer감각
⁉️PreparedStatement & MyBatis
JDBC의 핵심 3요소
- Connection / Statement / ResultSet
입니다.
⁉️ ABOUT Connection
Connection con = DBUtil.getConnection();
으로 선언해요.
Connection은 단순 유틸 객체가 아니라, 상태를 가진 객체입니다.살아있는 섹션이기때문에
절대 공유 객체가 되면 안돼요.
그래서
public static ArrayList<DeptDTO> deptAll() {
Connection con = null; // 지역변수
...
con = DBUtil.getConnection();
}
이런식으로 지역변수로 null값 초기화한 후 connection을 연결해둡니다.
이걸 Unit of Work단위라고 부르네요. 작업을 단위로 나누어 한다. 커넥션 객체를 재사용하지않고 pool을 재사용합니다.
DBUtil.getConnection()으로 저기서 만든 커넥션 pool에서 하나 빌려온다고 생각하시면돼요.
그래서 실제로 코드를 따라가보면,
Connection con = DBUtill.getConnection();으로 pool을요청하면
public static Connection getConnection() throws SQLException {
Connection con = DriverManager.getConnection(
dbinfo.getProperty("jdbc.url"),
dbinfo.getProperty("jdbc.id"),
dbinfo.getProperty("jdbc.pw")
);
return con;
}
메서드가 호출되어요. 메서드의 목적은. 요청당 1 Connection입니다. 저희가 dbeaver에서 하나의 세션당 db연결통로가 각각 세워지듯이... 이것도 하나의 세션은 각각의 connection을 가진다고 보면 됩니다.
새 Connection 객체를 생성하고
호출한 DAO에게 반환합니다
DAO메서드 지역변수로 존재하고
finally에 의해 close됩니다.
url, id, pw같은경우엔 재사용성을 고려하며 .properties파일로 관리하게끔 만들자는거죠.
그래서 잘못된 코드와 정석코드를 비교해보면
public void insertDept(DeptDTO dto) throws SQLException {
Connection con = null;
Statement stmt = null;
try {
con = DBUtil.getConnection();
stmt = con.createStatement();
// SQL 실행
} finally {
DBUtil.close(con, stmt);
}
}
public class DeptDAO {
private Connection con = DBUtil.getConnection(); // ❌
}
로 차이나는 걸 볼 수 이써요.
또 프로퍼티파일을 불러오는 코드같은 경우엔 static block으로 다음과 같이 설정합니다
static {
dbinfo.load(new FileInputStream("dbinfo.properties"));
Class.forName(dbinfo.getProperty("jdbc.driver"));
}
static이 JVM로딩시 딱 한 번 저장되잖아요. 그것처럼
드라이버 로딩과 DB설정 정보같은 경우엔 동적으로 바뀌지 않기 때문에 이렇게 static해줍니다.
구성 요소책임
| DBUtil | DB 설정 로딩, Connection 생성, 자원 반납 |
| DAO | SQL 실행, ResultSet → DTO 변환 |
| DTO | 레이어 간 데이터 전달 |
| Entity | DB 테이블과 1:1 구조 (ORM 관점) |
로 보실 수 있겠습니다...
⁉️ ABOUT Statement
Conncection 위에서 sql을 실행하기 위한 작업 단위 객체 입니다.
Statement stmt = con.createStatement();
와 같이 호출하게 됩니다.
얘도 connection과 매핑되는 상태입니다. 어떤 connection에 묶여있고, 마지막으로 실행한 sQL은 무엇이고, 실행결과에 대한 참조 등을 함께 가지기 대문에.... 공유대상이 되면 안됩니다.
Connection con = DBUtil.getConnection();으로 호출한 커넥션의 상태로 create하겠다는 문법이기에,
DAO메서드 내부의 지역변수로 들어갑니다,
⁉️ ABOUT ResultSet
이건 닉값대로... select 결과를 담은 db결과입니다. 위에서 했던 connection부터 아래로 갈수록 작아지기 때문에
rs.close();
stmt.close();
con.close();
으로 닫아주게됩니다.
참고로 resultSet을 외부로 반환하게되면 close불가이며 메모리 + 커넥션 누수가 일어나기때문에. DTO로 반환한다고 합니다.
⁉️ ABOUT 정석구조!
public ArrayList<DeptDTO> deptAll() throws SQLException {
ArrayList<DeptDTO> list = new ArrayList<>();
Connection con = null;
Statement stmt = null;
ResultSet rs = null;
try {
con = DBUtil.getConnection();
stmt = con.createStatement();
rs = stmt.executeQuery("select * from dept");
while (rs.next()) {
list.add(new DeptDTO(
rs.getInt("deptno"),
rs.getString("dname"),
rs.getString("loc")
));
}
} finally {
DBUtil.close(con, stmt, rs);
}
return list;
}
1. arrayList로 DeptoDTO값을 담은 이유는, 몇개의 리턴이 올 지 모르기 때문이다.
2. con stmt rs를 null로 먼저 초기화하는 이유는, 바로 생성하며 만들게되면 오류 생성시 client에게 멈춤경험을 해줄 수 잇기 때문에 유연한 남탓을 실천하기 위해 ....null로 초기화한다.
3. DBUtil로 따로 빼준 이유는, 공유자원이 아니기 때문에 properties에서 설정한 디비마다 connection을 생성할 수 있게끔
코드를 분리했기 때문이다. 좀 어설프게말하자면, 객체공유가아닌, 코드 중복성을 줄이기 위해서다.
4. rs가 현재 select문의 결과값이다. 이걸 그대로 반환하지 말고 DTO로 따로 빼서 반환하여라.
5. 꼭 finally로 close해주어야한다.
⁉️ ABOUT MVC Layer감각
View는 결과에대한 client에게 보여줄 view...
Model은 데이터 조작
Controller는 컨트롤러...
이 뜻과 어떤 역할을 하는지는 알겠거든요 근데
코드를 쓰는 순간 이 코드가 어떤 레이어 책임인지 판단이 안 서요.
View
“사용자에게 보여주는 것과 입력 받는 것만 책임진다”
Controller
“요청을 해석하고, 어떤 Model을 쓰고 어떤 View를 쓸지 결정한다”
Model (DAO 포함)
“비즈니스 규칙 + DB와의 실제 상호작용을 책임진다”
만을 외우면 된다는데...어렵걸랑요
그래서 지삐띠니가 판단 공식을 짜주었습니다.
1. 이 코드에 SQL이 들어가있는가?
-무조건 dao. M단
2. 이 코드가 Connection / Statement / ResultSet을 다루는가?
-무조건 dao
3. 이 코드가 화면에 출력 문구를 정하는가?
-무조건 view
4. 이 코드가 성공/실패분기를 결정하는가?
-controller
> 이거 melog로 생각해보면, service를 요청했을때 url요청시 실패 controller와 성공 controller두개 나눈걸로 생각하면 될 것 같아요.
⁉️ ABOUT preparedStatement
이건 문법이 아니라 DAO 내부 진화에요.
Statement stmt = con.createStatement();
stmt.executeQuery(
"select * from dept where deptno = " + deptno
);
이런식으로 연결된 stmt에 대해 쿼리를 짜면 문제점이
1. SQL문자열 직접 조합
2. SQL injection취약
3. db가 매번 sql파싱
4. 타입 안정성 없음이에요.
그래서 preparedStatement를 씁니다.
SQL실행 책임은 그대로 두고, 조립만 db에게 넘기는거예요
기존 대체
| 문자열 SQL 조합 | ? placeholder |
| Statement | PreparedStatement |
| 값 직접 삽입 | setXXX() |
PreparedStatement pstmt =
con.prepareStatement(
"select * from dept where deptno = ?"
);
pstmt.setInt(1, deptno);
ResultSet rs = pstmt.executeQuery();
mvc구조 변화없구
여전히 dao이며
여전히 unit of work
여전히 connection은 지역변수에요.
⁉️ ABOUT myBatis
jdbc dao는
while (rs.next()) {
list.add(new DeptDTO(
rs.getInt("deptno"),
rs.getString("dname"),
rs.getString("loc")
));
}
와같이 result에서 dto로 수동 매핑합니다.이게너무 귀찮고 오류가 생길수도 있다는거죠.
그래서 mybatis의 내부처리에 맡겨버립니다,.
이해가안되니 예시로 다시 생각해볼게요.
1. Statement vs PreparedStatement
중요 포인트는, db에서 sql을 언제 어떻게 해석하느냐입니다.
stmt.executeQuery(
"select * from dept where deptno = 10"
);
기본 statement사용시 db에서는
① SQL 문자열 수신
② SQL 문법 파싱
③ 테이블/컬럼 존재 검증
④ 실행 계획 수립 (Optimizer)
⑤ 컴파일
⑥ 실행
⑦ 결과 반환
그리고 10을 20으로고쳐도 다시 1~7을 반복해요. 이럴때 아까말한 문제점들이 생길 수 있다는점이구요.
그래서 prepared를쓰
PreparedStatement pstmt =
con.prepareStatement(
"select * from dept where deptno = ?"
);
① SQL 수신
② 문법 파싱
③ 실행 계획 수립
④ 컴파일
⑤ 캐시에 저장
에서 아직 실행을 안 했어요!
pstmt.setInt(1, 10);
pstmt.executeQuery();
이미 컴파일된 실행계획에 값만 바인딩해서 실행합니다.
그래서 복사본 리턴을 제어한다라고 볼 수 있습니다.는 또 아니래요;;;
아 여쭤봣는데 이게 지피티는 값 전체를 리턴해서 제어한다는 관점에서 봐서 틀렸다한거지
캐시를 사용한다를 복제본으로 생각하면 틀리진 않다고 하네요