원본 http://blog.naver.com/st95041/40001613429

http://www.oraclejava.co.kr 의 PRO*C 에서..
CGI강좌로 된것이 있더군요... 몇회에 걸쳐서 되어 있는것을
나름 대로 정리해서 실습을 하고 검증 후 여러분들이 실습이
가능한 형태로 만들어 올려 봅니다.
앞으로 계속 몇회에 걸쳐 올리겠습니다.

이 강좌는 위 사이트에서 펀글을 위주로 하므로 저작에 대한 문제제기시
즉시 삭제 하겠습니다.

예제1 )
Web에서 Oracle DB에 접근하는 CGI(Common Gateway Interface)를
Oracle 에서 제공하는 Pro C로 만들어 봅시다.
Pro C는 C언어를 기본으로 하고 있으니 C만 아시면 쉽게 이해하실 수 있구요
cgi를 작성하기 위하여 cgic library를 이용합니다.
cgic library에 대한 구체적인 함수나 환경변수들에 대한 설명은 http://www.boutell.com/cgic 에 있습니다.

============================
http://www.boutell.com/cgic 에서 다운로드 받으면 cgic.h와 cgic.c가 나온다.
header(cgic.h) 파일은 include 디렉토리에 넣고.. (프로젝트에 자동 첨가됨)
cgic.c는 프로젝트에 add한다. (프로젝트에 수동 첨가해야함)
cgic.c나 cgic.h는 그대로 사용은 안될것이며 각자의 환경에 따라 path부분은
수정해 주어야 할 수 도 있다.
수정된 소스는 첨부하여 올린다.
===============================================================

다음 코드를 살펴보면... (PC 파일)

/*
아래의 테이블을 일단 만들어야 한다.

create table s_reg_db (
s_id number,
s_name varchar2(20),
s_age number,
s_sex number,
s_address varchar2(60),
s_telephone varchar2(20),
s_date date,
s_memo varchar2(400)
) */

#include <stdio.h>
#include <cgic.h>


#define USERNAME "scott"
#define PASSWORD "tiger"
#define DBSTRING "ora9i"

EXEC SQL BEGIN DECLARE SECTION;
// DB에 관련된 변수들을 선언
char *username = USERNAME;
char *password = PASSWORD;
char *dbstring = DBSTRING;
int count1;
VARCHAR re1[20];
int re2;
int re3;
VARCHAR re4[60];
VARCHAR re5[20];
VARCHAR re6[2000];
VARCHAR re7[8];
EXEC SQL END DECLARE SECTION;

EXEC SQL INCLUDE sqlca;
void sqlerror();
int getCgiParameter();
void setMemory ( void );

/* 반드시 cgiMain 으로 시작함*/
int cgiMain ( void )
{

int result;

//각자 수정 필요 부분
putenv("ORACLE_HOME=c:\\oracle\\ora92");

putenv("ORACLE_SID=ora9i");

putenv("NLS_LANG=AMERICAN_AMERICA.KO16KSC5601");

//putenv("LD_LIBRARY_PATH=/oracle/733/lib");


EXEC SQL WHENEVER SQLERROR DO sqlerror();

EXEC ORACLE OPTION(HOLD_CURSOR=NO);

EXEC SQL CONNECT :username IDENTIFIED BY :password USING :dbstring;


cgiHeaderContentType("text/html");
setMemory(); // 변수초기화
getCgiParameter(); // html에서 받은 입력자료를 위에서 선언한 변수로 대입시켜주는 함수

EXEC SQL SELECT to_char(SYSDATE,'YY-MM-DD') into re7 from sys.dual;

EXEC SQL SELECT NVL(max(S_ID),0) INTO :count1 FROM s_reg_db order by s_date;
count1++;
EXEC SQL INSERT INTO s_reg_db ( s_id , s_name , s_age , s_sex , s_address , s_telephone , s_date , s_memo )
VALUES ( :count1, :re1 , :re2 , :re3 , :re4 , :re5, SYSDATE , :re6 );
fprintf(cgiOut, "Insert ok\n");
fprintf(cgiOut, "끝!");
EXEC SQL COMMIT RELEASE;

//commit을 한 후 연결을 계속 유지시킨다. 만약 EXEC SQL COMMIT하면 연결을 끊는다.

return 0;
}


void sqlerror()
{ // DB error 처리

EXEC SQL WHENEVER SQLERROR CONTINUE;
fprintf(cgiOut, "ORACLE Error detected: \n");
fprintf(cgiOut, "% .70s \n", sqlca.sqlerrm.sqlerrmc);
EXEC SQL ROLLBACK WORK RELEASE;
exit(1);

}


int getCgiParameter()
{
int result; char x[3];
result = cgiFormString ( "reg1", re1.arr , sizeof(re1.arr)); // reg1은 html에서 설정된 변수명

re1.len = strlen( (char *)re1.arr );
result = cgiFormString ( "reg4" , re4.arr , sizeof(re4.arr));
re4.len = strlen( (char *)re4.arr );
result = cgiFormString ( "reg5" , re5.arr , sizeof(re5.arr));
re5.len = strlen( (char *)re5.arr );
result = cgiFormString ( "reg6" , re6.arr , sizeof(re6.arr));
re6.len = strlen( (char *)re6.arr );
result = cgiFormString ( "reg2" , x , 3 );
re2 = atoi ( x ); result = cgiFormString ( "reg3" , x , 3 );
re3 = atoi ( x );
if ( result == cgiFormNotFound ) {
re3 = -1;
}
else {
re3 = atoi ( x );
}

fprintf(cgiOut," name : %s \n\n",re1.arr);
fprintf(cgiOut," age : %d\n\n",re2);
if ( re3 == 1 ) {
fprintf(cgiOut," sex : male\n");
}
else if ( re3 == 2 ) {
fprintf(cgiOut," sex : female\n");
}
fprintf(cgiOut," address : %s\n\n",re4.arr);
fprintf(cgiOut," telephone : %s\n\n",re5.arr);
fprintf(cgiOut,"memo : %s\n\n",re6.arr);
}


void setMemory ( void )
{

memset(re1.arr,0,sizeof(re1.arr)); // memset을 이용하여 문자열변수(varchar2)들을 초기화 해준다.

memset(re4.arr,0,sizeof(re4.arr));

memset(re5.arr,0,sizeof(re5.arr));

memset(re6.arr,0,sizeof(re6.arr));

memset(re7.arr,0,sizeof(re7.arr));

re2 = -1; re3 = -1; count1 = 0; // 숫자변수 초기화

}


컴파일되었다면 html은 아래와 같이 만들어서 APAHCH의
htdocs 디렉토리에 넣고
실행파일은 cgi-bin에 넣는다. web-browser를 돌려서
http://test20.html 을 실행한다.
각각 입력 후 submit한 다음
sqlplus 를 띄워 값이 insert 된는지 확인하라.

<html>

<body>

<form action=/cgi-bin/test20.exe method=post>

이름<input type=text name=reg1> <br>

나이<input type=text name=reg2><br>

성별<input type=text name=reg3> (male:1/female:2)<br>

주소<input type=text name=reg4><br>

전화<input type=text name=reg5><br>

메모<input type=text name=reg6><br>

<input type=submit value="완료">

</form>

</body>

</html>

, .


원본 http://blog.naver.com/st95041/40001613439

오늘은 게시판처럼 List를 Table로 보여주는 기능이 들어있는 cgi를 작성하여 보도록 하겠습니다.

이 소스는 첫번째 강좌의 cgic.c와 cgic.h를 필요로 합니다.
또한 이 소스 역시 제가 임의로 수정해서 올리는 것입니다.
물론 테스트 했습니다.
#include <stdio.h>

#include <cgic.h>

#define USERNAME "scott"
#define PASSWORD "tiger"

#define DBSTRING "ora9i"

#define NEXT 5 // 한화면에 5개의 개시물을 보여주기 위한 선언


EXEC SQL BEGIN DECLARE SECTION;

char *username = USERNAME;
char *password = PASSWORD;
char *dbstring = DBSTRING;

int re2;
int re3;

int page1,page2,page3;

int pagecount1;
int pagecount2;
int pagecount4;

int temp;
int allpage;

int i,j,flag1;
int id;
int xx;

VARCHAR re1[20];
VARCHAR re4[60];
VARCHAR re5[20];
VARCHAR re6[2000];
VARCHAR re7[10];
VARCHAR sqlstmt[200];

EXEC SQL END DECLARE SECTION;


EXEC SQL INCLUDE sqlca;

void sqlerror();
void setMemory ( );
void getCgiParameter();



int cgiMain ( void ) {
int nextflag = 0;
int prevflag = 0;
char nn[200];
char pr[200];

putenv("ORACLE_HOME=c:\\oracle\\ora92");
putenv("ORACLE_SID=ora9i");
putenv("NLS_LANG=AMERICAN_AMERICA.KO16KSC5601");
//putenv("LD_LIBRARY_PATH=/usr/oracle/733/lib");


EXEC SQL WHENEVER SQLERROR DO sqlerror();
EXEC ORACLE OPTION(RELEASE_CURSOR=YES);
EXEC ORACLE OPTION(HOLD_CURSOR=NO);

EXEC SQL CONNECT :username IDENTIFIED BY :password USING :dbstring;

cgiHeaderContentType("text/html"); // web으로 보여줘야 하게 때문에 html라고 header에 정의

EXEC SQL SELECT count(*) INTO : allpage FROM s_reg_db;//총 개수를 allpage변수에 저장시킴

sprintf(sqlstmt.arr," select S_NAME , S_AGE , S_SEX , S_ADDRESS , S_TELEPHONE , to_char(S_DATE, 'yy/mm/dd') , S_MEMO, S_ID "
" from s_reg_db "
" order by S_DATE "); // list에 넣을 데이터를 query해옴


sqlstmt.len = strlen( (char *)sqlstmt.arr); // 이것 꼭 써줘야 올바르게 값이 나옵니다!!



/* 커서 설정 */

EXEC SQL PREPARE s_reg_result FROM :sqlstmt;

EXEC SQL DECLARE reg_cursor CURSOR for s_reg_result;

EXEC SQL OPEN reg_cursor;


/* 나이별로 검색하는 부분 - 다음시간에 할것임*/
fprintf(cgiOut,"<form action=/cgi-bin/test20.exe method=post>");
fprintf(cgiOut,"<h5>Age Search</h5>");

fprintf(cgiOut,"<input type=text name=st_age maxlength=5 size=10 >~<input type=text name=ed_age maxlength=5 size=10 >");
fprintf(cgiOut,"<br><p>");
fprintf(cgiOut,"<input type=hidden name=s value=1>");
fprintf(cgiOut,"<input type=hidden name=page value=1>");
fprintf(cgiOut,"<input type=text name=search1>");
fprintf(cgiOut,"<input type=submit value=search>");
fprintf(cgiOut,"</form>");


/* 웹에 보여주는 list table의 label을 작성하는 부분*/
fprintf(cgiOut,"<table border=1 width=100%>");
fprintf(cgiOut,"<tr>");
fprintf(cgiOut,"<td>");
fprintf(cgiOut,"<font size=2>");
fprintf(cgiOut,"Number");
fprintf(cgiOut,"</font></td>");
fprintf(cgiOut,"<td>");
fprintf(cgiOut,"<font size=2>");
fprintf(cgiOut,"Write Date");
fprintf(cgiOut,"</font></td>");
fprintf(cgiOut,"<td>");
fprintf(cgiOut,"<font size=2>");
fprintf(cgiOut,"Name");
fprintf(cgiOut,"</font></td>");
fprintf(cgiOut,"<td>");
fprintf(cgiOut,"<font size=2>");
fprintf(cgiOut,"Age");
fprintf(cgiOut,"</font></td>");
fprintf(cgiOut,"<td>");
fprintf(cgiOut,"<font size=2>");
fprintf(cgiOut,"Sex");
fprintf(cgiOut,"</font></td>");
fprintf(cgiOut,"<td>");
fprintf(cgiOut,"<font size=2>");
fprintf(cgiOut,"Address");
fprintf(cgiOut,"</font></td>");
fprintf(cgiOut,"<td>");
fprintf(cgiOut,"<font size=2>");
fprintf(cgiOut,"Telephone");
fprintf(cgiOut,"</font></td>");
fprintf(cgiOut,"<td>");
fprintf(cgiOut,"<font size=2>");
fprintf(cgiOut,"Memo");
fprintf(cgiOut,"</font></td>");
fprintf(cgiOut,"</tr>");


getCgiParameter(); // 페이지수를 받아오는 함수
if(page1 == NULL || page1 == 0) page1 = 1;

pagecount1 = page1; //현재 페이지.



pagecount2 = pagecount1 * NEXT; //pagecount2 = pagecount1 * 5; //

for ( i=1 ; i <= pagecount2 ; i++)
{
setMemory(); // 변수 초기화

if ( page1 > 1 && flag1==0)
{
for ( j=1 ; j<=((page1-1)*5) ; j++ )

{
EXEC SQL FETCH reg_cursor INTO :re1 , :re2 , :re3 , :re4 , :re5 , :re7 , :re6 , id; //위에서 만든 커서를 가지고 1개씩 row를 fetch해옴
}

i=j;

flag1=1;
}

EXEC SQL FETCH reg_cursor INTO :re1, :re2 , :re3, :re4, :re5, :re7, :re6, id;

if ( sqlca.sqlcode == 1403 ) /* 더 이상의 데이터가 없다면 중단하라는 문장 */
break;

/* 실제 query해온 data를 table에 찍어줌 */

fprintf(cgiOut,"<tr>");
fprintf(cgiOut,"<td>");
fprintf(cgiOut,"<font size=2>");
fprintf(cgiOut,"%d",id);
fprintf(cgiOut,"</font></td>");
fprintf(cgiOut,"<td>");
fprintf(cgiOut,"<font size=2>");
fprintf(cgiOut,"%s",re7.arr);
fprintf(cgiOut,"</font></td>");
fprintf(cgiOut,"<td>");
fprintf(cgiOut,"<font size=2>");
fprintf(cgiOut,"%s",re1.arr);
fprintf(cgiOut,"</font></td>");
fprintf(cgiOut,"<td>");
fprintf(cgiOut,"%d",re2);
fprintf(cgiOut,"</font></td>");
fprintf(cgiOut,"<td>");
fprintf(cgiOut,"<font size=2>");

if ( re3 == 1 ) {
fprintf(cgiOut,"Male");
}
else if ( re3 == 2 ) {
fprintf(cgiOut,"Female");
}
fprintf(cgiOut,"</font></td>");
fprintf(cgiOut,"<td>");
fprintf(cgiOut,"<font size=2>");
fprintf(cgiOut,"%s",re4.arr);
fprintf(cgiOut,"</font></td>");
fprintf(cgiOut,"<td>");
fprintf(cgiOut,"<font size=2>");
fprintf(cgiOut,"%s",re5.arr);
fprintf(cgiOut,"</font></td>");
fprintf(cgiOut,"<td>");
fprintf(cgiOut,"<font size=2>");
fprintf(cgiOut,"%s",re6.arr);
fprintf(cgiOut,"</font></td>");

}

fprintf(cgiOut,"</tr>");
fprintf(cgiOut,"</table>");
fprintf(cgiOut,"<br><p>");


EXEC SQL close reg_cursor;

if ( allpage == 0 ) exit(1);

page3 = page1;
if ( allpage > NEXT && allpage > (page1*NEXT))
{
flag1=0;
nextflag =1;
sprintf(nn," \\\<a href=/cgi-bin/test21.exe?page=%d>Next</a>",++page1); // Next를 눌렀을 때 자신의 cgi를 다시 호출하여 사용 */
page2=page1-1;
page3 = page2;
}

if ( allpage > 5 && (page3*5) >= 10)
{
flag1=0;
prevflag = 1;
sprintf(pr," \\\<a href=/cgi-bin/test21.exe?page=%d>Prev</a>",--page3);
}

if(prevflag == 1)
fprintf(cgiOut,"%s",pr);

if(nextflag == 1)
fprintf(cgiOut,"%s",nn); // Next를 눌렀을 때 자신의 cgi를 다시 호출하여 사용 */


fprintf(cgiOut," \\\ <a href=/test20.html>Write</a>");

return 0;
}


void sqlerror() {
EXEC SQL WHENEVER SQLERROR CONTINUE;

fprintf(cgiOut, "ORACLE Error detected:<BR>\n");
fprintf(cgiOut, "% .70s <BR>\n", sqlca.sqlerrm.sqlerrmc);

EXEC SQL ROLLBACK WORK RELEASE;

exit(1);
}


void setMemory ( ) { /* 변수초기화*/

memset(re1.arr,0,sizeof(re1.arr));
memset(re4.arr,0,sizeof(re4.arr));
memset(re5.arr,0,sizeof(re5.arr));
memset(re6.arr,0,sizeof(re6.arr));
memset(re7.arr,0,sizeof(re7.arr));

re2=NULL;
re3=NULL;

id=NULL;
}


void getCgiParameter() { /* page값을 받아옴 */
int result;
char x[3];

result = cgiFormString ("page",x,3);
page1 = atoi (x);

}

, .

원본 http://blog.naver.com/st95041/40001613473

물론 sqlj라는게 있긴 하지만..저는 sqlj는 얼마전에 설명을 한번 했습니다.
그러나..sqlj보다는 pro*C가 수행능력이 훨씬 뛰어나고 더 많이 사용하고
있습니다.
그래서 우리는 pro*C를 하는 것입니다. 그런데 자바 프로젝트를 할 때
어떻게 pro*C 를 사용할 수 있을까 하는 의문이 생깁니다.
즉, 대량의 데이타 처리를 위해 우리는 그냥 java + JDBC, sqlj, pro*C 중
어느 것을 택할것인가에 대한 선택의 문제에 봉착하게 되는데..
우리는 그중에 pro*C를 선택한다는 것입니다.
java + JDBC, sqlj는 당연히 별 문제 없이 그냥 사용할 수 있는 단순한
문제 이므로 우리는 가장 사용하기 힘들지만 가장 많이 사용하는
pro*C를 사용하자는 것인데...자바와 C를 어케 인터페이스 하느냐?
그것이 바로 JNI 입니다. 즉, 바로 아래 게시물에 있는 java + C 인 것입니다.
그것을 좀더 제가 발전시켜 java (JNI) + pro*C를 구현해 보았습니다.


1. 환경 설정은 아래와 같다.

- jdk 는 오라클 인스톨시 사용한 것을 바로 사용함.
- vc 는 Visual Studio 6.0을 이용함.
- Oracle은 9i 사용


set JAVA_HOME=c:\oracle\ora92\jdk
set JDK_CLASSES=%JAVA_HOME%\lib\tools.jar;%JAVA_HOME%\lib\dt.jar
set CLASSPATH=%JDK_CLASSES%;.;
SET PATH=%PATh%;%JAVA_HOME%\bin;%JAVA_HOME%;

2. 작업 디렉토리는 c:\tmp\test 인데 Visual Studio(vc++)를 띄운 후
File메뉴-> New->Projects->Win32 Dynamic-Link Library 선택
project name을 test27으로 입력함 --> OK -->창이 바뀌면서 What kind of dll would like create? 에
An empty Dll project 선택 --> 확인
: 탐색기로 확인해 보라. C"\TMP\TEST\TEST27 디렉토리에 몇 개의 작업 파일이 생성되 있을 것이다.


3. 자바 파일(BackupBatch.java)을 생성한다.

public class BackupBatch {

static
{
System.out.println("=== library was loaded");
System.load("C:\\tmp\\test\\test27\\Debug\\test27.dll");
}

public native void startBackup();

public static void main(String[] args) throws Exception
{
new BackupBatch().startBackup();
}

}


4. javac BackupBatch.java
--> BackupBatch.class 생성


5. javah BackupBatch
--> BackupBatch.h 생성


6. vc에서
Project메뉴->Add to Project --> Files 하여 BackupBatch.h를 추가한다.


7. Pro* C파일을 생성한다.


/* MyBatchProc.pc */
/* 아래와 같은 두개의 테이블이 있는데 sales에서 stat로 데이터를 batch로 복사하는 프로그램
으로 구현함. sales는 10개 이상의 Data를 입력한 상태에서 테스트

SQL> desc sales;
이름 널? 유형
----------------------------------------- -------- ----------------
NO NOT NULL NUMBER
SALE_CODE NOT NULL NUMBER
AMOUNT NUMBER
SALE_DATE DATE
SALE_MAN VARCHAR2(200)

SQL> desc stat;
이름 널? 유형
----------------------------------------- -------- ----------------
NO NOT NULL NUMBER
SALE_CODE NOT NULL NUMBER
SALE_DATE VARCHAR2(20)
AMOUNT NUMBER
*/

#include <stdio.h>
#include <stdlib.h>
#include <sqlca.h>

#define ARR_LENGTH 10

char *username = "SCOTT";
char *password = "TIGER";

/* Declare a host structure tag. */
EXEC SQL INCLUDE sqlca;

struct {

int NO[ARR_LENGTH];
int SALE_CODE[ARR_LENGTH];
int AMOUNT[ARR_LENGTH];

VARCHAR SALE_DATE[ARR_LENGTH][20];
VARCHAR SALE_MAN[ARR_LENGTH][200];
} sale_rec;

EXEC SQL BEGIN DECLARE SECTION;


EXEC SQL END DECLARE SECTION;

void print_rows(int n);
void sql_error(char *msg);
int main()
{
int num_ret; /* number of rows returned */
int i;

/* Connect to ORACLE. */

EXEC SQL WHENEVER SQLERROR DO sql_error("Connect error:");
EXEC SQL CONNECT :username IDENTIFIED BY :password;

printf("\nConnected to ORACLE as user: %s\n", username);

EXEC SQL WHENEVER SQLERROR DO sql_error("Oracle error:");

/* Declare a cursor for the FETCH. */

EXEC SQL DECLARE c1 CURSOR FOR
SELECT no, sale_code,amount,to_char(sale_date,'yyyy-mm-dd'),sale_man
FROM sales
where to_char(sale_date,'yyyymmdd') like '200303%' ;
EXEC SQL OPEN c1;


//위 쿼리에서 to_char(sale_date,'yyyymmdd') like '200303%'는 엄청
//문제있는 부분입니다. 실제 쿼리 튜닝시 반드시 수정해 주어야 합니다만...
//여기서의 문제에서는 제외하도록 합니다. (문제의 핵심은 튜닝을 얘기하는것이 아니라서)
//튜닝시 해결하는 방법은 임시테이블을 만들고 그 임시테이블에다
//insert 하는데 2003-03-01 ~ 2003-03-31까지를 insert한 후
//where sale_date in(select tmp_date from tmp_tbl)
//이런식으로 쿼리하도록 합니다. 음하하 쉽지요?

num_ret = 0;


/* Array fetch loop - ends when NOT FOUND becomes true. */

EXEC SQL WHENEVER NOT FOUND DO break;
for (;;)
{
printf("----------------------------------- 10개 단위로 fetch\n");
//만약 레코드가 100만건 정도 된다면 시스템에 따라 다르지만
// 보통 5000건 단위로 fetch 하는것이 대체로 유리함
//여기서는 10건 단위의 fetch

EXEC SQL FETCH c1 INTO :sale_rec;

/* Print however many rows were returned. */
print_rows(sqlca.sqlerrd[2] - num_ret);
num_ret = sqlca.sqlerrd[2]; /* Reset the number. */
for(i = 0; i < num_ret; i++)
sale_rec.SALE_DATE[i].arr[sale_rec.SALE_DATE[i].len] = 0x00;

EXEC SQL
INSERT INTO stat(no,sale_code,sale_date,amount)
VALUES ( :sale_rec.NO, :sale_rec.SALE_CODE, :sale_rec.SALE_DATE, :sale_rec.AMOUNT);

EXEC SQL COMMIT;

printf("================= insert ok");

}

/* Print remaining rows from last fetch, if any. */
if ((sqlca.sqlerrd[2] - num_ret) > 0)
print_rows(sqlca.sqlerrd[2] - num_ret);

EXEC SQL CLOSE c1;
printf("OK insert.\n\n\n");
/* Disconnect from the database. */

exit(0);
}


void sql_error(char *msg)
{
EXEC SQL WHENEVER SQLERROR CONTINUE;
printf("\n%s", msg);
printf("\n% .70s \n", sqlca.sqlerrm.sqlerrmc);
EXEC SQL ROLLBACK WORK RELEASE;
exit(1);
}

void print_rows(int n)
{
int i;
printf("\nNumber sale_code sale_date");
printf("\n------ -------- -------\n");
for (i = 0; i < n; i++)
{
printf("%d %d %s\n", sale_rec.NO[i], sale_rec.SALE_CODE[i], sale_rec.SALE_DATE[i].arr);
}

}


8. vc에서
위에서 생성한 MyBatchProc.pc를 Project메뉴->Add to Project --> Files 하여 추가한다.
화면왼쪽의 FileView Tab에서 pc파일을 선택 후 마우스 오른쪽 클릭--> setting 선택

Custom Build 탭의 Command 부분에
proc parse=full iname="C:\tmp\test\test27\MyBatchProc.pc" include="c:\Program Files\Microsoft Visual Studio\vc98\include"


Outputs 부분에
oname="c:\tmp\test\test27\MyBatchProc.c"
라고 입력한후 ok를 클릭하고

다시 위에서 생성한 MyBatchProc.pc를 화면왼쪽의 FileView Tab에서 선택 후 마우스 오른쪽 클릭--> compile한다.


9. MyBatchProc.c를 더블클릭하여 Open한다.
아래가 proc를 precompile하여 생성한 c 파일로서 아래와 같이 편집한다.

/* 전체적으로 변경 부분은 3 부분이다.
1. header 추가
2. main 함수를 jni 함수로 변경
3. 원래 main함수의 exit(0)를 return으로 변경

*/

//////////////////////////////////////////////////////////////////////////////////////////////////

/* 이 부분은 c파일에 추가된 부분으로 jni를 사용하기 위해

신규로 추가해야 한다 */

#include <jni.h>

#include "BackupBatch.h"

#include <stdio.h>

//////////////////////////////////////////////////////////////////////////////////////////////////

/* Result Sets Interface */

#ifndef SQL_CRSR

# define SQL_CRSR

struct sql_cursor

{

unsigned int curocn;
void *ptr1;
void *ptr2;
unsigned int magic;
};
typedef struct sql_cursor sql_cursor;
typedef struct sql_cursor SQL_CURSOR;
#endif /* SQL_CRSR */


/* Thread Safety */

typedef void * sql_context;
typedef void * SQL_CONTEXT;

/* Object support */

struct sqltvn
{

unsigned char *tvnvsn;
unsigned short tvnvsnl;
unsigned char *tvnnm;
unsigned short tvnnml;
unsigned char *tvnsnm;
unsigned short tvnsnml;
};
typedef struct sqltvn sqltvn;

struct sqladts
{

unsigned int adtvsn;
unsigned short adtmode;
unsigned short adtnum;

sqltvn adttvn[1];

};
typedef struct sqladts sqladts;

static struct sqladts sqladt = {

1,1,0,
};

/* Binding to PL/SQL Records */

struct sqltdss
{

unsigned int tdsvsn;
unsigned short tdsnum;
unsigned char *tdsval[1];
};
typedef struct sqltdss sqltdss;
static struct sqltdss sqltds =
{

1,
0,
};

/* File name & Package Name */

struct sqlcxp
{

unsigned short fillen;
char filnam[34];
};
static const struct sqlcxp sqlfpn =
{

33,
"c:\\tmp\\test\\test27\\MyBatchProc.pc"

};
static unsigned int sqlctx = 2133591581;
static struct sqlexd {

unsigned int sqlvsn;
unsigned int arrsiz;
unsigned int iters;
unsigned int offset;
unsigned short selerr;
unsigned short sqlety;
unsigned int occurs;
const short *cud;
unsigned char *sqlest;
const char *stmt;
sqladts *sqladtp;
sqltdss *sqltdsp;
void **sqphsv;
unsigned int *sqphsl;
int *sqphss;
void **sqpind;
int *sqpins;
unsigned int *sqparm;
unsigned int **sqparc;
unsigned short *sqpadto;
unsigned short *sqptdso;
unsigned int sqlcmax;
unsigned int sqlcmin;
unsigned int sqlcincr;
unsigned int sqlctimeout;
unsigned int sqlcnowait;
int sqfoff;
unsigned int sqcmod;
unsigned int sqfmod;
void *sqhstv[5];
unsigned int sqhstl[5];
int sqhsts[5];
void *sqindv[5];
int sqinds[5];
unsigned int sqharm[5];
unsigned int *sqharc[5];
unsigned short sqadto[5];
unsigned short sqtdso[5];
} sqlstm = {12,5};

/* SQLLIB Prototypes */

extern void sqlcxt (void **, unsigned int *,
struct sqlexd *, const struct sqlcxp *);
extern void sqlcx2t(void **, unsigned int *,
struct sqlexd *, const struct sqlcxp *);
extern void sqlbuft(void **, char *);
extern void sqlgs2t(void **, char *);
extern void sqlorat(void **, unsigned int *, void *);

/* Forms Interface */

static const int IAPSUCC = 0;
static const int IAPFAIL = 1403;
static const int IAPFTL = 535;
extern void sqliem(char *, int *);

static const char *sq0002 =

"select no ,sale_code ,amount ,to_char(sale_date,'yyyy-mm-dd') ,sale_man fr\

om sales where to_char(sale_date,'yyyymmdd') like '200303%' ";

typedef struct { unsigned short len; unsigned char arr[1]; } VARCHAR;
typedef struct { unsigned short len; unsigned char arr[1]; } varchar;

/* cud (compilation unit data) array */

static const short sqlcud0[] =
{12,4130,846,0,0,
5,0,0,1,0,0,27,44,0,0,4,4,0,1,0,1,97,0,0,1,97,0,0,1,10,0,0,1,10,0,0,
36,0,0,2,145,0,9,56,0,0,0,0,0,1,0,
51,0,0,2,0,0,13,78,0,0,5,0,0,1,0,2,3,0,0,2,3,0,0,2,3,0,0,2,9,0,0,2,9,0,0,
86,0,0,3,73,0,3,86,0,0,4,4,0,1,0,1,3,0,0,1,3,0,0,1,9,0,0,1,3,0,0,
117,0,0,4,0,0,29,90,0,0,0,0,0,1,0,
132,0,0,2,0,0,15,100,0,0,0,0,0,1,0,
147,0,0,5,0,0,32,111,0,0,0,0,0,1,0,
};
#include <stdio.h>

#include <stdlib.h>

#include <sqlca.h>


#define ARR_LENGTH 10


char *username = "SCOTT";
char *password = "TIGER";


/* Declare a host structure tag. */


/* EXEC SQL INCLUDE sqlca;
*/

/*

* $Header: sqlca.h,v 1.3 1994/12/12 19:27:27 jbasu Exp $ sqlca.h

*/

/* Copyright (c) 1985,1986, 1998 by Oracle Corporation. */

/*

NAME

SQLCA : SQL Communications Area.

FUNCTION

Contains no code. Oracle fills in the SQLCA with status info

during the execution of a SQL stmt.

NOTES

**************************************************************
*** ***
*** This file is SOSD. Porters must change the data types ***
*** appropriately on their platform. See notes/pcport.doc ***
*** for more information. ***
*** ***
**************************************************************

If the symbol SQLCA_STORAGE_CLASS is defined, then the SQLCA
will be defined to have this storage class. For example:
#define SQLCA_STORAGE_CLASS extern
will define the SQLCA as an extern.

If the symbol SQLCA_INIT is defined, then the SQLCA will be
statically initialized. Although this is not necessary in order
to use the SQLCA, it is a good pgming practice not to have
unitialized variables. However, some C compilers/OS's don't
allow automatic variables to be init'd in this manner. Therefore, if you are INCLUDE'ing the SQLCA in a place where it would be
an automatic AND your C compiler/OS doesn't allow this style
of initialization, then SQLCA_INIT should be left undefined --
all others can define SQLCA_INIT if they wish.


If the symbol SQLCA_NONE is defined, then the SQLCA variable will
not be defined at all. The symbol SQLCA_NONE should not be defined
in source modules that have embedded SQL. However, source modules
that have no embedded SQL, but need to manipulate a sqlca struct
passed in as a parameter, can set the SQLCA_NONE symbol to avoid
creation of an extraneous sqlca variable.

MODIFIED
lvbcheng 07/31/98 - long to int
jbasu 12/12/94 - Bug 217878: note this is an SOSD file
losborne 08/11/92 - No sqlca var if SQLCA_NONE macro set
Clare 12/06/84 - Ch SQLCA to not be an extern.
Clare 10/21/85 - Add initialization.
Bradbury 01/05/86 - Only initialize when SQLCA_INIT set
Clare 06/12/86 - Add SQLCA_STORAGE_CLASS option.
*/


#ifndef SQLCA
#define SQLCA 1

struct sqlca

{

/* ub1 */ char sqlcaid[8];
/* b4 */ int sqlabc;
/* b4 */ int sqlcode;
struct

{

/* ub2 */ unsigned short sqlerrml;
/* ub1 */ char sqlerrmc[70];
} sqlerrm;
/* ub1 */ char sqlerrp[8];
/* b4 */ int sqlerrd[6];
/* ub1 */ char sqlwarn[8];
/* ub1 */ char sqlext[8];
};

#ifndef SQLCA_NONE

#ifdef SQLCA_STORAGE_CLASS

SQLCA_STORAGE_CLASS struct sqlca sqlca

#else

struct sqlca sqlca

#endif


#ifdef SQLCA_INIT
= {
{'S', 'Q', 'L', 'C', 'A', ' ', ' ', ' '},
sizeof(struct sqlca),
0,
{ 0, {0}},
{'N', 'O', 'T', ' ', 'S', 'E', 'T', ' '},
{0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0}

}

#endif

;
#endif


#endif


/* end SQLCA */


/* */

/*

* $Header: sqlca.h,v 1.3 1994/12/12 19:27:27 jbasu Exp $ sqlca.h

*/


/* Copyright (c) 1985,1986, 1998 by Oracle Corporation. */


/*

NAME

SQLCA : SQL Communications Area.

FUNCTION

Contains no code. Oracle fills in the SQLCA with status info

during the execution of a SQL stmt.

NOTES

**************************************************************
*** ***
*** This file is SOSD. Porters must change the data types ***
*** appropriately on their platform. See notes/pcport.doc ***
*** for more information. ***
*** ***
**************************************************************

If the symbol SQLCA_STORAGE_CLASS is defined, then the SQLCA
will be defined to have this storage class. For example:
#define SQLCA_STORAGE_CLASS extern
will define the SQLCA as an extern.

If the symbol SQLCA_INIT is defined, then the SQLCA will be
statically initialized. Although this is not necessary in order
to use the SQLCA, it is a good pgming practice not to have
unitialized variables. However, some C compilers/OS's don't
allow automatic variables to be init'd in this manner. Therefore,
if you are INCLUDE'ing the SQLCA in a place where it would be
an automatic AND your C compiler/OS doesn't allow this style
of initialization, then SQLCA_INIT should be left undefined --
all others can define SQLCA_INIT if they wish.


If the symbol SQLCA_NONE is defined, then the SQLCA variable will
not be defined at all. The symbol SQLCA_NONE should not be defined
in source modules that have embedded SQL. However, source modules
that have no embedded SQL, but need to manipulate a sqlca struct
passed in as a parameter, can set the SQLCA_NONE symbol to avoid
creation of an extraneous sqlca variable.

MODIFIED
lvbcheng 07/31/98 - long to int
jbasu 12/12/94 - Bug 217878: note this is an SOSD file
losborne 08/11/92 - No sqlca var if SQLCA_NONE macro set
Clare 12/06/84 - Ch SQLCA to not be an extern.
Clare 10/21/85 - Add initialization.
Bradbury 01/05/86 - Only initialize when SQLCA_INIT set
Clare 06/12/86 - Add SQLCA_STORAGE_CLASS option.
*/


#ifndef SQLCA

#define SQLCA 1


struct sqlca

{

/* ub1 */ char sqlcaid[8];
/* b4 */ int sqlabc;
/* b4 */ int sqlcode;
struct

{

/* ub2 */ unsigned short sqlerrml;
/* ub1 */ char sqlerrmc[70];
} sqlerrm;
/* ub1 */ char sqlerrp[8];
/* b4 */ int sqlerrd[6];
/* ub1 */ char sqlwarn[8];
/* ub1 */ char sqlext[8];
};

#ifndef SQLCA_NONE

#ifdef SQLCA_STORAGE_CLASS

SQLCA_STORAGE_CLASS struct sqlca sqlca

#else

struct sqlca sqlca

#endif


#ifdef SQLCA_INIT

= {

{'S', 'Q', 'L', 'C', 'A', ' ', ' ', ' '},
sizeof(struct sqlca),
0,
{ 0, {0}},
{'N', 'O', 'T', ' ', 'S', 'E', 'T', ' '},
{0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0}

}

#endif

;
#endif


#endif


/* end SQLCA */

struct
{

int NO[ARR_LENGTH];

int SALE_CODE[ARR_LENGTH];

int AMOUNT[ARR_LENGTH];

/* VARCHAR SALE_DATE[ARR_LENGTH][20]; */

struct { unsigned short len; unsigned char arr[22]; } SALE_DATE[10];

/* VARCHAR SALE_MAN[ARR_LENGTH][200]; */

struct { unsigned short len; unsigned char arr[202]; } SALE_MAN[10];

} sale_rec;

/* EXEC SQL BEGIN DECLARE SECTION; */


/* EXEC SQL END DECLARE SECTION; */ void print_rows(int n);
void sql_error(char *msg);

//////////////////////////////////////////////////////////////////////////////////////////////////////////

/* 이 부분이 핵심이다.
원래 이 부분은 int main(){ 이렇게 되어 있는 c 파일의 메인 함수
부분이였다. 그러나, JNI를 사용하기 위한 메소드 부분으로 교체해 주어야
한다. 즉,int main(){ 대신에 아래의 한 라인으로만 대체하면 끝난다.

JNIEXPORT void JNICALL Java_BackupBatch_startBackup(JNIEnv *env, jobject obj) {

///////////////////////////////////////////////////////////////////////////////////////////////////////////
int num_ret; /* number of rows returned */
int i;

/* Connect to ORACLE. */

/* EXEC SQL WHENEVER SQLERROR DO sql_error("Connect error:"); */


/* EXEC SQL CONNECT :username IDENTIFIED BY :password; */

{

struct sqlexd sqlstm;
sqlstm.sqlvsn = 12;
sqlstm.arrsiz = 4;
sqlstm.sqladtp = &sqladt;
sqlstm.sqltdsp = &sqltds;
sqlstm.iters = (unsigned int )10;
sqlstm.offset = (unsigned int )5;
sqlstm.cud = sqlcud0;
sqlstm.sqlest = (unsigned char *)&sqlca;
sqlstm.sqlety = (unsigned short)256;
sqlstm.occurs = (unsigned int )0;
sqlstm.sqhstv[0] = ( void *)username;
sqlstm.sqhstl[0] = (unsigned int )0;
sqlstm.sqhsts[0] = ( int )0;
sqlstm.sqindv[0] = ( void *)0;
sqlstm.sqinds[0] = ( int )0;
sqlstm.sqharm[0] = (unsigned int )0;
sqlstm.sqadto[0] = (unsigned short )0;
sqlstm.sqtdso[0] = (unsigned short )0;
sqlstm.sqhstv[1] = ( void *)password;
sqlstm.sqhstl[1] = (unsigned int )0;
sqlstm.sqhsts[1] = ( int )0;
sqlstm.sqindv[1] = ( void *)0;
sqlstm.sqinds[1] = ( int )0;
sqlstm.sqharm[1] = (unsigned int )0;
sqlstm.sqadto[1] = (unsigned short )0;
sqlstm.sqtdso[1] = (unsigned short )0;
sqlstm.sqphsv = sqlstm.sqhstv;
sqlstm.sqphsl = sqlstm.sqhstl;
sqlstm.sqphss = sqlstm.sqhsts;
sqlstm.sqpind = sqlstm.sqindv;
sqlstm.sqpins = sqlstm.sqinds;
sqlstm.sqparm = sqlstm.sqharm;
sqlstm.sqparc = sqlstm.sqharc;
sqlstm.sqpadto = sqlstm.sqadto;
sqlstm.sqptdso = sqlstm.sqtdso;
sqlstm.sqlcmax = (unsigned int )100;
sqlstm.sqlcmin = (unsigned int )2;
sqlstm.sqlcincr = (unsigned int )1;
sqlstm.sqlctimeout = (unsigned int )0;
sqlstm.sqlcnowait = (unsigned int )0;
sqlcxt((void **)0, &sqlctx, &sqlstm, &sqlfpn);
if (sqlca.sqlcode < 0) sql_error("Connect error:");
}


printf("\nConnected to ORACLE as user: %s\n", username);

/* EXEC SQL WHENEVER SQLERROR DO sql_error("Oracle error:"); */ /* Declare a cursor for the FETCH. */


/* EXEC SQL DECLARE c1 CURSOR FOR

SELECT no, sale_code,amount,to_char(sale_date,'yyyy-mm-dd'),sale_man

FROM sales

where to_char(sale_date,'yyyymmdd') like '200303%' ; */


/* EXEC SQL OPEN c1; */

{

struct sqlexd sqlstm;
sqlstm.sqlvsn = 12;
sqlstm.arrsiz = 4;
sqlstm.sqladtp = &sqladt;
sqlstm.sqltdsp = &sqltds;
sqlstm.stmt = sq0002;
sqlstm.iters = (unsigned int )1;
sqlstm.offset = (unsigned int )36;
sqlstm.selerr = (unsigned short)1;
sqlstm.cud = sqlcud0;
sqlstm.sqlest = (unsigned char *)&sqlca;
sqlstm.sqlety = (unsigned short)256;
sqlstm.occurs = (unsigned int )0;
sqlstm.sqcmod = (unsigned int )0;
sqlcxt((void **)0, &sqlctx, &sqlstm, &sqlfpn);
if (sqlca.sqlcode < 0) sql_error("Oracle error:");
}


//위 쿼리에서 to_char(sale_date,'yyyymmdd') like '200303%'는 엄청
//문제있는 부분입니다. 실제 쿼리 튜닝시 반드시 수정해 주어야 합니다만...
//여기서의 문제에서는 제외하도록 합니다. (문제의 핵심은 튜닝을 얘기하는것이 아니라서)
//튜닝시 해결하는 방법은 임시테이블을 만들고 그 임시테이블에다
//insert 하는데 2003-03-01 ~ 2003-03-31까지를 insert한 후
//where sale_date in(select tmp_date from tmp_tbl)
//이런식으로 쿼리하도록 합니다. 음하하 쉽지요?

num_ret = 0;

/* Array fetch loop - ends when NOT FOUND becomes true. */


/* EXEC SQL WHENEVER NOT FOUND DO break; */

for (;;)
{

printf("----------------------------------- 10개 단위로 fetch\n");

//만약 레코드가 100만건 정도 된다면 시스템에 따라 다르지만
// 보통 5000건 단위로 fetch 하는것이 대체로 유리함
//여기서는 10건 단위의 fetch
/* EXEC SQL FETCH c1 INTO :sale_rec; */

{

struct sqlexd sqlstm;
sqlstm.sqlvsn = 12;
sqlstm.arrsiz = 5;
sqlstm.sqladtp = &sqladt;
sqlstm.sqltdsp = &sqltds;
sqlstm.iters = (unsigned int )10;
sqlstm.offset = (unsigned int )51;
sqlstm.selerr = (unsigned short)1;
sqlstm.cud = sqlcud0;
sqlstm.sqlest = (unsigned char *)&sqlca;
sqlstm.sqlety = (unsigned short)256;
sqlstm.occurs = (unsigned int )0;
sqlstm.sqfoff = ( int )0;
sqlstm.sqfmod = (unsigned int )2;
sqlstm.sqhstv[0] = ( void *)sale_rec.NO;
sqlstm.sqhstl[0] = (unsigned int )sizeof(int);
sqlstm.sqhsts[0] = ( int )sizeof(int);
sqlstm.sqindv[0] = ( void *)0;
sqlstm.sqinds[0] = ( int )0;
sqlstm.sqharm[0] = (unsigned int )0;
sqlstm.sqharc[0] = (unsigned int *)0;
sqlstm.sqadto[0] = (unsigned short )0;
sqlstm.sqtdso[0] = (unsigned short )0;
sqlstm.sqhstv[1] = ( void *)sale_rec.SALE_CODE;
sqlstm.sqhstl[1] = (unsigned int )sizeof(int);
sqlstm.sqhsts[1] = ( int )sizeof(int);
sqlstm.sqindv[1] = ( void *)0;
sqlstm.sqinds[1] = ( int )0;
sqlstm.sqharm[1] = (unsigned int )0;
sqlstm.sqharc[1] = (unsigned int *)0;
sqlstm.sqadto[1] = (unsigned short )0;
sqlstm.sqtdso[1] = (unsigned short )0;
sqlstm.sqhstv[2] = ( void *)sale_rec.AMOUNT;
sqlstm.sqhstl[2] = (unsigned int )sizeof(int);
sqlstm.sqhsts[2] = ( int )sizeof(int);
sqlstm.sqindv[2] = ( void *)0;
sqlstm.sqinds[2] = ( int )0;
sqlstm.sqharm[2] = (unsigned int )0;
sqlstm.sqharc[2] = (unsigned int *)0;
sqlstm.sqadto[2] = (unsigned short )0;
sqlstm.sqtdso[2] = (unsigned short )0;
sqlstm.sqhstv[3] = ( void *)sale_rec.SALE_DATE;
sqlstm.sqhstl[3] = (unsigned int )22;
sqlstm.sqhsts[3] = ( int )24;
sqlstm.sqindv[3] = ( void *)0;
sqlstm.sqinds[3] = ( int )0;
sqlstm.sqharm[3] = (unsigned int )0;
sqlstm.sqharc[3] = (unsigned int *)0;
sqlstm.sqadto[3] = (unsigned short )0;
sqlstm.sqtdso[3] = (unsigned short )0;
sqlstm.sqhstv[4] = ( void *)sale_rec.SALE_MAN;
sqlstm.sqhstl[4] = (unsigned int )202;
sqlstm.sqhsts[4] = ( int )204;
sqlstm.sqindv[4] = ( void *)0;
sqlstm.sqinds[4] = ( int )0;
sqlstm.sqharm[4] = (unsigned int )0;
sqlstm.sqharc[4] = (unsigned int *)0;
sqlstm.sqadto[4] = (unsigned short )0;
sqlstm.sqtdso[4] = (unsigned short )0;
sqlstm.sqphsv = sqlstm.sqhstv;
sqlstm.sqphsl = sqlstm.sqhstl;
sqlstm.sqphss = sqlstm.sqhsts;
sqlstm.sqpind = sqlstm.sqindv;
sqlstm.sqpins = sqlstm.sqinds;
sqlstm.sqparm = sqlstm.sqharm;
sqlstm.sqparc = sqlstm.sqharc;
sqlstm.sqpadto = sqlstm.sqadto;
sqlstm.sqptdso = sqlstm.sqtdso;
sqlcxt((void **)0, &sqlctx, &sqlstm, &sqlfpn);
if (sqlca.sqlcode == 1403) break;
if (sqlca.sqlcode < 0) sql_error("Oracle error:");
}

/* Print however many rows were returned. */
print_rows(sqlca.sqlerrd[2] - num_ret);
num_ret = sqlca.sqlerrd[2]; /* Reset the number. */

for(i = 0; i < num_ret; i++)
sale_rec.SALE_DATE[i].arr[sale_rec.SALE_DATE[i].len] = 0x00;

/* EXEC SQL
INSERT INTO stat(no,sale_code,sale_date,amount)
VALUES ( :sale_rec.NO, :sale_rec.SALE_CODE, :sale_rec.SALE_DATE, :sale_rec.AMOUNT); */

{

struct sqlexd sqlstm;
sqlstm.sqlvsn = 12;
sqlstm.arrsiz = 5;
sqlstm.sqladtp = &sqladt;
sqlstm.sqltdsp = &sqltds;
sqlstm.stmt = "insert into stat (no,sale_code,sale_date,amount) va\

lues (:b0,:b1,:b2,:b3)";
sqlstm.iters = (unsigned int )10;
sqlstm.offset = (unsigned int )86;
sqlstm.cud = sqlcud0;
sqlstm.sqlest = (unsigned char *)&sqlca;
sqlstm.sqlety = (unsigned short)256;
sqlstm.occurs = (unsigned int )0;
sqlstm.sqhstv[0] = ( void *)(sale_rec.NO);
sqlstm.sqhstl[0] = (unsigned int )sizeof(int);
sqlstm.sqhsts[0] = ( int )sizeof(int);
sqlstm.sqindv[0] = ( void *)0;
sqlstm.sqinds[0] = ( int )0;
sqlstm.sqharm[0] = (unsigned int )0;
sqlstm.sqharc[0] = (unsigned int *)0;
sqlstm.sqadto[0] = (unsigned short )0;
sqlstm.sqtdso[0] = (unsigned short )0;
sqlstm.sqhstv[1] = ( void *)(sale_rec.SALE_CODE);
sqlstm.sqhstl[1] = (unsigned int )sizeof(int);
sqlstm.sqhsts[1] = ( int )sizeof(int);
sqlstm.sqindv[1] = ( void *)0;
sqlstm.sqinds[1] = ( int )0;
sqlstm.sqharm[1] = (unsigned int )0;
sqlstm.sqharc[1] = (unsigned int *)0;
sqlstm.sqadto[1] = (unsigned short )0;
sqlstm.sqtdso[1] = (unsigned short )0;
sqlstm.sqhstv[2] = ( void *)(sale_rec.SALE_DATE);
sqlstm.sqhstl[2] = (unsigned int )22;
sqlstm.sqhsts[2] = ( int )24;
sqlstm.sqindv[2] = ( void *)0;
sqlstm.sqinds[2] = ( int )0;
sqlstm.sqharm[2] = (unsigned int )0;
sqlstm.sqharc[2] = (unsigned int *)0;
sqlstm.sqadto[2] = (unsigned short )0;
sqlstm.sqtdso[2] = (unsigned short )0;
sqlstm.sqhstv[3] = ( void *)(sale_rec.AMOUNT);
sqlstm.sqhstl[3] = (unsigned int )sizeof(int);
sqlstm.sqhsts[3] = ( int )sizeof(int);
sqlstm.sqindv[3] = ( void *)0;
sqlstm.sqinds[3] = ( int )0;
sqlstm.sqharm[3] = (unsigned int )0;
sqlstm.sqharc[3] = (unsigned int *)0;
sqlstm.sqadto[3] = (unsigned short )0;
sqlstm.sqtdso[3] = (unsigned short )0;
sqlstm.sqphsv = sqlstm.sqhstv;
sqlstm.sqphsl = sqlstm.sqhstl;
sqlstm.sqphss = sqlstm.sqhsts;
sqlstm.sqpind = sqlstm.sqindv;
sqlstm.sqpins = sqlstm.sqinds;
sqlstm.sqparm = sqlstm.sqharm;
sqlstm.sqparc = sqlstm.sqharc;
sqlstm.sqpadto = sqlstm.sqadto;
sqlstm.sqptdso = sqlstm.sqtdso;
sqlcxt((void **)0, &sqlctx, &sqlstm, &sqlfpn);
if (sqlca.sqlcode == 1403) break;
if (sqlca.sqlcode < 0) sql_error("Oracle error:");
}

/* EXEC SQL COMMIT; */

{

struct sqlexd sqlstm;
sqlstm.sqlvsn = 12;
sqlstm.arrsiz = 5;
sqlstm.sqladtp = &sqladt;
sqlstm.sqltdsp = &sqltds;
sqlstm.iters = (unsigned int )1;
sqlstm.offset = (unsigned int )117;
sqlstm.cud = sqlcud0;
sqlstm.sqlest = (unsigned char *)&sqlca;
sqlstm.sqlety = (unsigned short)256;
sqlstm.occurs = (unsigned int )0;
sqlcxt((void **)0, &sqlctx, &sqlstm, &sqlfpn);
if (sqlca.sqlcode < 0) sql_error("Oracle error:");
}

printf("================= insert ok");

}

/* Print remaining rows from last fetch, if any. */
if ((sqlca.sqlerrd[2] - num_ret) > 0)
print_rows(sqlca.sqlerrd[2] - num_ret);


/* EXEC SQL CLOSE c1; */
{

struct sqlexd sqlstm;
sqlstm.sqlvsn = 12;
sqlstm.arrsiz = 5;
sqlstm.sqladtp = &sqladt;
sqlstm.sqltdsp = &sqltds;
sqlstm.iters = (unsigned int )1;
sqlstm.offset = (unsigned int )132;
sqlstm.cud = sqlcud0;
sqlstm.sqlest = (unsigned char *)&sqlca;
sqlstm.sqlety = (unsigned short)256;
sqlstm.occurs = (unsigned int )0;
sqlcxt((void **)0, &sqlctx, &sqlstm, &sqlfpn);
if (sqlca.sqlcode < 0) sql_error("Oracle error:");
} printf("OK insert.\n\n\n");
/* Disconnect from the database. */


////////////////////////////////////////////////////////////////////////////////////
/* 원래 proc로부터 생성된 C파일에서는 exit(0)로 되어 있지만
여기서는 return으로 변경한다. */
return;
////////////////////////////////////////////////////////////////////////////////////

}


void sql_error(char *msg)
{

/* EXEC SQL WHENEVER SQLERROR CONTINUE; */


printf("\n%s", msg);
printf("\n% .70s \n", sqlca.sqlerrm.sqlerrmc);
/* EXEC SQL ROLLBACK WORK RELEASE; */

{

struct sqlexd sqlstm;
sqlstm.sqlvsn = 12;
sqlstm.arrsiz = 5;
sqlstm.sqladtp = &sqladt;
sqlstm.sqltdsp = &sqltds;
sqlstm.iters = (unsigned int )1;
sqlstm.offset = (unsigned int )147;
sqlstm.cud = sqlcud0;
sqlstm.sqlest = (unsigned char *)&sqlca;
sqlstm.sqlety = (unsigned short)256;
sqlstm.occurs = (unsigned int )0;
sqlcxt((void **)0, &sqlctx, &sqlstm, &sqlfpn);
} exit(1);
}


void print_rows(int n)
{
int i;
printf("\nNumber sale_code sale_date");
printf("\n------ -------- -------\n");
for (i = 0; i < n; i++)
{
printf("%d %d %s\n", sale_rec.NO[i],
sale_rec.SALE_CODE[i],
sale_rec.SALE_DATE[i].arr);
}

}


10. 이때 반드시 File View 탭에서 MyBatchProc.pc를 선택 --> 마우스 오른쪽 클릭 --> setting 클릭하여 output 설정 부분을 삭제하도록 한다. 즉, proc파일이 precompile되면서 c 파일이 Rebuild 되어 위에서 수정한 c 파일 날아가지 않도록 조치한다.


11. Tools메뉴 -> Option -> Directory탭에서 C:\ORACLE\ORA92\JDK\include 추가하고, C:\ORACLE\ORA92\JDK\include\win32 추가한다. 만약 jdk를 별도로 설치한 것을 사용한다면 해당 디렉토리를 추가하도록 한다.

12. File View 탭에서 Project를 선택(여기서는 test27 files라고 표시된 부분임) 한 후 --> 마우스 오른쪽 클릭 -->setting 클릭 --> 모달 창이 하나 나타나면 왼쪽 부분의 settings for 부분에 'all configurations'을 선택하고 오른쪽 탭은 Link를 선택하여 Object/Library modules 부분에 orasql9.lib(oracle 9i인 경우 임)를 추가적으로 입력후 ok 클릭

13. File View 탭에서 MyBatchProc.c를 선택 후 --> 마우스 오른쪽 클릭 --> compile

14. Build메뉴--> build test27.dll을 선택하여 test27.dll을 생성한다.

--> 탐색기로 c:\tmp\test\test27\Debug에 test27.dll이 생성되었는지 확인하고
--> 도스창에서 java HelloWorld를 실행한다.


C:\tmp\test>cd test27

C:\tmp\test\test27>dir

C 드라이브의 볼륨에는 이름이 없습니다.

볼륨 일련 번호: 2749-1605

C:\tmp\test\test27 디렉터리

2003.04.09 오후 07:31 <DIR> .
2003.04.09 오후 07:31 <DIR> ..
2003.04.09 오후 07:31 535 test27.dsw
2003.04.09 오후 09:49 50,176 test27.ncb
2003.04.09 오후 07:31 <DIR> Debug
2003.04.09 오후 07:32 402 BackupBatch.java
2003.04.09 오후 07:32 654 BackupBatch.class
2003.04.09 오후 07:32 406 BackupBatch.h
2003.04.09 오후 07:33 302 BackupBatch.c.BAK
2003.04.09 오후 07:34 302 BackupBatch.c
2003.04.09 오후 07:41 3,720 MyBatchProc.pc.BAK
2003.04.09 오후 07:46 3,663 MyBatchProc.pc
2003.04.09 오후 09:48 828 test27.plg
2003.04.09 오후 07:53 24,626 MyBatchProc.c.BAK
2003.04.09 오후 07:53 25,028 MyBatchProc.c
2003.04.09 오후 09:49 4,608 test27.dsp
2003.04.09 오후 09:49 48,640 test27.opt
14개 파일 163,890 바이트
3개 디렉터리 15,563,145,216 바이트 남음


C:\tmp\test\test27>cd Debug


C:\tmp\test\test27\Debug>dir
C 드라이브의 볼륨에는 이름이 없습니다.
볼륨 일련 번호: 2749-1605

C:\tmp\test\test27\Debug 디렉터리
2003.04.09 오후 07:31 <DIR> .
2003.04.09 오후 07:31 <DIR> ..
2003.04.09 오후 11:52 33,792 vc60.idb
2003.04.09 오후 07:53 61,440 vc60.pdb
2003.04.09 오후 07:53 14,205 MyBatchProc.obj
2003.04.09 오후 09:48 533,504 test27.pdb
2003.04.09 오후 07:53 218,964 test27.pch
2003.04.09 오후 09:48 2,026 test27.lib
2003.04.09 오후 09:48 619 test27.exp
2003.04.09 오후 09:48 263,364 test27.ilk
2003.04.09 오후 09:48 208,948 test27.dll
9개 파일 1,336,862 바이트
2개 디렉터리 15,563,128,832 바이트 남음

C:\tmp\test\test27\Debug>cd ..


C:\tmp\test\test27>java BackupBatch
=== library was loaded
Connected to ORACLE as user: SCOTT
----------------------------------- 10개 단위로 fetch

Number sale_code sale_date
------ -------- -------
1 1 2003-03-31
1 2 2003-03-31
1 3 2003-03-31
2 1 2003-03-31
2 2 2003-03-31
2 3 2003-03-31
2 4 2003-03-31
3 1 2003-03-31
3 2 2003-03-31
4 1 2003-03-31
================= insert ok----------------------------------- 10개 단위로 fetch

Number sale_code sale_date
------ -------- -------
5 1 2003-03-31
5 2 2003-03-31

OK insert.

, .

[오라클 서버에 연결하기]

Net8 Configuration Assistant에서

로컬 네트 서비스 이름 구성 - 추가 - 데이터베이스 /서비스 버전 지정

- 서비스이름(보통 전역데이타베이스명) - 네트워크 프로토콜 (TCP)

- 호스트이름 (오라클 서버의 IP 포트는 그냥 1521)

- 테스트 수행 - 네트서비스이름 (서비스이름과 동일해도 되고, 변경해도 된다.)

- 완료

[xx.pc파일 만들기]

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//선언부
EXEC sql BEGIN DECLARE SECTION;
VARCHAR uid[20];
VARCHAR pwd[20];
VARCHAR ser[20];
long m_empno;
VARCHAR m_ename[20];
double m_sal;
VARCHAR m_hire[20];
EXEC SQL END DECLARE SECTION;

//포함부
EXEC SQL INCLUDE SQLCA;

void main()
{

dasi:
printf("Login Name?");
scanf("%s", uid.arr);
uid.len=strlen(uid.arr);
printf("Password?");
scanf("%s", pwd.arr);
pwd.len=strlen(pwd.arr);
printf("Server Alias?");
scanf("%s", ser.arr);
ser.len=strlen(ser.arr);
//접속부
EXEC SQL CONNECT :uid IDENTIFIED BY :pwd USING :ser;
if(sqlca.sqlcode ==0)
{
printf(" Login Name: %s Success...\n",uid.arr);
}
else
{
printf(" Login Name: %s Failed...\n",uid.arr);

goto dasi;
}

//실행부
printf("EmpNo?");
scanf("%ld", &m_empno);
EXEC SQL SELECT EMPNO,ENAME,SAL,HIREDATE INTO :m_empno,:m_ename,:m_sal,:m_hire
FROM emp WHERE empno=:m_empno;
if(sqlca.sqlcode==0)
{
printf("===================================================\n");
printf(" %ld %s %lf %s\n", m_empno,m_ename.arr,m_sal,m_hire.arr);
printf("====================================================\n");
}
else
{

if(sqlca.sqlcode==1403)
printf(" Not found...\n");

else

printf(" 시스템장애...\n");
}

EXEC SQL COMMIT WORK RELEASE;
fflush(stdin);
getchar();
exit(0);
}

[선행컴파일 : Pro*C/C++]

환경설정 : CPP (또는 C로 선택)

+ : 선행컴파일할 xx.pc 선택

옵션 : cpp로 (c로 선택인 경우 ANSI C로 선택)

선행 컴파일 : xx.cpp로 만들어짐

( SQL문장을 CPP가 실행할 수 있는 코드로 변환시켜줌)

[컴파일]

1. 만들어진 xx.cpp를 Open

2. 컴파일하면 default workspace를 만들거냐고 물어본다. (예)

3. 그리고 Oracle\Ora81\precomp\lib\msvc\oraSQL8.LIB추가한다.

4. 그러면 실행파일이 만들어진다.

[sqlca]

오라클의 SQLCA구조체는 SQL문 실행 후 그 결과값을 위해 만들어진 구조체이다.

· sqlca.sqlcode:4바이트 2진 정수이고, SQL 문의 실행결과를 나타낸다.

0 실행이 정상 종료

1403 NOT FOUND

음수 프로그램 또는 시스템 장애

[주의]

uid.len=strlen(uid.arr);

strlen 함수를 사용하려면 unsigned char는 않되므로 char로 변경시켜주면

에러 않난다.

struct { unsigned short len; unsigned char arr[20]; } uid;

struct { unsigned short len;char arr[20]; } uid;

· 호스트 변수

SQL 문 및 프로그램 문의 양쪽으로부터 참조되는 모든 값에 대해서 선언해야 한다. 호스트 변수의 데이터타입은 선언절에서 호스트 언어를 사용해서 선언해야 하며, 이 때 이 데이터타입은 테이블을 정의할 때에 사용되는 ORACLE 데이터타입과 일치할 필요는 없다.

EXEC sql BEGIN DECLARE SECTION;
VARCHAR uid[20];
VARCHAR pwd[20];
VARCHAR ser[20];
long m_empno;
VARCHAR m_ename[20];
double m_sal;
VARCHAR m_hire[20];
EXEC SQL END DECLARE SECTION;

EXEC SQL SELECT EMPNO,ENAME,SAL,HIREDATE INTO :m_empno,:m_ename,:m_sal,:m_hire

FROM emp WHERE empno=:m_empno;

printf("Login Name?");
scanf("%s", uid.arr);
uid.len=strlen(uid.arr);

printf("EmpNo?");
scanf("%ld", &m_empno);

표지 변수

emp_sal_indicator =-1;

EXEC SQL INSERT INTO emp(empno,sal)

VALUES(:emp_number,:emp_salary :emp_sal_indicator);

↖ 표지변수

EXEC SQL SELECT sal INTO :emp_salary :emp_salary_indicator FROM emp

WHERE empno=:emp_no;

if(emp_salary_indicator== -1) // -1 은 NULL을 의미

printf("Employee No %d Salary 0.00\n",emp_no);

else

printf("Employee No %d Salary %f\n", emp_no,emp_salary);

· 호스트 변수로서의 배열

선언 예)

EXEC SQL BEGIN DECLARE SECTION;

int emp_number[10];

float emp_salary[10];

char emp_name[10][20];

EXEC SQL END DECLARE SECTION;

주의사항)

.호스트 배열은 배열 포인터를 선언할 수 없다. (int *p[10];)

. 문자형 변수 이외에는 일차원 배열만 선언할 수 있다.

EXEC SQL SELECT sabun,saname,sajob,sapay,sahire

INTO :sawon_sabun,

sawon_saname,:sawon_sajob,:sawon_sapay,:sawon_sahire

FROM sawon where sajob=:job;

printf("count=%d \n",sqlca.sqlerrd[2]); /* 처리한 행수 */

for(a=0;a<sqlca.sqlerrd[2];a++)

{

printf("%2d %-10s %-10s %8.2f %-16s\n",

sawon_sabun[a],sawon_saname[a], sawon_sajob[a]

,sawon_sapay[a],sawon_sahire[a]);

}

· sqlca.sqlerrd: 4바이트 2진 정수 배열 ORACLE RDBMS의 내부상황

을 파악하기 위해 사용. sqlca.sqlerrd[2]는 INSERT 나

UPDATE 처럼 DML 처리에 대해서 몇 개의 행이 처리 됐는

지를 나타냄

Connected to ORACLE user : CHANG
Job is?부장
count=3
2 한국남 부장 3000.00 88/11/01
13 무궁화 부장 3000.00 96/11/01
17 이성계 부장 2803.00 84/05/01
처리상태 SQLCode=1403 메세지: ORA-01403: 데이터가 없습니다

(단점) 배열갯수가 맞지 않으면 에러남.

[커서]

쿼리가 복수의 행을 리턴하는 경우 그 리턴된 행에 대한 결과를 보관하고 있는 작업 영역을 말한다.

1.커서의 사용순서

DECLARE : 커서를 선언한다.

OPEN : 커서를 활성화 시킨다.

FETCH : 커서에서 데이터 행을 가져온다.

CLOSE : 커서 사용을 종료한다.

(sql서버에서는 반드시 deallocate해야한다.)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/***************************************************************
This is is a sample Pro*C program which uses the FOR option
by inserting records into the EMP table.
***************************************************************/
EXEC SQL BEGIN DECLARE SECTION;
VARCHAR uid[20];
VARCHAR pwd[20];
VARCHAR ser[20];
int sawon_sabun;
float sawon_sapay;
char sawon_saname[10];
char sawon_sajob[10];
char sawon_sahire[20];
char sawon_sasex[10];
char job[10];
EXEC SQL END DECLARE SECTION;

EXEC SQL INCLUDE SQLCA;

void main(void)
{
/* log into ORACLE */
strcpy(uid.arr, "CHANG"); /* copy the user name */
uid.len = strlen(uid.arr);
strcpy(pwd.arr, "CHANG"); /* copy the password */
pwd.len = strlen(pwd.arr);
strcpy(ser.arr, "ths");
ser.len = strlen(ser.arr);

//****

EXEC SQL WHENEVERSQLERROR DO return;

//****

EXEC SQL CONNECT :uid IDENTIFIED BY :pwd USING :ser;

printf("Connected to ORACLE user : %s\n", uid.arr);
printf(" JOB is?");
scanf("%s", job);
EXEC SQL DECLARE sawcur CURSOR FOR SELECT sabun,saname,sajob,sapay,sahire,sasex FROM sawon WHERE sajob=:job;
// EXEC SQL WHENEVERSQLERROR DO return;

EXEC SQL OPEN sawcur;


EXEC SQL WHENEVER NOT FOUND DO break;
while(1)
{
EXEC SQL FETCH sawcur INTO
:sawon_sabun,:sawon_saname,:sawon_sajob,:sawon_sapay,:sawon_sahire,:sawon_sasex;
printf(" %3d %-10s %-10s %10.2f %-15s %10s\n",
sawon_sabun,sawon_saname,sawon_sajob, sawon_sapay ,sawon_sahire,sawon_sasex);
}
EXEC SQL CLOSE sawcur;
EXEC SQL COMMIT WORK RELEASE;
fflush(stdin);
getchar();
exit(0);
}

===

u SQLCA의 사용

방법1) EXEC SQL INCLUDE SQLCA;

방법2) EXEC SQL INCLUDE sqlca.h;

Pro*C/C++소스 파일에 삽입하면 된다.

방법3) #include <sqlca.h>

Pro*C/C++소스 파일이나 생성된 Cpp파일에 삽입하면 된다.

그리고 sqlca.h화일을 VC++의 include Directory에 복사하거나

찾아갈 Directory를 설정해야 한다.

에러가 검출될 때 어떤 처리를 취해야 할 지를 지정해 주는 문장이다.

EXEC SQL WHENEVER [SQLERROR | SQLWARNING | NOT FOUND]

[STOP | CONTINUE | GOTO stmt-label];

· SQLERROR : sqlca.sqlcode가 -1인 경우에 설정

· SQLWARNING :sqlca.sqlwarn[0]에 "W"가 설정되어 있는 경우에 설정

· NOT FOUND : sqlca.sqlcode가 1403인 경우에 설정

· STOP : 프로그램을 종료시키고 논리적인 작업단위는 ROLLBACK 된다.

· CONTINUE : sqlca의 상태를 무시하고 프로그램을 계속 진행한다.

· GOTO :지정한 라벨이 붙은 문으로 제어를 옮긴다.

· DO :DO문장 다음에 지정된 함수를 수행한 다음 수행했던 SQL문 다음

문장을 계속하여 실행한다.

ã WHENEVER문장의 범위

WHENEVER문장은 하나의 선언문이기 때문에 이문장의 유효는 물리적인 위치에 의해 정해진다. 일단 WHENEVER문장이 선언되면 그 아래에 위치하는 모든 SQL문들은 WHENEVER문장의 영향을 받는다. 만약 기존의 WHENEVER문장 아래에 새로운 WHENEVER문장이 선언되면 아래 선언된 문장 다음에 나오는 SQL문장들은 새로 선언된 WHENEVER문장의 영향을 받게 된다.

[Pro*C/C++에서 PL/SQL의 블록 사용]

선행컴파일시 옵션 sql검사를 semantics로 해야함

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

/****************************************************************/

EXEC SQL BEGIN DECLARE SECTION;

VARCHAR uid[20];

VARCHAR pwd[20];

VARCHAR ser[20];

int sawon_sabun;

float sawon_sapay;

char sawon_saname[10];

char sawon_sajob[10];

char sawon_sahire[20];

char sawon_sasex[10];

char name[10];

EXEC SQL END DECLARE SECTION;

EXEC SQL INCLUDE SQLCA;

void main(void)

{

/* log into ORACLE */

strcpy(uid.arr, "CHANG"); /* copy the user name */

uid.len = strlen(uid.arr);

strcpy(pwd.arr, "CHANG"); /* copy the password */

pwd.len = strlen(pwd.arr);

strcpy(ser.arr, "CHANGS");

ser.len = strlen(ser.arr);

EXEC SQL CONNECT :uid IDENTIFIED BY :pwd USING :ser;

printf("Connected to ORACLE user : %s\n", uid.arr);

printf(" Name is?");

scanf("%s", name);

/* SQL문을 PL/SQL의 블럭으로 처리하는 경우 */

EXEC SQL EXECUTE

DECLARE

n NUMBER(3);

BEGIN

n:=0;

if n>0 then

SELECT sabun,saname,sajob,sapay,sahire,sasex INTO

:sawon_sabun,:sawon_saname,:sawon_sajob,:sawon_sapay,:sawon_sahire,:sawon_sasex

FROM sawon WHERE saname=:name;

else

SELECT sabun,saname,sajob,sapay,sahire,sasex INTO

:sawon_sabun,:sawon_saname,:sawon_sajob,:sawon_sapay,:sawon_sahire,:sawon_sasex

FROM sawon WHERE saname='홍길동';

end if;

END;

END-EXEC;

/* 블럭끝*/

printf(" %3d %-10s %-10s %10.2f %-15s %10s",

sawon_sabun,sawon_saname,sawon_sajob,sawon_sapay,sawon_sahire,sawon_sasex);

EXEC SQL COMMIT WORK RELEASE;

fflush(stdin);

getchar();

exit(0);

}

[동적]

실습예문) EXECUTE IMMEDIATE 의 사용

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

/****************************************************************/

EXEC SQL BEGIN DECLARE SECTION;

VARCHAR uid[20];

VARCHAR pwd[20];

VARCHAR ser[20];

char select[132];

EXEC SQL END DECLARE SECTION;

EXEC SQL INCLUDE SQLCA;

void main(void)

{

char where[80];

int scode;

/* log into ORACLE */

strcpy(uid.arr, "CHANG"); /* copy the user name */

uid.len = strlen(uid.arr);

strcpy(pwd.arr, "CHANG"); /* copy the password */

pwd.len = strlen(pwd.arr);

strcpy(ser.arr, "CHANGS");

ser.len = strlen(ser.arr);

EXEC SQL WHENEVER SQLERROR STOP;

EXEC SQL CONNECT :uid IDENTIFIED BY :pwd USING :ser;

printf("Connected to ORACLE user : %s\n", uid.arr);

strcpy(select, "UPDATE sawon SET sapay = 2000 WHERE ");

printf("Please enter where clause for the following : \n");

printf("%s", select);

scode = scanf("%s", where);

fflush(stdin);

if(scode == EOF || scode == 0)

{

printf("Invalid entry. \n");

exit(1);

}

strcat(select, where);

printf("%s\n", select);

EXEC SQL EXECUTE IMMEDIATE :select;

printf("%d records updated. \n", sqlca.sqlerrd[2]);

getchar();

EXEC SQL COMMIT WORK RELEASE;

exit(0);

}

원본 http://blog.naver.com/alice8790/60001523069

, .