반응형

개념

ORM?

  • Object-Relational Mapping (객체와 관계형 데이터베이스 매핑, 객체와 DB의 테이블이 매핑을 이루는 것)
  • 객체가 테이블이 되도록 매핑 시켜주는 프레임워크이다.
  • 프로그램의 복잡도를 줄이고 자바 객체와 쿼리를 분리할 수 있으며 트랜잭션 처리나 기타 데이터베이스 관련 작업들을 좀 더 편리하게 처리할 수 있는 방법

JPA?

  • Java Persistence API (자바 ORM 기술에 대한 API 표준 명세)
  • 한마디로 ORM을 사용하기 위한 인터페이스를 모아둔 것 이라고 볼 수 있다.
  • 자바 어플리케이션에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스이다.
  • MyBatis는 ORM이 아니고 SQL Mapper임

Hibernate?

  • JPA를 사용하기 위해서 JPA를 구현한 ORM 프레임워크중 하나.
  • JPA 인터페이스의 실제 구현부를 담당함

JPA의 장점

  • 객체 지향적인 코드로 인해 더 직관적이고 비즈니스 로직에 더 집중할 수 있게 도와줌
  • 객체 지향적으로 데이터를 관리할 수 있기 때문에 전체 프로그램 구조를 일관되게 유지할 수 있음
  • SQL을 직접 작성하지 않고 객체를 기준으로 동작하기 때문에 유지보수가 쉬움 (ex. 컬럼 수정시 해당 모델 객체 필드만 수정해주면 끝)
  • 쿼리 문법 오류를 런타임시점이 아닌 컴파일시점에 미리 알 수 있음
  • DBMS에 대한 코드 종속성이 줄어듬

JPA의 단점

  • 학습 비용이 높음
  • 통계와 같은 복잡한 쿼리 사용시 불리함 (실시간 처리용 쿼리에 최적화)
  • 잘못 사용할 경우 실제 SQL문을 직접 작성하는 것보다 성능이 떨어질 수 있음

Spring 기본 예제

테이블 생성

DROP TABLE IF EXISTS Student;
CREATE TABLE Student
(
    studentId    BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    name         VARCHAR(200),
    age          INTEGER,
    registerDate TIMESTAMP(6) — milliseconds 단위까지 저장
);

build.gradle.kts

plugins {
    …
    kotlin("plugin.jpa") version "1.8.22"
}

dependencies {
    …
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    runtimeOnly("com.mysql:mysql-connector-j")
}

application.yml

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      username: mysql
      password: 123456
  jpa:
    hibernate:
      ddl-auto: none  # 테이블 자동 생성하지 않도록 설정.
      naming:         # 변수명을 그대로 칼럼명으로 지정 가능하도록 설정(변수명이 카멜케이스이면 필드명도 카멜케이스)
        implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    properties:
      hibernate:
        show_sql: true            # 하이버네이트가 실행하는 모든 SQL문을 콘솔로 출력해 준다.
        format_sql: true          # 콘솔에 출력되는 JPA 실행 쿼리를 가독성있게 표현한다.
        use_sql_comments: false   # 디버깅이 용이하도록 SQL문 이외에 추가적인 정보를 출력해 준다.
        dialect: org.hibernate.dialect.MySQL8Dialect # MySQL 문법에 맞는 쿼리로 실행.
logging:
  level:
    org.hibernate.type.descriptor.sql: trace # SQL문에 바인딩된 파라미터값을 로그로 출력하기 위한 설정.

Student

@Entity
data class Student(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val studentId: Long = 0L,
    var name: String,
    var age: Int,
    val registerDate: LocalDateTime,
)

StudentRepository

@Repository
interface StudentRepository : JpaRepository<Student, Long> {
    fun findByName(name: String): List<Student>
}

StudentRepositoryTest

@SpringBootTest
class StudentRepositoryTest {
    @Autowired
    private lateinit var jdbcTemplate: JdbcTemplate

    @Autowired
    private lateinit var studentRepository: StudentRepository

    @BeforeEach
    fun before() {
        jdbcTemplate.execute("TRUNCATE TABLE Student")
    }

    @Test
    fun testInsert() {
        studentRepository.save(Student(0L, "john", 25, LocalDateTime.now()))
        studentRepository.save(Student(0L, "tom", 30, LocalDateTime.now()))

        val result = studentRepository.findAll()

        assertEquals(2, result.size)
        assertEquals(1L, result[0].studentId)
        assertEquals(2L, result[1].studentId)
    }

    @Test
    fun testUpdate() {
        studentRepository.save(Student(0L, "john", 25, LocalDateTime.now()))
        studentRepository.save(Student(1L, "john2", 25, LocalDateTime.now()))

        val result = studentRepository.findAll()

        assertEquals(1, result.size)
        assertEquals("john2", result[0].name)
    }

    @Test
    fun testDelete() {
        studentRepository.save(Student(0L, "john", 25, LocalDateTime.now()))
        studentRepository.deleteById(1L)

        val result = studentRepository.findAll()

        assertEquals(0, result.size)
    }

    @Test
    fun testSelect() {
        studentRepository.save(Student(0L, "john", 25, LocalDateTime.now()))
        studentRepository.save(Student(0L, "tom", 30, LocalDateTime.now()))

        val result1 = studentRepository.findAll()
        val result2 = studentRepository.findByName("john")
        val result3 = studentRepository.findByIdOrNull(2L)

        assertEquals(2, result1.size)
        assertEquals("john", result1[0].name)
        assertEquals("tom", result1[1].name)

        assertEquals(1, result2.size)
        assertEquals("john", result2[0].name)

        assertEquals("tom", result3?.name)
    }
}
반응형

+ Recent posts