반응형
개념
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)
}
}
반응형