no image
[Java] Optional<T>
자바에서 Optional 클래스는 null이 될 수 있는 객체를 감싸는 래퍼 클래스이다. java.util.Optional는 자바 8에서 도입되었으며, NullPointerException을 방지하고, 명시적으로 변수가 null 값을 가질 수 있음을 표현할 수 있는 방법을 제공한다. 이를 통해 개발자는 보다 깔끔하고 의도가 명확한 코드를 작성할 수 있다. 기본 사용법 Optional 객체를 생성하는 기본적인 방법은 Optional.of(value), Optional.ofNullable(value), 그리고 Optional.empty() 세 가지가 있다. Optional.of(value): null이 아닌 명시적인 값을 가지는 Optional 객체를 반환한다. 만약 인자로 넘긴 값이 null이라면, 즉시 ..
2024.03.15
no image
[Java] static import
static import는 클래스의 정적 멤버(메서드와 필드)를 클래스 이름 없이 직접 사용할 수 있도록 해준다. 이를 통해 코드의 가독성을 높일 수 있지만, 과도하게 사용할 경우 어느 클래스에서 메서드나 필드가 정의되었는지 혼란을 줄 수 있다. 예를들어, static import를 사용하지 않는 경우, 예를 들어 Math 클래스의 sqrt 메서드를 사용하려면 클래스 이름을 명시해야 한다. double result = Math.sqrt(25); // Math 클래스의 sqrt 메서드 사용 static import를 사용하면, 메서드를 클래스 이름 없이 직접 호출할 수 있다 import static java.lang.Math.sqrt; ... double result = sqrt(25); // Math.s..
2024.01.23
no image
[Java] Arrays.sort(), Collections.sort() Comparator, Comparable
Arrays.sort()와 Collections.sort() Arrays.sort()와 Collections.sort()는 Java에서 배열과 컬렉션을 정렬하는 메서드이다. Arrays.sort() Arrays 클래스에 속한 정적 메서드로, 배열을 정렬할 때 사용한다. 주로 기본 타입 배열(int, double)을 정렬하는 데 사용된다. 정렬 알고리즘으로는 기본적으로 퀵 소트(QuickSort)가 사용되지만, Java 7 이후로는 퀵 소트와 병합 소트(MergeSort)가 혼합된 팀소트(TimSort)가 사용된다. Arrays.sort()는 인자로 넘겨받은 배열을 직접 수정하며, 반환값은 void이다. int[] arr = {5, 2, 9, 1, 5}; Arrays.sort(arr); Collection..
2024.01.15
no image
[Java] char 타입을 정수 타입으로 변환
char num = '1'; int tmp = num; //자동 타입 변환 System.out.print(tmp); char num = '1'; System.out.print((int) num); //강제 타입 변환하여 출력 이렇게 문자 '1'을 자동 타입 변환을 하거나 강제 형 변환을 해서 출력을 하게되면 1이 출력이 되는것이 아니라 49가 출력이되어서 나온다. 위에서 49가 출력된 이유는 '1'은 아스키코드로 49이기 때문이다. 즉, 문자 '1'이 정수로 타입 변환이 될 때 아스키코드 49로 변환된 것이다. 이런 문제는 charAt()메소드를 사용할 때도 나타난다. String num = "12345"; for(int i = 0; i < num.length(); ++i) { int tmp = num...
2023.11.26
no image
[Java] next()와 nextLine()의 차이
import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int number; String string; System.out.print("숫자 입력 : "); number = sc.nextInt(); System.out.println("문자열 입력 : "); string = sc.nextLine(); System.out.println("숫자 입력 결과 : " + number); System.out.println("문자열 입력 결과 : " + string); } } /* 숫자 입력 : 6546201 문자열 입력 : 숫자 입력 결과 : 65462..
2023.08.22
no image
[Java] DB와 연동한 게시판 구현
이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. USER 테이블 Oracle MySQL BOARDS 테이블 Oracle MySQL 구현 Oracle Board.java import java.util.Date; public class Board { private int bno; private String btitle; private String bcontent; private String bwriter; private Date bdate; public int getBno() { return bno; } public void setBno(int bno) { this.bno = bno; } public String getBtitle() ..
2023.08.21
no image
[Java] DB 트랜잭션 처리
이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 트랜잭션 트랜잭션(transaction)은 기능 처리의 최소 단위를 말한다. 하나의 기능은 여러가지 소작업들로 구성된다. 최소 단위라는 것은 이 소작업들을 분리할 수 없으며, 전체를 하나로 본다는 개념이다. 트랜잭션은 소작업들이 모두 성공하거나 실패해야 한다. 예를 들어 계좌 이체는 출금과 입금으로 구성된 트랜잭션이다. 출금과 입금 작업 중 하나만 성공할 수 없으며, 모두 성공하거나 모두 실패해야 한다. 계좌 이체는 DB 입장에서 보면 두 개의 계좌 금액을 수정하는 작업이다. 출금 계좌에서 금액을 감소시키고, 입금 계좌에서 금액을 증가시킨다. 따라서 아래와 같이 두 개의 UPDATE ..
2023.08.20
no image
[Java] DB 프로시저와 함수 호출
이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 프로시저와 함수 이 글에서는 Oracle DB에 적용되는 프로시저와 함수를 다룬다. 클라이언트 프로그램(DB가 서버이고, DB를 이용하는 프로그램이 클라이언트)에서 매개값과 함께 프로시저 또는 함수를 호출하면 DB 내부에서 일련의 SQL문을 실행하고, 실행 결과를 클라이언트 프로그램으로 돌려주는 역할을 한다. 즉, 프로그램 내부에서 SQL문의 작업내용이 처리되지 않고 DB에서 처리가 된다. 클라이언트에서 처리하기 부담스러운 처리를 서버로 돌릴 수 있게 된다. 프로시저 : 리턴하는 값이 있지만, 주로 리턴하지 않고 작업 처리를 할 때 사용 예를 들어, 데이터 삽입, 삭제, 데이터 확인 ..
2023.08.19

자바에서 Optional 클래스는 null이 될 수 있는 객체를 감싸는 래퍼 클래스이다. java.util.Optional<T>는 자바 8에서 도입되었으며, NullPointerException을 방지하고, 명시적으로 변수가 null 값을 가질 수 있음을 표현할 수 있는 방법을 제공한다. 이를 통해 개발자는 보다 깔끔하고 의도가 명확한 코드를 작성할 수 있다.

 

기본 사용법

Optional 객체를 생성하는 기본적인 방법은 Optional.of(value), Optional.ofNullable(value), 그리고 Optional.empty() 세 가지가 있다.

 

  • Optional.of(value): null이 아닌 명시적인 값을 가지는 Optional 객체를 반환한다. 만약 인자로 넘긴 값이 null이라면, 즉시 NullPointerException을 발생시킨다.
  • Optional.ofNullable(value): 인자로 넘긴 값이 null일 수도 있는 경우 사용한다. 값이 null이면 빈 Optional 객체를 반환한다.
  • Optional.empty(): 빈 Optional 객체를 생성한다.
Optional<String> optional = Optional.of("hello");

// 값이 존재하는 경우에만 처리를 실행
optional.ifPresent(value -> System.out.println(value));

// 값이 존재하지 않는 경우 기본값을 제공
String valueOrDefault = optional.orElse("default");
System.out.println(valueOrDefault);

// 값이 존재하지 않을 때 예외를 던짐
String valueOrThrow = optional.orElseThrow(IllegalStateException::new);
System.out.println(valueOrThrow);

// 값이 존재할 때 변환을 수행
Optional<Integer> length = optional.map(String::length);
System.out.println(length.orElse(0));

 

아래의 예제에서는 간단한 사용자 정보를 나타내는 User 클래스를 만들고, 이를 Optional로 감싸서 처리하는 방법을 보여준다. 이를 통해 Optional의 기본적인 사용 방법과 Optional을 사용할 때의 장점을 이해할 수 있다.

public class User {
    private String name;
    private Integer age;

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public Integer getAge() {
        return age;
    }
}

 

Optional을 사용하여 객체를 감싸는 예제

이 예제에서는 Optional.ofNullable() 메소드를 사용하여 null일 수도 있는 User 객체를 Optional로 감싸고, ifPresent() 메소드를 사용하여 User 객체가 실제로 존재할 때만 특정 작업을 수행하도록 한다.

public class OptionalExample {
    public static void main(String[] args) {
        // User 객체가 존재하는 경우
        User user = new User("John Doe", 30);
        Optional<User> optionalUser = Optional.ofNullable(user);

        optionalUser.ifPresent(u -> System.out.println("이름: " + u.getName() + ", 나이: " + u.getAge()));

        // User 객체가 null인 경우
        User nullUser = null;
        Optional<User> emptyOptional = Optional.ofNullable(nullUser);

        // User 객체가 없을 때 기본값을 제공
        User defaultUser = emptyOptional.orElse(new User("기본 사용자", 0));
        System.out.println("이름: " + defaultUser.getName() + ", 나이: " + defaultUser.getAge());

        // User 객체가 없을 때 예외를 던지거나 특정 행동을 하기
        emptyOptional.orElseThrow(() -> new IllegalStateException("User 객체가 없습니다."));
    }
}

이 예제에서는 Optional을 사용하여 null 가능성이 있는 User 객체를 안전하게 처리한다.

ifPresent() 메소드를 사용하여 User 객체가 존재하는 경우에만 작업을 수행하고,

orElse() 메소드를 사용하여 User 객체가 없는 경우 기본값을 제공한다.

orElseThrow() 메소드를 사용하여 User 객체가 없을 때 예외를 던질 수도 있다.

ifPresent(Consumer<? super T> action) 메소드

Optional 객체가 값을 갖고 있을 경우, 즉 값이 비어 있지 않을 경우에 주어진 액션(람다 표현식 또는 메소드 참조)을 실행한다. 값이 없을 경우에는 아무런 동작도 하지 않는다. 이 메소드는 주로 Optional 객체가 값이 있을 때만 특정 작업을 실행하고 싶을 때 사용된다.

 

orElse(T other) 메소드

Optional 객체가 값이 있으면 그 값을 반환하고, 값이 없을 경우에는 메소드에 전달된 인자를 기본값으로 반환한다. 이 메소드는 Optional 객체가 비어 있을 때의 기본값을 제공하고자 할 때 유용하게 사용된다.

 

get() 메소드 대신 orElse() 또는 orElseGet()을 사용하는 이유

get() 메소드가 Optional 객체가 값을 가지고 있지 않을 때, 즉 Optional 객체가 비어 있을 때 NoSuchElementException을 발생시키기 때문이다. 이는 Optional을 사용하는 주된 목적 중 하나인 안전한 null 처리를 위배한다.

 

orElse() 메소드는 Optional 객체가 비어 있을 때 제공할 기본값을 반환한다. 이 메소드는 Optional 객체의 값이 있든 없든 항상 기본값을 평가하기 때문에, 기본값 생성 비용이 높은 경우에는 비효율적일 수 있다.

 

orElseGet() 메소드는 Optional 객체가 비어 있을 때만 제공할 기본값을 생성하기 위해 사용할 Supplier 함수형 인터페이스를 인자로 받는다. 이 방법은 Optional 객체가 비어 있을 때만 기본값을 생성하기 때문에, orElse()에 비해 기본값 생성 비용이 높은 경우에 효율적이다.

 

간단히 말해서, orElse()와 orElseGet()은 Optional 객체가 비어 있을 때 안전하게 기본값을 제공하며, orElseGet()은 필요할 때만 기본값을 생성하는 더 효율적인 방법을 제공한다. 따라서 get() 메소드의 사용은 값의 존재를 명시적으로 확인한 후에만 사용해야 하며, 그렇지 않은 경우 orElse() 또는 orElseGet() 같은 보다 안전한 대안을 사용하는 것이 좋다.

 

null이 아닌, 비어 있는 Optional 객체를 생성하는 방법

Optional을 사용할 때 null이 아닌, 비어 있는 Optional 객체를 생성하는 방법은 Optional.empty() 메소드를 사용하는 것이다. 

이 메소드는 값을 포함하지 않는, 즉 비어 있는 Optional 객체를 반환한다. 이 방법은 반환 값이 없음을 명시적으로 표현할 때 유용하며, 메소드의 시그니처를 통해 반환 값이 있을 수도 있고 없을 수도 있음을 명확하게 나타낼 수 있다.

 

Optional<String> emptyOptional = Optional.empty();

이 코드는 비어 있는 Optional 객체를 생성한다. 이 객체는 아무런 값도 포함하지 않으며, isPresent() 메소드를 호출했을 때 false를 반환한다.

Optional.empty()를 사용하는 상황의 예로는, 어떤 조건에 따라 값이 반환될 수도 있고 반환되지 않을 수도 있는 메소드에서, 조건에 맞지 않아 값이 반환되지 않는 경우에 사용할 수 있다.

public Optional<String> findUserNameById(Long userId) {
    // 사용자 ID로 사용자 이름을 조회하는 로직
    // 만약 해당 ID의 사용자가 없다면, 비어 있는 Optional을 반환
    return Optional.empty();
}

이렇게 Optional.empty()를 사용하면, 메소드의 반환 값이 항상 Optional 타입임을 보장할 수 있으며, 호출한 곳에서는 반환된 Optional 객체를 통해 값이 존재하는지 여부를 안전하게 확인하고 처리할 수 있다.

 

이 방식은 null 반환을 피하고, NullPointerException의 위험을 줄이며, 코드의 가독성과 안정성을 높이는 데 도움이 된다.

 

주의할 점

Optional은 반환 타입, 매개 변수, 필드 타입으로 사용할 수 있으나, 특히 API 설계 시에는 신중하게 사용해야 한다.

Optional을 매개 변수로 사용하는 것은 권장되지 않으며, 필드 타입으로 사용할 때도 주의가 필요하다.

또한, Optional은 Serializable 인터페이스를 구현하지 않기 때문에, 직렬화가 필요한 상황에서는 사용에 제약이 따른다.

static import는 클래스의 정적 멤버(메서드와 필드)를 클래스 이름 없이 직접 사용할 수 있도록 해준다.

이를 통해 코드의 가독성을 높일 수 있지만, 과도하게 사용할 경우 어느 클래스에서 메서드나 필드가 정의되었는지 혼란을 줄 수 있다.

 

예를들어, static import를 사용하지 않는 경우, 예를 들어 Math 클래스의 sqrt 메서드를 사용하려면 클래스 이름을 명시해야 한다.

double result = Math.sqrt(25);  // Math 클래스의 sqrt 메서드 사용

 

static import를 사용하면, 메서드를 클래스 이름 없이 직접 호출할 수 있다

import static java.lang.Math.sqrt;

...

double result = sqrt(25);  // Math.sqrt가 아닌 sqrt만으로 호출

 주의해야 할 것은 같은 클래스 내에 동일한 이름의 메소드가 있으면 클래스 자신의 메소드가 우선시 된다.

 

클래스내의 모든 정적 메소드를 import 하려면 다음과 같이 사용할 수 있다.

import static java.lang.Math.*;

하지만 일반적으로 import 에 * 를 사용하는 것은 권장 되지 않는다.

정적 메소드외에 정적 멤버 변수도 static import 의 대상이 된다.

import static java.lang.Math.PI;
System.out.println(PI);

static import는 특히 자주 사용하는 유틸리티 클래스나 상수 집합에 유용하다.

하지만, 너무 많은 정적 임포트는 코드의 명확성을 해칠 수 있으므로 적절히 사용하는 것이 중요하다.

Arrays.sort()와 Collections.sort()

Arrays.sort()와 Collections.sort()는 Java에서 배열과 컬렉션을 정렬하는 메서드이다.

Arrays.sort()

  • Arrays 클래스에 속한 정적 메서드로, 배열을 정렬할 때 사용한다.
  • 주로 기본 타입 배열(int, double)을 정렬하는 데 사용된다.
  • 정렬 알고리즘으로는 기본적으로 퀵 소트(QuickSort)가 사용되지만, Java 7 이후로는 퀵 소트와 병합 소트(MergeSort)가 혼합된 팀소트(TimSort)가 사용된다.
  • Arrays.sort()는 인자로 넘겨받은 배열을 직접 수정하며, 반환값은 void이다.
int[] arr = {5, 2, 9, 1, 5};
Arrays.sort(arr);

 

Collections.sort()

  • Collections 클래스에 속한 정적 메서드로, 리스트나 컬렉션 객체를 정렬할 때 사용한다.
  • 주로 객체 기반의 리스트(List<Integer>, List<String> 등)를 정렬하는 데 사용된다.
  • 정렬 알고리즘으로는 병합 소트(MergeSort)가 사용된다.
  • Collections.sort()는 인자로 넘겨받은 리스트를 직접 수정하며, 반환값은 void이다.
List<Integer> list = Arrays.asList(5, 2, 9, 1, 5);
Collections.sort(list);

 

두 메서드는 모두 정렬을 위해 객체의 비교를 위해 compareTo 또는 compare 메서드를 사용한다.

따라서 정렬하려는 객체가 Comparable 인터페이스를 구현하거나, 비교자(Comparator)를 제공해야 한다.

또한, Arrays.sort()와 Collections.sort()는 오름차순 정렬을 기본으로 수행한다.

만약 내림차순으로 정렬하려면, Collections.reverseOrder()나 비교자(Comparator)를 사용하여 정렬해야 한다.

Collections.reverseOrder를 사용하려면 객체 타입으로 선언되어야 한다.

따라서 Arrays.sort()를 사용할 때 아래와 같은 점을 조심해야 한다.

  • int, doubble 과 같은 자료형은 primitive type(원시 자료형)으로 내림차순으로 정렬되지 않는다.
  • 그래서 int형으로 배열을 선언하는 것이 아닌 Integer형으로 배열을 선언한다면 내림차순 정렬도 가능하게 된다.

 

Comparator와 Comparable

Comparable은 주로 클래스 자체에 정렬 기준을 포함할 때 사용되며, Comparator는 외부에서 정렬 기준을 제공할 때 사용된다.

때에 따라 둘을 함께 사용하여 다양한 정렬 기능을 구현할 수 있다.

Comparator

Comparator는 Java에서 객체들의 정렬(ordering)을 제어하기 위한 인터페이스이다.

Comparator는 두 객체를 비교하는 데 사용되며, 정렬 순서를 지정할 수 있도록 도와준다.

이 인터페이스를 구현한 클래스는 compare 메서드를 제공해야 한다.

compare 메서드의 일반적인 형태는 다음과 같다

int compare(T obj1, T obj2);

여기서 T는 비교하려는 객체의 유형을 나타낸다.

compare 메서드는 세 가지 경우를 반환할 수 있다:

  • obj1이 obj2보다 작으면 음수(음의 정수)를 반환한다.
  • obj1이 obj2와 같으면 0을 반환한다.
  • obj1이 obj2보다 크면 양수(양의 정수)를 반환한다.

이러한 반환 값들을 통해 정렬 알고리즘은 정렬 순서를 결정한다.

 

Comparator 인터페이스의 예시를 살펴보자면, 아래는 문자열 길이를 기준으로 정렬하는 StringLengthComparator 클래스이다.

즉, StringLengthComparator는 Comparator의 구현 클래스이다.

import java.util.Comparator;

public class StringLengthComparator implements Comparator<String> {
    @Override
    public int compare(String str1, String str2) {
        return Integer.compare(str1.length(), str2.length());
    }
}

 

Comparator를 사용하여 문자열의 길이에 따라 정렬을 수행할 수 있다.

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class Example {
    public static void main(String[] args)
    {
        List<String> strings = Arrays.asList("apple", "banana", "orange", "kiwi");

        // 문자열 길이에 따라 정렬
        Collections.sort(strings, new StringLengthComparator());

        // 정렬된 결과 출력
        for (String str : strings) {
            System.out.println(str);
        }
    }
}
/*
kiwi
apple
banana
orange
*/

 

Comparable

Comparable 인터페이스는 Java에서 객체의 자연 순서를 정의하기 위해 사용되는 인터페이스이다.

이를 구현하는 클래스들은 해당 클래스의 객체를 서로 비교하여 정렬할 수 있는 능력을 가진다.

Comparable은 한 가지 비교 기준을 정의하며, 이것이 해당 클래스의 "자연 순서"가 된다.

자연 순서(natural order)
Comparable 인터페이스를 구현한 클래스들은 이러한 기본 순서를 "자연 순서(natural order)"라고 한다.
예를 들어, 문자열의 경우, 자연 순서는 사전 순서이다.
즉, "apple"은 "banana"보다 작은 값을 가지는 것이 자연 순서에 따른 정렬이다.

 

Comparable 인터페이스는 다음과 같이 선언되어 있다

public interface Comparable<T> {
    int compareTo(T o);
}

여기서 T는 비교하려는 객체의 타입을 나타낸.

compareTo 메서드는 다른 객체와 비교하여 정렬 순서를 나타내는 정수를 반환한다.

반환값에 따라 정렬이 이루어진다.

  • 음수: 현재 객체가 다른 객체보다 작음을 나타냄
  • 0: 현재 객체가 다른 객체와 같음을 나타냄
  • 양수: 현재 객체가 다른 객체보다 큼을 나타냄

예를 들어, Integer 클래스는 Comparable을 구현하고 있어서 다음과 같이 사용할 수 있다.

Integer num1 = 5;
Integer num2 = 10;

int result = num1.compareTo(num2);

if (result < 0) System.out.println("num1 is less than num2");
else if (result == 0) System.out.println("num1 is equal to num2");
else System.out.println("num1 is greater than num2");

클래스에서 Comparable을 구현하면, 해당 클래스의 객체는 자연 순서에 따라 정렬될 수 있다.

예를 들어, 배열을 정렬하는 경우 Arrays.sort() 메서드가 Comparable을 사용하여 정렬한다

Integer[] numbers = {5, 2, 8, 1, 3};
Arrays.sort(numbers);

위의 코드에서 Arrays.sort(numbers)Comparable을 이용하여 numbers 배열을 자연 순서에 따라 정렬한다.

 

compareTo

compareTo 메서드는 Comparable 인터페이스를 구현한 클래스에서 사용되는 메서드로, 두 객체를 비교하여 정렬 순서를 결정하는 데 사용된다.

compareTo 메서드는 일반적으로 다음과 같은 형태를 가지고 있다.

int compareTo(T other);

여기서 T는 비교하려는 객체의 타입을 나타내며, other는 비교 대상 객체이다.

compareTo 메서드는 비교 결과에 따라 세 가지 경우를 반환한다.

  • this 객체가 other 객체보다 작으면 음수(음의 정수)를 반환.
  • this 객체와 other 객체가 같으면 0을 반환.
  • this 객체가 other 객체보다 크면 양수(양의 정수)를 반환.

이렇게 반환된 값을 기반으로 정렬 알고리즘은 객체들을 정렬한다.

Comparable 인터페이스를 구현한 클래스는 해당 클래스의 객체를 자연 순서(natural order)에 따라 정렬할 수 있게 된다.

예를 들어, String 클래스는 Comparable 인터페이스를 구현하고 있어서 compareTo 메서드를 제공한다.

문자열의 경우, 사전 순서로 비교가 이루어진다

String str1 = "apple";
String str2 = "banana";

int result = str1.compareTo(str2);
System.out.println(result);  // 결과는 음수(-1)이 나옴

이 경우, "apple"은 "banana"보다 사전 순서상으로 작기 때문에 음수가 반환된다.

마찬가지로, 정수형 데이터 타입인 Integer, Double 등도 Comparable 인터페이스를 구현하고 있어서 compareTo 메서드를 사용하여 비교할 수 있다.

'Java Category > Java' 카테고리의 다른 글

[Java] Optional<T>  (1) 2024.03.15
[Java] static import  (0) 2024.01.23
[Java] char 타입을 정수 타입으로 변환  (3) 2023.11.26
[Java] next()와 nextLine()의 차이  (0) 2023.08.22
[Java] DB와 연동한 게시판 구현  (0) 2023.08.21
char num = '1';
int tmp = num; //자동 타입 변환
System.out.print(tmp);
char num = '1';
System.out.print((int) num); //강제 타입 변환하여 출력

이렇게 문자 '1'을 자동 타입 변환을 하거나 강제 형 변환을 해서 출력을 하게되면 1이 출력이 되는것이 아니라 49가 출력이되어서 나온다.

 

위에서 49가 출력된 이유는 '1'은 아스키코드로 49이기 때문이다.

즉, 문자 '1'이 정수로 타입 변환이 될 때 아스키코드 49로 변환된 것이다.

 

이런 문제는 charAt()메소드를 사용할 때도 나타난다.

String num = "12345";
for(int i = 0; i < num.length(); ++i)
{
    int tmp = num.charAt(i);
    System.out.print(tmp);
}

이러한 코드를 실행해보면 출력값은 4950515253이 나온다.

'1' -> 49

'2' -> 50

'3' -> 51

'4' -> 52

'5' ->53

 

우리의 의도대로 문자 하나를 정수로 변환하려면 두 가지 방법이 존재한다.

  • 아스키 코드 사용('0'을 빼주기)
  • Character.getNumericValue() 메소드 사용

 

아스키 코드 사용('0'을 빼주기)

문자 '0'의 아스키 코드 값은 48이다.

위 예에서 말한것 처럼 '1'의 아스키코드는 49이므로 '1' - '0'을 하면 정수 1이되는 것이다.

char num = '1';
System.out.print(num - '0');

이렇게 하면 우리가 의도한대로 원하는 정수값이 출력이 된다.

 

Character.getNumericValue() 메소드 사용

Character 클래스의 getNumericValue() 정적 메소드를 사용하면 이 또한 우리가 의도한대로 원하는 정수값을 얻을 수 있다.

char num = '1';
System.out.print(Character.getNumericValue(num));

이 코드를 실행해보면 정상적으로 1이 출력되는 것을 확인할 수 있다.

import java.util.Scanner;
public class Main {
	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
        int number;
        String string;
        System.out.print("숫자 입력 : ");
        number = sc.nextInt();

        System.out.println("문자열 입력 : ");
        string = sc.nextLine();

        System.out.println("숫자 입력 결과 : " + number);
        System.out.println("문자열 입력 결과 : " + string);
	}
}
/*
숫자 입력 : 6546201
문자열 입력 : 
숫자 입력 결과 : 6546201
문자열 입력 결과 : 
*/

의도대로라면 숫자와 문자를 모두 입력을 받아야 하지만, 숫자만 입력받고 프로그램이 종료되었다.

 

이유는 next()는 개행문자(\n)을 무시하고 입력을 받고, 반대로 nextLine()은 개행문자를 포함해서 입력을 받기 때문이다.

따라서 입력 버퍼에는 6546201\n이 들어오게 되고 next()는 개행문자를 무시하기 때문에 6546201만 가져오게 되고, 따라서 버퍼에는 \n만 남게된다.

 

결과적으로 버퍼에 남은 \n을 nextLine()이 가져오게되고 프로그램이 종료된 것이다.

 

next()
버퍼에 입력된 문자나 문자열에서 공백과 개행문자 전까지를 가져온다.
nextLine()
개행문자를 만날 때까지의 문자열 전체를 입력받는다.
버퍼에 입력된 문자열(공백포함)을 개행문자까지 다 가져온다.

 

따라서 이러한 문제를 해결하기 위해서는 버퍼에 잔류하는 내용물을 비워줄 필요가 있다.

즉, 버퍼를 비워줘야 한다.

next()와 nextLine() 사이에 매개 문자열이 없는 nextLine()을 두면 버퍼를 비울 수 있다.

 

import java.util.Scanner;
public class Main {
	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
        int number;
        String string;
        System.out.print("숫자 입력 : ");
        number = sc.nextInt();
        
        sc.nextLine();
        
        System.out.println("문자열 입력 : ");
        string = sc.nextLine();

        System.out.println("숫자 입력 결과 : " + number);
        System.out.println("문자열 입력 결과 : " + string);
	}
}
/*
숫자 입력 : 1234
문자열 입력 : 
asd asdasd
숫자 입력 결과 : 1234
문자열 입력 결과 : asd asdasd
*/

 

이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 


USER 테이블

Oracle 기준

 

Oracle

users.sql
0.00MB

 

MySQL

users.sql
0.00MB

 


 

BOARDS 테이블

Oracle 기준

 

Oracle

boards.sql
0.00MB

 

MySQL

boards.sql
0.00MB

 


 

구현

Oracle

Board.java

import java.util.Date;
public class Board {
	private int bno;
	private String btitle;
	private String bcontent;
	private String bwriter;
	private Date bdate;
	public int getBno() {
		return bno;
	}
	public void setBno(int bno) {
		this.bno = bno;
	}
	public String getBtitle() {
		return btitle;
	}
	public void setBtitle(String btitle) {
		this.btitle = btitle;
	}
	public String getBcontent() {
		return bcontent;
	}
	public void setBcontent(String bcontent) {
		this.bcontent = bcontent;
	}
	public String getBwriter() {
		return bwriter;
	}
	public void setBwriter(String bwriter) {
		this.bwriter = bwriter;
	}
	public Date getBdate() {
		return bdate;
	}
	public void setBdate(Date bdate) {
		this.bdate = bdate;
	}
}

 

BoardExample9.java

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Scanner;

import ch20.oracle.sec09.exam02.Board;

public class BoardExample9 {
	//Field
	private Scanner scanner = new Scanner(System.in);
	private Connection conn;
	
	//Constructor
	public BoardExample9() {
		try {
			//JDBC Driver 등록
			Class.forName("oracle.jdbc.OracleDriver");
			
			//연결하기
			conn = DriverManager.getConnection(
				"jdbc:oracle:thin:@localhost:1521/orcl", 
				"java", 
				"oracle"
			);
		} catch(Exception e) {
			e.printStackTrace();
			exit();
		}
	}
	
	//Method	
	public void list() {
		//타이틀 및 컬럼명 출력
		System.out.println();
		System.out.println("[게시물 목록]");
		System.out.println("-----------------------------------------------------------------------");
		System.out.printf("%-6s%-12s%-16s%-40s\n", "no", "writer", "date", "title");
		System.out.println("-----------------------------------------------------------------------");
		
		//boards 테이블에서 게시물 정보를 가져와서 출력하기
		try {
			String sql = "" +
				"SELECT bno, btitle, bcontent, bwriter, bdate " +
				"FROM boards " + 
				"ORDER BY bno DESC";
			PreparedStatement pstmt = conn.prepareStatement(sql);
			ResultSet rs = pstmt.executeQuery();
			while(rs.next()) {		
				Board board = new Board();
				board.setBno(rs.getInt("bno"));
				board.setBtitle(rs.getString("btitle"));
				board.setBcontent(rs.getString("bcontent"));
				board.setBwriter(rs.getString("bwriter"));
				board.setBdate(rs.getDate("bdate"));
				System.out.printf("%-6s%-12s%-16s%-40s \n", 
						board.getBno(), 
						board.getBwriter(),
						board.getBdate(),
						board.getBtitle());
			}
			rs.close();
			pstmt.close();
		} catch(SQLException e) {
			e.printStackTrace();
			exit();
		}
		
		//메인 메뉴 출력
		mainMenu();
	}
	
	public void mainMenu() {
		System.out.println();
		System.out.println("-----------------------------------------------------------------------");
		System.out.println("메인 메뉴: 1.Create | 2.Read | 3.Clear | 4.Exit");
		System.out.print("메뉴 선택: ");
		String menuNo = scanner.nextLine();
		System.out.println();
		
		switch(menuNo) {
			case "1" -> create();
			case "2" -> read();
			case "3" -> clear();
			case "4" -> exit();
		}
	}	
	
	public void create() {
		//입력 받기
		Board board = new Board();
		System.out.println("[새 게시물 입력]");
		System.out.print("제목: "); 	
		board.setBtitle(scanner.nextLine());
		System.out.print("내용: "); 	
		board.setBcontent(scanner.nextLine());
		System.out.print("작성자: "); 	
		board.setBwriter(scanner.nextLine());
		
		//보조 메뉴 출력
		System.out.println("-----------------------------------------------------------------------");
		System.out.println("보조 메뉴: 1.Ok | 2.Cancel");
		System.out.print("메뉴 선택: ");
		String menuNo = scanner.nextLine();
		if(menuNo.equals("1")) {
			//boards 테이블에 게시물 정보 저장
			try {
				String sql = "" +
					"INSERT INTO boards (bno, btitle, bcontent, bwriter, bdate) " +
					"VALUES (SEQ_BNO.NEXTVAL, ?, ?, ?, SYSDATE)";
				PreparedStatement pstmt = conn.prepareStatement(sql);
				pstmt.setString(1, board.getBtitle());
				pstmt.setString(2, board.getBcontent());
				pstmt.setString(3, board.getBwriter());
				pstmt.executeUpdate();
				pstmt.close();
			} catch (Exception e) {
				e.printStackTrace();
				exit();
			}
		}
		
		//게시물 목록 출력
		list();
	}
	
	public void read() {
		//입력 받기
		System.out.println("[게시물 읽기]");
		System.out.print("bno: "); 	
		int bno = Integer.parseInt(scanner.nextLine());
		
		//boards 테이블에서 해당 게시물을 가져와 출력
		try {
			String sql = "" +
				"SELECT bno, btitle, bcontent, bwriter, bdate " +
				"FROM boards " +
				"WHERE bno=?";
			PreparedStatement pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, bno);
			ResultSet rs = pstmt.executeQuery();
			if(rs.next()) {
				Board board = new Board();
				board.setBno(rs.getInt("bno"));
				board.setBtitle(rs.getString("btitle"));
				board.setBcontent(rs.getString("bcontent"));
				board.setBwriter(rs.getString("bwriter"));
				board.setBdate(rs.getDate("bdate"));
				System.out.println("#############");
				System.out.println("번호: " + board.getBno());
				System.out.println("제목: " + board.getBtitle());
				System.out.println("내용: " + board.getBcontent());
				System.out.println("작성자: " + board.getBwriter());
				System.out.println("날짜: " + board.getBdate());
				//보조 메뉴 출력
				System.out.println("-------------------------------------------------------------------");
				System.out.println("보조 메뉴: 1.Update | 2.Delete | 3.List");
				System.out.print("메뉴 선택: ");
				String menuNo = scanner.nextLine();
				System.out.println();
				
				if(menuNo.equals("1")) {
					update(board);
				} else if(menuNo.equals("2")) {
					delete(board);
				}
			}
			rs.close();
			pstmt.close();
		} catch (Exception e) {
			e.printStackTrace();
			exit();
		}
		
		//게시물 목록 출력
		list();
	}
	
	public void update(Board board) {
		//수정 내용 입력 받기
		System.out.println("[수정 내용 입력]");
		System.out.print("제목: "); 	
		board.setBtitle(scanner.nextLine());
		System.out.print("내용: "); 	
		board.setBcontent(scanner.nextLine());
		System.out.print("작성자: "); 	
		board.setBwriter(scanner.nextLine());
		
		//보조 메뉴 출력
		System.out.println("-------------------------------------------------------------------");
		System.out.println("보조 메뉴: 1.Ok | 2.Cancel");
		System.out.print("메뉴 선택: ");
		String menuNo = scanner.nextLine();
		if(menuNo.equals("1")) {
			//boards 테이블에서 게시물 정보 수정
			try {
				String sql = "" +
					"UPDATE boards SET btitle=?, bcontent=?, bwriter=? " +
					"WHERE bno=?";
				PreparedStatement pstmt = conn.prepareStatement(sql);
				pstmt.setString(1, board.getBtitle());
				pstmt.setString(2, board.getBcontent());
				pstmt.setString(3, board.getBwriter());
				pstmt.setInt(4, board.getBno());
				pstmt.executeUpdate();
				pstmt.close();
			} catch (Exception e) {
				e.printStackTrace();
				exit();
			}
		}
		
		//게시물 목록 출력
		list();
	}
	
	public void delete(Board board) {
		//boards 테이블에 게시물 정보 삭제
		try {
			String sql = "DELETE FROM boards WHERE bno=?";
			PreparedStatement pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, board.getBno());
			pstmt.executeUpdate();
			pstmt.close();
		} catch (Exception e) {
			e.printStackTrace();
			exit();
		}
		
		//게시물 목록 출력		
		list();
	}
	
	public void clear() {
		System.out.println("[게시물 전체 삭제]");
		System.out.println("-------------------------------------------------------------------");
		System.out.println("보조 메뉴: 1.Ok | 2.Cancel");
		System.out.print("메뉴 선택: ");
		String menuNo = scanner.nextLine();
		if(menuNo.equals("1")) {
			//boards 테이블에 게시물 정보 전체 삭제
			try {
				String sql = "TRUNCATE TABLE boards";
				PreparedStatement pstmt = conn.prepareStatement(sql);
				pstmt.executeUpdate();
				pstmt.close();
			} catch (Exception e) {
				e.printStackTrace();
				exit();
			}
		}
			
		//게시물 목록 출력
		list();
	}
	
	public void exit() {
		if(conn != null) {
			try {
				conn.close();
			} catch (SQLException e) {
			}
		}
		System.out.println("** 게시판 종료 **");
		System.exit(0);
	}
	
	public static void main(String[] args) {
		BoardExample9 boardExample = new BoardExample9();
		boardExample.list();
	}
}

 


MySQL

Board.java

import java.util.Date;
public class Board {
	private int bno;
	private String btitle;
	private String bcontent;
	private String bwriter;
	private Date bdate;
	public int getBno() {
		return bno;
	}
	public void setBno(int bno) {
		this.bno = bno;
	}
	public String getBtitle() {
		return btitle;
	}
	public void setBtitle(String btitle) {
		this.btitle = btitle;
	}
	public String getBcontent() {
		return bcontent;
	}
	public void setBcontent(String bcontent) {
		this.bcontent = bcontent;
	}
	public String getBwriter() {
		return bwriter;
	}
	public void setBwriter(String bwriter) {
		this.bwriter = bwriter;
	}
	public Date getBdate() {
		return bdate;
	}
	public void setBdate(Date bdate) {
		this.bdate = bdate;
	}
}

 

BoardExample9.java

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Scanner;

public class BoardExample9 {
	//Field
	private Scanner scanner = new Scanner(System.in);
	private Connection conn;
	
	//Constructor
	public BoardExample9() {
		try {
			//JDBC Driver 등록
			Class.forName("com.mysql.cj.jdbc.Driver");
			
			//연결하기
			conn = DriverManager.getConnection(
				"jdbc:mysql://localhost:3306/thisisjava", 
				"java", 
				"mysql"
			);
		} catch(Exception e) {
			e.printStackTrace();
			exit();
		}
	}
	
	//Method	
	public void list() {
		//타이틀 및 컬럼명 출력
		System.out.println();
		System.out.println("[게시물 목록]");
		System.out.println("-----------------------------------------------------------------------");
		System.out.printf("%-6s%-12s%-16s%-40s\n", "no", "writer", "date", "title");
		System.out.println("-----------------------------------------------------------------------");
		
		//boads 테이블에서 게시물 정보를 가져와서 출력하기
		try {
			String sql = "" +
				"SELECT bno, btitle, bcontent, bwriter, bdate " +
				"FROM boards " + 
				"ORDER BY bno DESC";
			PreparedStatement pstmt = conn.prepareStatement(sql);
			ResultSet rs = pstmt.executeQuery();
			while(rs.next()) {		
				Board board = new Board();
				board.setBno(rs.getInt("bno"));
				board.setBtitle(rs.getString("btitle"));
				board.setBcontent(rs.getString("bcontent"));
				board.setBwriter(rs.getString("bwriter"));
				board.setBdate(rs.getDate("bdate"));
				System.out.printf("%-6s%-12s%-16s%-40s \n", 
						board.getBno(), 
						board.getBwriter(),
						board.getBdate(),
						board.getBtitle());
			}
			rs.close();
			pstmt.close();
		} catch(SQLException e) {
			e.printStackTrace();
			exit();
		}
		
		//메인 메뉴 출력
		mainMenu();
	}
	
	public void mainMenu() {
		System.out.println();
		System.out.println("-----------------------------------------------------------------------");
		System.out.println("메인메뉴: 1.Create | 2.Read | 3.Clear | 4.Exit");
		System.out.print("메뉴선택: ");
		String menuNo = scanner.nextLine();
		System.out.println();
		
		switch(menuNo) {
			case "1" -> create();
			case "2" -> read();
			case "3" -> clear();
			case "4" -> exit();
		}
	}	
	
	public void create() {
		//입력 받기
		Board board = new Board();
		System.out.println("[새 게시물 입력]");
		System.out.print("제목: "); 	
		board.setBtitle(scanner.nextLine());
		System.out.print("내용: "); 	
		board.setBcontent(scanner.nextLine());
		System.out.print("글쓴이: "); 	
		board.setBwriter(scanner.nextLine());
		
		//보조메뉴 출력
		System.out.println("-----------------------------------------------------------------------");
		System.out.println("보조메뉴: 1.Ok | 2.Cancel");
		System.out.print("메뉴선택: ");
		String menuNo = scanner.nextLine();
		if(menuNo.equals("1")) {
			//boards 테이블에 게시물 정보 저장
			try {
				String sql = "" +
					"INSERT INTO boards (btitle, bcontent, bwriter, bdate) " +
					"VALUES (?, ?, ?, now())";
				PreparedStatement pstmt = conn.prepareStatement(sql);
				pstmt.setString(1, board.getBtitle());
				pstmt.setString(2, board.getBcontent());
				pstmt.setString(3, board.getBwriter());
				pstmt.executeUpdate();
				pstmt.close();
			} catch (Exception e) {
				e.printStackTrace();
				exit();
			}
		}
		
		//게시물 목록 출력
		list();
	}
	
	public void read() {
		//입력 받기
		System.out.println("[게시물 읽기]");
		System.out.print("bno: "); 	
		int bno = Integer.parseInt(scanner.nextLine());
		
		//boards 테이블에서 해당 게시물을 가져와 출력
		try {
			String sql = "" +
				"SELECT bno, btitle, bcontent, bwriter, bdate " +
				"FROM boards " +
				"WHERE bno=?";
			PreparedStatement pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, bno);
			ResultSet rs = pstmt.executeQuery();
			if(rs.next()) {
				Board board = new Board();
				board.setBno(rs.getInt("bno"));
				board.setBtitle(rs.getString("btitle"));
				board.setBcontent(rs.getString("bcontent"));
				board.setBwriter(rs.getString("bwriter"));
				board.setBdate(rs.getDate("bdate"));
				System.out.println("#############");
				System.out.println("번호: " + board.getBno());
				System.out.println("제목: " + board.getBtitle());
				System.out.println("내용: " + board.getBcontent());
				System.out.println("쓴이: " + board.getBwriter());
				System.out.println("날짜: " + board.getBdate());
				//보조메뉴 출력
				System.out.println("-------------------------------------------------------------------");
				System.out.println("보조메뉴: 1.Update | 2.Delete | 3.List");
				System.out.print("메뉴선택: ");
				String menuNo = scanner.nextLine();
				System.out.println();
				
				if(menuNo.equals("1")) {
					update(board);
				} else if(menuNo.equals("2")) {
					delete(board);
				}
			}
			rs.close();
			pstmt.close();
		} catch (Exception e) {
			e.printStackTrace();
			exit();
		}
		
		//게시물 목록 출력
		list();
	}
	
	public void update(Board board) {
		//수정 내용 입력 받기
		System.out.println("[수정 내용 입력]");
		System.out.print("제목: "); 	
		board.setBtitle(scanner.nextLine());
		System.out.print("내용: "); 	
		board.setBcontent(scanner.nextLine());
		System.out.print("글쓴이: "); 	
		board.setBwriter(scanner.nextLine());
		
		//보조메뉴 출력
		System.out.println("-------------------------------------------------------------------");
		System.out.println("보조메뉴: 1.Ok | 2.Cancel");
		System.out.print("메뉴선택: ");
		String menuNo = scanner.nextLine();
		if(menuNo.equals("1")) {
			//boards 테이블에서 게시물 정보 수정
			try {
				String sql = "" +
					"UPDATE boards SET btitle=?, bcontent=?, bwriter=? " +
					"WHERE bno=?";
				PreparedStatement pstmt = conn.prepareStatement(sql);
				pstmt.setString(1, board.getBtitle());
				pstmt.setString(2, board.getBcontent());
				pstmt.setString(3, board.getBwriter());
				pstmt.setInt(4, board.getBno());
				pstmt.executeUpdate();
				pstmt.close();
			} catch (Exception e) {
				e.printStackTrace();
				exit();
			}
		}
		
		//게시물 목록 출력
		list();
	}
	
	public void delete(Board board) {
		//boards 테이블에 게시물 정보 삭제
		try {
			String sql = "DELETE FROM boards WHERE bno=?";
			PreparedStatement pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, board.getBno());
			pstmt.executeUpdate();
			pstmt.close();
		} catch (Exception e) {
			e.printStackTrace();
			exit();
		}
		
		//게시물 목록 출력		
		list();
	}
	
	public void clear() {
		System.out.println("[게시물 전체 삭제]");
		System.out.println("-------------------------------------------------------------------");
		System.out.println("보조메뉴: 1.Ok | 2.Cancel");
		System.out.print("메뉴선택: ");
		String menuNo = scanner.nextLine();
		if(menuNo.equals("1")) {
			//boards 테이블에 게시물 정보 전체 삭제
			try {
				String sql = "TRUNCATE TABLE boards";
				PreparedStatement pstmt = conn.prepareStatement(sql);
				pstmt.executeUpdate();
				pstmt.close();
			} catch (Exception e) {
				e.printStackTrace();
				exit();
			}
		}
			
		//게시물 목록 출력
		list();
	}
	
	public void exit() {
		if(conn != null) {
			try {
				conn.close();
			} catch (SQLException e) {
			}
		}
		System.out.println("** 게시판 종료 **");
		System.exit(0);
	}
	
	public static void main(String[] args) {
		BoardExample9 boardExample = new BoardExample9();
		boardExample.list();
	}
}

이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 


트랜잭션

트랜잭션(transaction)은 기능 처리의 최소 단위를 말한다.

하나의 기능은 여러가지 소작업들로 구성된다.

최소 단위라는 것은 이 소작업들을 분리할 수 없으며, 전체를 하나로 본다는 개념이다.

 

트랜잭션은 소작업들이 모두 성공하거나 실패해야 한다.

 

예를 들어 계좌 이체는 출금과 입금으로 구성된 트랜잭션이다.

출금과 입금 작업 중 하나만 성공할 수 없으며, 모두 성공하거나 모두 실패해야 한다.

계좌 이체는 DB 입장에서 보면 두 개의 계좌 금액을 수정하는 작업이다.

출금 계좌에서 금액을 감소시키고, 입금 계좌에서 금액을 증가시킨다.

따라서 아래와 같이 두 개의 UPDATE 문이 필요하다.

두 UPDATE 문은 모두 성공하거나 모두 실패해야 하며, 하나만 성공할 수 없다.

 

DB는 트랜잭션을 처리하기 위해 커밋(commit)과 롤백(rollback)을 제공한다.

커밋은 내부 작업을 모두 성공 처리하고, 롤백은 실행 전으로 돌아간다는 의미에서 모두 실패 처리한다.

 

JDBC에서는 INSERT, UPDATE, DELETE 문을 실행할 때마다 자동 커밋이 일어난다.

 

이 기능은 계좌 이체와 같이 두 가지 UPDATE 문을 실행할 때 문제가 된다.

출금 작업이 성공되면 바로 커밋이 되기 때문에, 입금 작업의 성공 여부와 상관없이 출금 작업만 별도 처리된다.

 

따라서 JDBC에서 트랜잭션을 코드로 제어하려면 자동 커밋 기능을 꺼야 한다.

자동 커밋 설정 여부는 Connection의 setAutoCommit() 메소드로 할 수 있다.

 

아래의 코드는 자동 커밋 기능을 끈다.

conn.setAutoCommit(false);

 

자동 커밋 기능이 꺼지면, 아래와 같은 코드로 커밋과 롤백을 제어할 수 있다.

conn.commit();
conn.rollback();

 

트랜잭션을 처리한 이후에는 원래대로 자동 커밋 기능을 켜둬야 한다.

Connection을 다른 기능 처리를 위해 계속 사용해야 한다면 setAutoCommit(true) 코드로 자동 커밋 기능을 켜둬야 한다.

참고
수동 커밋 모드나 자동 커밋 모드는 한번 설정하면 해당 세션에서는 계속 유지된다. 중간에 변경하는 것은 가능 하다.

특히, 커넥션 풀을 사용할 때 주의해야할 부분이다.

커넥션 풀(Connection Pool)
다수의 클라이언트 요청을 처리하는 서버 프로그램은 대부분 커넥션 풀을 사용한다.
커넥션 풀은 일정량의 Connection을 미리 생성시켜놓고, 서버에서 클라이언트 요청을 처리할 때 Connection을 제공해주고 다시 반환받는 역할을 수행한다.
출처 : 이것이 자바다 유튜브 동영상 강의

커넥션 풀을 사용하면 생성된 Connection을 재사용할 수 있기 때문에 DB 연결 시간을 줄일 수 있고, 전체 Connection 수를 관리할 수도 있다.

 


 

사용 예제

아래의 예제 코드는 하여름의 계좌에서 한겨울의 계좌로 10,000원을 송금하는 예제이다.

물론, DB에 등록되지 않은 계좌로 송금을 하려고 한다던가, 계좌 금액이 부족하면 커밋이 되지 않고 롤백이 된다.

아래의 코드를 실행하고 나면 DB는 아래처럼 된다.

 

Oracle

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class TransactionExample {
	public static void main(String[] args) {
		Connection conn = null;
		try {
			//JDBC Driver 등록
			Class.forName("oracle.jdbc.OracleDriver");
			
			//연결하기
			conn = DriverManager.getConnection(
				"jdbc:oracle:thin:@localhost:1521/orcl", 
				"java", 
				"oracle"
			);	
			
			//트랜잭션 시작 ----------------------------------------------------
				//자동 커밋 기능 끄기
				conn.setAutoCommit(false);
				
				//출금 작업
				String sql1 = "UPDATE accounts SET balance=balance-? WHERE ano=?";
				PreparedStatement pstmt1 = conn.prepareStatement(sql1);
				pstmt1.setInt(1,  10000);
				pstmt1.setString(2, "111-111-1111");
				int rows1 = pstmt1.executeUpdate();
				if(rows1 == 0) throw new Exception("출금되지 않았음");
				pstmt1.close();
				
				//입금 작업
				String sql2 = "UPDATE accounts SET balance=balance+? WHERE ano=?";
				PreparedStatement pstmt2 = conn.prepareStatement(sql2);
				pstmt2.setInt(1,  10000);
				pstmt2.setString(2, "222-222-2222");
				int rows2 = pstmt2.executeUpdate();
				if(rows2 == 0) throw new Exception("입금되지 않았음");
				pstmt2.close();
			
				//수동 커밋 -> 모두 성공 처리
				conn.commit();
				System.out.println("계좌 이체 성공");	
			//트랜잭션 종료 ----------------------------------------------------
		} catch (Exception e) {
			try { 
				//수동 롤백 -> 모두 실패 처리
				conn.rollback();
			} catch (SQLException e1) {}
			System.out.println("계좌 이체 실패");
			e.printStackTrace();
		} finally {
			if(conn != null) {
				try {
					//원래대로 자동 커밋 기능 켜기
					conn.setAutoCommit(true);
					//연결 끊기
					conn.close();
				} catch (SQLException e) {}
			}
		}
	}
}
/*
계좌 이체 성공
*/

 

 

MySQL

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class TransactionExample {
	public static void main(String[] args) {
		Connection conn = null;
		try {
			//JDBC Driver 등록
			Class.forName("com.mysql.cj.jdbc.Driver");
			
			//연결하기
			conn = DriverManager.getConnection(
				"jdbc:mysql://localhost:3306/thisisjava", 
				"java", 
				"mysql"
			);
			
			//트랜잭션 시작 ----------------------------------------------------
				//자동 커밋 기능 끄기
				conn.setAutoCommit(false);
				
				//출금 작업
				String sql1 = "UPDATE accounts SET balance=balance-? WHERE ano=?";
				PreparedStatement pstmt1 = conn.prepareStatement(sql1);
				pstmt1.setInt(1,  10000);
				pstmt1.setString(2, "111-111-1111");
				int rows1 = pstmt1.executeUpdate();
				if(rows1 == 0) throw new Exception("출금되지 않았음");
				pstmt1.close();
				
				//입금 작업
				String sql2 = "UPDATE accounts SET balance=balance+? WHERE ano=?";
				PreparedStatement pstmt2 = conn.prepareStatement(sql2);
				pstmt2.setInt(1,  10000);
				pstmt2.setString(2, "333-222-2222");
				int rows2 = pstmt2.executeUpdate();
				if(rows2 == 0) throw new Exception("입금되지 않았음");
				pstmt2.close();
			
				//커밋 -> 모두 성공 처리
				conn.commit();
				System.out.println("계좌 이체 성공");	
			//트랜잭션 종료 ----------------------------------------------------
		} catch (Exception e) {
			try { 
				//롤백 -> 모두 실패 처리
				conn.rollback(); 
				//원래대로 자동 커밋 기능 켜기
				conn.setAutoCommit(true);	
			} catch (SQLException e1) {}
			System.out.println("계좌 이체 실패");
			e.printStackTrace();
		} finally {
			if(conn != null) {
				try { 
					//원래대로 자동 커밋 기능 켜기
					conn.setAutoCommit(true);
					
					//연결 끊기
					conn.close(); 
				} catch (SQLException e) {}
			}
		}
	}
}

이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 


프로시저와 함수

이 글에서는 Oracle DB에 적용되는 프로시저와 함수를 다룬다.

 

클라이언트 프로그램(DB가 서버이고, DB를 이용하는 프로그램이 클라이언트)에서 매개값과 함께 프로시저 또는 함수를 호출하면 DB 내부에서 일련의 SQL문을 실행하고, 실행 결과를 클라이언트 프로그램으로 돌려주는 역할을 한다.

즉, 프로그램 내부에서 SQL문의 작업내용이 처리되지 않고 DB에서 처리가 된다.

클라이언트에서 처리하기 부담스러운 처리를 서버로 돌릴 수 있게 된다.

 

  • 프로시저 : 리턴하는 값이 있지만, 주로 리턴하지 않고 작업 처리를 할 때 사용
    예를 들어, 데이터 삽입, 삭제, 데이터 확인 등
  • 함수 : 작업 처리를 하고 특정 값을 리턴할 때 사용
    예를 들어, 연산 작업을 하고 연산의 값을 리턴

 

JDBC에서 프로시저와 함수를 호출할 때는 CallableStatement를 사용한다.

프로시저와 함수의 매개변수화된 호출문을 작성하고 Connection의 prepareCall() 메소드로부터 CallableStatement객체를 얻을 수 있다.

프로시저와 함수의 매개변수화된 호출문은 조금 차이가 있다.

중괄호로 감싼 call문이라는 점은 동일하지만, 함수는 call 문의 시행 결과를 대입할 좌측 리턴값의 자리(?=)를 명시해야 한다.

 

프로시저명과 함수명의 괄호 안에 작성된 ?는 호출 시 필요한 매개값의 자리이다.

주의할 점은 프로시저도 리턴값과 유사한 OUT 타입의 매개변수를 가질 수 있기 때문에 괄호 안의 ?중 일부는 OUT값(리턴값)일 수 있다는 점이다.

 

PrepareCall() 메소드로 CallableStatement를 얻었다면 리턴 값에 해당하는 ?는 registerOutParameter() 메소드로 지정하고, 그 이외의 ? 는 호출 시 필요한 매개값으로 Setter 메소드를 사용해서 값을 지정해야 한다.

 

함수는 첫 번째 ?가 무조건 리턴값이다.

 

프로시저

String sql = "{call 프로시저명(?, ?, ?)}";
CallableStatement cstmt = conn.prepareCall(sql);
cstmt.setString(1, "값");
cstmt.setString(2, "값");
cstmt.registerOutParameter(3, 리턴타입); //세 번째 ?는 OUT값(리턴값)임을 지정

 

함수

String sql = "{? = call 함수명(?, ?)}";
CallableStatement cstmt = conn.prepareCall(sql);
cstmt.registerOutParameter(1, 리턴타입);//첫 번째 ?는 리턴값임을 지정
cstmt.setString(2, "값");
cstmt.setString(3, "값");
프로시저와 함수의 리턴 값을 받기 위해 registerOutParameter()에 들어가는 두 번째 매개값(리턴 타입)의 종류는 아래의 링크에서 자세히 확인할 수 있다.(공식 API Document)
https://docs.oracle.com/en/java/javase/17/docs/api/java.sql/java/sql/Types.html

 

?에 대한 설정이 끝나면 프로시저 또는 함수를 호출하기 위해 execute() 메소드를 아래와 같이 호출한다.

cstmt.execute();

 

호출 후에는 Getter 메소드로 리턴값을 얻을 수 있다. 리턴 타입이 정수라고 가정하면, 프로시저의 세 번째 ?의 리턴값과 함수의 리턴값은 아래와 같이 얻을 수 있다.

 

더 이상 CallableStatement를 사용하지 않는다면 close() 메소드로 사용했던 메모리를 해제해야 한다.

cstmt.close();

 

 


 

프로시저 호출


create or replace PROCEDURE user_create (
    a_userid        IN  users.userid%TYPE, 
    a_username      IN  users.username%TYPE,
    a_userpassword  IN  users.userpassword%TYPE,
    a_userage       IN  users.userage%TYPE,
    a_useremail     IN  users.useremail%TYPE,
    a_rows          OUT PLS_INTEGER

IS
BEGIN
    INSERT INTO users (userid, username, userpassword, userage, useremail)
    VALUES (a_userid, a_username, a_userpassword,  a_userage, a_useremail);
    a_rows := SQL%ROWCOUNT;
    COMMIT;
END;


DB에 위와 같이 프로시저가 정의되어 있다.

위 프로시저는 SQL문에 의해 실행된 명령(users 테이블 행 추가)의 수를 반환하는 프로시저이다.

즉, 프로그램에서 매개값으로 넘긴 값들을 INSERT문으로 행을 추가하고, 처리된 행의 수를 리턴하는 것이다.

IN 매개변수는 호출 시 필요한 매개값으로 사용되며, OUT 매개변수는 리턴값으로 사용된다.

 

위와 같이 작성된 프로시저를 호출하기 위해 아래와 같이 매개변수화된 호출문을 작성하고 CallableStatement를 얻는다.

String sql = "{call user_create(?, ?, ?, ?, ?, ?)}";
CallableStatement cstmt = conn.prepareCall(sql);

위 호출문에서 5번째 ?까지가 매개값이고 6번째 ?가 리턴값이다.

따라서 아래와 같이 ?의 값을 지정하고 리턴 타입을 지정한다.

cstmt.setString(1, "summer");
cstmt.setString(2, "한여름");
cstmt.setString(3, "12345");
cstmt.setInt(4, 26);
cstmt.setString(5, "summer@mycompany.com");
cstmt.registerOutParameter(6, Types.INTEGER);
프로시저와 함수의 리턴 값을 받기 위해 registerOutParameter()에 들어가는 두 번째 매개값(리턴 타입)의 종류는 아래의 링크에서 자세히 확인할 수 있다.(공식 API Document)
https://docs.oracle.com/en/java/javase/17/docs/api/java.sql/java/sql/Types.html

 

이제 user_create 프로시저를 실행하고, 아래와 같이 리턴값을 얻는다.

user_create 프로시저의 리턴값은 사용자 정보가 성공적으로 저장되었을 때 항상 1이 된다.

cstmt.execute();
int rows = cstmt.getInt(6); //6번째 ? 값 얻기

 

사용 예제(Oracle)

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Types;

public class ProcedureCallExample {

	public static void main(String[] args) {
		Connection conn = null;
		try {
			//JDBC Driver 등록
			Class.forName("oracle.jdbc.OracleDriver");
			
			//연결하기
			conn = DriverManager.getConnection(
				"jdbc:oracle:thin:@localhost:1521/orcl", 
				"java", 
				"oracle"
			);	
			
			//매개변수화된 호출문 작성과 CallableStatement 얻기
			String sql = "{call user_create(?, ?, ?, ?, ?, ?)}";
			CallableStatement cstmt = conn.prepareCall(sql);
			
			//? 값 지정 및 리턴 타입 지정
			cstmt.setString(1, "summer");
			cstmt.setString(2, "한여름");
			cstmt.setString(3, "12345");
			cstmt.setInt(4, 26);
			cstmt.setString(5, "summer@mycompany.com");
			cstmt.registerOutParameter(6, Types.INTEGER);
			
			
			//함수 실행 및 리턴값 얻기
			cstmt.execute();
			int rows = cstmt.getInt(6);
			System.out.println("저장된 행 수 " + rows);
			
			//CallableStatement 닫기
			cstmt.close();
		}catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(conn != null) {
				try { 
					//연결 끊기
					conn.close(); 
				} catch (SQLException e) {}
			}
		}
	}
}
/*
저장된 행 수 1
*/

 


 

함수 호출


create or replace FUNCTION user_login (
    a_userid        users.userid%TYPE, 
    a_userpassword  users.userpassword%TYPE
) RETURN PLS_INTEGER
IS
    v_userpassword users.userpassword%TYPE;
    v_result PLS_INTEGER;
BEGIN
    SELECT userpassword INTO v_userpassword
    FROM users
    WHERE userid = a_userid;

    IF v_userpassword = a_userpassword THEN
        RETURN 0;
    ELSE
        RETURN 1;
    END IF;
EXCEPTION
    WHEN NO_DATA_FOUND THEN 
        RETURN 2;
END;

DB에 함수는 위와 같이 선언되어 있다.

 

user_login()은 2개의 매개변수와 PLS_INTEGER 리턴 타입으로 구성되어 있다. 2개의 매개변수는 호출 시 값을 제공하고, 호출 후에는 정수 값을 리턴한다.

 

위 함수는 받은 매개값(ID와 PW) 을 DB에서 검색을 하고, ID와 PW가 일치하면 0을 리턴하고, 일치하지 않으면 1을 리턴하고, 해당 ID가 없으면 2를 리턴한다.

 

user_login() 함수를 호출하기 위해 아래와 같이 매개변수화된 호출문을 작성하고 CallableStatement를 얻는다.

String sql = "{? = call user_login(?, ?)}";
CallableStatement cstmt = conn.prepareCall(sql);

첫 번째 ?가 리턴값이고, 괄호 안에 있는 ?들이 매개값이다.

그래서 아래와 같이 ?의 값을 지정하고 리턴 타입을 지정한다.

프로시저와 함수의 리턴 값을 받기 위해 registerOutParameter()에 들어가는 두 번째 매개값(리턴 타입)의 종류는 아래의 링크에서 자세히 확인할 수 있다.(공식 API Document)
https://docs.oracle.com/en/java/javase/17/docs/api/java.sql/java/sql/Types.html
cstmt.registerOutParameter(1, Types.INTEGER);
cstmt.setString(2, "winter");
cstmt.setString(3, "12345");

 

user_login() 함수는 userid와 userpassword가 일치하면 0을, userpassword가 틀리면 1을, userid가 존재하지 않으면 2를 리턴한다.

cstmt.execute();
int result = cstmt.getInt(1);

 

사용 예제(Oracle)

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Types;

public class FunctionCallExample {
	public static void main(String[] args) {
		Connection conn = null;
		try {
			//JDBC Driver 등록
			Class.forName("oracle.jdbc.OracleDriver");
			
			//연결하기
			conn = DriverManager.getConnection(
				"jdbc:oracle:thin:@localhost:1521/orcl", 
				"java", 
				"oracle"
			);	
			
			//매개변수화된 호출문 작성과 CallableStatement 얻기
			String sql = "{? = call user_login(?, ?)}";
			CallableStatement cstmt = conn.prepareCall(sql);
			
			//? 값 지정 및 리턴 타입 지정
			cstmt.registerOutParameter(1, Types.INTEGER);
			cstmt.setString(2, "winter");
			cstmt.setString(3, "12345");
			
			//함수 실행 및 리턴값 얻기
			cstmt.execute();
			int result = cstmt.getInt(1);
			
			//CallableStatement 닫기
			cstmt.close();
			
			//로그인 결과(Switch Expressions 이용)
			String message = switch(result) {
				case 0 -> "로그인 성공";
				case 1 -> "비밀번호가 틀림";
				default -> "아이디가 존재하지 않음";
			};
			System.out.println(message);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(conn != null) {
				try { 
					//연결 끊기
					conn.close(); 
				} catch (SQLException e) {}
			}
		}
	}
}
/*
로그인 성공
*/