-
자바DB / CallableStatement프로그래밍/JAVA 자바 2021. 9. 2.반응형
CallableStatement
간단히 말해서 오라클에서 시퀀스를 만들고 그걸 자바에서 불러온다고 생각하면 된다.
시퀀스는 쿼리문을 편하게 미리 작성해 놓는거니까 자바에서도 단순히 불러오기만 하면 된다.
예제 분석을 위해 오라클에 미리 테이블과 시퀀스를 작성해두자.
이대로 score 테이블 먼저 작성
이후에 프로시저 작성
-- 프로시저 만들기 CREATE OR REPLACE PROCEDURE insertScore( pHak IN score.hak%TYPE, pName IN score.name%TYPE, pBirth IN score.birth%TYPE, pKor IN score.kor%TYPE, pEng IN score.eng%TYPE, pMat IN score.mat%TYPE ) IS BEGIN INSERT INTO score(hak, name, birth, kor, eng, mat) VALUES (phak, pName, pBirth, pKor, pEng, pMat); COMMIT; END; / SELECT * FROM user_procedures; EXEC insertScore('3333', '후후후후', '2000-10-10', 80, 90, 70); EXEC insertScore('5555', '후후후후', '2000-10-10', 80, 90, 70); SELECT * FROM score;
p 붙은 애들은 score의 .누구누구~ 의 타입을 그대로 따라간다는 의미에서 %TYPE이 붙었음
BEGIN 부분이 작동하는 영역인데 일반적으로 쓰는 쿼리문을 넣었고,
벨류값에는 프로시저에서 사용하는 변수p~xx 대입
COMMIT는 잊지말고 꼭 해야한다.
그리고 프로시저를 만든 이후에 프로시저가 잘 만들어졌는지, 실제로 값이 잘 들어가는지 확인 해본다.
업데이트 관련 프로시저 만들기
-- 프로시저 업데이트 만들기 CREATE OR REPLACE PROCEDURE updateScore( pName IN score.name%TYPE, pBirth IN score.birth%TYPE, pKor IN score.kor%TYPE, pEng IN score.eng%TYPE, pMat IN score.mat%TYPE, pHak IN score.hak%TYPE ) IS BEGIN UPDATE score SET name=pName, birth=pBirth, kor=pKor, eng=pEng, mat=pMat WHERE hak=pHak; IF SQL%NOTFOUND THEN RAISE_APPLICATION_ERROR(-20100, '존재하지 않는 자료임'); END IF; COMMIT; END; / EXEC updateScore('마마마', TO_DATE('2000-10-10', 'YYYY-MM-DD'),100,100,100,'5555'); SELECT * FROM score;
여기서 IF는 만약 위에서 p~xx에서 같은 TYPE을 못찾으면 에러를 발생시키는 RAISE_APPLICATION_ERROR을 주었다.
삭제 프로시져 만들기
-- 삭제 프로시저 CREATE OR REPLACE PROCEDURE deleteScore( pHak IN score.hak%TYPE ) IS BEGIN DELETE FROM score WHERE hak = pHak; IF SQL%NOTFOUND THEN RAISE_APPLICATION_ERROR(-20100, '존재하지 않는 자료임'); END IF; COMMIT; END; / EXEC deleteScore('5555'); SELECT * FROM score;
학번 검색 프로시져
--학번 검색 프로시져 CREATE OR REPLACE PROCEDURE readScore( pResult OUT SYS_REFCURSOR, pHak IN VARCHAR2 ) IS BEGIN OPEN pResult FOR SELECT hak, name, TO_CHAR(birth, 'YYYY-MM-DD') birth, kor, eng, mat, (kor+eng+mat) tot, (kor+eng+mat)/3 ave FROM score WHERE hak = pHak; END; /
학번 검색같은 경우는 자바에서 매개변수 학번을 입력 받아서 그걸 가지고 오라클에 넘겨 줘야하는데
그때 쓰는게 IN - OUT이다.
그래서 pResult는 결과값을 담는 변수고 자바로 넘길거니까 OUT(결과 값을 담는 변수에 커서를 넘김)
pHak은 변수 받아서 오라클에서 쿼리문에 써야하니까 IN 이라고 생각하면 편하다.
그리고 OPEN 부분에는 어떤 결과값을 담을건지 쿼리문을 쓰면 된다.
WHERE 조건 부분에서 pHak을 쓴 모습도 확인이 된다.
전체 리스트 프로시져
-- 전체 리스트 프로시저 CREATE OR REPLACE PROCEDURE listScore( pResult OUT SYS_REFCURSOR ) IS BEGIN OPEN pResult FOR SELECT hak, name, birth, kor, eng, mat, (kor+eng+mat) tot, (kor+eng+mat)/3 ave, RANK() OVER( ORDER BY (kor+eng+mat) DESC) RANK FROM score; END; /
이것도 마찬가지로 자바로 결과값을 보내야 하니까 OUT이 존재한다.
이름 검색 프로시져
-- 이름 검색 프로시저 CREATE OR REPLACE PROCEDURE searchNameScore( pResult OUT SYS_REFCURSOR, pName IN VARCHAR2 ) IS BEGIN OPEN pResult FOR SELECT hak, name, birth, kor, eng, mat, (kor+eng+mat) tot, (kor+eng+mat)/3 ave FROM score WHERE INSTR(name, pName) >= 1; END; /
학번 검색 프로시져와 구성은 동일하며 단지 매개변수로 이름을 받는다.
각 과목당 평균 구하는 프로시져
-- 각 과목 평균 프로시저 CREATE OR REPLACE PROCEDURE averageScore( pKor OUT NUMBER, pEng OUT NUMBER, pMat OUT NUMBER ) IS BEGIN SELECT NVL(AVG(kor),0), NVL(AVG(eng),0), NVL(AVG(mat),0) INTO pKor, pEng, pMat FROM score; END; /
오라클 작업은 여기서 마무리하고 자바로 넘어간다.
package score3; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLDataException; import java.sql.SQLException; import java.sql.SQLIntegrityConstraintViolationException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import db.util.DBConn; import oracle.jdbc.OracleTypes; /* -- 쿼리실행 - Statement : 보안에 취약 - PreparedStatement : 전 처리된 Statement로 주어진 SQL문을 미리 전처리과정을 해둔 상태로 보관하기때문에 반복작업에 유리 : 보안에 유리 - CallableStatement : 프로시저 실행 */ public class ScoreDAOImpl implements ScoreDAO { private Connection conn=DBConn.getConnection(); @Override public int insertScore(ScoreDTO dto) throws SQLException { int result=0; CallableStatement cstmt = null; String sql; try { // 자바에서 프로시저를 호출할때는 CALL로 실행 한다. sql="{ CALL insertScore(?, ?, ?, ?, ?, ?) }"; cstmt = conn.prepareCall(sql); // setter를 이용하여 ?에 값을 할당한다. cstmt.setString(1, dto.getHak()); cstmt.setString(2, dto.getName()); cstmt.setString(3, dto.getBirth()); cstmt.setInt(4, dto.getKor()); cstmt.setInt(5, dto.getEng()); cstmt.setInt(6, dto.getMat()); // 쿼리를 실행한다. 프로시저의 반환 값은 프로시저 실행 여부이다. cstmt.executeUpdate(); result = 1; } catch (SQLIntegrityConstraintViolationException e) { // 기본키 중복, NOT NULL등의 제약조건 위반에 의한 예외 발생-무결성 제약 조건 위반 if(e.getErrorCode()==1) { System.out.println("학번 중복입니다."); } else if(e.getErrorCode()==1400){ // NOT NULL 위반 System.out.println("필수 입력사항을 입력하지 않았습니다."); } else { System.out.println(e.toString()); } throw e; } catch (SQLDataException e) { // 날짜등의 형식 잘못으로 인한 예외 if(e.getErrorCode()==1840 || e.getErrorCode()==1861) { System.out.println("날짜 입력 형식 오류입니다."); } else { System.out.println(e.toString()); } throw e; } catch (SQLException e) { e.printStackTrace(); throw e; } finally { if(cstmt!=null) { try { cstmt.close (); } catch (Exception e2) { } } } return result; } @Override public int updateScore(ScoreDTO dto) throws SQLException { int result=0; CallableStatement cstmt=null; String sql; try { sql="{ CALL updateScore(?,?,?,?,?,?) }"; cstmt = conn.prepareCall(sql); cstmt.setString(1, dto.getName()); cstmt.setString(2, dto.getBirth()); cstmt.setInt(3, dto.getKor()); cstmt.setInt(4, dto.getEng()); cstmt.setInt(5, dto.getMat()); cstmt.setString(6, dto.getHak()); cstmt.executeUpdate(); result = 1; } catch (SQLIntegrityConstraintViolationException e) { if(e.getErrorCode()==1400){ // NOT NULL 위반 System.out.println("필수 입력사항을 입력하지 않았습니다."); } else { System.out.println(e.toString()); } throw e; } catch (SQLDataException e) { // 날짜등의 형식 잘못으로 인한 예외 if(e.getErrorCode() == 1840 || e.getErrorCode() == 1861) { System.out.println("날짜 입력 형식 오류입니다."); } else { System.out.println(e.toString()); } throw e; } catch (SQLException e) { if(e.getErrorCode()==20100) { System.out.println("등록된 자료가 아닙니다."); } else { e.printStackTrace(); } throw e; } finally { if(cstmt!=null) { try { cstmt.close(); } catch (Exception e2) { } } } return result; } @Override public int deleteScore(String hak) throws SQLException { int result=0; CallableStatement cstmt = null; String sql; try { sql="{ CALL deleteScore(?) }"; cstmt=conn.prepareCall(sql); cstmt.setString(1, hak); cstmt.executeUpdate(); result = 1; } catch (SQLException e) { if(e.getErrorCode()==20100) { System.out.println("등록된 자료가 아닙니다."); } else { e.printStackTrace(); } throw e; } finally { if(cstmt!=null) { try { cstmt.close(); } catch (Exception e2) { } } } return result; } @Override public ScoreDTO readScore(String hak) { ScoreDTO dto=null; CallableStatement cstmt = null; ResultSet rs=null; String sql; try { sql = "{ CALL readScore(?, ?) } "; cstmt = conn.prepareCall(sql); // OUT 파라미터는 파라미터의 타입을 설정한다. cstmt.registerOutParameter(1, OracleTypes.CURSOR); // 오라클의 SYS_REFCURSOR // IN 파라미터 cstmt.setString(2, hak); // 프로시저 실행. 모든 프로시저는 executeUpdate()로 실행 cstmt.executeUpdate(); // OUT 파라미터 값 넘겨 받기 rs = (ResultSet) cstmt.getObject(1); // SYS_REFCURSOR는 ResultSet로 반환 받는다. // 기본키 조건에 만족하는 레코드는 1개 또는 0개 if(rs.next()) { dto=new ScoreDTO(); dto.setHak(rs.getString("hak")); dto.setName(rs.getString("name")); dto.setBirth(rs.getString("birth")); dto.setKor(rs.getInt("kor")); dto.setEng(rs.getInt("eng")); dto.setMat(rs.getInt("mat")); dto.setTot(rs.getInt("tot")); dto.setAve(rs.getInt("ave")); } } catch (SQLException e) { e.printStackTrace(); } finally { if(rs!=null) { try { rs.close(); } catch (Exception e2) { } } if(cstmt!=null) { try { cstmt.close(); } catch (Exception e2) { } } } return dto; } @Override public List<ScoreDTO> listScore() { List<ScoreDTO> list=new ArrayList<ScoreDTO>(); CallableStatement cstmt = null; ResultSet rs=null; String sql; try { sql = "{ CALL listScore( ? ) }"; cstmt = conn.prepareCall(sql); cstmt.registerOutParameter(1, OracleTypes.CURSOR); cstmt.executeUpdate(); rs = (ResultSet)cstmt.getObject(1); while(rs.next()) { ScoreDTO dto=new ScoreDTO(); dto.setHak(rs.getString("hak")); dto.setName(rs.getString("name")); dto.setBirth(rs.getDate("birth").toString()); dto.setKor(rs.getInt("kor")); dto.setEng(rs.getInt("eng")); dto.setMat(rs.getInt("mat")); dto.setTot(rs.getInt("tot")); dto.setAve(rs.getInt("ave")); dto.setRank(rs.getInt("rank")); list.add(dto); } } catch (SQLException e) { e.printStackTrace(); } finally { if(rs!=null) { try { rs.close(); } catch (Exception e2) { } } if(cstmt!=null) { try { cstmt.close(); } catch (Exception e2) { } } } return list; } @Override public List<ScoreDTO> listScore(String name) { List<ScoreDTO> list=new ArrayList<>(); CallableStatement cstmt = null; ResultSet rs=null; String sql; try { sql = "{ CALL searchNameScore(?, ?) }"; cstmt = conn.prepareCall(sql); cstmt.registerOutParameter(1, OracleTypes.CURSOR); cstmt.setString(2, name); cstmt.executeUpdate(); rs = (ResultSet) cstmt.getObject(1); while(rs.next()) { ScoreDTO dto=new ScoreDTO(); dto.setHak(rs.getString("hak")); dto.setName(rs.getString("name")); dto.setBirth(rs.getDate("birth").toString()); dto.setKor(rs.getInt("kor")); dto.setEng(rs.getInt("eng")); dto.setMat(rs.getInt("mat")); dto.setTot(rs.getInt("tot")); dto.setAve(rs.getInt("ave")); list.add(dto); } } catch (SQLException e) { e.printStackTrace(); } finally { if(rs!=null) { try { rs.close(); } catch (Exception e2) { } } if(cstmt!=null) { try { cstmt.close(); } catch (Exception e2) { } } } return list; } @Override public Map<String, Integer> averageScore() { Map<String, Integer> map = new HashMap<String, Integer>(); CallableStatement cstmt = null; String sql; try { sql = "{ CALL averageScore(?,?,?) }"; cstmt = conn.prepareCall(sql); cstmt.registerOutParameter(1, OracleTypes.INTEGER); cstmt.registerOutParameter(2, OracleTypes.INTEGER); cstmt.registerOutParameter(3, OracleTypes.INTEGER); cstmt.executeUpdate(); int kor = cstmt.getInt(1); int eng = cstmt.getInt(2); int mat = cstmt.getInt(3); map.put("kor", kor); map.put("eng", eng); map.put("mat", mat); } catch (Exception e) { e.printStackTrace(); } finally { if(cstmt!=null) { try { cstmt.close(); } catch (Exception e2) { } } } return map; } }
일단 처음 insertScore 함수부터 보면 기존에 PreparedStatement와 다른점은
CallableStatement 객체를 생성한 점
그리고 sql 문장 형태가 바뀐것
자바에서 오라클의 시퀀스를 불러오기 위해서는 {} 대괄호를 사용해야하고
{} 대괄호 안에는 무조건 CALL 로 시작해야한다.
CALL 시퀀스이름(시퀀스에 넣을 값들)
즉 "{ CALL insertScore(?, ?, ?, ?, ?, ?) }"; 이런 형태로 입력해야 하는 것
커넥션 형태도 바뀌었다.
Connection의 객체 conn.prepareCall << 이다.
쿼리 실행할때의 형태도 바뀌었다.
기존에는 쿼리를 실행하면 1이나오면 성공 0이나오면 실패여서 result 값에 넣어줘서 1이냐 0이냐로 판단했는데
시퀀스는 그냥 실행을 하기 때문에 굳이 result에 값을 넣을 필요가 없다.
그리고 수정이나 삭제는 생성과 비슷한 형태라서 넘어가고
readScore(String hak) 함수
아까 우리는 시퀀스를 만들때 OUT과 IN을 설정 했었다.
자바에서도 오라클로 값을 보내고 받아야 한다.
일단 sql 함수에 { CALL readScore(?, ?) } 명령어를 넣어놨는데 ?에 값을 넣어야한다.
cstmt.registerOutParameter(1, OracleTypes.CURSOR);
명령어를 이용해서 첫번째 물음표의 타입을 오라클의 커서로 설정한다. => 오라클에서 받아올 값
cstmt.setString(2, hak);
두번째 물음표는 함수 매개변수로 받은 hak을 넣는다. => 오라클로 넘길 변수임
// 프로시저 실행. 모든 프로시저는 executeUpdate()로 실행 cstmt.executeUpdate();
각각의 물음표를 설정 했으면 프로시져를 실행 시킨다.
rs = (ResultSet) cstmt.getObject(1); // SYS_REFCURSOR는 ResultSet로 반환 받는다.
이제 프로시져가 실행이 완료되었으면 프로시져에서 값을 받아온다.
그리고 ResultSet은 .next()로 돌려줘야 출력을 하든 설정을 하든 가능하다.
매개변수 없는 listScore() 함수도 마찬가지로 똑같은데 다만 자바로 넘기는 값은 없고 자바에서는 받아오기만 하면 된다.
그래서 받아올 값으로 ?를 넣어주고 그 ?물음표 를 오라클 타입으로 설정하고
* cstmt.registerOutParameter(1, OracleTypes.CURSOR);
오라클에서 쿼리문 실행(프로시져 실행) 시키고
* cstmt.executeUpdate();
이제 값을 자바로 받아오면 된다.
* rs = (ResultSet)cstmt.getObject(1);
listScore(String name)는 학번으로 검색하는 함수와 동일하다.
averageScore() 함수
얘 같은 경우에는 시퀀스에서 자바로 내보낼 변수를 3개나 잡았으니까 마찬가지로 자바에서도 받아올 값으로 물음표 ?개와 그 3개에 오라클 타입이라고 설정 해줘야한다.
단지 아까 시퀀스에서 타입을 커서가 아니라 NUMBER로 주었기 때문에 자바에서도 타입을 INTEGER로 주어야 한다.
이제 UI쪽에서 만든 함수들을 불러오는 상황을 보자
package score3; import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.List; import java.util.Map; import db.util.DBconn; /* * Statement, PreparedStatement = 필요시 트랜잭션 처리를 자바에서 해야함. * * CallableStatement = 프로시저 실행 * 프로시저에서 트랜잭션 처리함. */ public class ScoreUI { private BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); private ScoreDAO dao = new ScoreDAOImpl(); public void menu() { int ch; System.out.println("CallableStatement"); while(true) { try { do { System.out.println("1.등록 2.수정 3.삭제 4.학번검색 5.이름검색 6.리스트 7.과목별평균 8.종료 => "); ch = Integer.parseInt(br.readLine()); } while(ch<1 || ch>7); if(ch == 8) { DBconn.close(); return; } switch(ch) { case 1 : insert(); break; case 2 : update(); break; case 3 : delete(); break; case 4 : findByHak(); break; case 5 : findByName(); break; case 6 : listAll(); break; case 7 : average(); break; } } catch (Exception e) { // TODO: handle exception } } } public void insert() { System.out.println("데이터 등록"); ScoreDTO dto = new ScoreDTO(); try { System.out.println("학번 ? "); dto.setHak(br.readLine()); System.out.println("이름 ? "); dto.setName(br.readLine()); System.out.println("생년월일 ? "); dto.setBirth(br.readLine()); System.out.println("국어 ? "); dto.setKor(Integer.parseInt(br.readLine())); System.out.println("영어 ? "); dto.setEng(Integer.parseInt(br.readLine())); System.out.println("수학 ? "); dto.setMat(Integer.parseInt(br.readLine())); dao.insertScore(dto); System.out.println("데이터 추가 완료"); } catch (NumberFormatException e) { System.out.println("점수는 숫자만 가능합니다."); } catch (Exception e) { System.out.println("데이터 등록 실패"); } System.out.println(); } public void update() { System.out.println("데이터 수정"); String hak; try { System.out.println("수정할 학번"); hak = br.readLine(); ScoreDTO dto = dao.readScore(hak); if(dto == null) { System.out.println("등록된 자료가 없습니다."); return; } System.out.print("수정할 이름 ?"); dto.setName(br.readLine()); System.out.print("생년월일 ?"); dto.setBirth(br.readLine()); System.out.print("국어 ?"); dto.setKor(Integer.parseInt(br.readLine())); System.out.print("영어 ?"); dto.setEng(Integer.parseInt(br.readLine())); System.out.print("수학 ?"); dto.setMat(Integer.parseInt(br.readLine())); dao.updateScore(dto); System.out.println("데이터 수정 완료"); } catch (NumberFormatException e) { System.out.println("점수는 숫자만 가능"); } catch (Exception e) { System.out.println("데이터 수정 실패"); } System.out.println(); } public void delete() { System.out.println("데이터 삭제"); String hak; try { System.out.println("삭제할 학번"); hak = br.readLine(); dao.deleteScore(hak); System.out.println("데이터 삭제 완료"); } catch (Exception e) { //e.printStackTrace(); System.out.println("삭제 실패"); } } public void findByHak() { System.out.println("학번 검색"); String hak; try { System.out.println("검색할 학번 ?"); hak = br.readLine(); ScoreDTO dto = dao.readScore(hak); if(dto == null) { System.out.println("등록된 자료가 없습니다."); return; } System.out.print(dto.getHak() + "\t"); System.out.print(dto.getName() + "\t"); System.out.print(dto.getBirth() + "\t"); System.out.print(dto.getKor() + "\t"); System.out.print(dto.getEng() + "\t"); System.out.print(dto.getMat() + "\t"); System.out.print(dto.getTot() + "\t"); System.out.print(dto.getAve() + "\t"); System.out.println(); } catch (Exception e) { e.printStackTrace(); } } public void findByName() { System.out.println("이름 검색"); String name; try { System.out.println("검색할 이름 ? "); name = br.readLine(); List<ScoreDTO> list = dao.listScore(name); if(list.size() == 0 ) { System.out.println("등록된 자료가 없음"); return; } for(ScoreDTO dto : list) { System.out.print(dto.getHak() + "\t"); System.out.print(dto.getName() + "\t"); System.out.print(dto.getBirth() + "\t"); System.out.print(dto.getKor() + "\t"); System.out.print(dto.getEng() + "\t"); System.out.print(dto.getMat() + "\t"); } System.out.println(); } catch (Exception e) { e.printStackTrace(); } } public void listAll() { System.out.println("전체 리스트"); System.out.println("학번\t이름\t생일\t\t\t국어\t영어\t수학\t총점\t평균\t석차"); System.out.println("======================================================================================="); List<ScoreDTO> list = dao.listScore(); for(ScoreDTO dto : list) { System.out.print(dto.getHak() + "\t"); System.out.print(dto.getName() + "\t"); System.out.print(dto.getBirth() + "\t"); System.out.print(dto.getKor() + "\t"); System.out.print(dto.getEng() + "\t"); System.out.print(dto.getMat() + "\t"); System.out.print(dto.getTot() + "\t"); System.out.print(dto.getAve() + "\t"); System.out.println(dto.getRank()); } System.out.println(); } public void average() { System.out.println("과목별 평균 점수"); Map<String, Integer> map = dao.averageScore(); try { int kor = map.get("kor"); int eng = map.get("eng"); int mat = map.get("mat"); System.out.println("국어 : "+kor); System.out.println("영어 : "+eng); System.out.println("수학 : "+mat); } catch (Exception e) { e.printStackTrace(); } System.out.println(); } }
dto에 값을 잘 넣어놨으면 그냥 그대로 호출해서 매개변수만 잘 넣어주면 된다.
이제 리스트 같은 경우에는 반환값이 list 형태 이니까 UI쪽에서 list를 향상된 for문으로 출력 해주면 된다.
과목별 평균 값들은 Map을 반환 했으니까 여기서도 Map의 형태를 만들고 안에 함수 호출한 값을 넣어주면 된다.
그러면 map에 있는 값들을 출력만 하면 된다.
반응형'프로그래밍 > JAVA 자바' 카테고리의 다른 글
Thread 스레드 (2) / synchronized (0) 2021.09.06 프로세스, 쓰레드 Thread (0) 2021.09.05 자바DB연동 / PreparedStatement 활용 예제 (0) 2021.08.30 입출력 스트림 마무리 (0) 2021.08.29 자바로 DB 연동해서 작업하기 Statement 활용 예제 (0) 2021.08.27