728x90

초난감 DAO

DAO (Data Access Object)

데이터베이스를 사용해 데이터를 조회 및 조작하는 기능을 전담하는 오브젝트

 

JavaBean (Bean)

비주얼 틀에서 조작 가능한 컴포넌트

파라미터가 없는 디폴트 생성자를 갖고 있어야 함

Getter와 Setter를 통해 빈을 수정/조회 할 수 있어야 함

 

그냥 캡슐화된 객체라고 생각하면 편함

기존 JDBC를 활용한 초난감 DAO의 문제점

public class UserDao {
	public void add(User user) throws ClassNotFoundException, SQLException {
		Class.forName("com.mysql.cj.jdbc.Driver");
		Connection c = DriverManager.getConnection("jdbc:mysql://localhost/toby?characterEncoding=UTF-8&serverTimezone=Asia/Seoul", "DB계정명",
				"비밀번호");

		PreparedStatement ps = c.prepareStatement(
			"insert into users(id, name, password) values(?,?,?)");
		ps.setString(1, user.getId());
		ps.setString(2, user.getName());
		ps.setString(3, user.getPassword());

		ps.executeUpdate();

		ps.close();
		c.close();
	}


	public User get(String id) throws ClassNotFoundException, SQLException {
		Class.forName("com.mysql.cj.jdbc.Driver");
		Connection c = DriverManager.getConnection("jdbc:mysql://localhost/toby?characterEncoding=UTF-8&serverTimezone=Asia/Seoul", "DB계정명",
				"비밀번호");
		PreparedStatement ps = c
				.prepareStatement("select * from users where id = ?");
		ps.setString(1, id);

		ResultSet rs = ps.executeQuery();
		rs.next();
		User user = new User();
		user.setId(rs.getString("id"));
		user.setName(rs.getString("name"));
		user.setPassword(rs.getString("password"));

		rs.close();
		ps.close();
		c.close();

		return user;
	}

	public static void main(String[] args) throws ClassNotFoundException, SQLException {
		UserDao dao = new UserDao();

		User user = new User();
		user.setId("test");
		user.setName("한글");
		user.setPassword("abcd");

		dao.add(user);

		System.out.println(user.getId() + "등록성공");

		User user2 = dao.get(user.getId());
		System.out.println(user2.getName());
		System.out.println(user2.getPassword());

		System.out.println(user2.getId() + "조회성공");
	}

}

간단한 SQL CRUD 작업을 수행하는 코드이다.

 

초난감이라는 말에서 알 수 있듯이 DAO 코드가 많이 난감하다는 것인데

코드가 긴 것도 문제지만 코드의 유지보수성이 떨어진다.

 

객체지향설계는 유연하고 확장성 있는 코드로 미래를 생각하며 작성하는 것이 중요한데

초난감 DAO는 이러한 객체지향설계에 맞지 않는 방식이다.

객체지향적으로 해결하기 : 분리

기능이 분리가 되어있지 않다면

사용하는 데이터베이스가 바뀌거나 사용자 정보가 바뀌는 경우 등

기존에 코드들을 하나하나 모두 수정해야 하는 경우가 발생

 

관심이 같은 것들끼리만 모으고 같지 않는 것들은 떨어트려

서로 영향을 주지 않도록 분리하여 코드를 작성해야 함

 

DAO 코드에 어떤 기능들이 있는지를 살펴보고

관심사항을 추출해야 한다.

객체지향적으로 해결하기 : 관심사항 추출

DAO 코드의 관심사항은 아래와 같이 크게 3가지가 있다.

  1. 데이터베이스 연결에 사용할 커넥션 가져오기
  2. 데이터 추가 및 조회에 사용되는  SQL Statement 생성 및 실행
  3. 커넥션과 Statement 오브젝트 종료

또한 해당 관심사항들은 add와 get 메서드에서 반복적으로 실행 되는 것도 알 수 있는데

DAO 작업을 계속 하다보면 이러한 중복되는 코드들이 갈수록 많아질 것이기 때문에

이 각각의 관심사항들을 하나씩 나눠서 재사용성을 높이는 코드를 작성해야 한다.

객체지향적으로 해결하기 : 중복 코드 추출

인텔리제이에서 기본적으로 제공하는 리팩토링 기능처럼

중복되는 코드들을 하나의 메서드로 추출할 것인데

Class.forName("com.mysql.cj.jdbc.Driver");
Connection c = 
	DriverManager.getConnection(
		"jdbc:mysql://localhost/toby?characterEncoding=UTF-8&serverTimezone=Asia/Seoul", 
        "db 사용자 id","pw");

우선 add와 get 메서드에서 중복적으로 사용되는

커넥션을 가져오는 코드를 분리하여 하나의 메서드로 만든다.

private Connection getConnection() throws ClassNotFoundException, SQLException {
    Class.forName("com.mysql.jdbc.Driver");
    Connection c =
    	DriverManager.getConnection(
        	"jdbc:mysql://localhost/toby?characterEncoding=UTF-8&serverTimezone=Asia/Seoul", 
        	"db 사용자 id","pw");
    return c;
}

위와 같이 중복되는 부분을 하나의 메서드로 추출하여 해당 기능이 필요할 때만

해당 메서드를 호출하여 사용하도록 하는 것을 통해

public User get(String id) throws ClassNotFoundException, SQLException {
    Connection c = getConnection();
}

코드의 수정이 필요한 경우 해당 메서드의 코드만 수정하면 되기 때문에

코드가 간결해지고 유연해졌다.

객체지향적으로 해결하기 : 추출한 메서드의 독립 (상속)

해당 메서드의 코드를 공개하지 않고 상대방에게 제공하고 싶은 경우에는

상속을 사용하여 추상 메서드와 구현으로 분리하는 방법이 있다.

abstract protected Connection getConnection() throws ClassNotFoundException, SQLException;

이렇게 메서드를 독립시키면 하나의 getConnection 메서드를 상황에 맞게 작성된 코드로 사용할 수 있다.

 

즉, 기존의 mysql이 아니라 오라클이나 다른 데이터베이스를 사용해야 하는 경우에는

해당 추상 클래스를 구현한 다른 클래스의  getConnection 메서드를 사용하면 되는 것이고

새로운 데이터베이스를 사용해야 하는 경우 손쉽게 확장해서 사용할 수 있다.

템플릿 메서드 패턴

위와 같이 슈퍼 클래스에서 기본적인 로직의 흐름(변하지 않는 기능)만 제어하고

기능의 일부를 추상 메서드나 오버라이딩이 가능한 protected 메서드로 만들어

서브 클래스에서 실질적인 기능(자주 변경되거나 확장되는 기능)을 구현하여 상황에 맞게 사용하는 디자인 패턴

 

보통 서브 클래스에서 반드시 구현해야 하는 부분들은 추상 메서드로 만들고

선택적으로 구현해야 하는 것은 protected등의 접근 제어자를 통해 메서드를 오버라이딩 할 수 있게 한다.

팩토리 메서드 패턴

서브 클래스에서 구체적인 객체 생성 방법을 결정하게 하는 디자인 패턴

객체지향적으로 해결하기 : 상속의 문제점

하지만 상속을 통한 메서드의 독립은 간단하지만 좋은 방법은 아닌데,

자바는 다중 상속을 지원하지 않기 때문에 추가적인 상속을 받을 수 없고

상속을 통해 연결된 관계는 결합이 강하기 때문이다.

 

현재까지 중복되는 코드를 하나의 메서드로 분리하고

이를 다시 상하위 클래스로 분리하는 작업을 했는데,

이제는 다시 이 상하위 클래스를 서로 독립적인 클래스로 만들어야 한다.

객체지향적으로 해결하기 : 클래스의 분리

서로 독립적인 클래스로 만드는 것은 간단한데

그저 새로운 클래스를 하나 만들고 커넥션 관련 코드를 옮기는 것이다.

public class SimpleConnectionMaker {
	public Connection getConnection() throws ClassNotFoundException,
			SQLException {
		Class.forName("com.mysql.jdbc.Driver");
		Connection c = DriverManager.getConnection("jdbc:mysql://localhost/toby?characterEncoding=UTF-8", "id",
				"pw");
		return c;
	}
}

이렇게 별도의 클래스를 가져다 사용하기만 하면 된다.

public abstract class UserDao {
	private SimpleConnectionMaker simpleConnectionMaker;
	
	public UserDao() {
		this.simpleConnectionMaker = new SimpleConnectionMaker();
	}
}

SimpleConnectionMaker 클래스에서 커넥터를 가져오는 메서드를 사용하기 위해

SimpleConnectionMaker 객체를 가져와서 필드에 멤버로 등록하고

UserDao 클래스의 생성자를 통해 해당 인스턴스를 생성해준다.

Connection c = this.simpleConnectionMaker.getConnection();

만들어진 SimpleConnectionMaker 인스턴스를 통해 getConnection 메서드를 사용하면

기존에 상하위 클래스로 상속 받던 관계가 완전히 분리되었다.

객체지향적으로 해결하기 : 여전히 존재하는 문제점

하지만 클래스를 분리하니 새로운 문제가 생기는데

상속을 통해 메서드를 분리했을 때는 상황에 맞게 코드를 사용할 수 있었지만

지금은 UserDao 클래스가 SimpleConnectionMaker에 종속되었기 때문에 

새로운 데이터베이스를 사용하는 경우에 대한 커넥션 관련 코드들을 변경할 수 없다

객체지향적으로 해결하기 : 인터페이스

이러한 문제를 해결하기 위해 자바에서는 인터페이스를 사용할 수 있다.

 

인터페이스는 추상 메서드의 집합으로 다중 상속을 지원하지 않는 자바에서

다중 상속과 비슷한 역할을 수행할 수 있게 해준다.

 

공통적, 필수적으로 구현되어야 하는 기능들을 인터페이스에 정의하고

이 인터페이스를 사용하여 클래스들간 연결을 해주면서도 느슨한 관계를 유지시킬 수 있다.

 

즉, 기존에는 클래스들 간 밧줄로 강하게 묶여 있었다면 인터페이스를 통해 느슨하게 묶여 있는 것이다.

객체지향적으로 해결하기 : 인터페이스 적용하기

데이터베이스 커넥션 기능을 정의할 인터페이스를 생성 후

해당 인터페이스의 구현 클래스들이 구현해야 할 추상 메서드를 선언해준다.

public interface ConnectionMaker {
	public abstract Connection makeConnection() throws ClassNotFoundException, SQLException;
}

해당 인터페이스를 통해 상황에 맞게 실행되어야 할 코드들을 각각의 클래스에 구현한다.

public class DConnectionMaker implements ConnectionMaker {
	public Connection makeConnection() throws ClassNotFoundException, SQLException {
		// 커넥션 생성 구현 코드
	}
}

UserDao에서는 인터페이스를 사용하여

public class UserDao {
	private ConnectionMaker connectionMaker;
	
	public UserDao() {
		ConnectionMaker = new DConnectionMaker();
	}
    
    Connection c = connectionMaker.makeConnection();
}

이렇게되면 해당 인터페이스의 구현 클래스들이 서로 달라도 해당 인터페이스를 통해

makeConnection이라는 메서드를 상황에 맞게 사용할 수 있다.

 

하지만 아직도 UserDao의 생성자를 살펴보면 특정 객체의 생성자를 호출하여 사용하고 있다.

이러면 여전히 코드를 수정해야 하는 경우 여러 부분을 똑같이 수정해야 하는 것은 변하지 않았다.

객체지향적으로 해결하기 : 책임 분리

여전히 코드가 독립적이지 못한 이유는

어떤 커넥션 구현 클래스를 사용할지 직접 결정해줘야하기 때문인데,

이러한 부분을 제거해줘야 독립적이고 확장 가능한 클래스가 될 수 있다.

 

즉, 어떤 커넥션을 적용할지 결정하는 책임을 분리해야하는데

이러한 책임을 분리하기 위해서는 다른 클래스에 책임을 넘기는 것이 좋다.

 

UserDao라는 클래스(서비스)를 사용하는 클래스(클라이언트)에 이러한 책임을 넘겨서

클라이언트가 서비스에 어떠한 기능을 적용하라고 지정해주는 것이다.

 

고객(클라이언트)이 주문을 하면 해당 주문에 맞는 음식을 가게(서비스)가 제공하는 것과 같다.

객체지향적으로 해결하기 : 책임 분리의 원리

이제 클라이언트 오브젝트와 서비스 오브젝트의 관계를 설정해주면 되는데

서비스 오브젝트는 메서드나 생성자의 파라미터를 통해 외부에서 오브젝트를 전달 받고

클라이언트 오브젝트는 서비스 오브젝트의 파라미터에 맞게 오브젝트를 전달해준다.

 

현재 클래스들은 직접적으로 연결 되어 있지 않고 인터페이스를 통하여 간접적으로 연결되어 있기 때문에

파라미터로 전달되는 오브젝트의 클래스는 해당 인터페이스의 구현 클래스면 어떤 것이든 상관이 없다.

 

클래스 사이에 관계가 생기는 이유는 코드에 다른 클래스가 등장하기 때문이니

코드에 다른 클래스가 직접적으로 등장하지 않게 만들면 클래스 사이의 관계가 생기지 않는다.

 

하지만 오브젝트 사이의 관계는 특정 클래스가 등장하지 않아도 해당 클래스가 구현한

인터페이스를 사용했다면, 해당 인터페이스 타입으로 받아서 사용할 수 있다. (다형성)

 

즉, 모델링 시점(코드를 작성하는 시점)에는 존재하지 않던 관계

런타임 시에 클라이언트 오브젝트에서 관계를 주입 받아 생기게 되는 것이다.

객체지향적으로 해결하기 : 책임 분리 적용하기

클라이언트 클래스를 새로 생성하여 UserDao 클래스에 있던 main 메서드를 옮긴다.

이때 클라이언트의 메인 메서드의 UserDao의 생성자에 어떤 커넥션을 적용할지 전달한다.

public class Client {
	public static void main(String[] args) throws ClassNotFoundException, SQLException {
		ConnectionMaker connectionMaker = new DConnectionMaker();
		UserDao dao = new UserDao(connectionMaker);

		//기존의 main 메서드 코드
	}
}

UserDao의 생성자는 아래와 같이 구현 클래스를 직접 상속 받지 않고

클라이언트로부터 구현 클래스를 전달 받게 한다.

public UserDao(ConnectionMaker connectionMaker) {
    this.connectionMaker = connectionMaker;
}

드디어 인터페이스를 통해 완벽한 독립적인 코드 작성이 끝났다.

이제는 만약 데이터베이스를 다른 플랫폼으로 바꾸고자 한다면 메인 메서드의

생성자 부분의 코드만 수정하면 된다.

'Back-End > Spring' 카테고리의 다른 글

싱글톤 레지스트리 / 오브젝트 스코프  (0) 2023.06.07
스프링 IoC  (0) 2023.06.05
IoC(Inversion of Control, 제어의 역전)  (0) 2023.06.05
초난감 DAO와 객체지향  (0) 2023.06.05
Spring Framework  (0) 2023.06.05
728x90

Spring Framework

자바의 엔터프라이즈 애플리케이션 개발을 위한 프레임워크

 

스프링 컨테이너(애플리케이션 컨텍스트)

스프링 프레임워크가 제공하는 런타임 엔진

설정 정보를 바탕으로 애플리케이션의 오브젝트의 생성/관리 담당

독립적으로 실행 가능하지만 서블릿 같은 것으로 등록하여 사용

공통 프로그래밍 모델

프레임워크는 개발을 위한 틀을 제공해준다고 볼 수 있는데

스프링 프레임워크도 IoC/DI,  PSA, AOP라는 개념을 통해

개발자가 어떻게 코드를 작성해야할지 방향성을 제시해준다.

IoC(Inversion of Control, 제어의 역전) / DI(Dependency Injection, 의존성 주입)

객체의 생명주기와 의존관계에 대한 스프링 프레임워크가 제공하는 프로그래밍 모델

객체지향 설계와 디자인 패턴의 핵심 원리를 담고 있으며

스프링 프레임워크가 제공하는 모든 기능도 마찬가지로 해당 프로그래밍 모델로 작성되었다.

 

스프링을 제대로 사용하기 위해서는 해당 프로그래밍 모델에 맞게 코드를 작성하고

해당 프로그래밍 모델을 완벽하게 이해하는 것이 중요

PSA(Portable Service Abstractions, 서비스 추상화)

추상화라는 표현이 포함된 것에서 알 수 있듯이 객체지향 프로그래밍의 추상화처럼

구체적인 기술과 환경에 종속되지 않게 유연한 추상 계층을 두는 프로그래밍 모델

AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)

반복적인 코드를 하나의 메서드로 리팩토링하여 재사용하는 것처럼

공통적으로 사용되는 부가적인 기능을 독립적으로 모듈화하는 프로그래밍 모델

기술 API

UI 작성, 웹 프레젠테이션 계층, 비즈니스 서비스 계층, 기반 서비스 계층, 도메인 계층, 데이터 액세스 계층 등

여러 곳에서 필요한 주요 기술을 일관된 방식으로 사용할 수 있게 기능과 전략 클래스 등을 제공

 

스프링 프레임워크만의 가치

단순함

스프링은 목적을 이룰 수 있는 가장 단순하고 명쾌한 접근 방법을 지향하는 프레임워크로

이를 위해 단순한 객체지향적 개발 모델 POJO 프로그래밍을 사용

유연성

스프링은 유연성과 확장성이 매우 뛰어난 프레임워크로

다른 프레임워크들을 접목시켜 사용하는 것에 있어서 편리하고

개발자의 필요에 따라 스프링 프레임워크를 확장해서 사용할 수도 있다.

 

스프링 프레임워크를 공부하는 방법

핵심 가치와 원리 이해

스프링이 지향하는 핵심 가치를 이해하고

핵심 가치가 어떻게 적용되었는지 이해하기

 

POJO, IoC/DI, PSA, AOP

스프링의 기술 지식과 기술 선택 기준

스프링이 제공하는 기술의 종류와 접근 방법

상황에 맞는 기술과 접근 방법 선택에 대한 기준 마련에 대해 학습

적용과 확장

실제 애플리케이션에서 사용하기 위한 스프링의 응용과 확장 방법 공부

'Back-End > Spring' 카테고리의 다른 글

싱글톤 레지스트리 / 오브젝트 스코프  (0) 2023.06.07
스프링 IoC  (0) 2023.06.05
IoC(Inversion of Control, 제어의 역전)  (0) 2023.06.05
초난감 DAO와 객체지향  (0) 2023.06.05
초난감 DAO 리팩토링하기  (0) 2023.06.05
728x90

영속성 컨텍스트

엔티티 매니저로 엔티티를 저장하거나 조회 시 엔티티를 영구적으로 보관하고 관리하는 저장소 같은 개념

 

엔티티 매니저를 생성할 때 한 개의 영속성 컨텍스트가 만들어지고

엔티티 매니저는 이런 영속성 컨텍스트에 접근하고 관리하는 도구라고 볼 수 있음

em.persist(member);

persist 메서드는 정확하게는 엔티티를 저장하는 메서드가 아닌 영속성 컨텍스트에 저장하는 메서드인 것

 

엔티티의 생명주기

비영속

영속성 컨텍스트와 아무런 관계가 아닌 상태

엔티티 객체를 생성만 한 상태

 

영속

영속성 컨텍스트에 저장된 상태

엔티티 매니저를 통해 영속성 컨텍스트에 저장된 상태

즉, persist 메서드를 사용한 상태

 

영속성 컨텍스트에 의해 관리가 되고 있는 상태

 

준영속

영속 상태였다가 분리된 상태

// 영속성 컨텍스트에서 분리
em.detach(member);

// 자동으로 영속성 컨텍스트에서 분리되는 경우
em.close(); // 영속성 컨텍스트 닫기
em.clear(); // 영속성 컨텍스트 초기화

영속성 컨텍스트에 의해 관리가 되고 있지 않는 상태

 

삭제

엔티티가 영속성 컨텍스트와 데이터베이스에서 삭제된 상태

em.remove(member);

 

영속성 컨텍스트의 특징

  • 엔티티식별자 값(@Id 어노테이션으로 매핑된 기본키 컬럼)으로 구분하기 때문에 영속 상태는 식별자 값이 반드시 있어야 하고 없을시 예외 발생
  • 트랜잭션이 커밋되는 순간 영속성 컨텍스트의 엔티티들이 데이터베이스에 반영 (flush)
  • 영속 상태의 엔티티가 저장되는 1차 캐시라는 내부의 캐시를 가지고 있음
  • 엔티티의 동일성을 보장 (같은 엔티티(식별자가 같은)인 경우 주소가 같음)

영속성 컨텍스트의 엔티티 조회

비영속 상태의 엔티티를 영속성 컨텍스트에 영속 시키면

해당 엔티티의 식별자 값과 엔티티1차 캐시에 저장

 

즉, 1차 캐시의 키는 식별자 값이므로 영속성 컨텍스트의 모든 CRUD 작업의 기준은 기본키 값이다.

1차 캐시에서 식별자 값으로 엔티티를 조회한 후에 찾는 엔티티가 없는 경우 데이터베이스에서 조회

find(엔티티클래스, 기본키)

 

데이터베이스에서 엔티티를 조회한 경우

조회한 엔티티를 생성한 후 1차 캐시에 저장하여 영속 상태의 엔티티를 반환

 

엔티티 등록 - 쓰기 지연

엔티티 매니저는 트랜잭셕이 커밋되기 전까지 SQL을 내부 쿼리 저장소에 저장해 두고

트랜잭션이 커밋되는 순간 저장해 둔 SQL을 데이터베이스에 전달

  1. 트랜잭션 시작
  2. SQL 작업 수행
  3. SQL 작업 저장
  4. 트랜잭션 커밋
  5. 영속성 컨텍스트의 작업 내용을 DB와 동기화하는 flush 실행
  6. 실제 데이터베이스의 트랜잭션을 커밋

엔티티 수정 - 변경 감지

UPDATE 쿼리문을 일일히 작성할 필요 없이

변경 사항을 데이터베이스에 자동반영해주는 변경 감지 기능이 있기 때문에

수정하고자 하는 엔티티를 조회해서 Setter 메서드 등을 통해 값을 수정하기만 하면 됨

 

JPA의 변경 감지 기능은 엔티티의 영속성 컨텍스트 보관 시의 상태를 저장해두는 스냅샷

플러시 시점의  엔티티를 비교해주는 기능이다.

  1. 트랜잭션 커밋 시 엔티티 매니저 내부에서 플러시 호출
  2. 현재 엔티티와 스냅샷을 비교하여 변경 사항 조회
  3. 변경된 엔티티 존재 시 수정 쿼리를 생성해서 쓰기 지연 저장소에 전달
  4. 쓰기 지연 저장소의 SQL문을 데이터베이스에 전달
  5. 실제 데이터베이스의 트랜잭션을 커밋

영속 상태의 엔티티에만 적용되는 기능

 

엔티티 삭제

  1. 삭제할 엔티티 조회
  2. remove 메서드를 통해 엔티티 삭제 SQL 저장

플러시(flush)

플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 역할을 수행하는

즉, 작업한 쿼리 내용을 데이터베이스에 최종적으로 저장하는 역할이다.

  1. 변경 감지를 통해 영속성 컨텍스트에 있는 모든 엔티티를 기존 스냅샷과 현재 상태와 비교
  2. 수정 된 엔티티들은 쓰기 지연 저장소에 쿼리를 저장
  3. 데이터베이스에 저장소의 쿼리를 전달

플러시는 위의 과정을 통해 이루어진다.

 

영속성 컨텍스트의 플러시는 트랜잭션을 커밋하거나 객체지향 쿼리 실행자동으로 호출되며

엔티티 매니저의 flush 메서드를 통해 직접 호출할 수도 있다.

tx.commit();

트랜잭션을 커밋할 때 변경사항(SQL)을 데이터베이스에 전달하지 않으면

데이터베이스는 어떤 변경사항이 있는지 모르기 때문에 반영이 되지 않으므로

커밋이 실행되기 전에 자동으로 플러시를 호출한 후 커밋이 진행된다.

TypedQuery<Member> query = em.createQuery("select m from Member m", Member.class);

JQPL 같은 객체지향 쿼리는 SQL로 변환되어 영속성 컨텍스트가 아닌 데이터베이스에서

작업을 수행하는데 이 때 변경된 내용을 플러시하지 않으면 데이터베이스는

변경된 내용이 반영되지 않은 상태기 때문에 원활한 SQL 작업을 할 수 없기 때문에

객체지향 쿼리를 사용할 때도 자동으로 플러시를 호출

em.flush();

플러시를 직접 호출해서 사용하는 것은 영속성 컨텍스트를 강제로 플러시하는 것이기 때문에

테스트 같은 경우를 제외하고 거의 사용하지 않음

FlushModeType.AUTO
FlushModeType.COMMIT

기본값은 AUTO로 커밋과 쿼리를 실행할 때 자동으로 플러시를 호출하는 상태이고

COMMIT으로 설정 시 커밋할 때만 자동으로 플러시를 호출한다.

 

플러시 후영속성 컨텍스트가 비워지는 것은 아니고

영속성 컨텐스트와 데이터베이스 간의 동기화가 이루어지는 것 뿐이다.

 

준영속

영속성 컨텍스트의 관리에서 벗어난 엔티티

영속성 컨텍스트의 기능을 사용할 수 없는 상태의 엔티티다.

em.detach(entity);

엔티티 매니저의 detach 메서드를 사용해 수동으로 엔티티를 준영속 상태로 만들 수 있고

해당 엔티티와 관련된 1차 캐시와 쓰기 지연 저장소의 SQL 같은 모든 정보가 삭제

 

당연히 영속성 컨텍스트에서 분리된 상태니 데이터베이스에도 반영되지 않는다.

em.clear();

엔티티 매니저의 clear 메서드는 영속성 컨텍스트 자체를 초기화하는 메서드로

detach 메서드를 영속성 컨텍스트의 모든 엔티티에 적용한다고 볼 수 있다.

em.close();

영속성 컨텍스트를 종료하는 메서드로 영속성 컨텍스트가 사라지니

당연히 영속성 컨텍스트에 영속되어 있던 엔티티들이 준영속 상태로 만들어진다.

 

detach와 clear를 통해 개발자가 직접적으로 준영속 상태로 만드는 경우는 드물고

대부분 영속성 컨텍스트의 종료를 통해 자동으로 준영속 상태로 만든다.

 

영속성 컨텍스트에 속하지 않고 기능들도 사용할 수 없으니 비영속 상태와 가깝다고 볼 수 있지만

식별자 값이 없을 수도 있는 비영속 상태와는 다르게 영속된 상태였으므로

반드시 식별자 값을 갖고 있다는 차이점이 있다.

em.detach(entity);
Member newMember = em.merge(entity);

// 준영속 영속 상태인 엔티티를 햇갈리지 않기 위해
// 아래와 같은 코드로 짜는 것이 좋음
entity = em.merge(entity);

준영속 상태의 엔티티를 다시 영속 상태로 만들기 위해서는 병합(merge)을 사용하면 되는데

merge 메서드는 준영속 상태의 엔티티로 새로운 영속 상태의 엔티티를 반환해준다.

 

즉, newMember라는 엔티티의 값이 수정되고 데이터베이스에 반영되는 것이지

준영속 상태인 entity 엔티티는 수정은 가능하지만 데이터베이스에 반영되지 않는다.

// 비영속 엔티티 생성
Entity entity = new Entity();

// 비영속 병합
entity = em.merge(entity);

병합은 준영속 뿐만 아니라 비영속 상태의 엔티티도 영속 상태로 만들 수 있다.

 

merge 메서드의 파라미터로 전달 받은 엔티티의 식별자 값으로 영속성 컨텍스트를 우선적으로 조회 후

없는 경우 데이터베이스에서 조회하고 그럼에도 없으면 새로운 엔티티를 생성해서 반환한다.

 

병합하려는 엔티티가 있든 없든 상관 없이 무조건 적으로 엔티티를 반환하기 때문에

준영속, 비영속 상태와 상관 없이 모두 영속 상태로 만들 수 있는 것

'Back-End > JPA' 카테고리의 다른 글

엔티티 매핑 : 학습을 위한 스키마 자동 생성하기  (0) 2023.06.19
엔티티 매핑 : 매핑 어노테이션  (0) 2023.06.19
JPA 시작하기  (0) 2023.06.01
엔티티 매핑 기본  (0) 2023.05.31
JPA  (0) 2023.05.31
728x90

1. 엔티티 매니저 팩토리 생성하기

JPA를 시작하기 위해선 엔티티 매니저 팩토리를 생성해야 하므로

설정 정보(persistence.xml)를 통해 엔티티 매니저 팩토리를 생성

EntityManagerFactory emf = Persistence.createEntityManagerFactory("영속성 유닛명");

EntityManagerFactory는 인터페이스이기 때문에 이를 구현한

Persistence 클래스의 createEntityManagerFactory 메서드를 사용해 엔티티 매니저 팩토리 생성

 

하이버네이트 같은 JPA 구현체들은 팩토리 생성 시 커넥션풀도 생성

 

엔티티 매니저 팩토리는 생성 비용이 크기 때문에 한 번만 생성하고 공유해서 사용해야 함

 

2. 엔티티 매니저 생성하기

엔티티 매니저 팩토리를 통해 엔티티 매니저를 생성

JPA의 기능 대부분을 엔티니 매니저가 제공 (CRUD)

EntityManager em = emf.createEntityManager();

내부에 데이터소스를 유지하며 데이터베이스와 통신하는 역할로 가상의 데이터베이스라고 볼 수 있음

데이터베이스 연결이 필요한 시점에만 커넥션을 얻음 (보통 트랜잭션 시작 시 커넥션 획득)

 

동시성 문제가 생기기 때문에 엔티티 매니저를 스레드 간에 공유/재사용하면 안됨

 

3. 트랜잭션 생성 및 사용

데이터의 변경은 항상 트랜잭션 안에서 이루어져야 함

트랜잭션은 엔티티 매니저를 통해 받아올 수 있음

EntityTransaction tx = em.getTransaction();

트랜잭션과 예외 처리를 통해 효율적인 데이터 처리를 할 수 있음

try {
    tx.begin(); //트랜잭션 시작
    crud(엔티티매니저);  //CRUD 작업수행
    tx.commit();//트랜잭션 커밋
} catch (Exception e) { //작업 실패 시
    e.printStackTrace();
    tx.rollback(); //트랜잭션 롤백
} finally {
    em.close(); //엔티티 매니저 종료
}

begin 메서드를 통해 트랜잭션을 시작한 후, 필요한 CRUD 작업을 수행한다.

작업이 정상적으로 수행되지 못한 경우에는 예외가 발생하여 rollback 메서드를 통해 작업 내용이 롤백되고,

작업이 정상적으로 수행된다면 commit 메서드를 통해 작업 내용을 반영한다.

 

엔티티 매니저는 사용이 끝났으면 항상 종료시켜야 하기 때문에

반드시 실행되는 finally 영역에서 종료시켜준다.

 

4. CRUD

새로운 엔티티를 추가(INSERT) 하려는 경우

추가하고자 하는 테이블의 객체(엔티티)를 생성

Member member = new Member(); // 회원 엔티티 생성

해당 엔티티의 Setter 메서드를 통해 엔티티의 값을 수정

member.setId("id1");
member.setUsername("이름");
member.setAge(2);

엔티티 매니저의 persist 메서드에 엔티티를 전달하여 INSERT

//엔티티를 등록(CREATE - INSERT)
//inser into member(id, name, age) values(id1, 이름, 2)
em.persist(member);

기존 엔티티를 수정(UPDATE)하려는 경우

수정하고자 하는 엔티티 객체의 Setter 메서드를 통해 값을 변경

//UPDATE MEMBER SET AGE = 20, NAME = "이름" WHERE ID = 'ID1'
member.setAge(20);

테이블을 조회(READ)하려는 경우

조회한 테이블을 담을 동일 엔티티 타입의 참조변수를 선언

엔티티 매니저의 find 메서드에 조회하고자 하는 테이블과 검색조건을 매개변수로 전달

참조변수를 통해 해당 엔티티의 Getter를 통해 값을 조회

//조회(메서드 사용)(READ)
//select name, age from member
Member findMember = em.find(Member.class, "id1");
System.out.println("findMember=" + findMember.getUsername() + ", age=" + findMember.getAge());

createQuery 메서드를 통해 쿼리문을 사용하여 조회 할 수도 있으며,

getResultList 메서드로 리스트 타입으로 변환 가능

리스트에는 조회한 엔티티의 주소값이 담겨 있으므로 주소값을 통해 Getter, Setter 사용가능

//조회(쿼리 사용)(READ)
//SELECT * FROM MEMBER WHERE ID = 'ID1'
List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
System.out.println("members.size=" + members.size());
System.out.println(members.get(0).getUsername());

엔티티를 삭제하려는 경우

엔티티 매니저의 remove 메서드에 삭제하고자 하는 엔티티 객체를 전달

//DELETE FROM MEMBER WHERE ID = 'ID1'
em.remove(member);

객체지향 쿼리 언어 JPQL을 통해 조회하는 경우

JPQL은 엔티티 객체를 대상으로 쿼리를 수행

TypedQuery<Member> query = em.createQuery("select m from Member m", Member.class);
List<Member> memberList = query.getResultList();

TypedQuery 타입 참조 변수를 선언 후에

createQuery 메서드의 매개변수로 수행할 쿼리문과 반환타입을 전달하고

사용하기 편하게 getResultList 메서드를 통해 List에 담고

마찬가지로 주소값을 통해 Getter, Setter를 사용한다.

 

5. 종료

사용이 끝난 엔티티 매니저와 팩토리는 반드시 종료해야 함

em.close();
emf.close();

 

'Back-End > JPA' 카테고리의 다른 글

엔티티 매핑 : 학습을 위한 스키마 자동 생성하기  (0) 2023.06.19
엔티티 매핑 : 매핑 어노테이션  (0) 2023.06.19
영속성과 엔티티  (0) 2023.06.01
엔티티 매핑 기본  (0) 2023.05.31
JPA  (0) 2023.05.31
728x90

객체 매핑

MEMBER 테이블이 존재하고 ID(기본키), NAME, AGE 컬럼이 존재한다는 가정하에 매핑을 해보자

@Entity
@Table(name="MEMBER")
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private String id;

    @Column(name = "NAME")
    private String username;

    private Integer age;

    @Column(nullable = false, name = "LAST_MODIFIED_AT")
    private LocalDateTime modifiedAt = LocalDateTime.now();
    
    @Transient
    private String email;
}

매핑 어노테이션

@Entity

지정 클래스를 테이블과 매핑하겠다고 JPA에 전달하는 어노테이션

 

@Table(name = "테이블명")

매핑할 테이블이 어떤 것인지 알려주는 어노테이션

생략 시 클래스 이름(엔티티명)을 테이블명으로 자동 매핑

 

@Id

엔티티 클래스의 기본키를 매핑하는 어노테이션

 

@GeneratedValue(strategy = 사용할 전략 타입)

기본키의 생성전략을 지정해주는 어노테이션이다.

 

GenerationType.AUTO

GenerationType.SEQUENCE

GenerationType.IDENTITY

 

@Column(name = "컬럼명")

엔티티 클래스의 컬럼을 매핑하는 어노테이션

생략 시 필드명을 컬럼명으로 자동 매핑

 

name 속성 외에도

unique, nullable, updatable 등의 속성도 지원한다.

 

@Temporal

java.util.Date / java.util.Calendar 타입으로 매핑하기 위해서 필요한 어노테이션

LocalDateTime을 사용하는 경우에는 생략 가능

LocalDateTime은 TIMESTAMP와 매핑된다.

 

@Transient

테이블과 매핑하지 않는 열을 설정할 때 사용하는 어노테이션

매핑되지 않았기에 데이터베이스에 저장도 되지 않고, 조회도 되지 않는다.

보통 임시 데이터를 사용하기 위한 용도로 사용한다.

 

@Enumerated(EnumType.STRING)

enum 타입과 매핑할 때 사용하는 어노테이션

ORDINAL과 STRING타입을 가질 수 있으며

각각 enum의 순서를 나타내는 숫자 테이블을 저장하거나

enum의 이름을 테이블에 저장한다.

 

STRING 타입을 순서유지를 위해 권장한다.

'Back-End > JPA' 카테고리의 다른 글

엔티티 매핑 : 학습을 위한 스키마 자동 생성하기  (0) 2023.06.19
엔티티 매핑 : 매핑 어노테이션  (0) 2023.06.19
영속성과 엔티티  (0) 2023.06.01
JPA 시작하기  (0) 2023.06.01
JPA  (0) 2023.05.31
728x90

JPA

자바 진영의 ORM 기술 표준 프레임워크

애플리케이션과 JDBC 사이에서 동작

애플리케이션을 SQL 중심이 아닌 객체 중심으로 개발할 수 있게 도와줌

대표적인 JPA의 구현체는 하이버네이트가 있음

ORM

Object - Relational Mapping

객체와 관계형 데이터베이스 간의 차이를 중간에서 해결해주는 프레임워크

객체와 관계형 데이터베이스를 매핑한다는 뜻

 

직접적인 SQL 사용의 단점

  • 데이터베이스는 데이터 중심의 구조를 가지기 때문에 객체 구조와는 다름
  • 구조가 다르기 때문에 객체를 데이터베이스에 직접적으로 저장 및 조회 불가능
  • 개발자가 직접 변환 작업을 해줘야 함
  • 위와 같은 작업을 위해서 반복적으로 작성해야 할 코드가 너무 많음
  • SQL 중심의 개발로 인해 코드의 수정이 필요한 경우 변경해야 할 코드가 많음
  • 객체와 엔티티가 강한 의존 관계를 가지고 있음 (엔티티를 신뢰할 수 없음)
  • 같은 로우를 조회할 때마다 같은 인스턴스를 반환하도록 구현하기 쉽지 않음

JPA 사용의 장점

  • SQL 중심의 개발이 아닌 JPA  API를 사용하여 개발
  • 매핑 정보를 바탕으로 메서드 호출로만 적절한 SQL문을 수행해줌
  • 객체의 저장, 조회, 수정 등도 간단하게 가능
  • 객체와 데이터베이스 간의 구조 불일치를 중간에서 처리해 줌(패러다임 불일치)
  • 즉, 데이터베이스 구조에는 없는 자바의 상속, 참조 같은 객체지향 개념을 자동으로 처리해준다는 것
  • 연관된 객체를 사용하는 시점에 적절한 SQL문을 사용하여 연관된 객체를 신뢰할 수 있음
  • 같은 트랜잭션인 경우 같은 객체가 조회되도록 보장
  • 반복적인 코드와 SQL문을 개발자가 직접 작성할 필요가 없어 생산성을 향상 시킴
  • SQL과 JDBC와의 의존성이 떨어져 유지보수가 편함
  • 애플리케이션과 DB 사이에서 동작하여 성능 최적화 기회가 있음
  • 마찬가지로 사이에서 추상화된 데이터 접근 계층을 통해 사용하는 DB가 바껴도 DB만 변경하면 됨
  • 즉, 특정 DB 기술에 종속되지 않음

더 알아보면 좋은 내용

MyBatis

Spring JdbcTemplate

Hibernate

JDBC

'Back-End > JPA' 카테고리의 다른 글

엔티티 매핑 : 학습을 위한 스키마 자동 생성하기  (0) 2023.06.19
엔티티 매핑 : 매핑 어노테이션  (0) 2023.06.19
영속성과 엔티티  (0) 2023.06.01
JPA 시작하기  (0) 2023.06.01
엔티티 매핑 기본  (0) 2023.05.31
728x90

요청 메서드

 

GET

서버로부터 특정 리소스를 받아서 표시할 때 사용하며 데이터를 받기만 함

HEAD

GET과 동일하지만 응답 본문을 포함하지 않음

POST

특정 리소스의 엔티티를 서버에 보낼 때 사용

PUT

서버 리소스의 상태를 수정

DELETE

특정 리소스 삭제

CONNECT

서버와의 터널을 맺음

OPTIONS

서버 리소스의 통신을 설정

TRACE

서버 리소스의 경로를 따라 룩백 테스트 수행

PATCH

리소스의 일부분만 수정 시 사용

 

메시지

스타트 라인 / 스테이터스 라인

요청 / 응답에 대한 성공과 실패가 기록

헤더

요청 / 응답에 대한 설명

줄바꿈 라인

빈 공백으로 헤더와 몸통 구분

몸통

요청 / 응답과 관련된 내용이나 문서가 들어가는 영역

 

무상태성

HTTP는 특정한 상태를 유지하지 않고 가지지 않으므로 상태를 유지할 다른 방법을 사용해야 함

주로 캐시와 쿠키가 사용 됨

 

상태코드

100번대

정보응답에 관한 코드

200번대

요청과 응답이 성공적으로 이루어진 상황에 관련된 코드

300번대

요청과 응답에 대한 리다이렉션 코드

400번대

클라이언트 측의 오류에 대한 코드
500번대

서버 측의 오류에 대한 코드

 

'Back-End' 카테고리의 다른 글

Jenkins를 처음 사용해보며 느낀점  (0) 2024.05.09

+ Recent posts