Thread로 삽질을 하다가.....
알게 된....

우선 httpClient를 이용해서 웹스크래핑을 하는데요. Thread를 이용해서 5개정도 만들어서 5개를 스크래핑 시키면 더 빠르더라구요-_-; 그래서 Thread로 삽질을 하게 되었는데!

10분마다 도는 스케쥴러에 의해 실행되는 스케쥴러가 있다고 칩시다. 이 스케쥴러는 10분마다 해당 일을 실행합니다. 그 해당일은 스레드5개를 만들어서 5개를 웹스크래핑하는 겁니다.
이걸 그냥
[code]MyThread thread = new Thread();
thread.start();[/code]
이렇게 5개를 반복해서 넣어버리면......5개가 돌고 있는데 아직 끝나지 않았는데 이 스케쥴러는 다시 또 5개의 스레드를 만들어서 또 실행을 하게 됩니다-_-;
즉, 해당 메소드에서 해당 스레드가 끝날 때 까지 기다려주는 메소드인 것입니다.

[code]logger.info("DailySearch Start!");
        List<ScrapThread> threadList = new ArrayList<ScrapThread>();
       
        int count = bizNoDao.countBizListByStatus("T");
        for (int i=0; i<count / THREAD_SIZE; i++)
        {
            ScrapThread scrapThread = (ScrapThread)
                applicationContext.getBean("scrapThread");
            List<User> list = bizNoDao.selectUserBytStatusAndRow(
                    i * THREAD_SIZE, i * THREAD_SIZE + THREAD_SIZE, "T");
           
            scrapThread.setList(list);
            scrapThread.start();
           
            threadList.add(scrapThread);
        }
       
        for (int i=0; i<threadList.size(); i++)
        {
            try
            {
                threadList.get(i).join();
            }
            catch(InterruptedException e)
            {
                e.printStackTrace();
            }
        }
        logger.info("DailySearch End!");
[/code]
조낸 귀찮아서 그냥 소스 가져다 붙입니다-_-;
그냥 내용을 보면 ScrapThread라는 놈을 여러개 만들어서 Thrad start시켜놓고, 여러개가 돌게 한다음에 고 아래에서는 해당 스레드를 join을 시킵니다. 그럼 1번스레드가 끝날 때까지 웨이트 하다가 끝나면 다시 2번쓰레드를 join시키고, 그런식으로 다 끝날 때 까지 기다린 다음에 끝내야합니다.

그렇게 되면 이 해당 메소드가 끝나지 않았기 때문에 다 끝나고 난 다음에 DailySearch End라는 말이 호출이 되게 되는 겁니다-_-; join안하면 바로 DailySearch End찍고, 그냥 스케쥴러가 끝이 나는거죠.

이것도 장황하게 포스팅하는 이유는 반나절을 고생해서 입니다-_-;

 
Posted by 머드초보
,
 
Thread를 두개를 실행시켜서 DB에 동시에 INSERT를 시켜버리니 INSERT할 때 데이터가 AUTOINCRESEMENT가 아니라 ID부분을 직접 입력해서 하는 부분이면 문제가 발생합니다.
그래서 이 MERGE INTO문을 활용해서 데이터가 있으면 INSERT하고 없으면 UPDATE하는 구문을 만들었습니다.
이렇게 해도 제 생각이지만, Thread한 놈은 이미 데이터가 없는 것을 확인하고 insert를 시도 하려고 하고, 다른 Thread놈은 저 놈이 insert를 아직 하지 않았으니까 검색해서 안나오니 insert를 해보려고 하니 둘 다 insert를 시도하게 되더라구요.

이게 예제 입니다.
우선 테이블구조입니다.
테이블은 autoincreament가 아닌 수동적인 기본키를 사용합니다.
[code]CREATE TABLE "INSERTTABLE" ( "ID" NUMBER NOT NULL ENABLE, "DATA" VARCHAR2(4000) NOT NULL ENABLE, CONSTRAINT "INSERTTABLE_PK" PRIMARY KEY ("ID") ENABLE )
[/code]


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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-2.5.xsd">

    <context:component-scan base-package="thread" />
   
    <!-- 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="mudchobo" p:password="1234" />
 
    <!-- SqlMap setup for iBATIS Database Layer -->
    <bean id="sqlMapClient"
        class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"
        p:dataSource-ref="dataSource"
        p:configLocation="classpath:config/SqlMapConfig.xml" />
   
    <bean id="sqlMapClientTemplete"
        class="org.springframework.orm.ibatis.SqlMapClientTemplate"
        p:sqlMapClient-ref="sqlMapClient"/>
           
</beans>
[/code]
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="config/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">
           MERGE INTO INSERTTABLE
           USING DUAL
           ON (ID = 1)
           WHEN MATCHED THEN
           UPDATE SET
           DATA = 'HERMUSSERI'
           WHEN NOT MATCHED THEN
           INSERT (ID, DATA)
           VALUES (1, 'MUDCHOBO')
       </insert>
      
</sqlMap>
[/code]
ThreadTestDao.java
[code]package thread;

public interface ThreadTestDao {
    public void insertData();
}
[/code]
ThreadTestDaoImpl.java
[code]package thread;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.ibatis.SqlMapClientTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class ThreadTestDaoImpl implements ThreadTestDao {

    @Autowired
    private SqlMapClientTemplate sqlMapClientTemplate;

    @Override
    public void insertData() {
        sqlMapClientTemplate.insert("insertData");
    }
}
[/code]
TestThread.java
[code]package thread;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Scope("prototype")
@Component
public class TestThread extends Thread {

    @Autowired
    private ThreadTestDao threadTestDao;
   
    @Override
    public void run() {
        threadTestDao.insertData();
    }
}
[/code]
ThreadTest.java
[code]package thread;

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

public class ThreadTest {

    public static void main(String[] args) {
        String configLocation = "config/applicationContext.xml";
        ApplicationContext context = new ClassPathXmlApplicationContext(
                configLocation);

        TestThread testThread1 = (TestThread) context
                .getBean("testThread");
        TestThread testThread2 = (TestThread) context
                .getBean("testThread");
       
        testThread1.start();
        testThread2.start();
    }
}
[/code]
실행할 때 계속 몇번을 실행해보면-_-; unique constraint 또는 ORA-00001: 무결성 제약 조건(MUDCHOBO.INSERTTABLE_PK)에 위배됩니다를 볼 수 있을 겁니다.

이게 제 생각인데, 첫번째 스레드가 돌면서 데이터를 확인하니 없길래 insert를 하려던 찰나에 두번째 스레드놈도 거의 동시에 데이터를 확인하니 없어서 insert를 시키려다가 에러가 나는 듯한데요.

트랜잭션도 먹이고 별 지롤을 다 했는데 안되길래 그냥 싱크방식으로 변경해버렸습니다.
insertData라는 메소드에
[code]@Override
    synchronized public void insertData() {
        sqlMapClientTemplate.insert("insertData");
    }
[/code]
메소드 앞에 synchronized를 붙입니다.
이건 어떤 스레드가 호출을 했으면 다음 스레드는 기다린 다음에 접근하게 되는 즉 싱크방식으로 처리를 시킬 수 있는 메소드가 됩니다.
이렇게 처리하면 효율은 좀 떨어지겠지만, 유니크 중복에러는 그나마 막을 수 있습니다.

다른 방법 있으면 좀 알려주세요 ㅠㅠ 고생하고 있습니다 ㅠ




 
Posted by 머드초보
,
 

넷빈즈는 초보자들이 쉽게 적응할 수 있도록 QuickStart를 제공합니다.
일종의 튜토리얼 같은 건데 참 잘 되어 있습니다.
스프링프레임워크에 관해서도 QuickStart가 있는데 어노테이션을 사용해서 더욱 쉽게 접근할 수 있도록 바꿔봤습니다.

http://www.netbeans.org/kb/61/web/quickstart-webapps-spring.html
요게 원문입니다. 영어지만, 그냥 코드만 보고 따라하셔도 스프링의 기초를 이해하실 수 있을껍니다.
이 예제를 조금 변경해봤습니다. 어노테이션을 이용한 HelloSpring으로-_-;(똑같이 하면 왠지 안될것같아서-_-)

우선 NetBeans6.1, JDK 5 or 6, GlassFish or Tomcat이 필요해요.

우선 처음에 프로젝트를 만듭니다.
New Project(Ctrl-Shift-N)을 선택해서 Web -> Web Application을 선택합니다.

프로젝트이름은 HelloSpring이라고 하고, Steps 4단계에서 Spring Web MVC 2.5를 선택하세요.
그리고 Finish를 클릭하세요.

그리고 F6을 누르면 GlassFish가 실행이 되고, 웹페이지가 뜨면서 아래와 같은 화면이 나올껍니다.

사용자 삽입 이미지


저 페이지는 index.jsp파일인데요. web.xml파일에 보면 welcome-file이 redirect.jsp로 되어있습니다. redirect.jsp파일을 열어보게 되면, index.htm을 요청하게 되어있습니다.
또 web.xml에서 servlet태그를 보면 *.htm요청으로 들어오는 놈들은 DispatcherServlet으로 넘기게 되어있습니다.
이 놈은 서블릿이름-servlet.xml파일을 설정파일을 사용합니다.
그러면 dispatcher-servlet.xml파일을 보게 되면, urlMapping이라는 bean이 있는데, 이 놈에서 mappings를 보면 index.htm이 요청일 경우 indexController를 실행하게 되어있습니다.
indexController는 아래에 bean으로 정의가 되어있죠.
indexController는 ParameterizableViewController클래스인데 이 클래스는 p:viewName값이 index인 것을 보여주는것이죠. index는 viewResolver bean에 의해서 /WEB-INF/jsp/index.jsp파일이죠.
그래서 index.jsp파일이 보여지는 것이죠.

초간단 설명을 마치고-_-; 폼에다가 이름을 입력하고, 전송하면 "Hello 이름!"을 리턴하는 프로그램을 만들어봅시다.

설정파일을 조금 변경해야합니다.
<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/>
요런 bean이 있는데, 이건 Controller의 클래스명으로 url매핑을 하는 겁니다. 즉 HelloController로 선언을 시켰으면 hello.htm요청으로 해당 컨트롤러를 호출할 수 있게 하는겁니다.
근데 이건 어노테이션을 사용하면 안 먹힙니다-_-; 걍 지워버립시다.
그리고, beans에 xmlns:context="http://www.springframework.org/schema/context"를 추가합니다.
그리고, xsi:schemaLocation에도
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
이렇게 추가합니다. 이는 <context: 를 사용하기 위함입니다.
그리고, 이 아래에 이걸 추가합니다.
<context:component-scan base-package="hellospring" />
이건 객체를 스캔하는 건데 @Service, @Controller, @Component 등의 어노테이션이 base--package아래있는 클래스들 전부 적용하게 합니다. 빈이름은 클래스명에 따라서 정해집니다. 예를 들어 HelloController이면 helloController라고 자동으로 등록됩니다.
그리고, urlMapping bean에다가 우리가 만들 HelloController를 등록합니다.
<prop key="/hello.htm">helloController</prop> 이렇게 한줄을 추가하면 됩니다.
바뀐 dispatcher-servlet.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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-2.5.xsd">
   
    <context:component-scan base-package="hellospring" />
   
    <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/index.htm">indexController</prop>
                <prop key="/hello.htm">helloController</prop>
            </props>
        </property>
    </bean>
   
    <bean id="viewResolver"
          class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          p:prefix="/WEB-INF/jsp/"
          p:suffix=".jsp" />
   
    <bean name="indexController"
          class="org.springframework.web.servlet.mvc.ParameterizableViewController"
          p:viewName="index" />
   
</beans>
[/code]

우선 Service를 만들어봅시다.
Source Package에 오른쪽 버튼을 클릭하고, New -> Java Class를 선택합시다.
Class Name에는 HelloService라고 하고, Package에다가 hellospring.service라고 합시다.
[code]package hellospring.service;

@Service
public class HelloService {

    public String sayHello(String name) {
        return "Hello " + name + "!";
    }
}
[/code]
초간단 메소드입니다. name을 넣으면 Hello name! 을 리턴하게 하는 서비스입니다.

그 전에 폼으로 부터 데이터를 가져오는 bean을 하나 만들어봅시다.
Source Package에 오른쪽 버튼을 클릭하고, New -> Java Class를 선택합시다.
Class Name에는 Name이라고 하고, Package에다가 hellospring.domain라고 합시다.
[code]package hellospring.domain;

public class Name {
   
    private String value;
}
[/code]
여기서 getter, setter만드는 방법이 2가지가 있어요.

첫번째 방법은 Alt + Insert키를 누르면 Getter and Setter를 선택하면 해당 멤버변수에 대해서 나오는데 체크를 하고 Generate하면 됩니다.
두번째 방법은 소스에디터의 value에다가 마우스 오른쪽버튼을 클릭하고, Refactor -> Encapsulate선택하면 얘는 좀 더 상세하게 설정할 수 있습니다. 접근자를 수정할 수 있어요.
getter, setter가 완성이 되었습니다.

그 다음은 컨트롤러를 만들어봅시다.
Source Package에 오른쪽 버튼을 클릭하고, New -> Java Class를 선택합시다.
Class Name에는 HelloController라고 하고, Package에다가 hellospring.controller라고 합시다.
[code]@Controller
public class HelloController {

    @Autowired
    private HelloService helloService;
   
    private String formView = "nameView";
    private String successView = "helloView";
   
    @RequestMapping(method = RequestMethod.GET)
    public String hello() {
        return formView;
    }
   
    @RequestMapping(method = RequestMethod.POST)
    public String onSubmit(Name name, ModelMap model) {
        model.addAttribute("helloMessage", helloService.sayHello(name.getValue()));
        return successView;
    }
}
[/code]
여기서 보면 HelloService는 @Service라는 어노테이션을 사용해서 객체스캔으로 helloService라는 bean이름으로 등록이 되어있습니다. 그래서 @Autowired해버리면 helloService랑 자동으로 연결이 되죠.
그래서 helloService를 사용할 수 있습니다.

여기서 신기한 점은 @RequestMapping어노테이션이 있는데, GET방식이면 hello메소드를 호출하라는 겁니다.
그래서 처음에 form action을 하지 않은 상태에서는 hello메소드가 호출이 됩니다.
Controller어노테이션의 신기한점은 메소드의 리턴값이 String인데 이 값이 view이름이 됩니다.
formView는 nameView라고 적어놨는데, 이것은 viewResolver에 의해 nameView.jsp파일을 호출하게 됩니다.

더욱 신기한 점은 onSubmit메소드입니다. 값을 받아서 알아서 맞는 곳에다가 넣는 것 같습니다.
그리고 view에다가 값을 던져줄 때는 ModepMap이라는 클래스로 메소드파라메터에 넣고, 여기에 addAttribute를 해서 helloMessage값을 넣으면 됩니다.

이제 View를 만들어봅시다.
Web Page -> WEB-INF -> jsp를 선택하고 오른쪽 버튼 클릭, New해서 jsp선택.
formView이름을 nameView로 지었기 때문에 JSP File Name에 nameView라고 합니다.
[code]<body>
        <h2>Enter your name</h2>
        <form action="hello.htm" method="post">
            Name:
            <input type="text" name="value" />
            <input type="submit" value="OK" />
        </form>
    </body>
[/code]
action에서 hello.htm을 요청하는데 input필드는 한개입니다. 이름은 value입니다.
근데 신기하게도 우리가 만든 Controller를 보면 onSubmit에서 파라메터로 Name클래스로 받는 곳이 있습니다. 여기에 value멤버변수가 있는데 이름을 똑같이하면 그냥 Name클래스의 value값에 들어가버립니다-_-;

그럼 successView를 생성해봅시다.
Web Page -> WEB-INF -> jsp를 선택하고 오른쪽 버튼 클릭, New해서 jsp선택.
successView는 "helloView"라고 했기때문에 helloView라고 합니다.
[code]<h2>${helloMessage}</h2>
[/code]
라고 합니다. onSubmit메소드에서 넘겨주는 helloMessage값입니다.

그럼 이제 실행을 해봅시다.
http://localhost:8080/HelloSpring/hello.htm 라고 요청을 해봅시다.
그러면 Enter your name에다가 이름을 입력합시다.
이름이 나올껍니다.

사용자 삽입 이미지
사용자 삽입 이미지

아....자꾸 딴짓하네-_-; 일해야하는데-_-;

 
Posted by 머드초보
,
 

클라이언트에서 ajax로 요청했을 때 자료를 손쉽게 파싱하기 위해서 json으로 클라이언트에 던져줄 때가 편할 때가 있습니다. 그래서 손쉽게 json으로 변환하는 라이브러리가 있습니다.

http://json-lib.sourceforge.net/

여기보면 json-lib라는 놈이 있는데 json으로 손쉽게 변환해주는 라이브러리입니다.

  • jakarta commons-lang 2.3
  • jakarta commons-beanutils 1.7.0
  • jakarta commons-collections 3.2
  • jakarta commons-logging 1.1
  • ezmorph 1.0.4

    이것들이 필요하다더군요. 위에 4개는 apache에 가면 있구요. ezmorph는 구글링해서 찾아서 받으세요.
    그리고 JSON-LIB인 json-lib-2.2.1-jdk15.jar가 필요합니다.

    사용법은 매우 간단합니다.
    [code]
    public class JsonTest {
     
     @Test
     public void Bean2Json()
     {
      MyBean myBean1 = new MyBean();
      myBean1.setId(1);
      myBean1.setName("mudchobo");
      MyBean myBean2 = new MyBean();
      myBean2.setId(2);
      myBean2.setName("shit");
     
      List<MyBean> mybeanList = new ArrayList<MyBean>();
      mybeanList.add(myBean1);
      mybeanList.add(myBean2);
     
      JSONArray jsonArray = JSONArray.fromObject(mybeanList);
      System.out.println("mybeanList - " + jsonArray);
     
      Map<String, Object> map = new HashMap<String, Object>();
      map.put("beanlist", jsonArray);
     
      JSONObject jsonObject = JSONObject.fromObject(map);
      System.out.println("json - " + jsonObject);
     }
    }
    [/code]
    Bean 2개를 List에 add를 한다음에 JSONArray라는 객체가 List를 배열로 만드는놈입니다.

    mybeanList - [{"id":1,"name":"mudchobo"},{"id":2,"name":"shit"}]

    이런식으로 만듭니다.
    저거를 JSONObject클래스를 이용해서 앞에 이름을 붙여줍니다. Map을 이용하면 됩니다
    Map을 이용해서 put에서 첫번째 인자에 이름을 넣고, 두번째 인자에 방금 생성한 Array를 넣으면 됩니다.
    그리고 JSONObject.fromObject메소드를 이용해서 생성하게 되면 이렇게 됩니다.

    json - {"beanlist":[{"id":1,"name":"mudchobo"},{"id":2,"name":"shit"}]}

    이상입니다-_-;

  •  
    Posted by 머드초보
    ,
     

    이동국님이 번역해놓은 iBATIS SQL Maps 개발자 가이드를 보고있었습니다.
    INSERT를 한번에 많이 해야하는 상황이 발생해서 더 빠르게 하는 방법이 있나? 라는 생각이 들어서 가이드를 보고 있었습니다. 배치라는게 있었는데요.

    배치(Batches)
    만약 당신이 수행할 많은 수의 쿼리아닌 statement(insert/update/delete)를 가진다면 당신은 추가적인 최적화를 위해서 네트워크 트래픽을 줄이고 JDBC드라이버를 허락하는 배치 같은 작업을 수행하길 원할지도 모른다. 배치를 사용하는 것은 SQL Map API를 사용하면 간단하다. 배치의 경계를 지정하기 위해서 두가지 간단한 메소드를 제공한다.
    sqlMap.startBatch();
    //…execute statements in between
    sqlMap.executeBatch();
    executeBatch()를 호출함으로써 모든 배치 statement는 JDBC드라이버를 통해 수행될것이다.

    라는 글이 있더군요. 최적화를 시켜주는 것 같은데요. 그럼 더 빠른건가? 라는 생각에 삽질을 해봤습니다.

    [code]
    @Test
     public void testYesBatch()
     {
      StopWatch stopWatch = new StopWatch();

      stopWatch.start();
      try
      {
       sqlMapClient.startBatch();
       for(int i = 0 ; i < 100000 ; i++)
       {
        Testtemp testtemp = new Testtemp();
        testtemp.setId(i);
        testtemp.setName("성종천");
        sqlMapClient.insert("insertTesttemp", testtemp);
       }
       sqlMapClient.executeBatch();
      }
      catch(SQLException e)
      {
       e.printStackTrace();
      }
      stopWatch.stop();
      System.out.println("배치사용 걸린시간: " + stopWatch.getTotalTimeMillis());
     }
    [/code]
    Batch를 사용해서 시간을 측정해봤습니다. 100000건을 insert시켜봤는데, 198485가 나왔습니다.

    Batch를 안쓰고 해봤습니다.
    [code]
    @Test
     public void testNoBatch()
     {
      StopWatch stopWatch = new StopWatch();
      stopWatch.start();
      for(int i = 0 ; i < 100000 ; i++)
      {
       Testtemp testtemp = new Testtemp();
       testtemp.setId(i);
       testtemp.setName("성종천");
       sqlMapClientTemplete.insert("insertTesttemp", testtemp);
      }
      stopWatch.stop();
      System.out.println("배치미사용 걸린시간: " + stopWatch.getTotalTimeMillis());
     }
    [/code]
    100000건 insert하는데 202359나왔습니다.
    뭐지 이 차이없다는 것 같은 느낌은.....-_-;

    1000건 했을 때는 Batch사용시 20015, 미사용시 20032.......

    속도랑은 상관없이 최적화 하는건가-_-; 암튼 뭐 그렇습니다.

     
    Posted by 머드초보
    ,