[JAVA] 트랜잭션, try-with-resource, 제네릭

dejeong·2024년 9월 13일

JAVA

목록 보기
12/24
post-thumbnail

마지막 코드에서 lion과 tiger 둘 다 Predator을 가질 수 있다.

추상 클래스도 다형성을 가지고 있음으로 위와 같이 선언이 가능하다.
객체를 가지고 있기 때문에 배열로 넣어도 괜찮다.

인터페이스와 추상클래스 모두 상위 타입으로 선언 가능하다.
선언이 가능하기 때문에 배열에 자식 객체들이 들어갈 수 있다.
각각 타입으로도 사용할 수 있다.
추상 클래스와 인터페이스 둘 다 타입 자체만으로는 생성자 호출이 불가능하다.
생성자 호출은 각각의 구현체 혹은 상속 받은 자식 클래스에서 생성자 호출이 가능하다.
추상 클래스 자체 만으로는 생성자 호출, 생성은 불가능 하나 타입으로는 선언이 가능하다.
자식 클래스로 감쌀 수는 있지만 직접적인 생성자 호출은 불가능하다.


예외 트랜잭션 처리 방법

트랜잭션?

하나의 논리적인 작업 단위

ex) 송금의 트랜잭션

  • 송금 ‘처리중’ 상태로 변경
  • 송금인 계좌에 돈이 빠져나감
  • 수금인 계좌에 돈이 채워짐
  • 송금 ‘완료’ 상태로 변경

위 프로세스 중 하나라도 실패하면 2가지 모두 취소하고 ‘송금’ 전의 상태로 되돌려야
데이터의 *정합성이 유지된다. 되돌리는 행위를 롤백(rollback)이라고 한다.

*정합성 : 데이터의 값이 서로 일관성있게 일치하는 것

⭐ 트랜잭션의 특징(ACID)

  • 원자성(Atomicity) - 트랜잭션이 롤백하는 이유
    • 전체 성공 혹은 전체 실패
    • 완벽하게 성공하거나, 완벽하게 실패하거나를 말하며, 롤백을 하는 경우는 완벽하게 실패하는 경우를 말한다.
    • 트랜잭션의 작업이 부분적으로 실행되거나 중된되지 않는 것을 보장한다.
    • DB에 모두 반영되거나 혹은 전혀 반영되지 않아야 한다.
  • 일관성(Constency)
    • 트랜잭션의 작업 처리 결과는 항상 일관성이 있어야 한다.
  • 독립성(Isolation)
    • 둘 이상의 트랜잭션이 동시에 병행 실행되고 있을 때, 어떤 트랜잭션도 다른 트랜잭션 연산에 끼어들 수 없다.
  • 지속성(Durability)
    • 트랜잭션이 성공적으로 완료되었으면, 결과는 영구적으로 반영되어야 한다.

하나의 트랜잭션 작업 중 중간에 하나라도 실패하면 예외를 발생시켜서 catch문으로 받아서 롤백 작업을 한다.

돈_송금() {
	try {
		송금_상태변경(처리중);
		송금인_계좌_출금();
		수금인_계좌_입금();
		송금_상태변경(완료);
	} catch (예외) {
		모두취소(); // 롤백
	}
}

송금_상태변경(status) throws 예외 {
	...
}

송금인_계좌_출금() throws 예외 {
	...
}

수금인_계좌_입금() throws 예외 {
	...
}

자동 리소스 닫기(try-with-resource)

예외 발생 여부와 상관없이 사용했던 리소스 객체를 close() 메서드를 호출하여 자동으로 닫아주는 것

public class TryWithResourceSample {
    public static void main(String[] args) {
        // file 읽어오는 로직
        String filename = "example.txt";
        BufferedReader reader = null;
        try{
            reader = new BufferedReader(new FileReader(filename));
            reader.readLine();
            reader.close();
        } catch (IOException e){
            System.out.println("Error: " + e);
        } finally {
            // reader 객체에 값이 들어있을 경우 reader라는 자원을 닫아주기
            if (reader != null){
                try{
                    reader.close();
                } catch (IOException e){
                    // 예외 처리
                }
            }
        }
    }
}
finally {
    // reader 객체에 값이 들어있을 경우 reader라는 자원을 닫아주기
    if (reader != null){
        try{
            reader.close();
        } catch (IOException e){
            // 예외 처리
        }
    }
}

위와 같이 try-catch 부분에서 반복되는 것을 방지하기 위해 자바 7 버전 이후부터 try-with-resource 를 지원해주게 되었고, 해당 기능을 사용하게 된다면

public class TryWithResourceSample {
    public static void main(String[] args) {
        // file 읽어오는 로직
        String filename = "example.txt";
        try(BufferedReader reader = new BufferedReader(new FileReader(filename))){
            reader.readLine();
            reader.close();
        } catch (IOException e){
            System.out.println("Error: " + e);
        }
    }
}
try(BufferedReader reader = new BufferedReader(new FileReader(filename)))

과 같이 작성할 수 있다.

close()라는 메서드는 AutoClosable.close(); 인터페이스에서 가지고 있는 메서드이다.

실제 자바에서 close()메서드를 자동으로 호출해주는가?

AutoClosable 이라는 인터페이스를 가져다가 close() 라는 메서드를 구현해보면 알 수 있다.

public class FileStream implements AutoCloseable{
    private String fileName;

    FileStream(String fileName) {
        this.fileName = fileName;
    }

    void read() throws Exception{
        System.out.println("FileStream.read() 호출 : " + fileName);
//        throw new Exception("예외 발생 상황 가정");
    }

    // AutoCloseable 에 있는 close 메서드 반드시 호출 필요
    @Override
    public void close() throws Exception {
        System.out.println("close() 메서드 호출");
    }
}
public class TryWithResourceSample2 {
    public static void main(String[] args) {
        try(FileStream stream = new FileStream("example.txt")){
            stream.read();
        } catch (Exception e){
            System.out.println(e.getMessage());
        } finally {
            System.out.println("완료");
        }
    }
}

위 코드에서 close() 메서드를 호출하지 않았지만,
try(FileStream stream = new FileStream("example.txt")) 구문을 넣었음으로
public void close() throws Exception 이 호출되었다.
예외가 발생하지 않았을 때 호출되는 순서는 try문 → close() 호출

try 구문에서 예외를 발생 시키면 close 메서드가 예외가 발생했을 시점에 호출이 된다.

예외가 발생하더라도 자원은 바로 닫힌다는 것을 알 수 있다. → try 구문에서 예외가 발생하더라도 close 구문은 반드시 호출된다는 것을 알 수 있다. catch문으로 가기 이전에 자원이 닫힌다.

예외 발생 → close() 호출 → catch문


메서드 오버라이딩 규칙

부모 메서드에서 정의했던 메서드 시그니처에서 맨 뒤에 예외가 있다면(throw 하고 있는) 자식 메서드에서 그 뒤에 다른 예외를 작성해줄 수 없다.

  • 부모와 다른 예외 메서드를 throw 할 수 없음
  • 부모 메서드와 같은 예외, 혹은 그 하위 예외는 가능 (ex.IOEception / FileNotFoundException)
  • 부모에서 썼던 오류보다 상위 에러를 쓴다면 에러
  • 부모에서 체크드익셉션 자식에서는 언체크드 익셉션을 작성하는 것은 가능
  • 런타임 예외는 위 규칙 적용 안됨

부모 클래스 메서드에 들어갈 예외

  • IOEception
  • Exception
  • SQLException

자식 클래스 메서드

  • FileNotFoundException
  • NumberFormatException
  • ClassCaseException
  • ArithmeticException

⭐ 제네릭

데이터 타입을 미리 일반화한다는 것, <> 안에 미리 데이터 타입을 넣어두어서 클래스나 메서드에서 사용할 내부 데이터 타입을 컴파일 시에 미리 지정하는 방법으로, 컴파일을 하는 시점에 미리 타입을 검사할 수 있다. (다른 타입을 넣으려고 한다면 에러를 발생시킴)

// 리스트에는 String 이라는 데이터 타입만 넣을 수 있게 정의해둔 것
ArrayList<String> = new ArrayList<>();

(강력한 타입 체크를 해준다.)

public class Sample {
    void test(){
        // 제네릭 O
        ArrayList<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add(10.45); // 컴파일러에서 알려준다.

        // 제네릭 X
        ArrayList list2 = new ArrayList();
        list2.add(10);
        list2.add(13.45);
        list2.add("문자열");
    }
}

📢 제네릭은 클래스인터페이스, 그리고 메소드를 정의할 때 타입(Type)을 파라미터(Parameter)로 사용할 수 있다.

타입을 지정하지 않으면 뒤에 모든 타입이 될 수 있다는 것을 의미하고, 지정하지 않을 때 list.get은 최상위 클래스인 Object가 되고, 그것을 원하는 타입으로 casting 해주어야 한다. 그래야 str 타입에 변수로 값을 받을 수 있다. casting을 해주지 않는다면 컴파일 에러가 발생한다.

Object는 모든 클래스의 부모 클래스이기 때문에 모든 타입이 될 수 있다는 것을 의미한다.

obj = ""; // 어떤 값이 들어와도 obj에 들어갈 수 있다.
List list = new ArrayList();
list.add("Hello");
String str = (String) list.get(0);
List<String> list = new ArrayList<String>();
list.add("Hello");
String str = list.get(0);

제네릭 코드를 사용하게 되면 저장되는 요소가 String 타입으로 정해지기 때문에 따로 casting을 해주지 않아도 된다.

제네릭 타입(class, Interface)

public class 클래스명<T> { ... }
public interface 인터페이스명<T> { ... }

타입 파라미터는 변수명과 동일한 규칙에 따라 작성할 수 있지만, 일반적으로 대문자 알파벳 한 글자 T(Type)로 표현한다. 실제 코드에서 제네릭 타입을 사용하려면 타입 파라미터에 구체적인 타입을 지정해야한다.

public class Box {
    private Object object;

    // setter 메서드 생성
    public void set(Object object) {
        this.object = object;
    }

    public Object get() {
        return this.object;
    }
}

타입 파라미터 T를 사용해서 Object 타입을 모두 T로 대체했다.
T는 Box 클래스로 객체를 생성할 때 구체적인 타입으로 변경

public class Box<T> {
    private T Object;

   public T get(){
       return Object;
   }

   public void set(T Object){
        this.Object = Object;;
   }
}

타입 파라미터 T 는 String 타입으로 재구성되어 타입변환을 따로 하지 않아도 된다.
객체를 생성하는 시점에 타입을 지정해주었다면, 그 타입으로 값을 넣어줄 수도 있고, 빼줄 수도 있다.
이것을 가능한 이유는 박스 클래스 내부에서 제네릭 타입을 변환 해준 것이다.
내부적으로 T 대신에 실제 타입이 들어간 것
String 대신 다른 참조형 타입도 들어갈 수 있다.

Box<int> intBox = new Box<>(); // 불가

위와 같이는 불가능하다, 제네릭 타입 안에는 참조형 타입만 들어갈 수 있기 때문이다.

Box<Integer> intBox = new Box<>(); // 가능, 참조형 타입
Box<double> intBox = new Box<>(); // 불가, primitive 타입
Box<Double> intBox = new Box<>(); // 가능, wrapper 타입
Box<Boolean> intBox = new Box<>(); // 가능

내부적으로 변환이 되기 때문에 get 을 사용해도 제네릭 타입으로 가져올 수 있는 것

Box<Integer> box = new Box<Integer>();
box.set(6);             // 자동 Boxing
int value = box.get();  // 자동 Unboxing

Integer 타입의 값을 int 타입으로 Unboxing 할 때도 자동으로 변환된다.

JDK 안에 ArrayList 등 자료 구조들도 내부적으로 제네릭 타입을 가지고 있고, 이 타입을 가지고 있기 때문에 객체를 만들어서 객체의 메서드 호출을 해줄 때 타입들을 넣어줄 수도 있고, 빼줄 수 도 있다.

ArrayList<String> list = new ArrayList<>();

public boolean add(String e){
	// e 요소 추가
}

// get 메서드
E get(index){

}

제네릭 메서드

<> 를 사용하여 매개 타입과 리턴 타입으로 타입 파라미터를 갖는 메서드를 말한다.

public <타입파라미터, ..> 리턴타입 메소드명(매개변수, ...) {
}

타입파라미터가 여러 개가 될 수도 있다.

public <T> Box<T> boxing(T t) {
}

호출하는 방법은

리턴타입 변수 = <구체적인타입> 메소드명(매개값);  // 명시적으로 구체적 타입을 지정
리턴타입 변수 = 메소드명(매개값);              // 매개값을 보고 구체적 타입을 추정 

코드에서 타입 파라미터의 구체적인 타입을 명시적으로 지정해도 되고, 컴파일러가 매개값의 타입을 보고 구체적인 타입을 추정하도록 할 수도 있다.

public class Util {
    public static <T> Box<T> boxing(T t){ // 외부에서 호출할 수 있도록 static 사용
        Box<T> box = new Box<>();
        box.set(t);
        return box;
    }
}
public class BoxGenericExample {
    public static void main(String[] args) {
        Box<String> value =  Util.boxing("문자열 값"); // 매개변수의 타입이 메서드의 리턴 타입이 되기 때문에 Box<String>으로 호출되는 것이다.
    }
}

public static <T> Box<T> boxing(T t)
매개변수의 타입이 메서드의 리턴 타입이 되기 때문에 Box으로 호출되는 것이다.

Util.boxing(56789); // 리턴 타입은? Integer

Box<Integer> value = Util.boxing(56789) // 정수형 타입으로 반환됨

<T> 에 들어갈 파타미터는 뒤에 들어갈 파라미터의 타입이 들어가면 되는데, 정의해주기 나름이지만 캐스팅해주고 싶은 타입이 여러 개라면 <T, E>나열해주면 된다.

public static <T,E> Box<T> boxing(T t, E e)
profile
룰루

0개의 댓글