ORM(Object-Relational Mapping)
객체와 릴레이션을 맵핑할 때 발생하는 개념적 불일치를 해결하는 프레임워크
예를 들어 객체의 크기 또는 상속, 식별자와 같은 다양한 문제들을 ORM에서 다룬다. JPA는 그러한 ORM에 대한 표준이다.
JPA(Java Persistence API)
JPA : ORM을 위한 자바 (EE) 표준이며 구현체는 hibernate이다.
Spring Data JPA
스프링에서 ORM을 쉽게 사용할 수 있게 Spring data로 추상화 시켜둔 것이다. 구현체는 Hibernate를 사용하며 JPA는 Entity Manager로 감싸서 사용한다.
Spring Data JPA가 제공하는 인터페이스, 애노테이션 등을 사용해서 JPA, Hibernate를 사용하게된다.
- 추상화 단계 : SpringData JPA ⇢ JPA ⇢ Hibernate ⇢ DataSource
- Spring Data JDBC의 기능 전부와 JPA를 부가적으로 사용한다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>me.dsunni</groupId>
<artifactId>spring-boot-inflearn2</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
</parent>
<!-- Add typical dependencies for a web application -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>
<!-- Package as an executable jar -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@Entity
public class Account {
@Id @GeneratedValue
private Long id;
private String username;
private String password;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Account account = (Account) o;
return Objects.equals(id, account.id) &&
Objects.equals(username, account.username) &&
Objects.equals(password, account.password);
}
@Override
public int hashCode() {
return Objects.hash(id, username, password);
}
}
Java Bean Spec을 사용하기 때문에 Getter, Setter가 필요하다.
물론 Lombok을 사용해도된다.
public interface AccountRespository extends JpaRepository<Account, Long> {
}
JpaRepository<Account, Long>
DataSource
JDBC로 데이터베이스에 접근할 때마다 Connection을 맺고 끊는 작업을 한다. 이러한 반복되는 작업을 줄이기 위해 미리 Conenction을 생성해두고, DB에 접근하고자 할 때마다 미리 생성된 Connection을 제공한다.
이러한 여러 개의 Connection 객체들은 Connection Pool에 모아져있으며, Datasource는 Connection Pool을 관리하는 목적으로 사용되는 객체이다.
@RunWith(SpringRunner.class)
@DataJpaTest
public class AccountRespositoryTest {
@Autowired
DataSource dataSource;
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
AccountRespository accountRespository;
@Test
public void di() throws SQLException {
try (Connection connection = dataSource.getConnection()){
DatabaseMetaData metaData = connection.getMetaData();
System.out.println(metaData.getURL());
System.out.println(metaData.getDriverName());
System.out.println(metaData.getUserName());
}
}
}
슬라이싱 테스트란 Respository 포함해서 관련된 Bean들만 등록해서 테스트만드는 테스트이다.
@DataJpaTest : 슬라이싱 테스트 애노테이션
Connection으로 출력하면
jdbc:h2:mem:b962ded7-8750-4182-939e-ffea4fb83049
H2 JDBC Driver
SA
위와 같이 슬랑싱 테스트에서 h2디비를 사용함을 확인할 수 있다
Integration 테스트란 메인 애플리케이션을 찾아 모든 Bean을 등록해서 테스트하는 테스트이다.
따라서 application.properties
파일이 적용되기 때문에 등록한 PostgreSQL DB를 사용한다.
하지만.. 테스트할 때는 속도를 위해 인메모리 데이터베이스를 사용하는 편이 좋다.
또한 테스트에서 DB를 바꾸면 application context에서 설정한 DB가 변경되기 때문에 테스트용 DB 를 만들어서 사용한다.
@RunWith(SpringRunner.class)
@SpringBootTest
public class AccountRespositoryTest {
@Autowired
DataSource dataSource;
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
AccountRespository accountRespository;
@Test
public void di() throws SQLException {
try (Connection connection = dataSource.getConnection()){
DatabaseMetaData metaData = connection.getMetaData();
System.out.println(metaData.getURL());
System.out.println(metaData.getDriverName());
System.out.println(metaData.getUserName());
}
}
}
jdbc:postgresql://localhost:5432/springboot
PostgreSQL JDBC Driver
dsunni
@RunWith(SpringRunner.class)
@DataJpaTest
public class AccountRespositoryTest {
@Autowired
DataSource dataSource;
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
AccountRespository accountRespository;
@Test
public void di() throws SQLException {
Account account = new Account();
account.setUsername("dsunni");
account.setPassword("pass");
Account newAccount = accountRespository.save(account);
assertThat(newAccount).isNotNull();
Account existingaccount = accountRespository.findByUsername(newAccount.getUsername());
assertThat(existingaccount).isNotNull();
Account nonexistingaccount = accountRespository.findByUsername("notexisting");
assertThat(existingaccount).isNotNull();
}
}
테스트가 정상적으로 작동함을 확인할 수 있으며 아래돠 같은 쿼리들도 시행됐음을 볼 수 있다
Hibernate: insert into account (password, username, id) values (?, ?, ?)
Hibernate: select account0_.id as id1_0_, account0_.password as password2_0_, account0_.username as username3_0_ from account account0_ where account0_.username=?
Hibernate: select account0_.id as id1_0_, account0_.password as password2_0_, account0_.username as username3_0_ from account account0_ where account0_.username=?
docker run -p 5432:5432 -e POSTGRES_PASSWORD=pass -e POSTGRES_USER=dsunni -e POSTGRES_DB=springboot --name psql_boot -d postgres
아무런 DB를 설정해주면 기본적을 인메모리 디비를 사용하게되기 때문에 DB 정보를 업데이트해주자
spring.datasource.url=jdbc:postgresql://localhost:5432/springboot
spring.datasource.username=dsunni
spring.datasource.password=pass
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true