2022.12.24 JAVA 복습 데이터베이스 입출력
20장 데이터베이스 입출력
공유데이터는 데이터베이스에 저장하고 읽고 저장 수정 등을 한다.
여러 pc에서 프로그램이 공유한다면 특정pc에서 저장하는 건 불가능하다. 원격으로 db에 들어와서 관리를 하는게 필요하다.
db연결방법 데이터 가져오는 방법 데이터 저장방법등 배움
20장 데이터베이스 입출력
20.1 JDBC개요
자바는 데이터베이스와 연결해서 데이터 입출력 작업을 할 수 있도록 JDBC라이브러리를 제공한다.
java database connectivity 자바데이터베이스 연결 자바 언어를 이용해 db연결을 한다.
이는 하나의 프레임 워크 라고 할 수 있다.
이 프레임워크서 제공하는 클래스 인터페이스를 통해서 사용하는 것이다.
java.sql 모듈에 있다. 여기에 java.sql javax.sql패키지가 있다. 이것들을 총칭해서 jdbc다 jdbc api다 라고 한다.
이들은 데이터베이스 관리 '소프트웨어'이다.
어떠한 것을 사용하더라도 dbms와 관련없이 사용할 수 있다 JDBC 라이브러리를 사용할 수 있다.
이걸 사용하기 위해 외부에서 가져와서 사용해야하는데 이것이 JDBC드라이버이다.
작성할때는 JDBC인터페이스를 사용하고 이걸 구현하는 객체를 JDBC드라이버이다.
이걸 누가만드냐? DBMS만드는 회사가 만든다. 예를들어 오라클은 오라클이 제공함.
우리는 JDBC인터페이스를 가지고 JDBC드라이버를 가져와서 설계하는 것이다.
java.sql javax.sql패키지가 있다. 여기에 속한 인터페이스 클래스를 통해서 데이터베이스 작업을 한다.
다가 아니지만 대표적으로 사용하는 것은 다음과 같다.
여기서 화살표는 상속의 관계가 아닌 생성의 관계이다.
DriverManager: jdbc드라이버를 관리, 사용할수있게하는 역할
Connection 연결 인터페이스: 이런 데이터베이스 관리시스템도 하나의 서버여서 TCP를 사용한다. 연결하고 데이터를 주고받는다.
연결객체 연결자체를 객체화 한것 작업하려면 연결부터 해야함.
db와 작업을 하려면 sql로 db에 해달라 해야함.
Statement종류들은 이 sql을 실행할 수 잇는 객체있다. 인터페이스는 SQL을 DBMS에보내고 이 기술된 내용대로 데이터를 처리한다.
DBMS는 SQL만으로 부를 수 있는 것은 아님.
CallableStatment는 프로시저, 펑션등을 사용해서 데이터베이스 실행가능한 프로그램 --자바의 메소드역할임.
ResultSet 결과셋 결과가 담긴 객체, 요청-> 요청한결과 돌려줌 결과가 담긴 Set 결과를 얻어 DB에서 가져온 데이터를 읽을 때 사용
20.4 db구성
create table boards (
bno number primary key,
btitle varchar2(100) not null,
bcontent clob not null,
bwriter varchar2(50) not null,
bdate date default sysdate,
bfilename varchar2(50) null,
bfiledata blob null
);db에서 테이블 생성하기
세로에있는게 컬럼이름 가로로 각 컬럼의 설정이 있다. 2번째줄은 DATA_TYPE, 3번째줄은 null값이 가능한지, 기본값이 있는지 등등
테이블은 행과 열(컬럼)로 구성되어있다.
가로가 행 세로가 컬럼
열부분을 보면 값의 타입을 알수있다. 값의 타입으로만 값을 넣어줄 수 있다.
varchar2문자열 number숫자 clob긴문자열 blob바이너리데이터 date날짜
데이터를 추가한다는 것은 한 행을 추가하는 것임.
데이터가 없을때? 프로그램으로 추가하는 것임.
BORADS는 게시물이 들어가는 테이블
시퀸스: 순차적인 시작번호 1부터 맥스밸류까지 하나씩 줌
시퀸스 BNO가 순차적으로 제공되는 객체
번호를 하나씩 받아서 게시판의 BNO의 값으로 사용한다.
20.5 db연결
db와 연결해보자
jdbc jdk에서 제공하는 것이고 jdbc driver가 없으니 이것을 만들어줘야함.
클라이언트 프로그램에서 DBMS가 서버라면 사용하는쪽은 다 클라이언트 프로그램임
db와 연결하려면 해당 dbms의 jdbc driver가 필요하다.
4가지 정보가 있어야한다.
1.DBMS가 설치된 컴퓨터의 IP주소
2.DBMS가 허용하는 포트(Port)번호
3.사용자(db계정) 및 비밀번호
사용하고자 하는 DB이름
20.5.1 DB Driver설치
Oracle 같은 경우 설치할때 \Oracle\WINDOWS.X64_193000_db_home\jdbc\lib
설치경로 파일에 jar파일이 있다. ojdbc8.jar
여기에는 JDBC인터페이스를 구현한 클래스가 가 있다.
원격에서할경우 별도로 다운을 받아야한다. 메이저 버전만 맞으면 된다. 현재 교재는 19.몇버전임
롬복, json을 추가하듯이 lib파일에 넣고 빌드패스를 해주면 된다.
환경변수에 추가해서 cmd에서 사용할 수 있게도 할 수 있다.
20.5.2 db연결
연결하기 위해 JDBC Driver클래스를 메모리로 로딩하는 것이다.
로딩? 메소드영역에 불러오는 것
Class.forName("oracle.jdbc.OracleDriver"); 클래스객체를 얻을 때 사용할 수 있다고 배운 것.
문자열로 클래스이름을 주면 패스상에 있으면 메모리에 올린다. 그리고 이것의 클래스를 리턴한다. 근데리턴값이 필요없고 메모리에올리기만하면되니 그냥 이것만쓰면됨
이 클래스를 메모리에 로딩해야해
왜? 하나oracle드라이버를 사용하려면 DriverManager에 등록해야함.
로딩을 하는 순간 JDBC Driver클래스에 static블록 실행되면서 DriverManager에 자동적으로 등록이 됨.
->6장에서함 클래스를 사용하고 싶을때 언급하면 자동적으로 로딩이됨
class OracleDricer{
static {
Driver driver = new OracleDriver();
DrverManager.registerDriver(driver);
}DrverManager보면 registerDriver(Driver driver);
Driver 인터페이스임 여기에 이것을 구현한 객체를 넣어줌
Class.forName해서 로딩되면 위 정적블록이 시작되며 코드가 실행되는것임
즉 이클래스를 어떻게 작성햇나?
메모리 블록에 로딩이되면 실행이 됨. 그래서 등록되며 사용이 가능한 것임.
만약 드라이버가 없다면 ClassNotFoundException 예외가 발생하므로 예외처리를 해야한다.
예외가 발생하면 빌드패스를 제대로 했는지 안했는지를 볼 필요가 있다.
이걸하면 뭘할수있냐? db와 연결할 수 있다.
DriverManager.getConnection(url, "user이름", "비밀번호");
url? = IP번호 포트번호 DB이름
여기서 3가지 정보 IP번호 포트번호 DB이름 이것들이 들어가는 문자열 형식은 DBMS마다 다르다. api도큐먼트를 보고 알기
연결문자열
jdbc:orcale:thin:@ip주소:포트번호/DB이름
jdbc:orcale:thin:@localhost:1521/orcl
여기서 thin은? tcp프로토콜을 사용하면 이걸 가지고 있음. thin이 들어가있으면 tcp를 사용하는 것이다.
SQLException발생가능해서 예외처리 해줘야함.
getConnection()가 실행되면 Connection인터페이스가 리턴됨 즉 Connection을 구현한 객체가 리턴되는것임.
Connection conn = DriverManager.getConnection("jdbc:orcale:thin:@localhost:1521/orcl", "java", "oracle");
으로 받아줘야함.
닫기conn != null연결이 안되면 null임 이때는 닫을필요가 없음.
자동리소스닫기도 가능함.
public class ConnectionExample {
public static void main(String[] args) {
Connection conn = null;
try {
// JDBC Driver를 메모리로 로딩하고 DriverManager에 등록
Class.forName("oracle.jdbc.OracleDriver");
//DB 연결
conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521/orcl", "java", "oracle");
System.out.println("연결 성공");
//DB 작업
//~~
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
//DB 연결 끊기 예외나면 아래로 안가니 finally에서 닫아주기
if(conn != null) {
try {
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("연결 끊김");
}}}20.6 데이터 저장
db에 저장하려면 sql문을 알아야한다.
users 테이블에 하나의 행을 삽입하는 sql insert문은 다음과 같다.
각 컬럼에 값을 하나씩 지정해주어야한다.
테이블의 컬럼과 똑같은 순서를 할 필요는 없지만 가독성을 위해 가급적이면 같게 쓰자.
INSERT INTO users (userid, username, userpassword, userage, useremail)
VALUES ('winter', '한겨울', '12345', 25, 'winter@mycompany.com');
컬럼의 이름은 대소문자를 가리지않음. INSERT INTO같은거도 대소문자 상관없음.
이 문을 작성하면 클라이언트가 아닌 데이터베이스 내부에서 실행을 해야한다.
sql문 자체는 이클립스가 아닌 데이터베이스 내부 Oracle SQL Developer에 쓰기
SQL문에서 문자열은 ' '로 감싸줌 varchar2 문자타입 number숫자타입
이것을 실행한다고 바로 실행되는것은 아님
Oracle SQL Developer도 하나의 클라이언트일뿐임
실행한다는것의 의미는 이 SQL을 데이터베이스로 보내서 (서버로 보내서)안에서 실행한다는 것
실행하면 스크립트 출력에 어떻게 실행이 되었는지 보여줌(자바의 콘솔창같은느낌)
그러나 이것은 영구적으로 들어가는게 아니라 데이터베이스 메모리에만 저장된거고 물리적인 파일에 저장된 것이아님.
commit;을 작성해줘야 서버에 영구적으로 저장이 되는 것임.
새로운 데이터를 저장했다 이를 서버의 파일 시스템에 영구적으로 저장해줘라는 것
메뉴에 커밋 버튼을 눌러도 된다.
그냥 닫아버리면 서버에 영구적으로 저장이 안된다. commit전에는 메모리상에 저장하는 것이기때문이다.
예를들어 비정상적으로 종료시 다 날라감.
INSERT DELETE UPDATE등 SQL문을 사용한 후에는 무조건 커밋을 해줘야한다.
SQL에서 실행한걸 되돌리려면 rollback;을 치거나 롤백버튼을 누르면된다.
20.6.1 자바프로그램을 이용해서 user테이블에 추가하기
데이터가 들어가는 부분을 ?로 바꾼다 ?는 매개변수화이고 이것을 변경하면 매개변수화된 sql문이라고 할수있다.
즉 값을 ?(물음표)로 대체한 매개변수화된 INSERT문으로 변경하면 다음과 같다.
INSERT INTO users (userid, username, userpassword, userage, useremail)
VALUES (?, ?, ?, ?, ?);
그리고 INSERT문을 String 타입 변수 sql에 문자열로 대입한다.
String sql = new StringBuilder()
.append("INSERT INTO users (userid, username, userpassword, userage, useremail) ")
.append("VALUES (?, ?, ?, ?, ?)")
.toString();
또는
String sql = "INSERT INTO users (userid, username, userpassword, userage, useremail) " + "VALUES (?, ?, ?, ?, ?)";
문자열을 작성할때 한칸씩띄워줘야한다. 합칠때 같이써져서 실행이안된다.
이 문자열된 sql문을 db에 보내줘야함. 전송해서 실행을 담당하는 PreparedStatement객체를 사용해야한다.
즉 매개변수화 된 SQL문을 실행하려면 PreparedStatement가 필요하다.
다음과같이 Connection의 PrepareStatement()메소드로부터 PreparedStatement를 얻는다.
PreparedStatement pstmt = conn.PrepareStatement(sql);
준비된(PreparedStatement)에 넣기 위해 준비해(preparestatment)
그리고 ?에 들어갈 값을 지정해주는데 ?는 순서에 따라 1번부터 번호가 부여된다.
값의 타입에따라 Setter메소드를 선택한후 첫번째에는 ?순번, 두번째에는 값을 지정한다.
pstmt.setString(1, "winter");
pstmt.setString(2, "한겨울");
pstmt.setString(3, "12345");
pstmt.setInt(4, 25);
pstmt.setString(5, "winter@mycompany.com");
마지막으로 db에보내서 실행해 executeUpdate는 수정뿐만이아닌 추가 제거 수정등이 다 포함되어있다.
executeUpdate()는 결국 sql문을 db를 보내서 결과를 받아오는 것까지 하는 메소드임.
값을 저장한 후 executeUpdate()메소드를 호출하면 SQL문이 실행되면서 users테이블에 1개의 행이 저장된다.
executeUpdate()가 리턴하는 값은 저장된 행 수 인데 정상적으로 실행되었을 경우 1을 리턴한다.(1행씩 추가하기때문임)
int rows = pstmt.executeUpdate();
PreparedStatement가 더 이상 사용하지 않을 경우에는 close()메소드를 호출해서 PreparedStatement가 사용했던 메모리를 해제한다.
pstmt.close();
예제는 이전의 ConnectionExample을 가져와서 db작업 부분을 수정하였음.
연결방법은 같기 때문임.
java.sql안에 들어있는데 이것이 JDBC 프레임 워크다.
//DB 작업
//sql문작성
String sql = "INSERT INTO users (userid, username, userpassword, userage, useremail) " + "VALUES (?, ?, ?, ?, ?)";
//PreparedStatement 얻기 및 값 지정 인터페이스임
PreparedStatement pstmt = conn.prepareStatement(sql);
//데이터 넣기
//문자열 타입은 실제 테이블의 타입을 봐야함.
//데이터베이스 제약조건 보면 ID에 Primary_Key(기본키)가 되어있어서 같은 값을 못넣음. DB를 통해 배우는 것임 여기에선 주의하기
//이미 db에 winter가 있으니 다른거로 넣기
pstmt.setString(1, "fall");
pstmt.setString(2, "김단풍");
pstmt.setString(3, "1235");
pstmt.setInt(4, 27);
pstmt.setString(5, "fall@mycompany.com");
//sql문 실행
int rows = pstmt.executeUpdate();
System.out.println("저장된 행 수: " + rows);
//PreparedStatement 닫기
pstmt.close();앞의 예제에 DB작업을 했다.
java에서 넣으면 오토 커밋되고 성공적으로 추가된것을 알 수 있다.
같은 것을 한번 더 실행하면 무결성조건위배된다는 예외가 발생하는데
userid가 primary_key이기때문에 중복되면 안되는 것임.
오토 커밋을 비활성화 할수도 있음(트랜젝션)
20.6.2 boards테이블에 추가하기
SQL문 다음과 같다.
INSERT INTO boards (bno, btitle, bcontent, bwriter, bdate, bfilename, bfiledata)
VALUES (SEQ_BNO.NEXTVAL, '눈오는 날', '함박눈이 내려요', 'winter', SYSDATE, 'snow.jpg', binaryData)";
SEQ_BNO.NEXTVAL 숫자를 줘도 되는데 시퀀스 SEQ_BNO에서 숫자 알아서 넣어줌 시퀀스는 oracle의 객체인데 순차적으로 번호를 넣어줌
시퀸스.NEXTVAL 시퀸스에서 다음 번호를 줘라
bwriter 일단 아무거나 넣었는데 왠만해선 userid를 넣음. 보통은 외래키 사용함 원래대로면 user테이블과 연결했을듯 일단은 users에있는거만 넣기.
데이터베이스 NULLABLE 데이터가 없어도 된다. NULLABLE이 NO인 것은 모두 언급해줘야한다.
여기서는 bno, btitle, bcontent, bwriter컬럼들이 필수적으로 값이 있어야한다.
INSERT INTO boards (bno, btitle, bcontent, bwriter)
VALUES (SEQ_BNO.NEXTVAL, '눈 오는 날', '함박눈이 내려요', 'winter');
INSERT INTO boards (bno, btitle, bcontent, bwriter, bdate, bfilename, bfiledata)
VALUES (SEQ_BNO.NEXTVAL, '눈 오는 날2', '함박눈이 내려요2', 'winter', SYSDATE, NULL, NULL);
이를 매개변수화하면 밑에처럼 할 수 있다.
매개변수화하고 sql에 대입
String sql = "insert into boards (bno, btitle, bcontent, bwriter, bdate, bfilename, bfiledata) " + "values (SEQ_BNO.NEXTVAL, ?, ?, ?, SYSDATE, ?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql, new String[]{"bno"});
이전 예제에서는 sql만 줬는데 두번째 매개변수로 String배열을 주었음.
prepareStatement() 6가지 오버로딩되어있다. String배열의 의미?
이것은 결과값으로 가져올 컬럼을 가져와라는 뜻 new String[] colunmNames)
실제 열에 저장된 값을 알고 싶을 때 가져오는 것이고 colunmNames에는 {"열이름", "열이름"} 식으로 열이름의 배열이 들어갈 수 있다.
즉 여기서는 실행하고나서 bno값을 가져와라
SEQ_BNO.NEXTVAL의 다음 번호가 뭔지 알려면 실제 저장된 값을 알아야한다.
시퀸스에서 자동으로 값을 가져올 경우 값을 알아야 다음 작업을 할 수 있는 일이 많다.
다음번호를 알고나서 프로젝트를 하고 싶을때 사용 값이 뭔지 알아야 행동할수 있는 작업이 있다.
?을 넣을때 각 컬럼의 순서가 번호가 아니라 SEQ_BNO.NEXTVAL,SYSDATE를 무시하고 ?만 순서를 보면된다.
pstmt.setString(1, "눈오는 날");
pstmt.setString(2, "함박눈이 내려요.");
pstmt.setString(3, "winter");
pstmt.setString(4, "snow.jpg");
pstmt.setBlob(5, new FileInputStream("src/ch20/oracle/sec06/snow.jpg")); //상대경로 이 프로젝트를 기준으로함.
3번 데이터 타입은 CLOB인데(characterLOB) 매우큰 사이즈의 문자열 저장 4기가까지의 문자열을 저장할수 있다.
VARCHAR2는 알파벳 4000자 한글 4000/3자
매우 많은 내용 넣고싶으면 CLOB넣기
4번자리에는 파일명
5번자리에는 FileInputStream으로 파일의 내용을 읽고 넣기
5번은 데이터타입이 BLOB 바이너리 데이터를 넣을 수 있다는 것(파일 그자체를 저장 그림,음악,동영상파일 등)
SQL 문 실행
int rows = pstmt.executeUpdate();
if(rows == 1) { //실제 반영되었다면?(1을 리턴하니) 생성된 키들을 가져와 바로 언급된 컬럼의 값을 가져와라 즉
ResultSet rs = pstmt.getGeneratedKeys(); //new String[]{"bno"}에 기술된 컬럼값을 가져옴
if(rs.next()) { //값이 있다면
int bno = rs.getInt(1); //new String[]{"bno"}에 첫번째 항목 bno 컬럼값을 읽음
System.out.println("저장된 bno: " + bno);
}
rs.close(); //ResultSet이 사용했던 메모리 해제}
정상적으로 저장되었으면 1이 리턴되니 정상적으로 저장되었을경우 실행된다.
ResultSet는 테이블처럼 생김 ResultSet을 얻으면 커서가 테이블의 맨위에 있게됨 이부분은 데이터의 값이 없으니 일단대기함.
_ _ _ 빈공간
ㅁㅁㅁ
ㅁㅁㅁ 이런모양으로 되어있음. 제일처음에는 커서가 맨위에 가있음
여기서 이 커서를 한칸 내리는 것을 next()메소드이다. 그러면 다음 행으로 내려가서 지정했던 bno값을 읽어줌
setBlob에서 그냥 null값주면 컴파일 에러뜸
setBlob(인덱스, InputStream)
setBlob(인덱스, Blob b)
두개가 오버라이딩되서 뭔지 몰라서 컴파일에러뜨는것
그래서 Blob blob = null; 블롭타입 변수 선언하고 넣어주면 정확하게 됨
bno 숫자가 너무 커지거나 중간중간에 하나씩 삭제되면 어떤 bno가 들어가는지 모름
그래서 resultset으로 받아오는 것임.
//DB 작업
//sql문작성
String sql = "insert into boards (bno, btitle, bcontent, bwriter, bdate, bfilename, bfiledata) "
+ "values (SEQ_BNO.NEXTVAL, ?, ?, ?, SYSDATE, ?, ?)";
//PreparedStatement 얻기 및 값 지정 인터페이스임
PreparedStatement pstmt = conn.prepareStatement(sql, new String[] {"bno", "btitle"} );
//데이터 넣기
pstmt.setString(1, "눈 오는 날5");
pstmt.setString(2, "함박눈이 내려요5");
pstmt.setString(3, "winter");
pstmt.setString(4, "photo1.png");
//pstmt.setString(4, null);
//Blob blob = null;
//pstmt.setBlob(5, blob);
pstmt.setBlob(5, new FileInputStream("src/ch20ver2/sec06/photo1.png"));
//sql문 실행
int rows = pstmt.executeUpdate();
System.out.println("저장된 행 수: " + rows);
if(rows == 1) {
ResultSet rs = pstmt.getGeneratedKeys();
if (rs.next()) { //next있으면 true리턴 없으면 false리턴
int bno = rs.getInt(1); // 얻으려는 컬럼이 bno하나뿐이니 1
System.out.println("저장된 bno: " + bno);
//prepareStatement(sql, new String[] {"bno", "btitle"} ); 해주고
String btitle = rs.getString(2);
System.out.println("저장된 btitle: " + btitle);
}
rs.close();
}
//PreparedStatement 닫기
pstmt.close();20.7 데이터 수정
JDBC를 이용해서 UPDATE문을 실행하는 방법을 알아보자.
기존에 저장된 데이터를 바꾸는 것이고 update문을 사용한다.
update boards set
btitle = '눈사람',
bcontent = '눈으로 만든 사람',
bfilename = 'snowman.jpg',
bfiledata=binaryData
where bno = 3컬럼하나만 바꾸려면 하나만 넣으면되는데 동시에 여려 행을 바꾸고 싶을때는 ,로 넣으면됨 ,는 컬럼 구분자이기때문에 맨마지막엔 넣지 않으니 주의하자
where는 조건문이고 만약 where가 없으면 값이 전부다 바뀌므로 주의하자
바꾸고자 하는 값들을 매개변수로 ?를 주면됨
update boards set btitle = ?, bcontent = ?, bfilename = ?, bfiledata= ? where bno = ?
String sql에 매개변수화된 UPDATE 문을 저장한다.
String sql = new StringBuilder()
.append("update boards set ")
.append("btitle = ?, ")
.append("bcontent = ?, ")
.append("bfilename = ?, ")
.append("bfiledata= ? ")
.append("where bno = ? ")
.toString();
문자합쳐지니 꼭 한칸씩 띄어쓰기
스트링빌더를 만들고 스트링타입에 대입하면 컴파일에러
.toString()을해서 문자열로 바꿔야함
String sql = "update boards set" + "bitile = ?, " + "bcontent = ?, " + "bfilename = ?, " + "bfiledata= ? "+ "where bno = ? "
매개변수화된 UPDATE문을 실행하기 위해 preparStatment()메소드로부터 preparedStatement를 얻고 ?에 해당하는 값을 지정한다.
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "눈사람");
pstmt.setString(2, "눈으로 만든 사람");
pstmt.setString(3, "snowman.jpg'");
pstmt.setBlob(4, new FileInputStgream("파일경로 및 위치"));
pstmt.setInt(5, 3);
값을 모두 지정하였다면 UPDATE문을 실행하기 위해 executeUpdate()메소드를 호출한다.
성공적으로 실행되면 수정된 행의 수가 리턴된다. 0이 리턴되면 조건에 맞는 행이 없어 수정된 내용이 없음을 의미한다.
where의 조건에 따라 달라짐
즉 조건에 맞는 행이 없을 경우? 0이 리턴됨 즉 0이나온다고 잘못작성된 것이 아님.
>=7이런식으로 여러개를 고치면 2이상의 값이 나올수도 있음
실행하다가 연결성공만하고 수정이안됨. why?
데이터베이스에서 수정하던거 커밋 안눌러놔서 수정중이어서 lock이 걸림
여러 클라이언트가 db를 수정하려하려면 안된다. 그래서 수정끝날때까지 기다려줘야함.
데이터베이스에서 커밋이나 롤백을 해야 다른곳에서 lock이 풀려서 수정가능함.
//DB 작업
//sql문작성
//String sql = new StringBuilder().toString();
String sql = new StringBuilder()
.append("update boards set ") //문자합쳐지니 꼭 한칸씩 띄어쓰기
.append("btitle = ?, ")
.append("bcontent = ?, ")
.append("bfilename = ?, ")
.append("bfiledata= ? ")
.append("where bno = ? ")
.toString();
//PreparedStatement 얻기 및 값 지정 인터페이스임
PreparedStatement pstmt = conn.prepareStatement(sql);
//데이터 넣기
pstmt.setString(1, "비가 내려요");
pstmt.setString(2, "겨울비는 추워요");
pstmt.setString(3, "photo2.png");
pstmt.setBlob(4, new FileInputStream("src/ch20ver2/sec07/photo2.png"));
pstmt.setInt(5, 6);
//sql문 실행
int rows = pstmt.executeUpdate();
System.out.println("수정된 행 수: " + rows);
//PreparedStatement 닫기
pstmt.close();20.8 데이터 삭제
DELETE문을 실행해서 실제 데이터베이스의 행을 삭제한다.
sql문은 다음과 같다.
where조건절에 맞는 행만 지운다.
delete from boards where bwriter = 'winter'
삭제대상은 행임 컬럼하나의 값만 지울 수는 없다.
where가 빠지면 전체 행이 날아가버리니 주의하자.
조건절을 어떻게 작성하냐에 따라 0개의 행이 지워질수도 있고 2개이상이 될 수도 있다.
자바에서 실행하려면 매개변수를 ?로 하면됨
delete from boards where bwriter = ?
또 String타입 sql변수에 대입
String sql = "delete from boards where bwriter = ?";
매개변수화된 UPDATE문을 실행하기 위해 preparStatment()메소드로부터 preparedStatement를 얻고 ?에 해당하는 값을 지정
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "winter");
int rows = pstmt.executeUpdate();
실제 삭제된 행수
//DB작업
//매개변수화 된 SQL문 작성
String sql = "delete from boards where bwriter = ?";
//PreparedStatement 얻기 및 값 지정
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "winter");
int rows = pstmt.executeUpdate();
System.out.println("삭제된 행 수: " + rows);
pstmt.close();20.9 데이터 읽기
PreparedStatement를 생성할때 SQL문이 insert update delete일경우에는 executeUpdate()메소드를 호출하지만
데이터를 가져오는 select문일경우에는 executequery()메소드를 호출해야한다. executequery()메소드는 가져온 데이터를 ResultSet에 저장하고 리턴한다.
ResultSet rs = pstmt.executeQuery();
select userid, username, userage from users; 3가지 항목을뽑아준다
20.9.1 result set 구조

ResultSet은 select문에 기술된 컬럼으로 구성된 행의 집합이다.
제일앞에 있는 컬럼 1,2,3, 이름으로도 구분가능
제일앞행(beforeFirst)과 제일 뒤행(afterLast)은 빈칸임. 최초 커서위치는 비어있는 제일 앞에 잇는 행을 참조한다.
데이터가 존재하는 첫행은 First 마지막행은 Last이다.
커서를 다음행으로 옮기고 읽어야한다.
ResultSet에는 커서를 이동시키는 것이 있는데 이게 next()메소드이다. 만약 값이 있으면 true를 리턴값으로 주고 next()를 실행
이외에도 커서를 위아래로, 지정된행으로 이동가능한 ResultSet이 있다.
absolute(int row)절대적인 그 행수로 이동해라도 가능
afterFirst()첫행전으로 가라 first()데이터있는 첫행으로 가라
위의 rs에 넣은 ResultSet은 아래로만 갈 수 있다. ResultSet rs = 한경우
스크롤이 가능한 ResultSet을 만들수는 있으나 웹에선 데이터를 얻고 버리기때문에 자주 사용하지 않는다. 데이터만 읽고 안씀
PreparedStatement pstmt = conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATEABLE)
이런식으로 만들면 마음대로 이동이 가능하다.
이걸 하게 되면 만들기 매우 귀찮아짐.
과거에는 서버가 좋지않아 데이터 일부분을 클라이언트에 가져와서 서버와 동기화하는 식으로 했었는데 서버가 좋아져서 거의 쓰지 않는다.
1개의 데이터행만 가져올경우는 if(rs.next())사용 n개의 데이터행을 가져올경우는 while(rs.next())사용
rs의 첫번째 next()가 값이 없으면(false) 데이터가 하나도 안들어있는거다.
primary_key로 했을 경우 없거나 하나일 수 있지만 다른거로 했을 경우엔 여러개 잡힐수도있다.
ResultSet의 순번은 어떻게 정해지느냐? 데이터베이스의 기준이 아닌 select문에 넣은 순서대로 순번이 정해진다.
select userid, username, userage from users; 이라면 각각 1,2,3 인거고 순서가 바뀌면 번호도 바뀐다.
boolean result = rs.next();
ResultSet을 다 사용하고 나서는 close()메소드를 호출해서 메모리를 해제해야한다.
20.9.2 데이터행 읽기
1.컬럼이름으로읽기 또는 2.순번으로 읽기
커서가 있는 데이터행에서 각 컬럼의 값은 Getter메소드로 읽을수 있다.
컬럼의 데이터타입에 따라 메소드가 결정되며 매개값으로 컬럼의 이름 또는 순번을 줄 수 있다.
String userId = rs.getString("userid") or String userId = rs.getString(1);
int userAge = rs.getInt("userage") or int userAge = rs.getInt(3);
만약 select연산식이나 함수 호출이 포함되어 있다면 컬럼이름대신에 컬럼 순번으로 읽어야한다.
select userid, userage -1 from users
int userAge = rs.getInt(2) userage - 1은 컬럼이름이 아니기때문에 번호로 가져와야한다.
20.9.3 사용자 정보읽기
한행을 어떻게 가져올까?
변수 5개면 5개만들어서 저장하기? 객체로 만들어서 필드에 저장되게 가져오기?
하나씩 가져올지 하나의 객체로 올지 유저객체로 가져오는게 효율적인가?
데이터베이스의 하나의행은 자바 하나의 객체로 가져오는게 더 효율적임 = 객체 매핑
반대로도 하나의 객체를 하나의 행으로 왓다갓다할 줄알아야함.
전자는 데이터가 많으면 많을수록 불리해짐
행이 3개면 객체만 3개만들어서 List에 담아서 사용하면된다.
가져온 유저의 정보가 매우 많아도 객체만 만들어서 List에 담으면된다.
결론: 읽고나서 User객체에 담자.
클래스를 만들때 원본데이터의 데이터 타입을 보고 만들어야한다.
롬복을 이용해서 게터세터 등을 알아서 만들어주자.
이런것이 DTO이다. 데이터를 전달할 목적으로 사용하는 객체이다.
DB에서 가져온 데이터를 DTO에담아서 자바프로그램에 담는 것이다.
반대로 자바프로그램에서 DTO로만들고 DB에 전달해서 행에 저장하는 것이다.
DB에서 작업하기
select userid, username, userpassword, userage, useremail from users where userid= 'winter';
select문에는 연산식도 올수있고 함수도 올수 있다. where에도 여러 조건식이 올수잇다.
이것을 또 조건절의 값을 ?로 대체한 매개변수화된 SQL문을 String 변수타입 sql에 대입 String sql = "select userid, username, userpassword, userage, useremail " + "from users " + "where userid= ?";
매개변수화된 select문을 실행하기 위해 prepareStatement()메소드로부터 PreparedStatment를 얻고 ?에 값을 저장한다.
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1,"winter");
executeQuery()메소드로 select문을 실행해서 ResultSet을 얻는다.
userid는 기본키이므로 조건에 맞는 행은 1개이거나 0개이다.
if문을 이용해서 next()메소드가 true를 리턴할 경우는 데이터 행을 User객체에 저장하고 출력한다.
ResultSet rs = pstmt.executeQuery();
if (rs.next()) { //1개의 데이터행을 가져왓을경우
User user = new User();
user.setUserId(rs.getString("userid"));
user.setUserName(rs.getString("username"));
user.setUserPassword(rs.getString("userpassword"));
user.setUserAge(rs.getInt(4));//컬럼순번을 이용해서 컬럼지정
user.setUserEmail(rs.getString(5));//컬럼순번을 이용해서 컬럼지정
System.out.println(user);
} else { //데이터 행을 가져오지 않앗을경우
System.out.println("사용자 아이디가 존재하지않음");
}
system.out.println(user)는 롬복이 생성한 User객체의 toString() 메소드를 호출해서 받은 리턴값을 보여준다.
@Data // 생성자 게터 세터 hashCode() equals() toString()자동생성 롬복사용안하면 getter와 setter는 꼭 넣어줘야한다.
public class User {
private String userId;
private String userName;
private String userPassword;
private int userAge;
private String userEmail;//DB작업
String sql = "select userid, username, userpassword, userage, useremail " + "from users " + "where userid= ?";
//PreparedStatement 얻기 및 값 지정
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1,"fall");
//userid가 기본키라 하나밖에 못가져온다는 것을 이미 안다.
ResultSet rs = pstmt.executeQuery();
/*
//컬럼하나씩 데이터로 가져와서 읽기 컬럼이 5개뿐이라 짧아보이지만 더 길어지면 힘듦.
if (rs.next()) {
String userId = rs.getString("userid");
String userName = rs.getString("username");
String userPassword = rs.getString("userpassword");
int userAge = rs.getInt(4);
String userEmail = rs.getString(5);
printUser(userId, userName, userPassword, userAge, userEmail);
}
*/
if(rs.next()) {
User user = new User();
user.setUserId(rs.getString("userid"));
user.setUserName(rs.getString("username"));
user.setUserPassword(rs.getString("userpassword"));
user.setUserAge(rs.getInt("userage"));
user.setUserEmail(rs.getString("useremail"));
printUser(user); //System.out.println(user); 가있는메소드
}else {
System.out.println("사용자 아이디가 존재하지 않음");
}}20.9.4 게시물정보읽기
select문은 다음과 같다. "SELECT bno, btitle, bcontent, bwriter, bdate, bfilename, bfiledata FROM boards WHERE bwriter= 'winter';
조건절의 값을 ?로 바꾸고 sql에 대입
String sql = "SELECT bno, btitle, bcontent, bwriter, bdate, bfilename, bfiledata " + "FROM boards " + "WHERE bwriter=?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1,"winter");
executeQuery()메소드로 select 문을 실행하서 ResultSet을 얻는데 조건의 맞는행이 n개이므로 while문을 이용해서 반복해야한다.
SQL 문 실행 후, ResultSet을 통해 데이터 읽기
ResultSet rs = pstmt.executeQuery();
while(rs.next()) {
//데이터 행을 읽고 Board 객체 생성
Board board = new Board();
board.setBno(rs.getInt("bno"));
board.setBtitle(rs.getString("btitle"));
board.setBcontent(rs.getString("bcontent"));
board.setBwriter(rs.getString("bwriter"));
board.setBdate(rs.getDate("bdate"));
board.setBfilename(rs.getString("bfilename"));
board.setBfiledata(rs.getBlob("bfiledata"));
//콘솔에 출력
System.out.println(board);
리스트에 담아서 읽기도 가능
List<Board> boards = new ArrayList<>();
boards.add(board);
객체로 불러오고 리스트자체에 넣어버리기도 가능하다.
printBoards(boards); 프린트하는 메소드 작성하고 리스트 자체를 넘겨버리고 프린트했다.
리스트 자체 넘기기 는 웹에서 아주 잘 사용되니 잘알아두자
Board의 bfiledata는 Blob객체이므로 콘솔에 출력하면 oracle.sql.BLOB@1972e513와 같이 의미없는 타입정보만 출력된다.
Blob객체의 저장된 바이너리 데이터를 얻기 위해서는 스트림또는 배열을 얻어내야한다.
파일로 다시 얻어내기, 읽고 다시 다른 프로그램으로 전달하고자 할 때
BLOB blob = board.getBfiledata();
Inputstream is = blob.getBinaryStream();
OutputStream os = new FileOutputStream("C:/Temp/" + board.getBfilename()); //temp폴더 밑에 파일네임과 동일한 이름으로
//읽고쓰고 읽고쓰고 파일복사처럼 해도되지만 transferTo()메소드는 인풋스트림을 바로 아웃풀스트림으로 보내버리는 메소드이다.
is.transferTo(os);
os.flush();
os.close();
is.close();
//바이트배열로 만들기 UI가있으면 거기에 그림을 그리기
BLOB blob = board.getBfiledata();
byte[] bytes = blob.getBytes(0,blob.length()); //0사이즈부터 blob의 사이즈만큼 얻기
@Data
public class Board {
private int bno;
private String btitle;
private String bcontent;
private String bwriter;
private Date bdate;
private String bfilename;
private Blob bfiledata;
}//DB작업
//매개변수화 된 SQL문 작성
String sql = "SELECT bno, btitle, bcontent, bwriter, bdate, bfilename, bfiledata " + "FROM boards " + "WHERE bwriter=?";
//PreparedStatement 얻기 및 값 지정
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1,"winter");
ResultSet rs = pstmt.executeQuery();
//리스트에 담아서 읽기도 가능하다
//While문이 읽은것을 리스트에 담아서 읽기
List<Board> boards = new ArrayList<>();
while(rs.next()) {
Board board = new Board();
board.setBno(rs.getInt("bno"));
board.setBtitle(rs.getString("btitle"));
board.setBcontent(rs.getString("bcontent"));
board.setBwriter(rs.getString("bwriter"));
board.setBdate(rs.getDate("bdate"));
board.setBfilename(rs.getString("bfilename"));
board.setBfiledata(rs.getBlob("bfiledata"));
//콘솔에 출력
//System.out.println(board);
//내부반복자도 이용가능
//boards.stream().forEach(b -> System.out.println(b));
boards.add(board); //객체를 List에담기
//파일로 저장
Blob blob = board.getBfiledata();
if(blob != null) {
InputStream is = blob.getBinaryStream();
OutputStream os = new FileOutputStream("C:/Temp/" + board.getBfilename());
/* 원래 읽고 출력하는 방법
byte[] data = new byte[1024];
while(true) {
int num = is.read(data);
if(num==-1) break;
os.write(data, 0 , num);
}
*/
is.transferTo(os);
os.flush();
os.close();
is.close();
}
}
rs.close();
printBoards(boards);
pstmt.close();20.10 프로시저와 함수 호출
프로시저와 함수는 DB에 저장된 프로그램이다.
함수는 메소드와 비슷한것 메소드는 객체내부 기능정리한것 함수는 객체와 상관없이 실행코드를 묶어놓은것임.
프로시저와 함수는 오라클에 저장되는 프로그램이다.
제어문 변수 sql문등을 결합해서 실행가능한 코드임.
이걸 어떤 언어로 만드느냐? PL/SQL로 만드는데 이것은 오라클에서만 사용하고 다른데선 작성방법이 좀 다르다.
SQL Developer에서 프로시저
create or replace PROCEDURE user_create (
a_userid IN users.userid%TYPE,
a_username IN users.username%TYPE,
a_userpassword IN users.userpassword%TYPE,
a_userage IN users.userage%TYPE,
a_useremail IN users.useremail%TYPE,
a_rows OUT PLS_INTEGER
)
IS
BEGIN
INSERT INTO users (userid, username, userpassword, userage, useremail)
VALUES (a_userid, a_username, a_userpassword, a_userage, a_useremail);
a_rows := SQL%ROWCOUNT;
COMMIT;
END;
외부에서 데이터를받아 BEGIN과 END사이를 실행함.
create or replace PROCEDURE user_create이 메소드 선언부
()사이가 메소드의 매개변수 메소드의 실행부가 BEGIN END;사이라고 생각하면된다.
SQL Developer에서 함수create or replace FUNCTION user_login (
a_userid users.userid%TYPE,
a_userpassword users.userpassword%TYPE
) RETURN PLS_INTEGER
IS
v_userpassword users.userpassword%TYPE;
v_result PLS_INTEGER;
BEGIN
SELECT userpassword INTO v_userpassword
FROM users
WHERE userid = a_userid;
IF v_userpassword = a_userpassword THEN
RETURN 0;
ELSE
RETURN 1;
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN 2;
END;함수도 비슷하게 보인다.
함수와 프로시저의 차이점은?
일반적으로 프로시저는 실행하고 끝나고 함수는 결과값을 리턴받아야한다.
개념적으로는 그렇지만 사실 둘다 실행결과를 리턴할 수 있음.
함수는 단일값을 전달하지만 프로시저는 복잡한 구조의 데이터를 전달할 수 있다.
프로시저는 업무처리 로그인하기, 장바구니에담기 주문내역보기 등에 사용 함수는 보통 연산할때 사용
프로시저는 리턴값이 있어도 없어도되고 함수는 반드시 있어야함.
이것을 실행은 DB안에서 실행한다. 이것을 요청을 어디서하나? 이거는 클라이언트쪽에서 요청하거나 다른 프로시저에서도 호출 할 수 있다.
10절에서는 클라이언트 프로그램에서 호출하는 방법을 알아보고자 한다.
sql문들을사용할때 PreparedStatement사용했다.
JDBC에서 프로시저와 함수를 호출할때는 CallableStatement를 사용한다. 프로시저와 함수의 매개변수화된 호출문을 작성하고
Connection의 prepareCall()메소드로부터 CallableStatement 객체를 얻는다.
프로시저를 호출할 경우
String sql = "{call 프로시저명(?, ?, ..) }";
CallableStatement cstmt = conn.prepareCall(sql);
함수를 호출할 경우
String sql = "{? = call 함수명(?, ?, ..) }";
CallableStatement cstmt = conn.prepareCall(sql);
둘다 중괄호로 감싼 call문이라는 점은 동일하지만 함수는 call문의 실행결과를 대입할 좌측 리턴값 자리(?=)을 명시해야한다.
프로시저명과 함수 명 괄호안에 작성된 ?는 호출시 필요한 매개값의 자리이다.
주의할점은 프로시저도 리턴값과 유사한 OUT타입의 매개변수를 가질 수 있기 때문에 괄호안의?중일부는 OUT값(리턴값)일수 있다는 점이다.
prepareCall()메소드로 CallableStatement을 얻엇다면 리턴값에 해당하는 ?는 registerOutParameter()메소드로 지정하고
그외의 ?는 호출시 필요한 매개값으로 Setter메소드를 사용해서 값을 지정해야한다.
함수는 첫번째?가 무조건 리턴값이다.
프로시저
csmt.setString(1,"값"); //프로시저의 첫번째 매개값
csmt.registerOutParameter(2, 리턴타입) // 프로시저의 두번째 ?을 OUT값(리턴값)임을 지정
자바에서는 매개변수가 전부다 in값이지만 프로시저에서는 매개변수에 out값도 있다. out은 프로시저의 결과값이라고 보면된다.
함수
함수는 첫번째?무조건 out 나머지매개값들은 무조건 in이다.
csmt.registerOutParameter(1, 리턴타입) // 첫번째 ?는 리턴값임을 지정
csmt.setString(2,"값"); //함수의 첫번째 매개값
?에 대한 설정이 끝나면 프로시저 또는 함수를 호출하기 위해 execute()메소드를 호출한다.
csmt.execute();
호출후에는 Getter메소드로 리턴값을 얻을 수 있다. 리턴 타입이 정수라고 가정하면 프로시저와 함수의 리턴값은 다음과같다.
몇번째?가 들어갓냐에 따라 getInt에 넣으면된다.
프로시저 int result = cstmt.getInt(3); 함수 int result = cstmt.getInt(1);
더이상 사용하지 않는다면 close()함수를 호출해서 메모리를 해제해야한다.
20.10.1 프로시저 호출
위의 프로시저를 가져오면 이런식으로 구성되어있다.
IN은 외부에서 받아야하는 값 OUT은 실행후 외부로 보내는 값(리턴값)
%뒤는 매개변수의 타입 프로시저나 함수는 테이블과 관련이있다.
create or replace PROCEDURE user_create (
a_userid IN users.userid%TYPE, //userid의 컬럼과 같은 타입으로 해라
...
a_rows OUT PLS_INTEGER
String sql = "{call user_create(?, ..., ?) }";
CallableStatement cstmt = conn.prepareCall(sql);
이 호출문에서 앞에까지는 매개값기로 마지막이 리턴값이다. 따라서 다음과 같이 ?의 값을 지정하고 리턴값으로 저장한다.
cstmt.setString(1,"summer");
...
cstmt.regisetOutParameter(6, Types.INTEGER);
SQL타입이나 JDBC타입을 사용해야하는데 java.sql.Types에 상수로 들어있다.
리턴타입은 Types의 상수인 INTEGER로 지정하였는데 그 이유는 user_create프로시저의 6번째 매개변수값이 PLS_INTEGER이기때문이다.
실제 실행하면 BEGIN END사이의 문이 실행된다.
위에서보면 INSERT INTO문이 실행되고 마지막에 a_rows := SQL%ROWCOUNT; 이렇게되어있는데 :=은 우측값을 좌측에 대입하는 것인데 바로직전에 실행한 행의 수 반환임.
user_create을 실행하고 리턴값을 얻는다 성공적으로 저장되면 a_rows := SQL%ROWCOUNT;때문에 1이 리턴된다.
csmt.execute();
int rows = cstmt.getInt(6); //6번째 ?값얻기
//DB작업
//매개변수화 된 SQL문 작성
String sql = "{call user_create(?, ?, ?, ?, ?, ?) }";
//CallableStatement 얻기 및 값 지정
CallableStatement cstmt = conn.prepareCall(sql);
cstmt.setString(1, "summer");
cstmt.setString(2, "한여름");
cstmt.setString(3, "12345");
cstmt.setInt(4, 26);
cstmt.setString(5, "summer@mycompany.com");
cstmt.registerOutParameter(6, Types.INTEGER);
cstmt.execute();
int rows = cstmt.getInt(6);
System.out.println("저장된 행수: " + rows);
cstmt.close();20.10.2 함수호출
user_login()함수는 2개의 매개변수와 PLS_INTEGER리턴타입으로 구성되어있다.
사실 로그인 이런건 프로시저로 작성하는게 오히려 더 좋다. 함수는 무조건 받아야하기때문에 IN으로만 표시한다.
String sql = "{? = create or replace FUNCTION user_login (? ,?)}";
앞의 ?인 리턴값이 RETURN PLS_INTEGER이다.
IS는 BEGIN과 END에서 사용하는 변수를 선언하는 곳이다.
BEGIN과 END사이를 보면
로그인이란것은 사용자로부터 ID를 받고 userpassword를 비교하는 것이다.
SELECT userpassword INTO v_userpassword 라고 나와있는 것은 userpassword를 가져와서(into) v_userpassword 저장해라는뜻
v_userpassword와 사용자가 입력한 a_userpassword와 값을 비교
맞으면 0값리턴 패스워드 틀리면 1값
예외발생시? NO_DATA_FOUND id가 없으면 예외가 발생하니 2값 리턴
이걸로 리턴값지정, 값지정은 프로시저와 같다.
출력값은 int result = cstmt.getInt(1); 첫번째 ?값얻기 0|1|2중하나임.
//DB작업
//매개변수화 된 SQL문 작성
String sql = "{ ? = call user_login(?, ?) }";
//CallableStatement 얻기 및 값 지정
CallableStatement cstmt = conn.prepareCall(sql);
cstmt.registerOutParameter(1, Types.INTEGER);
cstmt.setString(2, "winter");
cstmt.setString(3, "12345");
cstmt.execute();
int result = cstmt.getInt(1);
//이거대신 if문사용해도됨.
String message = switch(result){
case 0 -> "로그인성공";
case 1 -> "비밀번호가 틀림";
default -> "아이디가 존재하지 않음";
};
System.out.println(message);
cstmt.close();직접 insert문 쓴거랑 프로시저 쓴것의 차이가 무엇인가?
결국 DB에 전송해서 SQL문을 처리하는데 전송량이 전자가 더많고 프로시저가 더 적다.
그래서 프로시저가 더빠르게 실행된다. 그래서 이것이 더 유리하다.
인서트문은 SQL문을 컴파일해서 실행해야한다. 프로시저는 이미 컴파일이 되어있다.
이미 컴파일된거를 실행하는것이 속도가 더 빠르다.
현업에서는 속도, 보안의 이유로 프로시저를 사용한다.
보안? insert문에는 어떤 컬럼이 있는지 다 드러난다. 그래서 유출되면 테이블 구조가 유출된다.
그런데 요즘엔 전자를 더많이쓰기도한다.
근데 모든 걸 다 insert문에 쓰지않는데 이것은 ORM이 이를 해결해준다.
ORM?
JAVA는 개별적인 행이 아닌 이것을 DTO객체로 매핑해서 만들고 보내고자 한다.
ORM은 이 기능을 제공하는 프로그램, 라이브리러리이다.
반대로 DTO객체를 행으로 저장해주는 것도 가능하다.
ORM은 종류가 많고 프로젝트할때 대부분 이것을 사용한다. 이걸 사용하면 insert select문을 직접 사용하는 것이 더 편하다.
요즘엔 DB가 잘만들어져잇어서 뭘 사용하던 차이가 없긴하다. 개발할때는 편리성을 고려해서 맞는 것을 사용한다.
결론적으로 ORM내부에서는 단편적인 sql문을 더 많이 사용한다.
20.11 트랜젝션 처리
우리나라말로 대체 불가능
트랜젝션은 기능 처리의 최소 단위를 말한다.
하나의 기능은 여러가지 소작업들로 구성된다. 최소단위라는 것은 이 소작업들을 분리할 수 없으며 전체를 하나의 본다는 개념
트랜젝션은 소작업들이 모두 성공하거나 실패해야한다.
계좌이체는 출금과 입금이 동시에 이루어져야 한다.
출금계죄에서 금액을 감소시키고 입금계좌에서 금액을 증가시킨다. 따라서 다음과 같이 두개의 UPDATE문이 필요하고 둘다 성공해야한다.
출금작업 UPDATE accounts SET balance=balance-이체금액 WHERE ano=출금계좌번호
입금작업 UPDATE accounts SET balance=balance+이체금액 WHERE ano=입금계좌번호
트랜젝션을 처리하기 위해 커밋과 롤백을 제공한다. 커밋은 내부작업을 모두 성공처리하고 롤백은 실행전으로 돌아간다는 의미에서 모두 실패처리한다.
JDBC에서는 INSERT UPDATE DELETE문을 실행할때마다 자동커밋이 일어난다. 이 기능은 두가지 UPDATE문을 실행할때 문제가 된다.
출금 작업이 성공되면 바로커밋되기때문에 입금작업의 성공여부와 상관없이 출금작업만 별도처리된다.
따라서 JDBC에서 트랜젝션을 코드로 제어하려면 자동 커밋 기능을 꺼야한다. 자동 커밋 설정여부는 Connection의 setAutoCommet()메소드로 할 수 있다.
conn.setAutoCommit(false);
자동커밋기능이 꺼지면 conn.commit(); conn.rollback();처럼 커밋과 롤백을 제어할 수 있다.
일반적인 코드 작성패턴은 다음과같다.
트랜젝션시작--
연결
자동커밋끄기conn.setAutoCommit(false);
소작업처리
소작업처리
커밋->모두 성공처리
트랜젝션 종료
catch문
롤백 -> 모두실패처리
finally
conn.setAutoCommit(true); 끊기전에 자동커밋 켜기
연결끊기
지금은 다시 켜줄 필요가 없지만 실제 프로젝트에서는 커넥션풀을 사용하기 때문에 다시 키는게 필요하다.
커넥션풀이란?
연결속도를 단축하는 것이다. db와 연결하는 시간이 오래걸리는데 커넥션풀은 미리 연결해놓앗다가 필요할때 제공하므로 속도가 빨라진다.
connection Pool에서 conn.close()를 하면 connection을 반환하는 것이다.
반납할땐 돌려줘야하니 다시 자동커밋을 키고 보내는 것이다.
//트랜잭션시작------------------------
//DB작업
//자동 커밋 기능 끄기
conn.setAutoCommit(false);
//출금 작업
String sql1 = "UPDATE accounts SET balance=balance-? WHERE ano=?";
PreparedStatement pstmt1 = conn.prepareStatement(sql1);
pstmt1.setInt(1, 10000);
pstmt1.setString(2, "111-111-1111");
int rows1 = pstmt1.executeUpdate();
if(rows1 == 0) throw new Exception("출금되지 않았음");
//실패하면 예외를 발생시킴 -> catch문으로 보내버림 그냥 Exception했는데 사용자정의예외만드는게 best임
pstmt1.close(); //이게닫힌다고 커밋을 안해서 연결이 끊기는게 아님.
//입금 작업
String sql2 = "UPDATE accounts SET balance=balance+? WHERE ano=?";
PreparedStatement pstmt2 = conn.prepareStatement(sql2);
pstmt2.setInt(1, 10000);
pstmt2.setString(2, "222-222-2222");
int rows2 = pstmt2.executeUpdate();
if(rows2 == 0) throw new Exception("입금되지 않았음");
pstmt2.close();
//수동 커밋 -> 모두 성공 처리
conn.commit();
System.out.println("계좌 이체 성공");