2014년 7월 6일 일요일

Java Object의 hashCode()와 equals() 메소드

참고 :  http://www.javaworld.com/article/2074996/hashcode-and-equals-method-in-java-object---a-pragmatic-concept.html

java.lang.Object의 메소드인 hashCode()와 equals()는 보편적으로 사용되는 메소드는 아니지만 상황에 따라 오버라이드 되어 사용되기도 합니다. 오버라이드시 어떤 기능을 할 수 있는지 한번 알아보도록 하겠습니다.

hashCode()

Object에 포함된 기본 상태의 hashCode() 메소드는 객체에 대한 메모리 주소 매핑 정보를 기반으로 정수 값을 반환 합니다. Object 클래스의 소스를 보면 알 수 있겠지만 hashCode() 메소드는 다음과 같이 작성 되어 있습니다.

public native int hashCode();

hashCode() 메소드가 JVM상에 네이티브 코드로 직접 구현되어 있다는 것을 의미하며 메모리 주소를 특정한 지역 정보 형태로 제공한다는 것을 알 수 있습니다. 이런 특징이 있음에도 불구하고 이 메소드는 구현체에서 오버라이드 될 수 있습니다.

equals()

equals() 메소드는 두개의 객체가 동일한지 비교할 수 있는 특별한 메소드 입니다. 자바에는 동일함을 비교하는 방식이 두 종류가 있는데 하나는 "==" 연산자를 사용하는 것이며 다른 하나는 "equals()" 메소드 입니다. equals() 메소드의 경우 다음과 같이 작성 되어 있습니다.

public boolean equals(Object obj) {
        return (this == obj);
    }

오버라이드가 가능하다는 측면에서 둘의 차이를 좀더 생각해 보자면 객체의 비교에 있어 ".equals()"는 서로 같은 값을 갖는 관계를 이야기 하며 "==" 연산자의 경우 물리적으로 완전히 동일함을 이야기 한다고 볼 수 있을 것 입니다.

규약

hashCode()와 equals()는 결국 상호 연관관계에 있는 메소드로 둘은 동시에 오버라이드 되어야 하며 그렇지 않으면 API문서에 나와있는 메소드 규약을 위반하게 됩니다.

테스트

테스트를 위해 hashCode()와 equals()모두를 오버라이드한 클래스와 둘다 변경하지 않은 클래스를 작성 하였습니다.

package reference.value;

public class Robot {
 private int productNumber;
 private String modelName;

 public Robot(int productNumber, String modelName) {
  super();
  this.productNumber = productNumber;
  this.modelName = modelName;
 }

 public int getProductNumber() {
  return productNumber;
 }

 public void setProductNumber(int productNumber) {
  this.productNumber = productNumber;
 }

 public String getModelName() {
  return modelName;
 }

 public void setModelName(String modelName) {
  this.modelName = modelName;
 }

}

package reference.value;

public class CustomRobot {
 private int productNumber;
 private String modelName;

 public CustomRobot(int productNumber, String modelName) {
  super();
  this.productNumber = productNumber;
  this.modelName = modelName;
 }

 public int getProductNumber() {
  return productNumber;
 }

 public void setProductNumber(int productNumber) {
  this.productNumber = productNumber;
 }

 public String getModelName() {
  return modelName;
 }

 public void setModelName(String modelName) {
  this.modelName = modelName;
 }

 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + ((modelName == null) ? 0 : modelName.hashCode());
  result = prime * result + productNumber;
  return result;
 }

 @Override
 public boolean equals(Object obj) {
  if (this == obj)
   return true;
  if (obj == null)
   return false;
  if (getClass() != obj.getClass())
   return false;
  CustomRobot other = (CustomRobot) obj;
  if (modelName == null) {
   if (other.modelName != null)
    return false;
  } else if (!modelName.equals(other.modelName))
   return false;
  if (productNumber != other.productNumber)
   return false;
  return true;
 }

}

이 둘의 차이를 알아보기 위해 다음과 같은 테스트 클래스를 작성하였습니다.

package reference.value;

import java.util.HashSet;

public class TestHashAndEqual {

 public static void main(String[] args) throws Exception {
  Robot r1 = new Robot(1999, "R2D2");
  Robot r2 = new Robot(1999, "R2D2");
  Robot r3 = r1;
  
  // toString in Object
  System.out.println("r1 is " + r1 + " , r2 is " + r2 );
  System.out.println("r1 == r2 result : " + (r1 == r2 ? true : false));
  System.out.println("r1 == r3 result : " + (r1 == r3 ? true : false));
  System.out.println("Equals between r1 and r2 : " + r1.equals(r2));
  System.out.println("HashCode r1 : " + r1.hashCode() + " , r2 : " + r2.hashCode());
  System.out.println("Identity HashCode r1 : " + System.identityHashCode(r1) + " , r2 : " + System.identityHashCode(r2));
  
  HashSet<Robot> rHS = new HashSet<Robot>();
  
  rHS.add(r1);
  rHS.add(r2);
  rHS.add(r3);
  
  System.out.println("HashSet Size : " + rHS.size());
  
  System.out.println("\n==============================\n");
  
  HashSet<CustomRobot> cRHS = new HashSet<CustomRobot&gt();
  
  CustomRobot cR1 = new CustomRobot(1999, "R2D2");
  CustomRobot cR2 = new CustomRobot(1999, "R2D2");
  CustomRobot cR3 = cR1;
  
  System.out.println("cR1 is " + cR1 + " , cR2 is " + cR2 );
  System.out.println("cR1 == cR2 result : " + (cR1 == cR2 ? true : false));
  System.out.println("cR1 == cR3 result : " + (cR1 == cR3 ? true : false));
  System.out.println("Equals between cR1 and cR2 : " + cR1.equals(cR2));
  System.out.println("HashCode cR1 : " + cR1.hashCode() + " , cR2 : " + cR2.hashCode());
  System.out.println("Identity HashCode cR1 : " + System.identityHashCode(cR1) + " cR2 : " + System.identityHashCode(cR2));
  
  cRHS.add(cR1);
  cRHS.add(cR2);
  cRHS.add(cR3);
  
  System.out.println("HashSet Size : " + cRHS.size());
  
 }

}

실행한 결과는 다음과 같습니다.

r1 is reference.value.Robot@15db9742 , r2 is reference.value.Robot@6d06d69c
r1 == r2 result : false
r1 == r3 result : true
Equals between r1 and r2 : false
HashCode r1 : 366712642 , r2 : 1829164700
Identity HashCode r1 : 366712642 , r2 : 1829164700
HashSet Size : 2

==============================

cR1 is reference.value.CustomRobot@49b52c2 , cR2 is reference.value.CustomRobot@49b52c2
cR1 == cR2 result : false
cR1 == cR3 result : true
Equals between cR1 and cR2 : true
HashCode cR1 : 77288130 , cR2 : 77288130
Identity HashCode cR1 : 2018699554 cR2 : 1311053135
HashSet Size : 1

두 메소드의 오버라이드를 통해 내용면에서 같은 멤버변수의 값을 갖는 객체라면 물리적으로 동일하지 않더라도 논리적으로는 서로 같은 객체로 인식하는 것을 알 수 있습니다.

HashSet의 경우 중복을 배제하는 컬렉션 클래스이기 때문에 사이즈 또한 이에 기반하여 증가하는 것을 확인 할 수 있으며 HashMap의 경우 이를 이용하여 Key에 일반적으로 사용되는 String이 아닌 별도의 객체를 이용하여 키를 구성하는 것 또한 가능하게 됩니다.

2014년 6월 17일 화요일

Java 8의 새로운 기능 Lambda

참고 :  http://programming.oreilly.com/2014/04/whats-new-in-java-8-lambdas.html

아직 익숙한 개념은 아니지만 Java 8에서 새롭게 추가된 기능 중 큰 비중을 차지하는 람다 표현식에 대해 이야기 해보려고 합니다.

람다란 무엇인가?

람다는 어떠한 행위를 나타내는 축약된 단일 메소드 클래스이며 변수 처럼 어딘가에 할당시키거나 다른 메소드들에 변수를 이용해 값을 전달 하는 것과 동일한 형태로 전달 될 수 있습니다. 솔식히.. 말로는 잘 와닿지 않습니다.. 그렇기 때문에 진행하면서 코드로 이야기를 하도록 하죠..

람다 구문 

input arguments -> body

람다 표현식은 일반 메소드와 같이 입력 변수부분과 동작을 기술하는 부분 그리고 선택적인 반환값으로 이루어져 있습니다. 위와 같이 화살표(->)를 기준으로 좌측은 메소드 변수를 우측은 이 변수들로 무엇을 할 것인가 하는 행위를 나타냅니다.

람다의 타입 : Functional Interface

자바는 형식화된 언어(Typed Language)로 필수적으로 타입을 선언하는 것이 일반적입니다. 그럼 람다의 타입은 대체 무엇인가? 라는 질문을 하지 않을 수 없는데 람다는 다음과 같이 설계 되었다고 합니다.

람다는 기존의 익명 클레스를 통해서 익숙한 익명 메소드 전략을 재활용한 것으로 새롭게 타입이 추가된 것은 아니라고 합니다. 대신 특별한 인터페이스인 Functional Interface를 갖습니다. 이는 일반적인 인터페이스와 동일하지만 다음의 추가적인 2개의 특징을 갖는다고 합니다.
  1. 단 하나의 추상 메소드를 갖음.
  2. 선택적 이지만 @FunctionalInterface 주석을 추가하여 람다 표현식으로 사용 될 수 있음(이 방식이 강력 추천 됨).
자바에는 무수히 많은 단일 메소드의 인터페이스들이 존재하며 이것들은 Functional Interface로 개조 되었습니다. 만약 필요에 의해 Functional Interface를 만들고자 한다면 하나의 추상 메소드를 갖는 인터페이스를 정의하고 @FunctionalInterface만 상단에 붙여주면 됩니다. 버전업 된 API 문서에서 Functional Interface가 적용된 것인지 확인 할 수 있습니다.

조건에 맞춰 다음과 같은 인터페이스를 정의해 보았습니다.

package lambda.works;

@FunctionalInterface
public interface TestFunctionalInterface<T> {
    public T doSomething(T t1, T t2);

}

그리고 다음과 같은 모델 클레스를 하나 작성 하였습니다.

package lambda.works;

public class CargoWorks {
    private int boxQty;

    public CargoWorks(int boxQty) {
        this.boxQty = boxQty;

    }

    public int getBoxQty() {
        return boxQty;

    }

    public void setBoxQty(int boxQty) {
        this.boxQty = boxQty;

    }

} 

람다 표현식을 이용하여 Functional Interface를 다음과 같이 구현해 보았습니다.

//두 스트링의 연결
TestFunctionalInterface<String> stringAdder = (String s1, String s2) -> s1 + s2;

//두 수의 곱
TestFunctionalInterface<Integer> multipleNumbers = (Integer i1, Integer i2) -> i1 * i2;

//두 박스의 합
TestFunctionalInterface<CargoWorks> quantityAdder = (CargoWorks c1, CargoWorks c2) -> {
        c1.setBoxQty(c1.getBoxQty() + c2.getBoxQty());
        return c1;

};


TestFunctionalInterface가 generic type 인터페이스이기 때문에 서로 다른 타입으로 구현이 가능한 것을 알 수 있습니다. 결국 람다 표현식의 타입은 Functional Interface라고 볼 수 있습니다.

이렇게 정의된 것은 다음과 같이 사용 될 수 있습니다.

package lambda.works;

public class ImplFunctions {
    //두 스트링의 연결
    TestFunctionalInterface<String> stringAdder = (String s1, String s2) -> s1 + s2;

    private void concatnateStrings(String s1, String s2) {
        System.out.println("Concatenated result : " + stringAdder.doSomething(s1, s2));

    }

}

매우 다양하게 정의 된 비지니스 로직이 위와 같이 단순한 절차에 의해 사용될 수 있습니다.

Java 8 이전과의 차이

package lambda.works;

public class TestPreJava8 {
    TestFunctionalInterface<CargoWorks> quantityMerger = new TestFunctionalInterface<CargoWorks>() {
        @Override
        public CargoWorks doSomething(CargoWorks c1, CargoWorks c2) {
            c1.setBoxQty(c1.getBoxQty() + c2.getBoxQty());
            return c1;
        }
        
    };

}

Java 8 이전이라면 앞서 소개한 람다 표현식은 위의 익명 클레스와 같이 표현 될 것입니다. 이걸 사용하려면 다음과 같겠죠..

public void preJava8Method() {
        TestFunctionalInterface<CargoWorks> quantityMerger = ...;
        
        CargoWorks c1 = new CargoWorks(1000);
        CargoWorks c2 = new CargoWorks(2000);
        
        CargoWorks mergedQunatiy = quantityMerger.doSomething(c1, c2);

}

아마도 기존의 방식대로면 람다 표현식과 같은 기능을 구현할 수는 있겠지만 불필요한 코드들이 많이 늘어나게 될 것 입니다. 람다의 간결함을 예로 들기 위해 다음은 다양한 행위가 있을 경우에 대한 예 입니다.

//두 박스의 합
TestFunctionalInterface<CargoWorks> quantityAdder = (CargoWorks c1, CargoWorks c2) -> {
        c1.setBoxQty(c1.getBoxQty() + c2.getBoxQty());
        return c1;
};
    
//수가 많은 박스
TestFunctionalInterface<CargoWorks> compareBoxes = (CargoWorks c1, CargoWorks c2) -> {
        if(c1.getBoxQty() > c2.getBoxQty()) {
            return c1;
        } else {
            return c2;
        }
        
};
    
//또 다른 박스관련 작업(람다에서는 기존에 존재하는 다른 메소드(여기에선 thatThingYouDo)도 사용이 가능합니다)
TestFunctionalInterface<CargoWorks> otherJobs = (CargoWorks c1, CargoWorks c2) -> thatThingYouDo(c1, c2);

위와 같은 여러 행위가 있다고 가정하면 다음의 메소드는 어떻게 처리할 것인지를 매개변수로 함께 받아서 처리할 수 있습니다.

private void applyBehavior(TestFunctionalInterface<CargoWorks> applySomething, CargoWorks c1, CargoWorks c2) {
        applySomething.doSomething(c1, c2);
}


Functional Interface인 Runnable

단일 메소드를 갖는 인터페이스 중 가장 있기 있는 걸 꼽으라면 아마도 Runnable일 것 입니다. 아무것도 반환하지 않는 run 메소드를 갖고 있는데 보통 쓰레드를 이용해서 프로그램의 성능을 향상시키기 위해 많이 사용되는 인터페이스 입니다.

기존에 익명클래스 방식을 사용한 코드는 다음과 같습니다.

new Thread(new Runnable() {
            @Override
            public void run() {
                doSomething();
                
            }
        }).start();


이것을 람다 표현식을 사용하면 다음과 같이 사용할 수 있습니다.

new Thread(() -> doSomething()).start();

바로 Thread의 생성자에 람다 표현식을 이용해서 행위를 전달할 수 있게 되는 거죠. 바로 Thread 클래스가 받아들이는 Runnable이 Functional Interface이기 때문에 가능해 지는 코드 입니다.

이것을 조금 더 응용해본다면 다음과 같은 코드도 작성할 수 있습니다.

package lambda.works;

public class AsyncManager {
    public void runAsync(Runnable r) {
        new Thread(r).start();
    }
}

그리고 이것을 이용하여..

package lambda.works;

public class TestFunctions {
    private AsyncManager manager = new AsyncManager();

    //두 스트링의 연결
    TestFunctionalInterface<String> stringAdder = (String s1, String s2) -> s1 + s2;
       
    //두 수의 곱
    TestFunctionalInterface<Integer> multipleNumbers = (Integer i1, Integer i2) -> i1 * i2;
    
    //두 박스의 합
    TestFunctionalInterface<CargoWorks> quantityAdder = (CargoWorks c1, CargoWorks c2) -> {
        c1.setBoxQty(c1.getBoxQty() + c2.getBoxQty());
        return c1;
    };
    
    private void takeResults() {
        CargoWorks cargoNoOne = new CargoWorks(1000);
        CargoWorks cargoNoTwo = new CargoWorks(2000);
       
        manager.runAsync(() -> System.out.println("Running in Async mode"));
        manager.runAsync(() -> {
            for(int i = 0; i < 100; i++) {
                System.out.println("just counting : " + i);
            }
        });
        manager.runAsync(() -> System.out.println("String : " + stringAdder.doSomething("A", "b")));
        manager.runAsync(() -> System.out.println("Number : " + multipleNumbers.doSomething(1, 2)));
        manager.runAsync(() -> System.out.println("Qty : " + quantityAdder.doSomething(cargoNoOne, cargoNoTwo).getBoxQty()));

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

이렇게 테스트를 해보면 각각은 쓰레드로 동작하기 때문에 결과가 뒤섞여서 나옴을 알 수 있습니다. 참 쉽죠?

마치며..

개인적으로 람다 표현식은 기존의 Java 프로그래밍에 꽤 많은 변화를 가져오지 않을까 생각됩니다. 아마도 병렬 프로그래밍과 비동기 프로그래밍이 보편화된 시대의 도래를 촉진하겠죠.. 아직 람다에 대해 많은 것을 알지는 못하지만 Java 8에서 가장 핫한 녀석인것 같다는 생각입니다.

2014년 2월 16일 일요일

Java에서 예외가 발생할 경우 프로그램의 흐름에 대해..

Java 프로그램을 작성하는데 있어 예외를 처리하는 것은 프로그램의 신뢰성을 보장하기 위해 매우 중요합니다. 실제 프로젝트를 진행하게 되면 예외처리와 관련한 코드의 비중이 매우크기도 하죠.

만약 하나의 커다란 테스크가 여러개의 작업으로 나뉘어진 경우 그중 하나의 작업에서 예외가 발생할 경우 테스크 전체를 어떻게 처리할 것인가 하는 문제를 생각해보면 예외처리를 통해 상황에 맞는 전략을 세우게 될 것 입니다.

다른 측면에서 보면 이는 로깅 전략과도 연관이 있는데 이 글에서는 Java에서 예외가 발생하는 경우 기본적인 코드의 흐름이 어떻게 되는지에 대해 이야기 하고자 합니다.

Java의 코드 수행은 좌에서 우로 위에서 아래로 흘러간다는 것을 염두하고 다음의 몇가지 상황을 상정해 보았습니다.

1. 예측하지 못한 상황에서 예외가 발생할 경우.

서브루틴이라면 예외를 상위로 전가시킬 수 있겠지만 메인 프로그램이라면 예외가 발생한 시점에 프로그램의 동작이 중단됩니다. 현실에 빗대어 생각해 보자면 프로그램 상의 일련의 작업 파이프 라인을 공장에서의 하나의 생산 라인이라고 하면 예외가 발생한다는 것은 생산 라인의 어딘가에서 문제가 발생해서 라인 전체의 작업이 중단되는 것과 같다고 볼 수 있겠습니다.

1의 상황이 발생한다면 프로그램의 치명적인 결함이 되기 때문에 이를 해소하기 위해 try-catch 구문을 이용한 예외처리를 Java에서는 제공하고 있습니다.

2. 예외가 발생하는 시점의 코드를 try-catch 구문으로 처리한 경우.

예외는 역시 발생하지만 예외처리를 하지 않은 곳에서 문제가 발생하지 않는 한 코드는 끝까지 수행되게 됩니다. 하지만 이경우에도 고려해야할 문제가 있는데 다음의 코드를 보면..
package when.error.occurs;

public class SumOfErrors {
 public static void main(String[] args) throws Exception{
  MakeError error = new MakeError();
  int loopCount = 1;
  System.out.println("Start Message !");

  while (true) {
   System.out.println("Number of loop count : " + loopCount);
   
   try {
    System.out.println("Before error");
    error.makeSomeErrors();
    System.out.println("After error");
   } catch (Exception e) {
    e.printStackTrace();
    //error occurs in catch area..
    //error.makeSomeErrors();
    System.out.println("Message in catch area");
   } finally {
    //error occurs in finally area..
    //error.makeSomeErrors();
    System.out.println("Message in finally area");
   }
   
   loopCount++;
   System.out.println("Wait for 3000ms...");
   Thread.sleep(3000);
  }

 }

}

이 코드에서 MakeError 클래스는 단순히 강제로 예외를 발생시켜 호출한 곳으로 전가(throws)시키는 메소드인 makeSomeErrors를 가지고 있습니다.

error.makeSomeErrors(); 호출 시점에 예외가 발생하게 되는데 해당 라인 이후의 try 영역의 코드는 수행되지 않고 catch 영역으로 넘어가는 것을 알 수 있습니다. 그렇기 때문에 try영역으로 감싸게 될 코드는 이러한 흐름을 고려하여 포함시켜야 한다고 볼 수 있습니다. finally 영역의 경우는 예외 발생 여부와 관계없이 반드시 수행을 진행하기 때문에 이러한 문제를 해소하기 위해 사용될 수 있습니다.

실행 결과는 다음과 같습니다.
Start Message !
Number of loop count : 1
Before error
java.lang.Exception: Exception in subroutine
 at when.error.occurs.MakeError.makeSomeErrors(MakeError.java:5)
 at when.error.occurs.SumOfErrors.main(SumOfErrors.java:14)
Message in catch area
Message in finally area
Wait for 3000ms...
...

"After Error" 메시지는 건너 뛴것을 확인할 수 있습니다.

2-1. catch 영역에서 예외가 발생할 경우.

샘플 코드의 주석을 해제하고 테스트해보면 알 수 있겠지만 catch 영역의 예외가 발생한 시점 이후의 코드는 수행되지 않습니다 "Message in catch area" 메시지는 건너 뛰게 되며 finally 영역의 코드가 수행된 후 1번과 같은 결과에 도달함을 알 수 있습니다. 만약 하위 Exception 클래스로 catch 영역을 세분화 하는 경우 정의되지 않은 예외가 발생하는 문제에 주의 해야함을 알 수 있습니다.

2-2. finally 영역에서 예외가 발생할 경우.

역시 샘플 코드의 주석을 해제하고 테스트해보면 finally 영역의 예외 발생 이후의 코드는 수행되지 않고 "Message in finally area" 메시지는 건너 뛰는 것을 확인할 수 있습니다. 역시 1과 같은 결과에 도달함을 알 수 있습니다.

마치며..

일반적으로 J2EE 어플리케이션을 작성하는 경우 서블릿 컨테이너 위에서 프로그램이 동작하기 때문에 main 메소드를 작성할일이 없어 어지간한 상황이 아니라면 컨테이너가 죽는 경우는 없겠지만 독립 어플리케이션을 작성하는 경우라면 이런 흐름을 충분히 고려해서 프로그램을 작성해야 할 것 입니다.

2014년 2월 15일 토요일

Springframework release 저장소 링크

Springframework의 라이브러리가 Maven이나 Gradle을 이용하여 프로젝트에 임포트 시키는 것을 권장하게 되면서 기존에 zip파일 형태로 다운 받을 수 있었던 링크 찾기에 애로사항이 꽃피는 관계로 해당 링크를 포스팅 합니다.

http://repo.spring.io/release/org/springframework/spring/

최상위 저장소 위치는 http://repo.spring.io/release/  입니다.

2014년 2월 10일 월요일

CentOS 6.5에 Oracle Database 11g R2 x86_64 설치하기

참고 : http://www.tecmint.com/oracle-database-11g-release-2-installation-in-linux/ , http://matthiashoys.wordpress.com/2012/04/20/automatically-start-oracle-11g-on-oracle-linux-6-2-after-server-reboot/ , http://www.binarytides.com/open-http-port-iptables-centos/ , http://www.transnexus.com/index.php/how-to-fix-oracle-dbconsole-after-hostname-or-ip-change

1. 선행 작업으로 의존성 해결을 위해 메뉴얼 방식이 아닌 오라클 공개 YUM 저장소의 "oracle-rdbms-server-11gR2-preinstall" 패키지를 설치합니다.

다음 일련의 작업들은 root 계정으로 수행합니다.

먼저 오라클의 저장소 정보와 키를 받아옵니다.
cd /etc/yum.repos.d
wget https://public-yum.oracle.com/public-yum-ol6.repo
wget https://public-yum.oracle.com/RPM-GPG-KEY-oracle-ol6 -O /etc/pki/rpm-gpg/RPM-GPG-KEY-oracle
oracle-rdbms-server-11gR2-preinstall 패키지를 설치합니다.
yum install oracle-rdbms-server-11gR2-preinstall
2. 호스트 네임과 관련한 정보를 수정합니다. HOSTNAME 값은 FQDN(Fully Qualified Domain Name)에 맞춰 설정합니다.
vi /etc/sysconfig/network
vi /etc/hosts
* hosts 파일에는 서버의 아이피에 HOSTNAME을 할당합니다.

3. 앞서 설치한 패키지에서 자동으로 생성된 oracle 계정에 대한 설정을 진행합니다.
passwd oracle
"/etc/security/limits.d/90-nproc.conf"에 다음 항목을 추가 합니다.
vi /etc/security/limits.d/90-nproc.conf

# Default limit for number of user's processes to prevent
# accidental fork bombs.
# See rhbz #432903 for reasoning.

*          soft    nproc     1024
# for Oracle database
* - nproc 16384
"/etc/selinux/config"를 열어 SELINUX 항목을 permissive로 변경합니다.
vi /etc/selinux/config

SELINUX=permissive
설정후 서버를 재시작 합니다.

다음 일련의 작업들은 앞서 생성한 oracle 계정으로 수행합니다. oracle 계정의 .bash_profile에 다음 내용을 추가합니다.
vi .bash_profile

# Oracle Settings
TMP=/tmp; export TMP
TMPDIR=$TMP; export TMPDIR

ORACLE_HOSTNAME=localhost; export ORACLE_HOSTNAME
ORACLE_UNQNAME=DB11G; export ORACLE_UNQNAME
ORACLE_BASE=/u01/app/oracle; export ORACLE_BASE
ORACLE_HOME=$ORACLE_BASE/product/11.2.0/dbhome_1; export ORACLE_HOME
ORACLE_SID=DB11G; export ORACLE_SID
PATH=/usr/sbin:$PATH; export PATH
PATH=$ORACLE_HOME/bin:$PATH; export PATH

LD_LIBRARY_PATH=$ORACLE_HOME/lib:/lib:/usr/lib; export LD_LIBRARY_PATH
CLASSPATH=$ORACLE_HOME/jlib:$ORACLE_HOME/rdbms/jlib; export CLASSPATH export PATH
ORACLE_HOSTNAME을 앞서 설정한 HOSTNAME으로 변경합니다. ORACLE_UNQNAME, ORACLE_SID값을 설치에 앞서 확인합니다.

다음 작업을 위해 root로 계정을 변경합니다.

X Server에 oracle 계정이 접근할 수 있도록 다음을 실행합니다.
xhost +
설치를 위해 다음 디렉토리를 생성하고 권한을 수정합니다.
mkdir -p /u01/app/oracle/product/11.2.0/dbhome_1
chown -R oracle:oinstall /u01
chmod -R 775 /u01
4. 다운로드 한 Oracle database의 설치를 위해 oracle 계정으로 전환 합니다.

"/home/oracle/ " 디렉토리에 다운로드한 파일의 압축을 해제합니다.
unzip linux.x64_11gR2_database_1of2.zip
unzip linux.x64_11gR2_database_2of2.zip
OUI(Oracle Universal Installer)를 실행 합니다.
cd database
./runInstaller
** 인스톨러를 통한 설치 항목은 비교적 단순하기에 생략합니다. 몇가지 체크할 사항으로 앞서 .bash_profile에 설정한 환경변수와 실재 값이 일치하도록 확인합니다. 필요 조건 체크와 관련한 페이지에서 Failed가 뜨는 항목은 "Fix & Check Again"을 통해 문제를 해결이 가능한데 제 경우는 패키지를 YUM 저장소에서도 찾을 수 없는 것들이 존재해서 "Ignore All"을 체크하고 넘어갔으나 별다른 문제는 발생하지 않는 것 같습니다.

설치 마지막 즈음 나오는 2개의 스크립트를 root 계정으로 실행하라는 메시지를 확인하고 계정을 전환하여 다음의 작업들을 진행합니다.
cd /u01/app/oraInventory
./orainstRoot.sh
cd /u01/app/oracle/product/11.2.0/dbhome_1/
./root.sh
5. 방화벽에 Oracle EM(Enterprise Manager Console)과 LISTENER에 접근 가능 하도록 포트를 등록합니다.
iptables --line -vnL

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination         
1        1   328 ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0           state RELATED,ESTABLISHED 
2        0     0 ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0           
3        0     0 ACCEPT     all  --  lo     *       0.0.0.0/0            0.0.0.0/0           
4        0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0           state NEW tcp dpt:22 
5        3   234 REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0           reject-with icmp-host-prohibited 

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination         
1        0     0 REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0           reject-with icmp-host-prohibited 

Chain OUTPUT (policy ACCEPT 13 packets, 808 bytes)
num   pkts bytes target     prot opt in     out     source               destination
iptables -I INPUT 5 -m state --state NEW -m tcp -p tcp --dport 1158 -j ACCEPT
iptables -I INPUT 6 -m state --state NEW -m tcp -p tcp --dport 1521 -j ACCEPT
service iptables save
service iptables restart
iptables 정보를 조회하여 INPUT 체인의 REJECT항목보다 선행 넘버로 해당 포트를 등록합니다. 해당 설정 값은 "/etc/sysconfig/iptables" 파일을 수정하여 변경이 가능합니다.

6. 스크립트와 서비스등록을 위해 oracle 계정으로 전환합니다. 데이터베이스의 인스턴스를 자동으로 실행할 수 있도록 하기 위해 앞서 root.sh 스크립트를 실행하여 생성된 "/etc/oratab" 파일을 수정합니다.
vi /etc/oratab

# This file is used by ORACLE utilities.  It is created by root.sh
# and updated by the Database Configuration Assistant when creating
# a database.

# A colon, ':', is used as the field terminator.  A new line terminates
# the entry.  Lines beginning with a pound sign, '#', are comments.
#
# Entries are of the form:
#   $ORACLE_SID:$ORACLE_HOME:[N|Y]:
#
# The first and second fields are the system identifier and home
# directory of the database respectively.  The third filed indicates
# to the dbstart utility that the database should , "Y", or should not,
# "N", be brought up at system boot time.
#
# Multiple entries with the same $ORACLE_SID are not allowed.
#
#
DB11G:/u01/app/oracle/product/11.2.0/dbhome_1:Y
다음의 실행과 종료 스크립트를 생성합니다.
vi /home/oracle/scripts/ora_start.sh

#!/bin/bash
 
# script to start the Oracle database, listener and dbconsole
 
. ~/.bash_profile
 
# start the listener and the database
$ORACLE_HOME/bin/dbstart $ORACLE_HOME
 
# start the Enterprise Manager db console
$ORACLE_HOME/bin/emctl start dbconsole
 
exit 0
vi /home/oracle/scripts/ora_stop.sh

#!/bin/bash
 
# script to stop the Oracle database, listener and dbconsole
 
. ~/.bash_profile
 
# stop the Enterprise Manager db console
$ORACLE_HOME/bin/emctl stop dbconsole
 
# stop the listener and the database
$ORACLE_HOME/bin/dbshut $ORACLE_HOME
 
exit 0
스크립트에 실행할 수 있는 권한을 줍니다.
chmod u+x ora_start.sh ora_stop.sh
서비스 등록을 위해 root 계정으로 전환 합니다. "/etc/init.d/oracle" 스크립트 파일을 생성합니다.
vi /etc/init.d/oracle

#!/bin/bash
# chkconfig: 345 99 10
# description: Oracle auto start-stop script.
 
# Set ORA_OWNER to the user id of the owner of the
# Oracle database in ORA_HOME.
 
ORA_OWNER=oracle
RETVAL=0
 
case "$1" in
    'start')
        # Start the Oracle databases:
        # The following command assumes that the oracle login
        # will not prompt the user for any values
        su - $ORA_OWNER -c "/home/oracle/scripts/ora_start.sh"
        touch /var/lock/subsys/oracle
        ;;
    'stop')
        # Stop the Oracle databases:
        # The following command assumes that the oracle login
        # will not prompt the user for any values
        su - $ORA_OWNER -c "/home/oracle/scripts/ora_stop.sh"
        rm -f /var/lock/subsys/oracle
        ;;
    *)
        echo $"Usage: $0 {start|stop}"
        RETVAL=1
esac
exit $RETVAL
생성한 스크립트의 권한을 변경합니다.
chmod 750 /etc/init.d/oracle
oracle 스크립트를 서비스에 등록합니다.
chkconfig --add oracle

2014년 2월 1일 토요일

java.util.logging.Logger의 콘솔 출력 레벨 변경

java.util.logging.Logger를 사용하는 경우 로깅 레벨을 코드상에서 변경 하더라도 콘솔에 출력되는 항목은 INFO 레벨 이상만 표시되게 된다. 이유는 기본적으로 참조하는 JAVA_HOME\jre\lib\logging.properties 파일에 콘솔항목은 INFO레벨 이상일 경우만 출력 하도록 되어 있으며 코드상에서 레벨을 변경 하더라도 둘의 교집합 영역만 표시되기 때문이다.

콘솔 출력과 관련한 기본 설정 값은 다음과 같다.

# Limit the message that are printed on the console to INFO and above.
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

로깅 레벨은 "java.util.logging.ConsoleHandler.level = INFO" 항목을 수정하여 변경할 수 있다. 참고로 java.util.logging.Logger에서 제공하는 로깅 레벨은 다음과 같다.
  • SEVERE (highest value)
  • WARNING
  • INFO
  • CONFIG
  • FINE
  • FINER
  • FINEST (lowest value)
참조하는 프로퍼티 파일의 위치를 변경하고 싶다면 VM 변수 "-Djava.util.logging.config.file=[프로퍼티 파일 위치]" 로 지정이 가능하다.

2014년 1월 17일 금요일

오라클, Java 9에서 Java 8으로의 하위 호환성에 제약을 걸다

원문 : http://www.javaworld.com/article/2078927/java-se/oracle-to-limit-backward-compatibility-from-java-9-to-java-8.html

JDK 9이 JDK 8에서 초기화 되면 릴리즈 간의 코드라인 병합은 중단 될 예정.


개발자들과 함께 JDK 8에서 JDK 9으로 JDK 구축의 이행을 준비중인 오라클의 자바 최고 책임자는 두 JDK간의 코드라인의 병합을 제한 할 것을 제안하였다.

오라클 자바 플랫폼 그룹의 치프 아키텍트인 Mark Reinhold는 OpenJDK 메일 링 리스트에서 JDK 8으로의 변경 내역은 줄어드는 반면 JDK 9의 포레스트(참고: OpenJDK Mercurial Forest)는 조만간 오픈 될 것 이며 개발자들은 이제 두 개의 개별 릴리즈에 대한 변경 내역들을 각각 관리 해야 한다는 것 을 언급 하였다.

현재의 일반적인 규칙은 변경 내역 들이 개발 중인 릴리즈에 먼저 이행 되고 앞선 릴리즈에 백 포트 되는 것 이다. 하지만 새롭게 시작하는 후속 릴리즈(지금의 경우 JDK 9)에서 보다 기능 릴리즈의 최종단계인 릴리즈 준비단계 이후(지금의 경우 JDK 8)의 주기에서 변경사항 들이 좀더 충분히 테스트 될 수 있을 것이기 때문에 이러한 규칙은 크게 의미가 없게 된다. 이는 후속 릴리즈에 변경 내역들이 먼저 전달 됨으로 인해 최종 릴리즈 단계의 작업이 지연되는 것을 의미한다.

JDK 7까지는 이러한 두 버전간의 평행선상의 변경 내역을 처리하기 위한 정책이 없었다. 개발자는 일반적으로 먼저 요청 된 릴리즈를 대상으로 변경 내역을 적용하는 반면 썬/오라클의 릴리즈 엔지니어링 팀의 누군가는 최종 버전과 후속 릴리즈 사이의 반자동 병합을 수행 하며 이러한 병합 작업은 더 이상 병합이 불가능 하게 되는 시점까지 수행되었다. 그렇게 되면 개발자는 이러한 변경 내역들이 정확하게 해당하는 릴리즈에 반영되는 것을 보장하는데 도움을 주는 버그 데이터베이스 쿼리를 이용하여 두 릴리즈 모두에 변경 내역을 적용하는 것을 요청하게 되어 있었다.

Reinhold는 이러한 접근 방식은 최종 릴리즈에 기여하는 수백명의 개발자들 모두가 반자동 병합 작업 들이 아직 수행 중에 있는지 모니터링 해야만 하고 병합 작업이 중단 된 즉시 자신들의 코드 통합과 관련한 작업 절차들을 변경해야 하기 때문에 작업들을 효율적으로 조율하기가 어렵다고 이야기 하고 있다.

이러한 최총 릴리즈 절차를 단순화 하기 위해 Reinhold는 JDK 9의 개발 포레스트는 JDK 8의 특정 빌드로부터 초기화 할 것을 제안 하였다. 이 특정 빌드의 JDK 8에서 JDK 9이 분기 된 이후에는 두 릴리즈 간의 코드 라인 병합은 허용되지 않을 것 이며 JDK 8에 변경 내역을 반영한 개발자는 해당 변경 내역이 JDK 9에도 적용 가능한 경우 JDK 9에도 개별적으로 적용 시켜야 한다.

Reinhold는 이러한 변화로 인해 기존 프로세스의 문제가 해결되길 원하고 있다. 유일한 단점으로 보이는 것은 GA(General Availability)이전의 JDK 8으로부터 분기된 JDK 9의 포레스트를 이용해서는 JDK 8 GA의 빌드가 불가능해 진다는 것이다. 기술적인 관점에서 이를 가능하게 하는 것은 다소 편리하면서도 멋진 일이겠지만 이러한 변화는 미학적인 면을 좀더 고려한 것 임을 밝혔다. 한편 JDK 8 포레스트를 통해 JDK 7 업데이트 릴리즈를 빌드 할 수 없다는 점에서 본다면 이 문제와 관련하여 상황이 달라지는 것은 아니다.

Java SE 8 기반의 환경에서 멀티코어 프로세서에서 동작하는 코드의 작성을 손쉽게 하기 위한 람다 프로젝트를 지원할 JDK 8은 해당 체험판 빌드가 이미 존재하는 상태이다. 한편 2016년 초 등장 예정이며 Java의 모듈화 기능인 Jigsaw 프로젝트를 제공할 Java SE 9의 릴리즈에 착수한 상태이다.