JDBC(Java Database Connectivity)
Java 애플리케이션과 데이터베이스간의 연결과 데이터베이스 작업을 수행하는데 사용된다.
JAVA 표준 API이다.
JDBC 드라이버
Java에서 날린 SQL 쿼리에 프로토콜을 적용시킨 후 DB에 쿼리를 날리고, 그 응답을 프로토콜에 맞춰 파싱하여 Java로 전달한다.
✅ DBMS 별로 알맞은 드라이버가 필요하다.
Object Mapping
=> JDBC를 사용하면 개발자는 직접 SQL 쿼리를 작성하고 실행하며, 결과로 얻은 ResultSet을 필요한 객체에 직접 매핑(Object Mapping) 해야한다.
JDBC 코드
public class UserDAO {
private final DataSource dataSource;
public UserDAO(DataSource dataSource) {
this.dataSource = dataSource;
}
public User getUserById(int id) {
try (Connection connection = dataSource.getConnection();
PreparedStatement statement = connection.prepareStatement("SELECT *
FROM users WHERE id = ?")) {
statement.setInt(1, id);
try (ResultSet resultSet = statement.executeQuery()) { //쿼리 전송 후 응답
if (resultSet.next()) {
//DB 데이터로 모델 만들기
User user = new User();
user.setId(resultSet.getInt("id"));
user.setEmail(resultSet.getString("email"));
user.setPassword(resultSet.getString("password"));
user.setUsername(resultSet.getString("username"));
user.setRoles(resultSet.getString("roles"));
return user;
} }
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
}
MyBatis
기존 JDBC 코드의 반복과 복잡성을 줄이고, 데이터베이스와 객체간의 매핑을 편리하게 처리할 수 있도록 돕는다.
Model ➡️ DAO ➡️ MyBatis ➡️ DB
MyBatis 코드
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User getUserById(int id);
}
위의 코드와 같은 작업을 수행하는데 훨씬 간단하다.
JPA(Java Persistence API)
자바 진영에서 관계형 데이터베이스와 객체 지향 프로그래밍 간의 데이터를 매핑하고 관리하기 위한 표준 인터페이스
📌 JPA는 라이브러리가 아닌 표준 인터페이스이다. (표준 규약, 명세)
JPA는 객체 지향 프로그래밍의 개념과 관계형 데이터베이스의 테이블간의 매핑을 처리하기 위한 ORM(Object-Relational Mapping) 기술을 기반으로 한다.
ORM(Object-Relational Mapping)
개발자가 SQL을 직접 작성하는 대신에 객체 지향 코드를 통해 데이터베이스를 조작할 수 있도록 한다.
➡️ JPA는 ORM 개념을 추상화하여 표준 인터페이스로 제공하고, 다양한 ORM 프레임워크를 JPA 구현체로 사용할 수 있도록 한다.
✅ JPA는 Entity 클래스를 정의하고, 이를 데이터베이스의 테이블과 매핑하는 어노테이션 기반의 설정을 제공한다.
✅ JPA를 사용하면 엔티티 객체를 생성하고 조회, 추가, 수정, 삭제 등의 데이터 조작을 객체 지향적으로 처리할 수 있고,
JPA 구현체가 해당 작업을 데이터베이스에 대한 적절한 SQL 쿼리로 변환하여 실행한다.
JPA는 Java와 DB 사이의 중간다리 역할을 한다.
DB
Board와 User가 N:1 관계를 맺을 때, 외래키는 N에 있다.
데이터베이스에서 속성의 도메인은 원자값이므로, 객체가 아닌 참조 테이블의 키를 가져와 참조한다.
Java
User
~ id
~ username
Board
~ id
~ title
~ User(참조객체)
java는 참조로 객체를 가져올 수 있다.
JPA
Board
~ id
~ title
~ User(참조객체)
JPA는 DB와 Java 사이에 존재하여 DB가 Select 후 Join하여 참조 데이터를 가져올 때
참조 객체로 담아 Java에게 전달한다.
Java가 JPA에게 Object를 요청하면, JPA는 이를 쿼리로 바꿔 DB로 날려준다. (flush)
DB는 테이블 데이터를 ResultSet으로 반환하면, JPA는 Java Object로 관리한다.
✅ JPA는 Java와 DB가 각각의 관점에서 데이터를 관리할 수 있도록 돕는다.
Hibernate & JPA
Hibernate는 JPA의 표준을 구현하고 확장한 ORM 프레임워크
📌 Hibernate는 JPA 인터페이스를 구현한 구현체이다.
Hibernate
- JPA의 인터페이스와 기능을 제공하면서도 자체적인 고급 기능과 확장 기능 추가 제공
- JPA의 스펙을 따르면서도 특정 DB 기술에 종속되지 않고, 다른 JPA 구현체로 전환 가능
Persistence Context
DB Entity를 영구 저장(Persistence)하는 환경(Context)
Repository와 DB 사이에 존재한다.
(Tomcat) ➡️ Filter DispatcherServlet ➡️ Controller ➡️ Service ➡️ Repository ➡️ Persistence Context ➡️ DB
✳️ 새로운 user 객체 Insert
- 새로운 user 객체를 생성한다.
- PC에 들어오기 전이므로 비영속 객체이다.
- 생성한 객체를 PC에 담는다.
- id가 autoincrement라면 아직 id=null(PK가 없음)이다.
- DB에 insert 쿼리 전송(flush)한다.
- DB에 새로운 유저 객체가 생기고(id(PK)가 존재) PC에 그 객체가 반영된다. (동기화)
- user는 영속 객체 (관리 가능)
- 클라이언트에게 응답 후 PC 초기화
✳️ user 객체 select
- PC의 1차 캐시에서 Entity 조회
- 없으면 데이터베이스에서 조회 (쿼리 날리기)
- 그 후 1차 캐시에 entity 저장
- 응답 전 동일 객체를 조회하는 코드가 Service에 또 존재한다면 1차 캐시에서 조회해서 가져온다.
- 100번을 조회해도 1차 캐시에서 1번 조회한다.
- 만약 Service에서 한 객체를 100번 조회하는 코드가 존재할때, 쿼리는 DB에 1번만 날라간다.(1번 이후 캐싱되므로)
- 응답 이후 PC 초기화
✳️ user 객체 수정
Dirty Checking (변경 감지)
DB의 Entity 변경사항을 데이터베이스에 자동으로 반영한다.
📌 PC는 Entity가 들어올 때 각 Entity의 처음 상태를 스냅샷으로 저장한다.
📌 트랜잭션 commit 시에 Entity Manager가 PC의 변경 내용이 있다면 DB에 반영한다 (flush)
이때 각 Entity의 스냅샷과 commit 시점의 Entity를 비교하여 변경된 Entity를 반영한다.
연관관계
Hibernate는 객체-관계 매핑(ORM)을 지원하므로 객체 지향 프로그래밍에서 사용되는 객체와 관계형 데이터베이스의 테이블 간의 연관관계를 매핑할 수 있다.
- 일대일 관계 : 2개의 엔티티 간에 하나의 관계만 존재 (@OneToOne)
- 일대다 관계 : 하나의 엔티티가 다른 엔티티 여러개와 연관된 경우 (@OneToMany)
- 다대일 관계 : 여러개의 엔티티가 하나의 엔티티와 연관된 경우 (@ManyToOne)
- 다대다 관계 : 여러개의 엔티티가 다른 여러개의 엔티티와 연관된 경우 (@ManyToMany)
✅ @OneToMany보단 @ManyToOne 사용하기
JPA 코드
package com.example.kakaoboardjpa.reply;
import com.example.kakaoboardjpa.board.Board;
import com.example.kakaoboardjpa.user.User;
import lombok.*;
import javax.persistence.*;
@ToString
@Builder
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity //JPA 객체 매핑
@Table(name="reply_tb") // 매핑할 테이블 지정
public class Reply {
@Id //기본키 매핑
@GeneratedValue(strategy = GenerationType.IDENTITY) //id auto increment
private Integer id;
@ManyToOne(fetch = FetchType.LAZY) //연관관계 매핑 : Reply: User = N:1
private User user;
@ManyToOne(fetch = FetchType.LAZY)
private Board board;
@Column(nullable = false) //Field와 Column 매핑
private String comment;
}
JPA 어노테이션
- @Id : 기본키 매핑
- @GeneratedValue(strategy = GenerationType.IDENTITY) : id auto increment
- @Entity : JPA 객체 매핑
- @Table(name="reply_tb") : 객체와 매핑할 테이블 지정
- @ManyToOne(fetch = FetchType.LAZY) : 연관관계 매핑 : Reply: User = N:1
- @Column(nullable = false) : Field와 Column 매핑
JPA 쿼리
public interface UserRepository extends JpaRepository<User, Integer> {
// JPA가 제공하는 namedQuery
// 파라메터가 두개 이상이면 @Param은 필수이다. 한개일때는 생략가능하다.
Optional<User> findByUsernameAndPassword(@Param("username") String username, @Param("password") String password);
// 직접 JPQL 객체지향 쿼리를 작성할 수 있다.
@Query("select u from User u where u.username = :username and u.password = :password")
Optional<User> jpqlFindByUsernameAndPassword(@Param("username") String username, @Param("password") String password);
// native query를 사용하는 방법이다.
@Query(value = "select * from user_tb where username = :username and password = :password", nativeQuery = true)
Optional<User> nativeFindByUsernameAndPassword(@Param("username") String username, @Param("password") String password);
}
✅ 객체지향 쿼리 추천
참고
카테캠 강의 (MetaCoding)
'Spring > Spring 개발 상식' 카테고리의 다른 글
즉시 로딩과 지연 로딩(+프록시), Fetch Join, Join (0) | 2023.07.13 |
---|---|
@Builder와 @Getter, @Setter (1) | 2023.07.10 |
DTO 생성 방법 (0) | 2023.07.10 |
DAO vs DTO vs VO (2) | 2023.07.10 |
Spring Security Test (0) | 2023.07.08 |