이는 10g에서의 SCN 번호를 가져올 수 있는 ORA_ROWSCN 함수를 사용해서 체크하는 방법을 이용하면 간단히 구현될 수 있다. 10g에서는 SCN 값의 변경사항 중 하나가 종래의 block level SCN에서 Row level SCN을 지원하게 되었다는 것이다(SCN은 System Change Number 또는 System Commit Number라고 병행해서 사용된다. 이 값은 커밋시마다 부여되는 오라클의 내부시계와 같은 역할을 수행한다).
기존에 BLOCK 레벨로 부여하던 SCN 값이 로우 레벨에 따라 다른 번호를 가질 수 있게 된 것이다. 따라서 이러한 ora_ rowscn 값을 이용해 테이블의 로우가 언제 변경되었는지에 대한 정보도 뽑아볼 수 있다. 그러나 모든 테이블에 다 적용되는 것이 아닌 ROW LEVEL SCN을 적용하게끔 테이블 생성시 ROW DEPENDENCIES 옵션으로 생성해야 한다는 것이다.
오라클 DB에 접속하는 Java 프로그램을 컴파일 하거나, 실행할 때Unsupported majar.minor version에러가 발생할 수 있습니다.
$ javac jdbcTest_CNT.java warning: oracle/jdbc/driver/OracleDriver.class(oracle/jdbc/driver:OracleDriver.class): major version 51 is newer than 50, the highest major version supported by this compiler. It is recommended that the compiler be upgraded. 1 warning
$ java jdbcTest_CNT Exception in thread "main" java.lang.UnsupportedClassVersionError: oracle/jdbc/driver/OracleDriver :Unsupported major.minor version51.0 at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631) at java.lang.ClassLoader.defineClass(ClassLoader.java:615) at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141) at java.net.URLClassLoader.defineClass(URLClassLoader.java:283) at java.net.URLClassLoader.access$000(URLClassLoader.java:58) at java.net.URLClassLoader$1.run(URLClassLoader.java:197) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:190) at java.lang.ClassLoader.loadClass(ClassLoader.java:306) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301) at java.lang.ClassLoader.loadClass(ClassLoader.java:247) at jdbcTest_CNT.main(jdbcTest_CNT.java:56)
원인은 OracleJDBC 버전과 JDK 버전, Java 버전이 서로 다르기 때문입니다.
예를 들어,
아래와 같이 ClassPath 에는 ojdbc7.jar 파일을 사용하도록 설정해놓고,
컴파일 or 실행할 때는 Java 6 버전(1.6 버전)을 쓰게 되면 이런 에러가 발생합니다.
sftp 접속 시 아이디와 패스워드를 사용하지 않고, 아이디와 개인키, 공개키를 사용하면 보안에 좀 더 좋을 수 있다.
(aws의 s3 ftp 사용시에도 키 파일을 이용하여 접속하라고 권고하고 있다.)
1. 접속하려는 클라이언트가 되는 서버에서 아래 명령으로 개인키, 공개키를 생성한다.
ssh-keygen -P "" -m PEM -f 원하는키명
(윈도우, 리눅스 동일)
위 명령 실행 시 파일이 2개가 생성되는데, 생성된 키는 아래와 같다.
원하는키명 <- 개인키
원하는키명.pub <- 공개키
끝에 .pub가 붙은 파일이 공개키 파일이다.
접속하려는 서버 담당자에게 공개키 파일을 전달하고, 개인키는 sftp 접속 시 사용해야 하므로 잘 보관해둔다.
JSch 접속 예제 소스
package com.tistory.hitomis.util;
import com.jcraft.jsch.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Vector;
public class SFTPUtil {
private Session session = null;
private Channel channel = null;
private ChannelSftp channelSftp = null;
/**
* 서버와 연결에 필요한 값들을 가져와 초기화 시킴
*
* @param host 서버 주소
* @param userName 아이디
* @param password 패스워드
* @param port 포트번호
* @param privateKey 개인키
*/
public void init(String host, String userName, String password, int port, String privateKey) {
JSch jSch = new JSch();
try {
if(privateKey != null) {//개인키가 존재한다면
jSch.addIdentity(privateKey);
}
session = jSch.getSession(userName, host, port);
if(privateKey == null && password != null) {//개인키가 없다면 패스워드로 접속
session.setPassword(password);
}
// 프로퍼티 설정
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no"); // 접속 시 hostkeychecking 여부
session.setConfig(config);
session.connect();
//sftp로 접속
channel = session.openChannel("sftp");
channel.connect();
} catch (JSchException e) {
e.printStackTrace();
}
channelSftp = (ChannelSftp) channel;
}
/**
* 디렉토리 생성
*
* @param dir 이동할 주소
* @param mkdirName 생성할 디렉토리명
*/
public void mkdir(String dir, String mkdirName) {
if (!this.exists(dir + "/" + mkdirName)) {
try {
channelSftp.cd(dir);
channelSftp.mkdir(mkdirName);
} catch (SftpException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 디렉토리( or 파일) 존재 여부
* @param path 디렉토리 (or 파일)
* @return
*/
public boolean exists(String path) {
Vector res = null;
try {
res = channelSftp.ls(path);
} catch (SftpException e) {
if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
return false;
}
}
return res != null && !res.isEmpty();
}
/**
* 파일 업로드
*
* @param dir 저장할 디렉토리
* @param file 저장할 파일
* @return 업로드 여부
*/
public boolean upload(String dir, File file) {
boolean isUpload = false;
SftpATTRS attrs;
FileInputStream in = null;
try {
in = new FileInputStream(file);
channelSftp.cd(dir);
channelSftp.put(in, file.getName());
// 업로드했는지 확인
if (this.exists(dir +"/"+file.getName())) {
isUpload = true;
}
} catch (SftpException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return isUpload;
}
/**
* 파일 다운로드
*
* @param dir 다운로드 할 디렉토리
* @param downloadFileName 다운로드 할 파일
* @param path 다운로드 후 로컬에 저장될 경로(파일명)
*/
public void download(String dir, String downloadFileName, String path) {
InputStream in = null;
FileOutputStream out = null;
try {
channelSftp.cd(dir);
in = channelSftp.get(downloadFileName);
} catch (SftpException e) {
e.printStackTrace();
}
try {
out = new FileOutputStream(new File(path));
int i;
while ((i = in.read()) != -1) {
out.write(i);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
out.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 연결 종료
*/
public void disconnection() {
channelSftp.quit();
session.disconnect();
}
}
사용방법 예제 소스
package com.tistory.hitomis;
import com.tistory.hitomis.util.SFTPUtil;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 테스트를 위한 클래스
*/
public class JavaTest {
public static void main(String[] args) {
final SFTPUtil sftpUtil = new SFTPUtil();
final String host = "접속할 서버 아이피";
final String userName = "접속할 아이디";
final int port = 22;
final String uploadPath = "업로드경로";
final String downloadPath = "다운로드경로";
final String privateKey = "개인키경로/파일명";
// 업로드 시 업로드 폴더 아래에
// 현재 날짜 년월일을 생성하고 그 아래 올리기 위한 날짜 변수
final Date today = new Date();
final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
// 접속
sftpUtil.init(host, userName, null, port, privateKey);
// 업로드 테스트
File uploadfile = new File("업로드할 경로+파일명"); // 파일 객체 생성
String mkdirPath = sdf.format(today); //현재날짜 년월일
sftpUtil.mkdir(uploadPath, mkdirPath); // 업로드경로에 현재날짜 년월일 폴더 생성
boolean isUpload = sftpUtil.upload(uploadPath+mkdirPath, uploadfile); //업로드
System.out.println("isUpload -" + isUpload); // 업로드 여부 확인
/* 다운로드 테스트 */
sftpUtil.download(downloadPath, "다운로드파일명", "로컬에저장할경로+파일명");
File downloadFile = new File("로컬에저장할경로+파일명");
if (downloadFile.exists()) {
System.out.println("다운로드 완료");
System.out.println(downloadFile.getPath());
System.out.println(downloadFile.getName());
}
// 업로드 다운로드 수행 후 꼭 연결을 끊어줘야 한다!!
sftpUtil.disconnection();
}
}
재미있는 기능 중 1가지는 SftpProgressMonitor 라는 인터페이스를 통해서 파일전송 진행현황(프로그래스, Progress)을 볼 수 있다.
//SftpProgressMonitor 인터페이스 정의 모습 ----
package com.jcraft.jsch;
public interface SftpProgressMonitor{
public static final int PUT=0;
public static final int GET=1;
public static final long UNKNOWN_SIZE = -1L;
void init(int op, String src, String dest, long max);
boolean count(long count);
void end();
}
위 인터페이스를 상속받아서 파일을 전송하는 put 메소드의 3번째 인자값으로 전달해주면 동작한다.
//..생략
public void sendFileToOtherServer(String sourcePath, String destinationPath) throws Exception {
channel = session.openChannel("sftp");
channel.connect();
channelSftp = (ChannelSftp) channel;
channelSftp.put(sourcePath, destinationPath, new SftpProgressMonitor() {
private long max = 0; //최대
private long count = 0; //계산을 위해 담아두는 변수
private long percent = 0; //퍼센트
@Override
public void init(int op, String src, String dest, long max) { //설정
this.max = max;
}
@Override
public void end() {
//종료시 할 행동
}
@Override
public boolean count(long bytes) {
this.count += bytes; //전송한 바이트를 더한다.
long percentNow = this.count*100/max; //현재값에서 최대값을 뺀후
if(percentNow>this.percent){ //퍼센트보다 크면
this.percent = percentNow;
System.out.println("progress : " + this.percent); //Progress
}
return true;//기본값은 false이며 false인 경우 count메소드를 호출하지 않는다.
}
});
channelSftp.disconnect();
channel.disconnect();
}