저는 전에 데이터를 SELECT해와서 데이터가 0개면 INSERT하고, 있는 놈이면 해당 값을 업데이트 하는 식으로 했었는데요. 우연히 MERGE INTO라는 것을 알게 되었네요.

ORACLE에서만 되는 듯 하네요-_-; 9i이상에서만 된다고 하네요.

MERGE INTO의 목적은 어떤 테이블이나 뷰테이블을 해당 목표테이블과 합체(MERGE)하기 위한 목적인데요. 이걸 이용해서 데이터가 들어왔을 때 있는 데이터면 UPDATE하고, 없는 데이터면 INSERT하는 형태로도 쓰일 수 있습니다.

http://radiocom.kunsan.ac.kr/lecture/oracle/sql/merge.html
위에는 MERGE INTO를 잘 설명한 사이트네요.

여기서 조금 응용하면 원하는 대로 구현할 수 있습니다-_-;

MERGE INTO 테이블명  별칭
USING 대상테이블/뷰  별칭
ON 조인조건
WHEN MATCHED THEN
  UPDATE SET
   컬럼1=값1
   컬럼2=값2
WHEN NOT MATCHED THEN
  INSERT (컬럼1,컬럼2,...)
       VALUES(값1,값2,...);

MERGE INTO다음에 나오는 테이블명은 실제로 데이터가 들어가거나 업데이트 되는 테이블이구요.
USING 다음에 나오는 테이블명은 실제 데이터를 가져오거나 할 테이블이구요.
ON은 WHERE과 같은 조건문이죠.
WHEN MATCHED THEN은 매치되는게 있으면 UPDATE하라는 얘기구요.
WHEN NOT MATCHED THEN은 매치되는게 없으면 INSERT하게 되죠.

응용해봅시다.
[code]
           MERGE INTO INSERTTABLE
           USING DUAL
           ON (ID = 1)
           WHEN MATCHED THEN
           UPDATE SET
           DATA = 'idoori'
           WHEN NOT MATCHED THEN
           INSERT (ID, DATA)
           VALUES (1, 'mudchobo')
[/code]
INSERTTABLE이라는 곳에 USING은 DUAL이라고 했는데 DUAL은 dual은 1개의 레코드 만을 갖는 dummy 테이블이라고 합니다. select 1 from dual해버리면, 1이 나오죠. 대상테이블은 필요없으니 dual로 설정합니다.
ON에서 ID = 1은 ID가 1인게 만약 있으면, DATA부분을 idoori로 업데이트하고, 없으면 mudchobo로 넣게 되는겁니다.

아놔 별거 없는데 막 늘어썼네-_-;
 
Posted by 머드초보
,
 

http://mudchobo.tomeii.com/tt/257 에 이어서-_-;

오라클에서 해보겠습니다.
아 우선 오라클용 jdbc가 필요해요!
ojdbc6.jar 등의 jbdc ^^

걍 오라클용 dataSource로 바꿔주면 돼요-_-;

applicationContext.xml
[code]
<!-- oracle용 -->
 <bean id="dataSource"
  class="org.springframework.jdbc.datasource.DriverManagerDataSource"
  p:driverClassName="oracle.jdbc.driver.OracleDriver"
  p:url="jdbc:oracle:thin:@localhost:1521:XE"
  p:username="dbid" p:password="dbpw" />

<bean id="insertDataOracle" class="com.mudchobo.TestInsertOracle"
  p:sqlMapClient-ref="sqlMapClient"/>
[/code]
아까 그 TestInsert 인터페이스입니다.
TestInsert.java
[code]
package com.mudchobo;

import org.springframework.transaction.annotation.Transactional;

public interface TestInsert {

 @Transactional(readOnly=true, rollbackFor={Throwable.class})
 public void insertData() throws Throwable;
 
}
[/code]
저기 선언된 bean인 TestInsertOracle을 구현해봅시다.
TestInsertOracle.java
[code]
package com.mudchobo;

import java.io.IOException;

import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport;

public class TestInsertOracle extends SqlMapClientDaoSupport implements
  TestInsert {

 @Override
 public void insertData() throws Throwable {
   getSqlMapClientTemplate().insert("insertData");
   System.out.println("oracle에 insert성공");
   throw new IOException();
 }
}
[/code]
MySQL처럼 IOException을 임의로 발생시킵니다.
main부분을 봅시다.
TestTransaction.java
[code]
package com.mudchobo;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestTransaction {

 public static void main(String[] args) {
  String[] configLocation = {"applicationContext.xml"};
  ApplicationContext context =
   new ClassPathXmlApplicationContext(configLocation);
 
  TestInsert testInsertOracle =
   (TestInsert) context.getBean("insertDataOracle");
 
  try {
   testInsertOracle.insertData();
  } catch (Throwable e) {
   System.out.println("예외발생");
  }
 }
}
[/code]
오라클에서는 readOnly로 걸어 놨는데도 불구하고 insert가 됩니다.
그리고 예외를 발생시켰기 때문에 rollbackfor 값을 Throwable로 줬기때문에 IOException이 발생했을 때 롤백하게 됩니다. 잘보면 롤백이 되어있습니다.

오라클에서 set transaction read only 라고 쿼리를 날리면 transaction을 read only로 해버릴 수 있더라구요.
뭐 그냥 그렇다구요-_-;

 
Posted by 머드초보
,
 

스프링에서 제공하는 트랜젝션을 MySQL과 Oracle에다가 적용을 해봤습니다.
제가 잘 이해를 못하는 건지 모르겠는데, Oracle에서는 ReadOnly가 안먹혀요-_-;
MySQL에서는 먹히는데....-_-; Oracle에서는 자체적으로 transaction에서 ReadOnly를 설정하는게 있더군요.
또 Oracle은 트랜젝션기반이라고 하더라구요. 암튼, 참 어렵습니다-_-;
우선 삽질한 코드입니다.
MySQL로 해보았습니다.

필요한 lib입니다.
spring.jar - 스프링할껀데 필요하겠죠?-_-;
commons-logging.jar - 스프링하려면 이눔이 있어야 돼요-_-; 위에 있는 lib와 종속돼요-_-;
ibatis-2.3.0.677.jar - ibatis로 할껍니다. 이게 db연동을 쉽게 해주니까요-_-;
mysql-connector-java-5.1.5-bin.jar - mysql connector가 필요하겠죠!

프로젝트를 만듭시다. TestTransaction이라는 프로젝트를 만듭시다.
최상위 src폴더에다가 applicationContext.xml파일을 만듭시다.

applicationContext.xml
[code]
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:p="http://www.springframework.org/schema/p"
 xmlns:tx="http://www.springframework.org/schema/tx"
 xsi:schemaLocation="
  http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
  http://www.springframework.org/schema/tx
  http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

 <tx:annotation-driven />
 
 <!-- MySQL용 -->
 <bean id="dataSource"
  class="org.springframework.jdbc.datasource.DriverManagerDataSource"
  p:driverClassName="com.mysql.jdbc.Driver"
  p:url="jdbc:mysql://db주소"
  p:username="dbid" p:password="dbpw" />
 
   <!--  Transaction manager for iBATIS Daos -->
 <bean id="transactionManager"
  class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
  p:dataSource-ref="dataSource" />
 
 <!-- SqlMap setup for iBATIS Database Layer -->
 <bean id="sqlMapClient"
  class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"
  p:dataSource-ref="dataSource"
  p:configLocation="classpath:SqlMapConfig.xml" />
 
<bean id="insertDataMysql" class="com.mudchobo.TestInsertMysql"
  p:sqlMapClient-ref="sqlMapClient"/>
</beans>
[/code]
TestInsert라는 interface를 작성해봅시다.
TestInsert.java
[code]
package com.mudchobo;

import org.springframework.transaction.annotation.Transactional;

public interface TestInsert {

 @Transactional(readOnly=true, rollbackFor={Throwable.class})
 public void insertData() throws Throwable;
}
[/code]
인터페이스에다가 트랜젝션을 걸어도 되더라구요-_-; 구현되는 모든놈이 저 옵션으로 트랜젝션이 먹히는 듯 합니다. 근데 별로 안좋은 방법인듯한데-_-; 그냥 메소드에 거는게.....-_-; 보면 readOnly옵션에다가 Throwable예외가 발생하면 롤백합니다.

구현해봅시다.
TestInsertMysql.java
[code]
package com.mudchobo;

import java.io.IOException;

import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport;

public class TestInsertMysql extends SqlMapClientDaoSupport implements
  TestInsert {

 @Override
 public void insertData() throws Throwable {
   getSqlMapClientTemplate().insert("insertData");
   System.out.println("mysql에 insert성공");
   throw new IOException();
 }
}
[/code]
insert를 시키고, 강제로 IOException을 발생시켰습니다.

ibatis xml부분을 봅시다.
SqlMapConfig.xml
[code]
<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE sqlMapConfig     
    PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN"     
    "http://ibatis.apache.org/dtd/sql-map-config-2.dtd">

<sqlMapConfig>
 <sqlMap resource="Insert.xml" />
</sqlMapConfig>
[/code]
Insert.xml
[code]
<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE sqlMap     
PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"     
"http://ibatis.apache.org/dtd/sql-map-2.dtd">

<sqlMap namespace="Sqlmap">
    <insert id="insertData">
     insert into TESTDATA (DATA) values ('MUDCHOBO')
    </insert>
</sqlMap>
[/code]
보시면 단순 insert를 하고 있습니다-_-;

main부분입니다.
TestTransaction.java
[code]
package com.mudchobo;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestTransaction {
 public static void main(String[] args) throws Throwable{
  String[] configLocation = {"applicationContext.xml"};
  ApplicationContext context =
   new ClassPathXmlApplicationContext(configLocation);
 
  TestInsert testInsertMysql =
   (TestInsert) context.getBean("insertData");
  try {
   testInsertMysql.insertData();
  } catch (Throwable e) {
   System.out.println("예외발생");
  }
}
[/code]
이걸 실행하면 트랜젝션이 readOnly이기 때문에 insert하려고 하면 에러가 나버리겠죠.
Connection is read-only. Queries leading to data modification are not allowed; nested exception is com.ibatis.common.jdbc.exception.NestedSQLException:
요런 에러를 보실 수 있습니다.

Oracle은 다음 장에서-_-;
http://mudchobo.tomeii.com/tt/258 여기에 계속-_-;

 
Posted by 머드초보
,
 

심심해서 오라클을 설치해봤습니다-_-;

이거 상당히 좋네요. 잘 되어있어요 ^^ 인터넷에서 익스프레션에디션은 10g밖에 없네요.
10g를 설치했어요. 처음으로 해봤어요-_-;

설치하고 보니까 웹에서 db조작하는 프로그램(Application Express)이 8080포트를 쓰더라구요-_-;
톰캣도 디폴트로 8080을 쓰는데-_-; 같이 못 띄웁니다 ^^

둘중 하나 바꿔야하는데 오라클을 바꿔봅시다-_-;

시작->프로그램->ORACLE어쩌구->SQL명령줄 실행 을 클릭합니다.

[code]
SQL> connect
사용자명 입력: system
암호 입력:
연결되었습니다.
SQL>EXEC DBMS_XDB.SETHTTPPORT(원하는포트번호);
[/code]

SQL에서 그냥 저렇게 처주면 되더군요. 그리고 홈페이지 뜨게하는 URL파일을 수정하는데요.

C:\oraclexe\app\oracle\product\10.2.0\server\Database_homepage.url 이 있는데요.
이거 열어서 바뀐포트로 수정해주세요.
[code]
[InternetShortcut]
URL=http://127.0.0.1:바뀐포트/apex
[/code]

그리고 SQLPLUS에서 전체테이블을 검색하려면
SELECT TABLE_NAME FROM ALL_TABLES WHERE OWNER = '해당DBID';

해주면 되더군요-_-;

 
Posted by 머드초보
,
 

오늘도 자바랑 오라클과 씨름을 합니다-_-;

삽질하던 중에 검색 결과에 대한 레코드 수가 필요했습니다. 그래서 JAVA API를 뒤지고 있는데 getRow() 라는 메소드가 ResultSet클래스에 있는 겁니다. 아... 이거를 쓰면 수를 리턴하는구나 하고 사용했습니다.

그래서 동적 할당을 getRow() 수만큼 해서 받았습니다. 그런데 of out array 어쩌네 하면서 배열의 범위를 넘었네 그럽니다. 디버깅해보니 executeQuery 하자마자 getRow를 하면 0을 받아옵니다-_-;

그렇습니다. 이눔은 현재 가리키고 있는 곳까지의 수만 받아옵니다. 그래서 맨 끝으로 보내야합니다.

ResultSet클래스의 메소드 중에 last()가 있습니다. 맨 끝으로 보내고, 다시 처음으로 보내서 검색하면 되겠구나 라는 생각에
[code]
rs = stmt.executeQuery("select * from USERS");
rs.last();
int rowCount = rs.getRow();
rs.beforeFirst();
[/code]
위와 같이 하면 오라클에서는
"전방향 전용 결과 집합에 부적합한 작업이 수행되었습니다"
라는 메시지를 띄우게 됩니다.

찾아본 결과 createStatement에 옵션을 주어야 하더군요.
statement생성
[code]
stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
            ResultSet.CONCUR_UPDATABLE);

rs = stmt.executeQuery("select * from USERS");
rs.last();
int rowCount = rs.getRow();
rs.beforeFirst();
[/code]
라고 선언하고 하면 됩니다.

 
Posted by 머드초보
,