2024.10.23
어째서인가 어제 작성한 JdbcDaoSupport의 앞부분이 오늘 작성한 MyBatis의 내용으로 덮어씌워져 있었다.... ㅠ
주말간에 복구를 해야겠다....
1. JdbcDaoSupport 클래스란?
2. Spring Boot에서 JdbcDaoSupport 클래스 사용하기
3. Object를 사용하는 메소드
4. 최종 결과물
5. 마무리
간단한 상황을 가정해서 JdbcDaoSupport 클래스를 사용하는 상황을 가정해 보자. 회사에서 각 부서별 or 직급별로 검색을 해서 해당 직원의 정보를 출력하고자 한다. 추가로 공백을 입력시 전체 직원의 정보를 출력해야한다. DB는 2가지로 나누어져 있으며, 부서 정보가 담겨있는 buser Table, 직원 정보가 담겨져 있는 jikwon Table로 이루어져 있다.
위의 조건대로 어플리케이션을 만들기 위해서는 입력 데이터, 출력데이터간에 필요한 요소와 진행 과정을 우선적으로 체크해봐야한다. 체크를 해보니 크게 5단계의 진행이 필요했다.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>직급으로 직원 찾기!</h2>
<form method="post" action="controller/findLevel">
직급 입력 : <input type="text" name="level"> <input type="submit"
value="확인">
</form>
<h2>부서로 직원 찾기!</h2>
<form method="post" action="controller/findDepart">
부서 입력 : <input type="text" name="depart"> <input type="submit"
value="확인">
</form>
</body>
</html>
CONTROLLER에서 필요한 요건은 유저가 입력한 데이터, 즉 파라미터를 받아오기, 해당 입력값이 유효한지의 검사다. 파라미터를 받아오는 방법은 어노테이션 (@RequestParam)을 사용했고, 유효성 검사는 부서와 직급이 저장된 List를 통해 유저가 입력한 정보가 해당 리스트안에 있는지 contains 메소드를 사용해 검사했다. (이부분은 MODEL로 넘어가서 검사할 수도 있다.)
package pack.controller;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import pack.model.JikwonDto;
import pack.model.JikwonInter;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
@RequestMapping(value = "controller")
public class JikwonController {
@Autowired
private JikwonInter jikwonInter;
//직급 리스트
private String[] level = {"","사원","대리","부장","과장","이사"};
private List<String> levelList = Arrays.asList(level);
//부서 리스트
private String[] depart = {"","총무부","영업부","전산부","관리부"};
private List<String> departList = Arrays.asList(depart);
//직급으로 사원의 정보 가져오기
@RequestMapping(value = "findLevel", method=RequestMethod.POST)
public String getJikwonL(@RequestParam("level")String level, Model model) {
ArrayList<JikwonDto> list=null;
//유저가 입력한 값이 직급 리스트에 있는지 확인
if(levelList.contains(level)) {
list = (ArrayList<JikwonDto>)jikwonInter.findJikwonL(level);
model.addAttribute("data", list);
model.addAttribute("msg", null);
model.addAttribute("display", level);
}
else {
model.addAttribute("data", list);
model.addAttribute("msg", "잘못 입력하셨습니다");
}
return "show";
}
//부서명으로 사원의 정보 가져오기
@RequestMapping(value = "findDepart", method=RequestMethod.POST)
public String getjikwonD(@RequestParam("depart")String depart, Model model) {
ArrayList<JikwonDto> list=null;
//유저가 입력한 값이 부서 리스트에 있는지 확인
if(departList.contains(depart)) {
list = (ArrayList<JikwonDto>)jikwonInter.findJikwonD(depart);
model.addAttribute("data", list);
model.addAttribute("msg", null);
model.addAttribute("display", depart);
}
else {
model.addAttribute("data", list);
model.addAttribute("msg", "잘못 입력하셨습니다");
}
return "show";
}
}
직원의 경우 WHERE 조건문을 통해서 해당 직급을 조회한후 Return하면 된다. 그러나 부서의 경우 부서명이 jikwon Table에는 없고 buser Tabel에 있기 때문에 JOIN을 사용해야 한다.
또한 Statement를 사용하면 SQL Injection의 위험이 있기 때문에 PreparedStatement 객체를 사용해야한다.
package pack.model;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.stereotype.Repository;
@Repository
public class JikwonImpl extends JdbcDaoSupport implements JikwonInter {
//JdbcDaoSupport에 dataSource 설정하기
@Autowired
public JikwonImpl(DataSource dataSource) {
setDataSource(dataSource);
}
//직급으로 직원 찾기
@Override
public List<JikwonDto> findJikwonL(String level) {
String sql;
List<JikwonDto> list;
if(level.equals("")) {
sql = "SELECT jikwonno, jikwonname, jikwongen, jikwonpay FROM jikwon";
list = (ArrayList<JikwonDto>) getJdbcTemplate().query(sql,(RowMapper) (rs, rowNum) -> {
JikwonDto dto = new JikwonDto();
dto.setJikwonno(rs.getString("jikwonno"));
dto.setJikwonname(rs.getString("jikwonname"));
dto.setJikwongen(rs.getString("jikwongen"));
dto.setJikwonpay(rs.getString("jikwonpay"));
return dto;
});
}
//PreparedStatement로 조건 추가 => Object 사용
else {
sql = "SELECT jikwonno, jikwonname, jikwongen, jikwonpay FROM jikwon WHERE jikwonjik=?";
list = (ArrayList<JikwonDto>) getJdbcTemplate().query(sql, new Object[] {level}, (RowMapper) (rs, rowNum) -> {
JikwonDto dto = new JikwonDto();
dto.setJikwonno(rs.getString("jikwonno"));
dto.setJikwonname(rs.getString("jikwonname"));
dto.setJikwongen(rs.getString("jikwongen"));
dto.setJikwonpay(rs.getString("jikwonpay"));
return dto;
});
}
return list;
}
//부서명으로 직원 찾기
@Override
public List<JikwonDto> findJikwonD(String depart) {
String sql;
List<JikwonDto> list;
if(depart.equals("")) {
//jikwon Table에는 부서명이 없기 때문에 INNER JOIN으로 buser Table의 부서명을 가져오기.
sql = "SELECT j.jikwonno, j.jikwonname, j.jikwongen, j.jikwonpay FROM jikwon AS j INNER JOIN buser AS b ON j.busernum = b.buserno";
list = (ArrayList<JikwonDto>) getJdbcTemplate().query(sql,(RowMapper) (rs, rowNum) -> {
JikwonDto dto = new JikwonDto();
dto.setJikwonno(rs.getString("jikwonno"));
dto.setJikwonname(rs.getString("jikwonname"));
dto.setJikwongen(rs.getString("jikwongen"));
dto.setJikwonpay(rs.getString("jikwonpay"));
return dto;
});
}
//PreparedStatement로 조건 추가 => PrepareStatementSetter 사용
else {
//jikwon Table에는 부서명이 없기 때문에 INNER JOIN으로 buser Table의 부서명을 가져오기.
sql = "SELECT j.jikwonno, j.jikwonname, j.jikwongen, j.jikwonpay FROM jikwon AS j INNER JOIN buser AS b ON j.busernum = b.buserno WHERE b.busername=?";
//PreparedStatement와 RowMapper는 추상메소드를 1개만 가지는 Interface이다. 즉 Lambda 함수를 사용 할 수 있다.
list = (ArrayList<JikwonDto>) getJdbcTemplate().query(sql, pss -> pss.setString(1, depart), (RowMapper) (rs, rowNum) -> {
JikwonDto dto = new JikwonDto();
dto.setJikwonno(rs.getString("jikwonno"));
dto.setJikwonname(rs.getString("jikwonname"));
dto.setJikwongen(rs.getString("jikwongen"));
dto.setJikwonpay(rs.getString("jikwonpay"));
return dto;
});
}
return list;
}
}
그런데 PrepareStatement 객체에 값을 넣기 위해 query(sql,object, RowMapper) 메소드를 사용하는 순간 해당 코드가 Deprecated임을 발견하게 되었다. 이후 다른 메소드를 찾아봤는데 PrepareStatement 에 Object를 통해 값을 입력하는 모든 메소드가 deprecated가 되어있음을 발견하게 되었다.
정보를 찾아보니 해당 메소드가 deprecated가 된 이유는 가독성 향상과 Java에서 Object 타입을 사용하면 컴파일 타임에 타입 체크가 이루어지지 않아 런타임 오류가 발생할 수 있는 위험이 커지기 때문이라고 한다. 그래서 대체할 방법을 찾아봤고 곧바로 'PreparedStatementSetter Interface'를 발견하게 되었다.
해당 인터페이스는 JdbcTemplate class 안에서 PreparedStatement의 값을 입력하는데 사용하는 인터페이스 이다. 그리고 해당 인터페이스는 serValues라는 단 1개의 추상메소드만 갖는다.
해당 인터페이스의 사용법은 아주 간단한데 setValues 메소드를 사용하거나 기존 PreparedStatement와 같이 setString, setInt등의 메소드를 사용하면 된다.
또한 RowMapper인터페이스 역시 1개의 추상메소드만 갖는 인터페이스이다. 즉 둘다 람다 표현식을 사용 가능하다.
