| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
- transactional
- springboot
- Adapter
- simplejpaRepository
- Spring
- Spring Data JPA
- Hexagonal
- Transaction
- JPA
- JDBC
- Layered Architecture
- hexagonal architecture
- 실무
- Today
- Total
Ezcho
[Spring] JDBC, JPA + Hibernate, Transaction 본문
1. JDBC란?
JDBC(Java DataBase Connectivity의 약자)는 Java가 데이터베이스와 통신하기위한 API다.
java 어플리케이션에서 JDBC 문법을 통해 데이터베이스에 자동으로 데이터를 CRUD할 수 있다.
왜 그럴까?

JDBC API의 전반적인 동작구조는 위와같다.
Application 에서작성한 jdbc문법을 jdbcAPI에서 분기, jdbc Driver에서 각 DB환경에 맞게 변환해주는것이다.
즉, H2, Mysql, Oracle, MariaDB... 등 DB마다 환경이 다르고 저장방식도 통신도 전부 다르다. 그래서 API가 각 환경에 맞는 드라이버를 찾아서 연결해주는것이다.
즉 JDBC API는 인터페이스라고 볼 수 있고, 구현은 Driver에서 하는것이다.
JDBC DriverManager의 역할은 주로 데이터베이스 연결 관리와 JDBC 드라이버를 등록하고, 데이터베이스 연결을 설정하는 것입니다. JDBC DriverManager는 데이터베이스 연결에 대한 환경 설정과 연결 관리를 담당합니다.
JDBC는 데이터베이스의 종류에 상관없이 똑같은 코드로 해결할 수 있습니다.
그럼 JDBC 문법 예시를 한번보자, DriverManager와 Statement를 사용하였다. 사실 JDBC문법은 사용할일이 없으니까 어떤식으로 동작하는지만 파악해보자
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class InsertExample {
public static void main(String[] args) {
// JDBC 연결 정보 설정
String jdbcUrl = "jdbc:mysql://localhost:3306/SampleDatabase";
String username = "root";
String password = "abc123";
// 데이터베이스 연결 객체 생성
try (Connection connection = DriverManager.getConnection(jdbcUrl, username, password)) {
// INSERT 쿼리 작성
String insertQuery = "INSERT INTO your_table_name (column1, column2, column3) VALUES ('값1', 42, '값3')";
// Statement를 사용하여 쿼리 실행
try (Statement statement = connection.createStatement()) {
// INSERT 실행
int affectedRows = statement.executeUpdate(insertQuery);
if (affectedRows > 0) {
System.out.println("데이터가 성공적으로 삽입되었습니다.");
} else {
System.out.println("데이터 삽입에 실패했습니다.");
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
크게 어려운것은 없다. SQL쿼리문을 작성하면 연결된 DB에 맞게 환경설정해서 띄워주는 느낌이다.
정리하면 아래와 같이 볼 수 있을 것 같다.
1. JDBC API 사용
2. DB와 Connection
3. JDBC API는 사용된 DB환경에 맞는 드라이버 선택
4. 사용자의 코드에서 SQL 쿼리문 입력 후 통신
5. JDBC Driver에서 환경에 맞게 쿼리문변환.
2. JPA란?
https://spring.io/projects/spring-data-jpa/
Spring Data JPA
Spring Data JPA, part of the larger Spring Data family, makes it easy to easily implement JPA-based (Java Persistence API) repositories. It makes it easier to build Spring-powered applications that use data access technologies. Implementing a data access l
spring.io
우리가 생성한 하나의 객체(엔티티) 를 DB의 특정 테이블의 row로 본다면, 아니 JPA를 한번 쯤 써 보았다면 row로 본다는건 다들 알것이다.
이때 JPA가 적용되는데, DB와 객체를 매핑하는 기술을 JPA라고 한다.
자 근데 몇가지 이슈가 있다.
1. 객체내에 리스트가 정의되어 있는경우 DB는 하나의 row에 하나의 값만 들어가야하기때문에 곤란하다.
2. 혹은 다른 객체를 참조하는 경우 Java에서는 참조 라고 하지만 DB에서는 Join 이다.
이때 생기는 문제들을 패러다임의 불일치 라고한다.
매핑은 해야겠고 패러다임의 불일치는 생기고... 그래서, JPA에서 이를 대신해준다!
JPA에서 패러다임의 불일치를 해결하는것을 ORM(Object Relational Mapping) 이라고 한다. 자세한것은 아래블로그를 참고하자.
https://jgrammer.tistory.com/76
[JPA] 패러다임 불일치
애플리케이션은 발전하면서 점점 복잡성이 커진다. 지속 가능한 애플리케이션을 개발하는 일은 끊임없이 증가하는 복잡성과 의 싸움이다. 복잡성을 제어하지 못하면 유지보수하기 어려운 애플
jgrammer.tistory.com
개발하는입장에서는 매핑하기위해서 많은 쿼리를 작성할 필요없이 JPA가 알아서 해주는것이다.
그런데 JPA를 한번이라도 써본 입장에서, JDBC랑 함께 쓰이는것을 알 수있다. JPA는 위 정의에서도 설명했듯이 DB와 객체를 매핑하는 기술이다. 하지만 DB와의 Connection은 여전히 JDBC Driver를 사용해야한다.
JPA는 객체 지향적인 방식으로 데이터베이스를 다룰 수 있도록 도와주는 ORM (Object-Relational Mapping) 기술입니다.
자 그럼 JPA의 특징을 살펴보자
JPA로 엔티티 매핑: JPA를 사용하여 엔티티 클래스를 정의하고 데이터베이스 테이블과 객체 간의 매핑을 설정(테이블을 짠다 라는 뜻으로 이해해보자), JPA는 객체(Entity)를 데이터베이스 레코드로 매핑하고 데이터베이스 레코드를 Entity로 매핑하는 일반적인 데이터베이스 작업을 처리
JPA를 사용한 쿼리: JPA는 JPQL (Java Persistence Query Language) 또는 Criteria API를 사용하여 객체 지향적인 방식으로 데이터베이스 쿼리를 수행.
CRUD 작업: 일반적인 CRUD작업은 JPA에서 간단하게 처리할 수 있습니다.
트랜잭션 관리: JPA는 트랜잭션 관리를 지원하며, 데이터베이스 작업을 트랜잭션 내에서 안전하게 수행함. 트랜잭션을 커밋하거나 롤백하는 데 필요한 기능역시 제공됨.
다양한 데이터베이스 지원: JPA는 다양한 데이터베이스 관리 시스템을 지원하며, 데이터베이스 종류에 관계없이 동일한 코드를 사용한다(JDBC API와 동일한 성격)
이제 JPA를 사용해보자.
위의 JDBC 예제대로 엔티티를 만들었다.
@Getter
@Setter
@Entity
@Table(name = "sampleTable")
public class YourEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "column1")
private String column1;
@Column(name = "column2")
private int column2;
@Column(name = "column3")
private String column3;
}
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class InsertExample {
public static void main(String[] args) {
// EntityManagerFactory 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory("root");
// EntityManager 생성
EntityManager em = emf.createEntityManager();
// 트랜잭션 시작
em.getTransaction().begin();
// 엔티티 객체 생성 및 값 설정
YourEntity entity = new YourEntity();
entity.setColumn1("값1");
entity.setColumn2(42);
entity.setColumn3("값3");
// 엔티티를 영속성 컨텍스트에 추가
em.persist(entity);
// 트랜잭션 커밋
em.getTransaction().commit();
// EntityManager 및 EntityManagerFactory 닫기
em.close();
emf.close();
System.out.println("데이터가 성공적으로 삽입되었습니다.");
}
}
트랜젝션이라는 개념이 생소한데, 작업의 완전성을 보장해주는것이다.
다시말에 DB테이블 작업중 특정부분에서 문제가 생기거나 오류가나면 다시 최초 작업 상태로 돌아가야하는것을 의미한다.
그래서 우리가 위 JPA문법을 사용할때 트랜젝션 코드를 써주는것이다.. 라고 설명하기엔 너무 짧아서 아래에서 한번 짚고 넘어가자

Hibernate
JPA도 JDBC와 마찬가지로 인터페이스이기 때문에 구현체가 필요하고, 그 구현체 중 하나가 Hibernate이다.
JPA는 자바에서 제공하는 인터페이스로 ORM 기술에 대한 명세서라고 했습니다. 우리가 엔티티와 테이블을어떻게 매핑할건지에 대한,
이 JPA 인터페이스를 구현한 것이 Hibernate다.
하이버네이트를 사용하여 SQL을 직접 작성하지 않고 매서드로 작성한다고 해서 JDBC API를 사용하지 않는 것은 아닙니다.
그 매서드 내부에는 JDBC API와의 동작이 연결되어 있으며 개발자가 SQL에 대한 작업보다는 비즈니스 로직에 더 집중 할 수 있도록 도와주는 역할을 한다.
3. Transaction
Transaction 이란 작업의 완전성을 보장해주는것을 말한다.
논리적인 작업셋(여기서는 SQL쿼리)을 묶어서 처리하고 처리하지 못할경우에는 원상태로 복구하여 작업의 일부만 처리되는 경우를 방지하는것이다.
Lock과 unLock을 통한 공유자원에 대한 접근을 관리하는개념과 비슷할 수도 있겠다.
트랜젝션은 ACID 라는 다음의 네가지 조건을 보장받아야한다.
- 원자성 (Atomicity):
- 원자성은 트랜잭션을 단일 원자 단위로 처리하는 것을 의미합니다. 트랜잭션 내의 모든 작업은 성공적으로 완료되거나 실패해야 합니다. 실패한 트랜잭션은 이전 상태로 롤백되어야 하며, 성공한 트랜잭션은 모든 작업이 반영되어야 합니다. 이렇게 하면 데이터베이스는 언제나 일관된 상태를 유지할 수 있습니다.
- 일관성 (Consistency):
- 일관성은 트랜잭션이 실행 전과 실행 후에도 데이터베이스가 일관된 상태를 유지해야 함을 의미합니다. 트랜잭션이 실행 중에 데이터베이스의 일관성이 깨지면 안 됩니다. 예를 들어, 트랜잭션이 데이터베이스의 제약 조건을 위반하거나 무결성 규칙을 어길 경우 해당 트랜잭션은 롤백되어야 합니다.
- 고립성 (Isolation):
- 고립성은 여러 트랜잭션이 동시에 실행될 때 각각의 트랜잭션이 서로 영향을 주지 않고 고립되어야 함을 의미합니다. 다시 말해, 한 트랜잭션의 작업이 다른 트랜잭션에게 누설되어서는 안 됩니다. 이것은 데이터베이스 동시성 제어를 통해 구현됩니다.
- 지속성 (Durability):
- 지속성은 트랜잭션이 성공적으로 완료되면 그 결과가 영구적으로 저장되어야 함을 의미합니다. 시스템이 중단되거나 다시 시작되더라도 데이터 손실이 발생하지 않아야 합니다. 이를 위해 데이터베이스 시스템은 트랜잭션의 결과를 영구 저장 매체에 기록하고 보호해야 합니다.
하나도 어렵지 않으니 차근차근 보고 이해해보자 다 맞는 말들이다.
이제 트랜젝션의 상태에 대해서 알아보자.
- 시작 상태 (Begin State):
- 트랜잭션이 시작되면 이 상태입니다. 트랜잭션은 아직 어떤 작업도 수행하지 않았으며, 아무런 영향을 데이터베이스에 주지 않은 상태입니다.
- 활동 상태 (Active State):
- 트랜잭션이 데이터베이스 조작 작업을 수행하고 있는 상태입니다. 이 단계에서는 트랜잭션이 데이터베이스 내용을 변경하거나 읽어오고 있습니다. 활동 상태에서는 트랜잭션은 일련의 작업을 수행하며, 언제든지 커밋 또는 롤백을 통해 완료될 수 있습니다.
- 커밋 대기 상태 (Waiting for Commit State):
- 트랜잭션이 모든 작업을 성공적으로 완료하고 커밋을 대기하고 있는 상태입니다. 커밋은 트랜잭션에서 수행한 모든 작업을 데이터베이스에 반영하고 트랜잭션을 종료하는 작업입니다. 커밋 대기 상태에서는 다른 트랜잭션과 충돌하지 않도록 고립성을 유지해야 합니다.
- 종료 상태 (End State):
- 트랜잭션이 커밋되거나 롤백되면 종료 상태가 됩니다. 커밋된 경우, 트랜잭션의 모든 작업은 영구적으로 데이터베이스에 반영되고 트랜잭션은 성공적으로 종료됩니다. 롤백된 경우, 트랜잭션은 이전 상태로 되돌아가고 어떤 작업도 데이터베이스에 반영되지 않은 상태입니다.
영속성 개념에 대해서 모른다면 아래 글을 참고해보자
결론적으로, SQL쿼리문을 JPA에서 처리할때 트랜젝션을 지원하고 우리는 트랜젝션을 적극 활용하여 코드를 짜야한다.
https://devwithpug.github.io/spring/jpa-2/
JPA - 영속성 & 영속성 컨텍스트에 대해
JPA에서 가장 중요한 영속성(Persistence)에 대해 정리해보았다.
devwithpug.github.io
4. @Transactional
그럼 해당 어노테이션이 어떤것인지 대충 이해했을것이다.
우리가 코드를 짜다보면, Service에서 Repository를통해 DB와 상호작용을 한다.
이때 특정 사용자만 접근가능한 DB인경우(혹은 그렇게 설계를 할 경우)Transactional을 적용하지 않아도 된다.
혹은 단순한 쿼리문으로 값변경이 크게 일어나지 않는경우에(+1을 하는경우 한줄로 처리가능) 뭐 기타등등.
상관없다. 사실 트랜젝션을 쓰는것보다 사용자구분을 확실히 해주는게 효율적이다.
하지만 여러 사용자가 접근하는(특히 값을 수정하는) 데이터에 대해서 생각해보자.
예를들어 table내의 어떤 수들을 가져와서 새로운 연산을 하고 다시 해당 row 에 저장해야 한다고 생각해보자.
1. A라는 사용자가 table의 특정행의 갑인 100을 가져온다.
2. A라는 사용자가 서버에서 100/6*2+3... 등 연산을 수행한다.
3. 수행하는와중 B라는 사용자가 100을가져온다. 그리고 100*32/73*2... 등 2번과는 다른 연산을 수행한다.
4. A는 연산이 완료되어 DB에 저장한다.
5. B도 연산이 완료되어 DB에 저장한다.
6. A는 본인이 수정한 값이 이상한것을 확인한다.
이때 발생하는 문제가 무결성 문제이다.
데이터 무결성 문제
트랜잭션을 사용하지 않으면 데이터베이스 내의 데이터 무결성을 보장하기 어렵습니다. 여러 사용자 또는 응용 프로그램이 동시에 데이터를 읽고 쓸 때, 데이터 무결성 위반 및 데이터 불일치 문제가 발생할 수 있습니다.
이 외에도
데이터 일관성 문제, 데이터 손실 문제, 동시성 문제, 회복 문제등이 있으니까 우리는 Transaction을 적절히 써야한다.
사용법
@Service
public class BookService {
private final BookRepository bookRepository;
@Transactional
public List<BookResponseServiceDto> getBooks() {
return bookRepository.findAll()
.stream()
.map(BookResponseServiceDto::new)
.collect(Collectors.toList());
}
}
트랜젝션의 문제는 교착상태에 빠질 수 있다는것인데, 자세한 내용은 나중에 다루겠다.
5. Spring Data JPA
최종적으로 많이 사용하고 우리가 개발에서 실제로 쓰게될 것이다.
기존 JPA에서는 Create를 수행할 때 persist(), Update를 수행할 때 merge()를 호출하여 인스턴스의 영속성 관리를 해주었다.
Data JPA를 사용할 때는 조금 다른데, 먼저 생성할 Repository에 대한 인터페이스를 생성하고 JpaRepository와 같이 Data JPA에서 제공하는 인터페이스를 상속받는다.
사용법은 아래와 같다.
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ExampleRepository extends JpaRepository<Entity, Integer> {
Entity findById(int id);
void deleteById(int id);
}
그리고 우리는 서비스에 이걸 사용한다.
import org.springframework.beans.factory.annotation.Autowired;
@Service
public class ExampleService {
@Autowired
private Repository repository;
public Iterable<Entity> getAllBoard(){
return repository.findAll();
}
public Entity getBoard(int id) {
return repository.findById(id);
}
public Entity setBoard(DTO dto){
Entity entity = new Entity();
entity.setContent(dto.getContent());
entity.setTitle(dto.getTitle());
return repository.save(entity);
}
public Entity updateBoard(int id, DTO dto) {
if (repository.existsById(id)) {
Entity entity = repository.findById(id);
entity.setContent(dto.getContent());
entity.setTitle(dto.getTitle());
repository.save(entity);
return entity;
}
return null;
}
}
크게 달라진것은 없다. JPA에서 CRUD를 지원하고, JpaRepository를 상속받는 SimpleJpaRepository를 보면
@Transactional
@Override
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null.");
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
save()메서드를 정의해두었는데, 기존 JPA문법의 merge와 persist를 사용해서 Create와 Update를 구현해놓은것을 알 수 있다.
@Transactional 어노테이션 역시 구현된것을 알 수 있다. 그래서 엔티티 저장 로직이 수행되는동안 해당 Table행은 Lock상태가 된다.
6. 정리

Application은 단순히 JDBC API를 사용하여, 직접적인 SQL쿼리문으로 DB와 소통할 수 있음을 보였다.
하지만 우리는 엔티티와 테이블관의 관계 매핑을 위해서 JPA개념을 도입하였고, JPA는 그 매핑에 대한 명세라는것을 알 수 있다.
그리고 그것에 대한 구현을 Hibernate에서 한다. Hibernate와 JDBC API가 소통하여 DB와 Connection하는것을 이해하자.
Spring Data JPA의 경우 Raw JPA를 사용하는것을 조금더 비즈니스로직에 집중할 수 있게 구현해놓은것이다.
JPA Repository의 구현체인 SimpleJpaRepository 를 참고하고 학생수준에서는, 이게 레포지토리 에서 JPA문법을 미리 작성해놓은 메서드를 지원한다고 이해하면될것같다.
'BE > DB' 카테고리의 다른 글
| [Spring] Bean객체 (1) | 2024.01.05 |
|---|