JDBC는 Java DataBase Connectivity 의 약자로서 Java에서 데이터 베이스에 접속할 수 있도록 해주는 Java API 인데요. Java 언어를 사용하여 데이터베이스에 접근할 때 일반적으로 사용하는 API입니다. JDBC를 사용하게 되면 default로 구현해야 하는 코드가 존재하고, 이를 반복적으로 사용하는 것이 불가피하였는데요. Spring에서는 이러한 반복적인 코드를 사용하지 않고 코드를 작성할 수 있는 방법을 JdbcTemplate을 통해 제공해주었습니다. 어떻게 반복을 하지 않게 제공을 했는지 궁금하지 않나요?ㅋ😤
한번 알아보는 시간을 가져봅시다‼️
Java DataBase Connectivity : Java에서 데이터 베이스에 접속할 수 있도록 해주는 Java API
먼저 JDBC를 통해 데이터베이스(DB)에 접근하는 과정을 알 필요가 있는데요.
다음과 같습니다.
코드로 보면 다음과 같습니다.
(1) Driver loading & DB Connection 예시 코드
public class DBConnection { // DB 연결 클래스
public static Connection getConnection() throws ClassNotFoundException {
**// 드라이버 로딩**
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = null;
try {
//자신의 DB 정보에 맞는 user와 pw 설정
String user = "user";
String pw = "pw";
String url = "jdbc:mysql://localhost:3306/project?serverTimezone=Asia/Seoul&characterEncoding=utf8";
**// DB 연결**
conn = DriverManager.getConnection(url, user, pw);
} catch (SQLException sqle) {
System.out.println("DB error : " + sqle.toString());
} catch (Exception e) {
System.out.println("Unkonwn error");
}
return conn;
}
}
(2) Execute SQL and Close Resource object 예시 코드
//Login 관련하여 User 테이블에 접근하는 클래스
public class LoginDAO {
@SuppressWarnings("unchecked")
//등록 유저 데이터를 받아 id와 pw 일치여부를 확인하고 성공여부를 반환하는 Method
public String loginUser(JSONObject registUser) throws ClassNotFoundException {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
String rst = "NOTFOUND";
String id;
String pw;
conn = DBConnection.getConnection();
//**select SQL Query** ( id,pw를 조건문으로 하여 검색 함 )
String sql = "select user_id,user_pw from user " + "where user_id = ? AND user_pw = ? ";
try {
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, (registUser.get("user_id")).toString());
pstmt.setString(2, (registUser.get("user_pw")).toString());
rs = pstmt.executeQuery();
// 일치하는 아이디 및 패스워드를 확인함 . 없으면 "fail"
while (rs.next()) {
id = rs.getString(1);
pw = rs.getString(2);
if (id.equals(registUser.get("user_id").toString()) && pw.equals(registUser.get("user_pw").toString())) {
rst = "success";
}
}
**// 자원 해제**
rs.close();
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
rst = "fail";
}
return rst;
}
}
위와 같은 수순으로 JDBC는 데이터베이스에 접근하고, 특정 데이터에 원하는 행위를 취할 수 있는데요. 이렇게 할게 많은 DB 연결 코드를 보고 스프링 개발자는 이런 생각을 했을 것 같아요.
"아니 데이터베이스에 접근하는데 sql문만 개발자가 작성하면 되지, 뭐 드라이버 로딩하고, DB를 연결하고, 자원을 해제하는 것까지 알아야돼? 수행해야돼?"
이래서 위 3가지 기능은 Spring framework가 담당하게 된 것이고, Spring framework을 사용하는 우리 개발자들은 편하게 sql문만 작성하면 되게 된것입니다. 무엇을 통해서요⁉️ 바로 JdbcTemplate을 통해서 입니다!
일단 위에서 언급된 것처럼 JdbcTemplate을 사용하면 우리는 sql문만 작성하면 되는데요. 그 외에 반드시 해야될 것이 DataSource에 대한 정보를 제공해주는 것입니다. 왜냐구요? 아무리 Spring Framework가 대부분의 기능을 대신 해준다고 하더라도 우리가 어떤 DB를 사용하고 해당 DB에 어떤 로그인 정보를 통해 접근할 수 있는지 모르거든요~😅
그럼 DataSource는 무엇일까요? DataSource는 다음과 같습니다!
DataSource를 설정하는 방법은 application.properties 과 같은 설정파일을 통해서 할 수 있습니다.
spring.datasource.url=jdbc:mysql://localhost/test
spring.datasource.username=user
spring.datasource.password=pw
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
dataSource를 설정만 하면 우리는 JdbcTemplate 생성자에 넘겨주고 SQL문만 작성하면 됩니다. 즉, 위에서 처럼 개발자가 Plain JDBC API를 작성할 필요 없이 SQL문만 넘겨주면 된다는 말입니다. 다음과 같이 말이죠.
JdbcTemplate 예시 코드
@Repository
public class ProfileJdbcRepository implements ProfileRepository {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
public void setDataSource(DataSource dataSource){
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public String findNameByIdx(int userIdx) {
String query = "select name from user where userIdx =?";
String name = this.jdbcTemplate.queryForObject(query,new Object[]{userIdx},String.class);
return name;
}
}
그럼 우리는 JdbcTemplate 안의 코드가 Plain JDBC API를 내부적으로 어떻게 사용하길래 "sql문만 넘겨주면 알아서 척척해주는 메소드를 제공해줄 수 있는 것인가?" 하며 궁금해할 수 있습니다( 말이 좀 웃기네요. 궁금해주시면 감사할 것 같네요😅)
일단 결론부터 말하면 JdbcTemplate은 템플릿 콜백 패턴이라는 전략 패턴의 변형패턴을 사용합니다.
템플릿 : 어떤 목적을 두고 미리 만들어둔 모양의 틀을 의미합니다. 템플릿 메소드 패턴은 고정된 틀의 로직(반복되는 부분)을 갖는 템플릿 메소드를 슈퍼 클래스에 두고, 바뀌는 부분을 서브 클래스의 메소드에 두는 구조로 이루어집니다.
콜백 : 실행되는 것을 목적으로 다른 오브젝트의 메소드에 전달되는 오브젝트를 의미합니다. 자바에서는 Function Object를 통해서 콜백을 구현합니다.
그럼 우리는 위에서 템플릿이 무엇이고 콜백이 무엇이 될지 찾아봐야 할 것입니다.
Plain Jdbc 예시코드
public void deleteAll() throws Exception{
Connection conn = null;
PreparedStatement ps = null;
try{
conn = dataSource.getConnection();
ps = conn.prepareStatement("delete from Exercise");
ps.executeUpdate();
} catch(SQLException e){
throw e;
} finally{
if(ps!=null){
try{ps.close();}catch(Exception e){}
}
if(conn!=null){
try{conn.close();}catch(Exception e){}
}
}
}
위 예시는 delete를 하는 코드이지만, 특정 부분만 바꾸면 나머지 코드들은 변하지 않는 부분입니다. 그 특정 부분은 delete을 수행하는 sql 문을 전달하는 코드인
ps = conn.prepareStatement("delete from Exercise");
가 될 것입니다.
그럼 delete를 하든, insert를 하든, update를 하든 위 코드의 SQL문을 제외하면 모두 동일할 것입니다.
그럼 우리는 SQL문을 제외한 부분을 따로 빼서 템플릿으로 만들어 볼 수 있습니다. 다음과 같이 말이죠!
템플릿(Template) 생성 코드
public class JdbcContext {
DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void executeUpdate(String query) throws SQLException{
Connection conn = null;
PreparedStatement ps = null;
try{
conn = dataSource.getConnection();
ps = conn.preparedStatment(query); // 변경되는 부분
ps.executeUpdate();
} catch(SQLException e){
throw e;
} finally{
if(ps!=null){
try{ps.close();}catch(Exception e){}
}
if(conn!=null){
try{conn.close();}catch(Exception e){}
}
}
}
}
그렇다면 우리는 위 코드의 executeUpdate(String query)
를 통해 sql문을 전달해줌으로써 데이터 조작 작업(insert, delete, update)를 수행할 수 있습니다.
우리는 위의 템플릿 생성 코드에서 한가지 문제점을 발견할 수 있습니다. 위와 같은 경우는 따로 sql문에 인자를 전달해줄 수 없기 때문에 인자를 전달받는 메서드를 하나 더 구현해야된다는 점인데요!
그렇다면 우리는 이렇게 생각할 수 있습니다. 그럼 query를 String으로 넘겨주지말고 PrepareStatement를 생성하는 메서드를 넘겨주면 어떨까?
여기서 이 메서드는 자바에서 Functional Object 즉, 콜백을 통해 구현할 수 있습니다.
PrepareStatement 생성 Functional Interface 코드
public interface PSCreation {
public PreparedStatement makePreparedStatement(Connection conn) throws SQLException;
}
그렇다면 이것을 통해 우리는 Delete를 수행하는 PrepareStatement를 생성하는 구현체를 만들 수 있겟죠?
Delete PrepareStatement 구현체 코드
public class DeleteAllExercise implements PSCreation{
@Override
public PreparedStatement makePreparedStatement(Connection conn) throws SQLException {
PreparedStatement ps = conn.prepareStatement("delete from Exercise");
return ps;
}
}
그러면 맨 위의 Plain Jdbc API 코드를 다음과 같이 우리는 수정할 수 있습니다.
템플릿 콜백 패턴 적용 코드
public void deleteAll() throws Exception{
this.jdbcContext.executeUpdate(new DeleteAllExercise());
}
새로운 구현체 객체를 생성하지 않고 이전 시간에 배운 람다를 활용하면 다음과 같이 변경될 수 있겠죠!
람다를 사용한 템플릿 콜백 패턴 적용 코드
public void deleteAll() throws Exception{
this.jdbcContext.executeUpdate((conn)-> conn.prepareStatement("delete from Exercise");
}
실제로 JdbcTemplate 클래스를 들여다보면 수많은 메서드들이 콜백 을 전달받는 형태로 오버로딩 되어있고, 이 메서드들을 따라가보면 템플릿 메서드에 도달하는 형식으로 구현되어있습니다.
앞으로 JdbcTemplate을 다룰 때 이러한 동작원리를 생각하면서 사용을 하면 더욱 흥미롭게 또 감사하게😅 사용할 수 있을 것 같습니다‼️
3장 템플릿 III - 스프링의 JdbcTemplate