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 머드초보

댓글을 달아 주세요

  1. BlogIcon 정원희 2008.07.02 17:29  댓글주소  수정/삭제  댓글쓰기

    DB에서 제공되는 auto_increment 값이 아니라 사용자가 수동으로 넣어주는 값이면 뭘 어떻게 하든간에 중복 insert 문제가 발생할 수 밖에 없습니다. 일단 unique 라서 DB의 무결성이 깨지진 않습니다만 민감한 부분의 코드라면 두명이 동시에 사용하는 경우 한명은 오류화면을 보게 된다는 거겠네요 -_-;

    DBMS에서 원자성을 제공하는 기능을 쓰도록 변경하셔야 합니다. 수동으로 뭔가 하거나 키값의 max 를 구해서 입력해도 동일한 문제가 발생할 것이구요, 자바에서 synchorinized 로 처리를 했다고 해도 해당 어플리케이션 내에서만 보장되는 것이지 다른 프로그램이나 DBMS 자체에 로그인해서 수작업으로 쿼리를 날리거나 할때는 또 문제가 될 수 있습니다.

    • 머드초보 2008.07.02 19:46  댓글주소  수정/삭제

      오...좋은 의견감사합니다 ㅠ
      도저히 방법이 생각나지 않아 그냥 synchronized로 처리했는데 ^^
      원자성을 제공하는 기능이라함은 오라클에서 제공하는 그런 기능이 있다는 말씀이신가요?
      음 찾아봐야겠네요 감사해요~ ^^

  2. 2008.07.03 13:25  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

  3. 2008.11.24 15:59  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

    • 머드초보 2008.11.25 11:29  댓글주소  수정/삭제

      전 그냥 synchronized로 해결했는데요.
      그....오라클에서 시퀀스를 사용하면 된다고 하네요^^
      저도 안 써봐서 모르겠네요 ㅠ

  4. BlogIcon yeori 2009.07.13 16:20  댓글주소  수정/삭제  댓글쓰기

    흠... 제가 지금 저것때문에 좀 골치가 아픈데... 저는 스프링 거치지 않고 iBATIS만 사용중인데 unique constraint를 침범하는 상황인데도 iBATIS 자체에서 "ORA-00001: 무결성 제약 조건" 에러를 던지지 않고 bean 클래스의 프로퍼티를 설정하려다가 null poiner exception만 던지고 있어서 iBATIS 코드를 보는 중이네요. -_-

    일단 예외 발생하면 똑같은 데이터가 존재한다고 가정하고 select 로 시도해서 해결중인데 좀 깔끔하지가 못하죠... 고민입니다..

    • 머드초보 2009.07.19 16:14  댓글주소  수정/삭제

      안녕하세요~
      사실 시퀀스를 걸어서 개발하는 게 맞는건데, 어쩔 수 없는 상황도 있죠. 저도 저 당시 고민을 좀 했었는데-_- 그냥 뭐 느리더라도 sync를 써서 했던 기억이^^
      암튼, 방문해주셔서 감사해요~ ^^

  5. BlogIcon huikyun 2009.12.16 14:49  댓글주소  수정/삭제  댓글쓰기

    안녕하세요. 같은 문제로 고민하다가 유익한 글 잘 읽었습니다.
    저는 성능이 중시되는 상황에서 Insert문만 딱 한가지여서 무결성 예외가 뜨는걸 그냥 로그로 찍기만 하고 그냥 두었습니다.
    synchronized 를 이용하려고 하니 저보다도 다른 개발자가 오히려 ㅎㄷㄷ 해버리네요...

    • 머드초보 2009.12.17 09:55  댓글주소  수정/삭제

      그렇군요-_-
      성능이 우선시 된다면야....시퀀스를....-_-

    • BlogIcon huikyun 2009.12.17 11:56  댓글주소  수정/삭제

      아. 모 대기업의 개발건이라서
      데이터베이스에 스키마는 정해진대로 가야합니다.
      혹시 다음에 기회가 된다면 꼭 시퀀스 사용을 고려해보도록 하겠습니다.^^;

    • 머드초보 2009.12.20 13:55  댓글주소  수정/삭제

      아 그렇군요^^
      방문해주셔서 감사해요~ ^^

 

클라이언트에서 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 머드초보

    댓글을 달아 주세요

    1. BlogIcon Keep Burning 2010.03.16 12:00  댓글주소  수정/삭제  댓글쓰기

      안녕하세요.
      http://www.json.org/ 안에 있는 기존 lib만 사용하다가 문제점이 생겨서(List에 있는 bean은 제대로 못 만들더군요...) 고생하고 있었는데, 좋은 방법 가르쳐 주셔서 감사합니다.

    2. Developer 2015.07.19 22:45  댓글주소  수정/삭제  댓글쓰기

      좋은 정보네요. 감사합니다.

     

    일주일에 한번, 하루에 한번 작업을 수행하는 프로세스를 실행하고 싶을 때가 있을 껍니다.
    한가한 시간에 자동으로 배치작업을 실행하는 등의 작업을 자바에서 수행할 수 있습니다.
    Timer라는 클래스에다가 시작날짜, 시간을 설정한 뒤, TimerTask클래스를 상속받은 클래스에서 run메소드를 구현하게 되면 설정된 시간에 run메소드가 자동으로 수행되게 됩니다.

    코드를 보도록 합시다.

    [code]
    package com.mudchobo.scheduler;

    import java.util.TimerTask;

    public class WeeklySearch extends TimerTask {

     @Override
     public void run() {
      System.out.println("WeeklySearch!");
     }
    }
    [/code]
    TimerTask를 상속받아서 run메소드를 구현했습니다. run메소드는 간단히 WeeklySearch라고 보여주는군요.

    그럼 메인을 보도록 합시다.
    [code]
    package com.mudchobo.scheduler;

    import java.util.Calendar;
    import java.util.Timer;

    public class Scheduler {

     public static void main(String[] args) {
      WeeklySearch weeklySearch = new WeeklySearch();
     
      Timer timer = new Timer();
      Calendar date = Calendar.getInstance();
      date.set(Calendar.DAY_OF_WEEK, Calendar.SATURDAY);
      date.set(Calendar.AM_PM, Calendar.PM);
      date.set(Calendar.HOUR, 11);
      date.set(Calendar.MINUTE, 29);
      date.set(Calendar.SECOND, 0);
      date.set(Calendar.MILLISECOND, 0);
     
      timer.scheduleAtFixedRate(weeklySearch, date.getTime(),
        1000 * 60 * 60 * 24 * 7);
     }
    }
    [/code]
    Timer객체, Calendar객체를 선언합니다. Calendar객체에는 이 스케쥴이 시작될 시간을 설정해서 넣습니다. 그 뒤에 timer에 있는 scheduleAtFixedRate메소드에 첫번째 인자는 맨 위에서 생성한 TimerTask객체를 넣으면 되구요. 두번째는 이 스케쥴이 시작될 시간을 설정해서 넣으면 되구요. 3번째는 얼마만큼의 주기로 실행될 지 기간을 설정하게 됩니다.
    밀리초여서 1000밀리초 * 60초 * 60분 * 24시간 * 7일 하게 되면 저것은 1주일에 한번 실행되게 됩니다.

    이상입니다!

     
    Posted by 머드초보

    댓글을 달아 주세요

    1. BlogIcon 시난 2008.06.19 18:25  댓글주소  수정/삭제  댓글쓰기

      quartz 스케쥴링을 이용해보세요!

      • 머드초보 2008.06.22 20:27  댓글주소  수정/삭제

        앗 시난님^^ 반가워요 ^^ 제 블로그를 방문해주셔서 감사해요^^
        quartz도 한번 써봐야겠네요 ^^
        좋은 정보 감사해요~ ^^

    2. BlogIcon moncler online 2013.01.05 20:09  댓글주소  수정/삭제  댓글쓰기

      Le nombre de morts causées par le tabac pourrait tripler d'ici 2030 en Chine si des mesures draconiennes ne sont pas prises, http://www.moncleroutletespain.com/ moncler chaquetas, a mis en garde un rapport réalisé conjointement par des experts chinois et étrangers, http://www.moncleroutletespain.com/ moncler. Sciences nouveau report du lancement de Discovery Actu en continu Wallonie : alerte aux crues et aux inondationsRelated articles:


      http://englpocket.tistory.com/24 http://englpocket.tistory.com/24

      http://yentownband.com/category/MotorcycleDiaries/Vietnam?page=3 http://yentownband.com/category/MotorcycleDiaries/Vietnam?page=3

     

    로그 찍는 거 별로 안좋아하는데 로그를 찍어보니까 더 좋은 것 같아요 ^^
    게다가 log4j라는 매우 우수한 로그찍는 프로그램이 있습니다.
    sysout에서 벗어나봅시다-_-; 습관적으로 sysout을-_-(System.out.println()......-_-)

    우선 이클립스에서 프로젝트를 하나 만들어봅시다.
    log4j를 받아봅시다.
    http://logging.apache.org/log4j/1.2/download.html
    1.2버전입니다. 받아서 log4j-1.2.15.jar파일을 라이브러리에 추가합시다.

    log4j설정파일을 만들어봅시다.
    최상위 폴더에다가 log4j.properties파일을 만듭시다.
    [code]
    # Log4j Setting file
    log4j.rootLogger=INFO, console

    # Daily file log
    log4j.appender.logfile=org.apache.log4j.DailyRollingFileAppender
    log4j.appender.logfile.File=D:/mudchobo/Log/glv.log
    log4j.appender.logfile.DatePattern='.'yyyy-MM-dd
    log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
    log4j.appender.logfile.layout.ConversionPattern=[%d{HH:mm:ss}][%-5p](%F:%L) - %m%n

    # Console log
    log4j.appender.console=org.apache.log4j.ConsoleAppender
    log4j.appender.console.layout=org.apache.log4j.PatternLayout
    log4j.appender.console.layout.ConversionPattern=%-5p %l - %m%n

    # log level and appender
    log4j.logger.com.mudchobo=DEBUG, console
    log4j.logger.com.mudchobo.Test=INFO, logfile
    [/code]
    대략 내용을 살펴보면 log4j.rootLogger는 최상위 로거입니다.
    모든 INFO레벨이상의 로그는 다 console로 찍겠다는 겁니다.
    (레벨에는 DEBUG, INFO, WARN, ERROR, FATAL 순인데, 예를 들어 INFO레벨로 지정해두면 logger.debug로 찍는 로그는 나타나지 않습니다. INFO레벨 이상것만 나타납니다.)

    console은 아래 #Console log쪽에 보시면
    log4j.appender.console <- 요 이름입니다.
    요 console은 자세히보면 ConsoleAppender라는 클래스입니다. 이건 말그대로 콘솔에 로그를 찍어준다는 겁니다. layout에는 PatternLayout을 지정할 수 있는데 저 패턴은 뭐 레벨이 뭐고, 클래스가 뭐고, 메시지찍고 뭐 그런 내용입니다. 검색 고고싱-_-;

    그리고, 파일에다가 출력 할 수 있는데, DailyRollingFileAppender클래스를 이용합니다. 이눔은 말그대로 매일매일 다른로그를 사용하게 만듭니다. 로그이름이 위와 같이 glv.log라면, 해당로그가 어제날짜인데 로그를 찍으려고 하면 기존에 있던 파일은 glv.log.2008-04-17 이렇게 바꿔줍니다.

    아래부분에 보면 log4j.logger. 다음에 패키지명이나 클래스명을 지정해놓고, 로그레벨과 출력할 로그를 지정할 수 있는데요. 해당 클래스나 패키지의 로그는 저걸로 찍겠다는 겁니다. Test클래스는 logfile로 찍힌다는 겁니다.
    그리고, rootLogger가 colsole로 지정되어 있기 때문에 console에도 찍히겠죠? ^^

    로그를 찍어봅시다.
    TestLogging이라는 프로젝트 이름으로 만듭시다.

    Test클래스를 만들어봅시다.
    Test.java
    [code]
    package com.mudchobo;

    import org.apache.log4j.Logger;

    public class Test {

     private Logger logger = Logger.getLogger(getClass());
     
     public void println() {
      logger.info("안녕하세요! Test입니다");
     }
    }
    [/code]
    Test2클래스를 만들어봅시다.
    Test2.java
    [code]
    package com.mudchobo;

    import org.apache.log4j.Logger;

    public class Test2 {

    private Logger logger = Logger.getLogger(getClass());
     
     public void println() {
      logger.info("안녕하세요! Test2입니다.");
     }
    }
    [/code]
    TestLogging클래스를 만들어봅시다. 메인을 만들어야합니다.
    [code]
    package com.mudchobo;

    public class TestLogging {

     public static void main(String[] args) {
      Test test = new Test();
      Test2 test2 = new Test2();
     
      test.println();
      test2.println();
     }
    }
    [/code]
    자 그럼 콘솔에는
    INFO  com.mudchobo.Test.println(Test.java:10) - 안녕하세요! Test입니다.
    INFO  com.mudchobo.Test.println(Test.java:10) - 안녕하세요! Test입니다.
    INFO  com.mudchobo.Test2.println(Test2.java:10) - 안녕하세요! Test2입니다.
    INFO  com.mudchobo.Test2.println(Test2.java:10) - 안녕하세요! Test2입니다.
    이렇게 출력이 될 것이고 로그파일에는
    [19:56:35][INFO ](Test.java:10) - 안녕하세요! Test입니다.
    이것만 출력될 것입니다.
    위에 콘솔에 두번 찍힌 이유는 Rootlogger도 찍고, 아래 패키지를 지정한 로그도 찍었기 때문이죠.
    그리고, 파일에는 한번만 쓰여진 이유는 파일에 쓰는건
    log4j.logger.com.mudchobo.Test=INFO, logfile 여기 이 Test클래스 하나죠-_-;
    이상입니다-_-;


     
    Posted by 머드초보

    댓글을 달아 주세요

    1. BlogIcon 아이 2008.04.18 14:51  댓글주소  수정/삭제  댓글쓰기

      오오오~~멋진데~
      똑같은거 두번씩 찍히는건 log4j.additivity 옵션 한번 찾아보삼~

      • 머드초보 2008.04.18 19:23  댓글주소  수정/삭제

        두번찍는거 막는 옵션도 있는건가보군!
        방문해줘서 고맙네그려 ^^
        여긴 자네 서버인데-_-;

    2. 2008.05.14 13:55  댓글주소  수정/삭제  댓글쓰기

      log4j 의 막강한 기능은 로그 db 저장 및 원격 포트로 데이터를 쏘는 기능 까지 ..
      한번 테스트 겸 해보면 재미 있더라구.
      로칼의 알 ftp 에 보기 창에 로그가 찍히는데 재미 있던데 ㅋㅋ

      • 머드초보 2008.05.16 08:26  댓글주소  수정/삭제

        아 그렇군요 ^^
        log4j를 그냥 로그찍는 용도 밖에 안써봐서 ^^
        데이터를 원격으로 전송도 되는건가요?
        오...멋지네!

    3. BlogIcon 검쉰 2008.05.26 22:56  댓글주소  수정/삭제  댓글쓰기

      이번에 진행하는 프로젝트에 log4j 꼭 써야겠어요 ㅎㅎ
      좋은 글 감사합니다 ;)

    4. 솜소미 2012.08.10 11:27  댓글주소  수정/삭제  댓글쓰기

      오오 자세하고 쉬운 설명 감사드립니다~
      많은 도움 받고 갑니다. ^^

    5. MookMook 2014.07.11 10:32  댓글주소  수정/삭제  댓글쓰기

      감사합니다~ spring 말고 그냥 jsp/java 상에 넣을려고 많이 찾아다녔는데 여기서 답을 얻네요

    6. 으라챠챠 2015.06.17 16:46  댓글주소  수정/삭제  댓글쓰기

      좋은글이네요... 잘 읽고 갑니다... 수고하세요...