2016년 2월 23일 화요일

네이버의 카페(http://cafe.naver.com/gugucoding) 로 이전합니다.


안녕하세요.

구멍가게 코딩단의 집필 담당 쿠키입니다.

구멍가게 코딩단의 '코드로 배우는 스프링 웹 프로젝트'가 예상 밖(?)의 호응을 얻고 있어, 참으로 감사한 마음입니다.

많은 분들이 질문과 답변이 너무 지연 되는 경우가 많아서 불편하다고 말씀하셔서, 조심스럽게 네이버의 카페를 만들어서 운영하기로 결정하였습니다.


http://cafe.naver.com/gugucoding




그동안 빠르게 응대해 드리지 못해서 죄송합니다. 

앞으로 카페에 글을 올리시면 메일로 확인 가능하므로, 지금보다는 훨씬 빠른 답변이 가능할 것이라고 생각합니다. 

많은 이용 부탁드립니다. 

2016년 1월 6일 수요일

오라클버전 P502 테이블 변경


alter table tbl_board add (replycnt NUMBER(10,0) default 0);


P503

resources/mappers/boardMapper.xml의 일부


<select id="listCri"  resultType="org.zerock.domain.BoardVO">
 <![CDATA[
select
  bno, title, content, writer, viewcnt, regdate, replycnt
from
  (
  select
    /*+INDEX_DESC(tbl_board, pk_board)*/
    rownum rn, bno, title, content, writer, viewcnt, regdate
  from tbl_board
  where rownum <= #{page} * #{perPageNum}
  and bno > 0
  )
where rn > (#{page} -1) * #{perPageNum}
 ]]>
</select>

오라클버전 P418에 필요한 댓글의 페이징 처리 쿼리


<select id="listPage" resultType="ReplyVO">
<![CDATA[
 select
   rno, bno, replytext, replyer, regdate, updatedate
 from
   (
   select /*+INDEX_DESC(tbl_reply pk_reply))*/
     rownum rn, rno, bno, replytext, replyer, regdate, updatedate
   from tbl_reply
   where bno = #{bno}
   and rno > 0
   and rownum <= #{cri.page} * #{cri.perPageNum}
   )
where rn > ( #{cri.page} -1) * #{cri.perPageNum}
]]>
</select>

오라클버전 P373 resources/mappers/replyMapper.xml의 일부

<select id="list" resultType="ReplyVO">
select
*
from
tbl_reply
where bno =
#{bno}
order by rno desc
</select>

<insert id="create">
insert into tbl_reply (rno, bno, replytext, replyer)
values (seq_reply.nextval, #{bno},#{replytext},#{replyer})
</insert>

<update id="update">
update tbl_reply set replytext = #{replytext},
updatedate = sysdate
where rno = #{rno}
</update>

<delete id="delete">
delete from tbl_reply where rno =#{rno}
</delete>

오라클버전 P370 tbl_reply 테이블의 생성 스크립트



create table tbl_reply (
 rno number,
 bno number not null,
 replytext varchar2(2000) not null,
 replyer varchar2(50) not null,
 regdate date default sysdate,
 updatedate date default sysdate);

 --PK
 alter table tbl_reply add constraint pk_reply
 primary key (rno);

 --FK
 alter table tbl_reply add constraint fk_board_reply
 foreign key (bno) references tbl_board(bno);

 --sequence
 create sequence seq_reply;

오라클버전 P327 ~ P330 resources/mappers 밑의 boardMapper.xml의 일부




<sql id="search">
 <if test="searchType != null" >
   <if test="searchType == 't'.toString()">
     and title like '%'|| #{keyword}||'%'
   </if>
   <if test="searchType == 'c'.toString()">
     and content like '%'|| #{keyword}||'%'
   </if>
   <if test="searchType == 'w'.toString()">
     and writer like '%'|| #{keyword}||'%'
   </if>    
   <if test="searchType == 'tc'.toString()">
     and ( title like '%'|| #{keyword}||'%' OR content like '%'|| #{keyword}||'%')
   </if>      
   <if test="searchType == 'cw'.toString()">
     and ( content like '%'|| #{keyword}||'%' OR writer like '%'|| #{keyword}||'%')
   </if>      
   <if test="searchType == 'tcw'.toString()">
     and (   title like '%'|| #{keyword}||'%'
           OR
             content like '%'|| #{keyword}||'%'
           OR
             writer like '%'|| #{keyword}||'%')
   </if>            
 </if>
</sql>

<select id="listSearch"  resultType="org.zerock.domain.BoardVO">
 <![CDATA[
select
  bno, title, content, writer, viewcnt, regdate
from
  (
  select
    /*+INDEX_DESC(tbl_board, pk_board)*/
    rownum rn, bno, title, content, writer, viewcnt, regdate
  from tbl_board
  where 1=1
]]>
  <include refid="search"></include>
<![CDATA[
  and rownum <= #{page} * #{perPageNum}
  and bno > 0
  )
where rn > (#{page} -1) * #{perPageNum}
 ]]>
</select>

<select id="listSearchCount"  resultType="int">
 <![CDATA[
select
  count(bno)
from
  tbl_board
where 1=1
]]>
  <include refid="search"></include>
<![CDATA[
and bno > 0
 ]]>
</select>

오라클버전 P258 쿼리

select
*
from
  (
  select
  /*+INDEX_DESC(tbl_board pk_board) */
  rownum rn, bno, title, content, writer, regdate, viewcnt
  from tbl_board
  where bno > 0
  and rownum <= 40 )
where rn > 20;

오라클버전 P249,P253, P257 /resources/mappers/boardMapper.xml의 일부


<select id="listPage"  resultType="org.zerock.domain.BoardVO">
 <![CDATA[
select
  bno, title, content, writer, viewcnt, regdate
from
  (
  select
    /*+INDEX_DESC(tbl_board, pk_board)*/
    rownum rn, bno, title, content, writer, viewcnt, regdate
  from tbl_board
  where rownum <= #{page} * #{perPageNum}
  and bno > 0
  )
where rn > (#{page} -1) * #{perPageNum}
 ]]>
</select>



오라클버전 P244- 오라클 페이징 기법


오라클의 페이징은 MySQL과 같이 간단하게 처리되지 못하고 rownum을 활용해 주어야 합니다.

쿼리를 실행할 때 primary key와 관련된 조건을 준다면  이를 이용하는 '실행 계획(Execution Plan)'을 생성하게 됩니다.

예를 들어 아래와 같은 SQL문을 실행해 sqldeveloper에서 실행해 봅니다.

select * from tbl_board
where bno > 0
order by bno desc;

sqldevloper의 실행 계획을 보면 아래와 같이 만들어 지는 것을 볼 수 있습니다.



실행 계획을 보면 내부적으로 primary key로 지정한 PK_BOARD를 이용해서 TBL_BOARD를 접근하는 것을 볼 수 있습니다.

이 때 DESCENDING이 적용되면서 접근하기 때문에 결과는 최근 데이터가 가장 먼저 나오게 됩니다.




order by의 경우 join등을 할 때 내부적으로 최적화된 방법을 찾기 때문에 항상 게시물의 역순으로 데이터를 검색한다는 보장은 없습니다.

이를 위해서 오라클에서는 개발자가 직접 실행계획을 유도하는 '힌트(Hint)'라는 것을 이용합니다.



select
/*+INDEX_DESC(tbl_board pk_board) */
*
from tbl_board
where bno > 0;


위의 SQL은 오라클의 힌트를 이용해서 강제로 primary key의 역순으로 데이터를 검색하도록 합니다. 실행 결과는 위의 그림과 동일하게 게시물의 역순으로 출력되게 됩니다.




실행된 결과물을 페이징 처리하기 위해서 필요한 또 다른 개념은 'ROWNUM'입니다.

ROWNUM은 쿼리의 처리가 끝나고 최종적으로 출력될 때 각 레코드에 붙는 일종의 일련 번호 입니다.

아래의 쿼리를 실행해 봅니다.


select
/*+INDEX_DESC(tbl_board pk_board) */
rownum rn, bno, title, content, writer, regdate, viewcnt
from tbl_board
where bno > 0;


위의 쿼리를 실행하면 출력되면서 번호가 붙는 것을 확인할 수 있습니다.



오라클의 페이징은 이렇게 실행 결과의 ROWNUM 값을 이용해서 페이지에 해당하는 데이터를 걸러내는 방식을 이용합니다.


아래의 쿼리를 실행해 봅니다.


select
/*+INDEX_DESC(tbl_board pk_board) */
rownum rn, bno, title, content, writer, regdate, viewcnt
from tbl_board
where bno > 0
and rownum <= 10;


rownum 이 10보다 작다는 조건은 마지막에 처리되는데, 이를 이용하면 아래와 같이 최신 데이터 10개를 가져올 수 있습니다. 





여기서 주의할 점은 ROWNUM 조건은 반드시 1이 포함되어야 한다는 점입니다. ROWNUM은 마지막에 데이터가 출력되면서 붙는 번호이기 때문에 where 조건에 1이 포함되지 않으면 매번 새로운 데이터가 1번이 되면서 아무런 데이터가 출력되지 않는 문제가 생깁니다.

예를 들어 아래의 쿼리는 기대와 달리 아무런 결과가 만들어지지 않습니다.

select
/*+INDEX_DESC(tbl_board pk_board) */
rownum rn, bno, title, content, writer, regdate, viewcnt
from tbl_board
where bno > 0
and rownum between 11 and 20;




이 문제를 해결하기 위해서는 'in-line view'기법을 이용해야 합니다.

in-line view는 쉽게 말해서 실행된 SQL문을 하나의 테이블처럼 간주해서 사용하는 방식입니다.

만일 10개씩 화면에 내용을 출력하는 경우 2페이지의 데이터는 우선 20개의 레코드를 가져온 후에 다시 rownum으로 생성된 번호를 이용해서 최신 10개의 데이터를 배제하는 방식입니다.

위의 설명을 쿼리로 만들면 아래와 같습니다.

select
*
from
  (
  select
  /*+INDEX_DESC(tbl_board pk_board) */
  rownum rn, bno, title, content, writer, regdate, viewcnt
  from tbl_board
  where bno > 0
  and rownum <= 20 )
where rn > 10;


쿼리의 실행 결과는 아래와 같습니다.



실행된 결과를 보면 RN 컬럼이 ROWNUM번호를 의미하고 있고, BNO가 역순으로 정렬된 것을 볼 수 있습니다.

이 때 발생하는 실행 계획은 다음과 같습니다.



실행 계획을 보면 우선 ROWNUM <= 20 조건을 먼저 실행하고, 실행된 결과 내에서 RN > 10 조건을 이용하므로 2페이지의 데이터를 출력하게 됩니다.






오라클버전 P163 tbl_board 테이블 생성 SQL



create table tbl_board (
  bno number,
  title varchar2(200) not null,
  content varchar2(4000) not null,
  writer varchar2(50) not null,
  regdate date default sysdate,
  viewcnt number default 0
);

--Primary key 추가 
alter table tbl_board add constraint pk_board
primary key(bno);

-- 시퀀스 추가 

create sequence seq_board;


P164 새로운 게시물의 등록에 사용하는 SQL 

새로운 게시물의 추가 
insert into tbl_board (bno, title,content,writer)
values (seq_board.nextval,'제목','내용','user00');


게시물의 수정 
update tbl_board set title= '수정된 제목',content = '수정된 내용', writer='user01'
where bno = 1;


게시물의 삭제 
delete tbl_board where bno = 1;


테이블의 자가 복사를 이용한 많은 양의 데이터 넣는 SQL 
여러번 실행하면 현재 테이블의 기하급수적인 데이터가 생성 

insert into tbl_board (bno, title,content,writer)
(select seq_board.nextval, title, content, writer from tbl_board);



MySQL과 달리 오라클은 기본이 Auto Commit이 아니므로 반드시 작업후 커밋을 할것 
commit;

오라클버전 P139 root-context.xml의 수정



오라클 데이터베이스와 Log4jdbc-log4j2를 적용하기 위해서 가장 중요한 것은 적절한 JDBC드라이버를 사용하는 것입니다.


테스트 결과 ojdbc14.jar는 제대로 동작하지 않으므로 주의하시기 바랍니다.



<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"></property>
<!-- <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"></property> -->
<!-- <property name="url" value="jdbc:oracle:thin:@localhost:1521:XE"></property> -->
<property name="url" value="jdbc:log4jdbc:oracle:thin:@localhost:1521:XE"></property>
<property name="username" value="zerock"></property>
<property name="password" value="zerock"></property>
</bean>

2016년 1월 4일 월요일

오라클버전 P131 resources/mappers의 memberMapper.xml파일

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.zerock.mapper.MemberMapper">

<select id="getTime" resultType="string">
select sysdate from dual
</select>


<insert id="insertMember">

insert into tbl_member (userid, userpw, username, email)
values (#{userid}, #{userpw}, #{username}, #{email})

</insert>


</mapper>

오라클버전 P126 tbl_member테이블의 SQL 스크립트

create table tbl_member (
 userid varchar2(50) not null,
 userpw varchar2(50) not null,
 username varchar2(50) not null,
 email varchar2(100),
 regdate date default sysdate,
 updatedate date default sysdate);

 alter table tbl_member add constraint pk_member primary key (userid);


오라클의 경우 varchar 대신에 varchar2를 이용하도록 합니다.

regdate와 updatedate의 경우 오라클의 Date 타입을 이용하고, 데이터의 생성시 현재 시간을 기본으로 사용하기 위해서 sysdate 를 이용합니다.


오라클버전 P86-DataSource 설정하기

오라클 버전의 경우 root-context.xml은 아래와 같은 내용으로 수정되어야 합니다.


P88의 DataSource의 테스트 코드는 기존과 동일합니다.

package org.zerock.controller;

import java.sql.Connection;

import javax.inject.Inject;
import javax.sql.DataSource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"file:src/main/webapp/WEB-INF/spring/**/*.xml"})
public class DataSourceTest {

@Inject
private DataSource ds;

@Test
public void test()throws Exception {

Connection con = ds.getConnection();

System.out.println(con);

con.close();

}

}


오라클버전 P70- 오라클 연결 테스트와 jUnit


오라클의 설치가 올바르게 되었다면 JDBC를 이용해서 연결이 가능한지 테스트를 진행해 보아야 합니다.


1. JDBC 드라이버의 설정

MySQL과 달리 오라클의 경우는 저작권의 문제로 인해 공식적으로 Maven Repository에 등록된 라이브러리가 존재하지 않습니다.


위의 화면은 Maven Repository에 등록된 오라클 드라이버입니다. ojdbc14버전의 드라이버만 지원합니다.

ojdbc14버전의 경우 JDK1.4에 최적화된 드라이버이므로, JDK1.6이상을 사용하는 경우라면 적합하지 않습니다.


만일 ojdbc14버전을 사용하는 경우 책의 설명에 나오는 JDBC의 로그를 기록하지 못하는 문제점 등이 발생합니다.


JDBC드라이버를 사용하는 방법에는 크게 다음과 같은 방법을 고려할 수 있습니다.

1. 복잡하지만 Maven에 JDBC드라이버를 추가해서 사용하는 방법
2. STS상의 프로젝트에 라이브러리를 추가해서 사용하는 방법
3. 클래스 패스를 이용해서 JDBC드라이버를 설정하고 사용하는 방법

1번의 경우 Maven 프로그램이 설치되어야 하기 때문에 복잡하므로,  초급자에게는 권장하고 싶지는 않습니다.

3번의 경우는 시스템 전체에 영향을 주기 때문에, 여러 프로젝트를 진행해야 하는 개발자들에게는 유용하지 않습니다.

이 블로그에서는 2번을 기준으로 설명하도록 합니다.



2. JDBC드라이버 파일의 확보

SQL Developer를 설치했다면 최신의 JDBC드라이버를 이미 가지고 있는 상태입니다.


SQL Developer의 압축이 풀린 경로에 jdbc폴더 내에 lib 폴더안에는 ojdbc6.jar파일이 포함되어 있습니다.


3. 프로젝트 내에 JDBC드라이버 추가하기

프로젝트 내에 JDBC드라이버를 추가하는 가장 간단한 방법은 'WEB-INF'폴더 내에 'lib'라는 폴더를 생성하고 JDBC 드라이버를 넣어주는 방식입니다.

작성하는 프로젝트의 'Properties'를 이용해서 추가한 'lib'폴더를 참조하도록 설정합니다.




4. JDBC의 연결 테스트 코드 작성하고 테스트 하기

위의 설정이 완료되었다면 아래와 같은 테스트 코드를 이용해서 JDBC연결 테스트 코드를 작성합니다.

package org.zerock.controller;

import java.sql.Connection;
import java.sql.DriverManager;

import org.junit.Test;

public class OracleConnectionTest {

private static final String DRIVER ="oracle.jdbc.driver.OracleDriver";
private static final String URL ="jdbc:oracle:thin:@localhost:1521:XE";
private static final String USER="zerock";
private static final String PW="zerock";

@Test
public void testConnect() throws Exception{

Class.forName(DRIVER);

try(Connection con = DriverManager.getConnection(URL, USER, PW)){

System.out.println(con);

}
}
}




오라클버전 P64- 실습용 사용자 계정 추가


SQL Developer를 이용해서 오라클 DB에 연결이 되었다면 다음 작업은 실습에 사용할 사용자의 계정을 생성하는 것입니다.


1. SQL Developer 화면에서 '다른 사용자'를 선택합니다.


2. 사용자의 계정과 패스워드를 지정합니다(아래의 화면은 'zerock'이라는 계정을 추가하고 있습니다).

테이블 스페이스는 기본적으로 'USERS'를 지정합니다.



3. '부여된 롤' 탭을 선택해서 'CONNECT'와 'DBA'를 선택해 줍니다.


이후 적용버튼을 눌러서 설정을 완료합니다.




4. 사용자의 계정이 생성된 후에는 'SYSTEM'계정으로 연결된 화면을 종료시키고, 방금 전에 생성한 'zerock'계정으로 연결을 시도합니다.




오라클버전- P58 SQL Developer 설치


MySQL과 달리 오라클의 경우 SQL을 편집, 실행해 볼 수 있는 별도의 SQLEditor가 존재하기는 합니다(SQL Plus)만 이에 대한 설정은 복잡하기 때문에 외부에서 사용하기 위해서는 무료로 제공되는 SQL Developer를 이용하는 것이 편리합니다.


1. 오라클 사이트에서 SQL Developer를 다운로드 합니다(http://www.oracle.com/technetwork/developer-tools/sql-developer/downloads/index.html) .
 

SQLDeveloper의 경우 JDK가 포함된 버전이 있으므로, JDK가 설치된 경우라면 JDK가 포함되지 않은 버전을 받아서 사용할 수도 있습니다.


2. SQLDeveloper를 다운로드 받고 압축을 해제하면 모든 설치의 과정은 완료됩니다.



3. SQLDeveloper의 최초 실행시에는 JDK의 설치 경로를 물어볼 수 있습니다. 이 경우 JDK가 설치된 폴더까지만 지정하면 됩니다. 별도의 설정이 없었을 경우 C:\Program Files\Java\JDK... 경로가 될 것입니다.



4. SQL Developer의 추가적인 설정을 무시하고, 오라클 설치시에 기억해 둔 아이디와 패스워드를 이용해서 SQL Developer에서 연결을 진행합니다.

이 때 사용자의 이름은 'system' 계정으로 지정하고, 비밀번호는 설치시에 설정한 패스워드를 입력해 줍니다.


화면 아래쪽의 SID는 반드시 'XE' 로 지정되어야 합니다(대소문자의 구분은 없음).

하단의 '테스트'를 이용해서 오라클에 제대로 연결되는지를 확인합니다.


만일 현재 머신이 아닌 네트워크를 통해서 연결하는 경우에는 반드시 '호스트 이름'과 '포트'를 확인해 주어야 합니다.


초기에 많이 실수 하는 부분이 주로 '방화벽' 설정을 하지 않고, 연결을 시도하는 경우입니다. 방화벽에서 1521번 포트가 제대로 허용되지 않으면 원격에서의 연결이 안될 수 있으므로 반드시 확인해 주어야 합니다. 


오라클버전 P58 예제를 위한 Oracle의 설정과 스프링 테스트


3.1 Oracle XE의 설치

오라클 데이터베이스의 경우 Enterprise Edition, Standard Edition, Personal Edition, Express Edition이 존재합니다.

각 에디션 중에 가장 간단하게 사용할 수 있는 버전은 Express Edition입니다.

오라클(www.oracle.com)사이트를 방문합니다.

상단 메뉴의 downloads를 이용해서 Express Edition을 찾습니다.


Express Edition(http://www.oracle.com/technetwork/database/database-technologies/express-edition/downloads/index.html)을 선택해서 다운로드 합니다.

다운 받은 파일의 압축을 풀면 setup.exe파일이 존재하게 되는데, 이를 이용해서 설치과정을 진행합니다.



설치에는 크게 '데이터베이스의 설치'와 '데이터베이스 인스턴스의 설치'과정으로 이루어 집니다.


설치 과정중에서 가장 중요한 것은 위의 그림에서 보듯이 관리자 계정의 패스워드입니다. 패스워드를 분실할 경우 필요한 계정을 생성할 수 없으므로 반드시 기억해야만 합니다.