[Spring Boot #24] 스프링 부트 Spring-Data-JPA 연동
| Spring-Data-JPA란
- ORM은 "관계형 데이터베이스의 구조화된 데이터와 자바와 같은 객체 지향 언어 간의 구조적 불일치를 어떻게 해소할 수 있을까"라는 질문에서 나온 객체-관계 매핑 프레임워크입니다. 즉, 객체와 릴레이션을 매핑할 때 생기는 다양한 문제들을 해결할 수 있는 솔루션이라 생각하면 됩니다.
- JPA은 ORM을 위한 자바 EE 표준이며 Spring-Data-JPA는 JPA를 쉽게 사용하기 위해 스프링에서 제공하고 있는 프레임워크입니다.
- 추상화 정도는 Spring-Data-JPA -> JPA -> Hibernate -> Datasource (왼쪽에서 오른쪽으로 갈수록 구체화) 입니다. 참고로, Hibernate는 ORM 프레임워크이며 DataSource는 스프링과 연결된 MySQL, PostgreSQL 같은 DB를 연결한 인터페이스입니다.
| Spring-Data-JPA 연동 ( + PostgreSQL )
PostgreSQL Docker 구동법은 다음 글에 나와있습니다.
[Spring Framework/Spring boot2] - [Spring Boot #23] 스프링 부트 PostgreSQL 연동하기
프로젝트 구조
├── pom.xml
├── spring-boot-tutorial.iml
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── tutorial
│ │ │ └── springboottutorial
│ │ │ ├── Account.java
│ │ │ ├── AccountRepository.java
│ │ │ └── SpringBootTutorialApplication.java
│ │ └── resources
│ │ ├── application.properties
│ └── test
│ └── java
│ └── com
│ └── tutorial
│ └── springboottutorial
│ └── AccountRepositoryTest.java
의존성 설정
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</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-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
</dependency>
</dependencies>
- h2 데이터베이스와 postgresql 데이터베이스 의존성을 동시에 추가하는 이유는 h2 데이터베이스는 인메모리 데이터베이스로서 테스트에 활용되고 postgresql은 실제 운영 DB 역할을 할 것이기 때문입니다.
설정 파일
# application.properties
spring.datasource.hikari.maximum-pool-size=4
spring.datasource.url=jdbc:postgresql://localhost:5432/springboot
spring.datasource.username=saelobi
spring.datasource.password=pass
# 드라이버가 createClub을 지원하지 않아서 warning 뜨는 것을 방지
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
테스트 코드
@RunWith(SpringRunner.class)
@DataJpaTest // 슬라이싱 테스트를 할 때는 인메모리 데이터베이스가 필요함
// @SpringBootTest // 이 어노테이션을 사용할 시에는 모든 테스트에 필요한 스프링 빈을 등록하기 때문에
// 인 메모리 db를 사용하는 것이 아닌 postgresql을 사용한다.
public class AccountRepositoryTest {
@Autowired
DataSource dataSource;
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
AccountRepository accountRepository;
@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());
}
}
@Test
public void accountTest() throws SQLException {
Account account = new Account();
account.setUsername("saelobi");
account.setPassword("pass");
Account newAccount = accountRepository.save(account);
assertThat(newAccount).isNotNull();
Account existingAccount = accountRepository.findByUsername(newAccount.getUsername());
assertThat(existingAccount).isNotNull();
Account nonExistingAccount = accountRepository.findByUsername("superman");
assertThat(nonExistingAccount).isNull();
}
}
- @DataJpaTest 어노테이션은 슬라이싱 테스트를 할 때 필요한 스프링 빈을 등록시키고 그 의존성을 추가하는 역할을 합니다.
- @DataJpaTest를 이용한 슬라이싱 테스트를 진행할 경우에는 H2 인메모리 데이터베이스, @SpringBootTest를 추가하여 테스트를 진행하게 될 경우에는 실제 PostgreSQL DB 의존성이 추가됩니다. 왜냐하면 @SpringBootTest는 모든 테스트에 필요한 스프링 빈을 등록하기 때문입니다.
- AccountRepository는 해당 프로젝트에서 정의한 인터페이스로 JpaRepository 인터페이스를 상속받아 ORM과 관련된 여러 유용한 메서드를 지원합니다. 이 메서드를 통해 관계형 데이터베이스의 데이터를 사용자가 정의한 객체와 매핑하여 마치 관계형 데이터베이스의 정보를 자바 객체 다루는 듯이 추상화합니다.
소스 코드
@SpringBootApplication
public class SpringBootTutorialApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootTutorialApplication.class, args);
}
}
@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);
}
}
- @Entity : 엔티티 클래스임을 지정하며 DB 테이블과 매핑하는 객체를 나타내는 어노테이션입니다. 여기서 앤티티(Entity)란 데이터베이스에서 표현하려고 하는 유형, 무형의 객체로서 서로 구별되는 것을 뜻합니다. 이 객체들은 DB 상에서는 보통 table로서 나타내어 집니다. (ex) 학생, 컴퓨터, 회사
- @Id : 엔티티의 기본키를 나타내는 어노테이션입니다.
- @GeneratedValeu : 주 키의 값을 자동 생성하기 위해 명시하는 데 사용되는 어노테이션입니다. 자동 생성 전략은 크게 (AUTO, IDENTITY, SEQUENCE, TABLE) 이 있습니다.
public interface AccountRepository extends JpaRepository<Account, Long> {
// 따로 구현체에 대한 코드를 작성하지 않아도 Spring-Data-JPA가 자동적으로
// 해당 DB의 유저네임에 대한 객체를 반환한다.
Account findByUsername(String username);
}
- AccountRepository의 구현체를 따로 작성하지 않아도 Spring-Data-JPA가 자동적으로 해당 문자열 Username에 대한 인수를 받아 자동적으로 DB Table과 매핑합니다.
- findByUsername은 유저네임에 대한 계정 정보를 반환하는 메서드입니다.
결과화면
| @Query 어노테이션
소스 코드
public interface AccountRepository extends JpaRepository<Account, Long> {
@Query(nativeQuery = true, value = "select * from account where username")
Account findByUsername(String username);
}
- @Query 어노테이션을 통해 SQL 쿼리문을 실제로 작성하여 DB 테이블과 매핑할 수 있습니다.
| Optional 객체 반환
소스 코드
public interface AccountRepository extends JpaRepository<Account, Long> {
Optional<Account> findByUsername(String username);
}
- Spring-Data-JPA에서 제공하는 메서드의 반환 객체를 옵셔널 클래스로 감쌀 수 있습니다.
테스트 코드
@RunWith(SpringRunner.class)
@DataJpaTest
public class AccountRepositoryTest {
@Autowired
DataSource dataSource;
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
AccountRepository accountRepository;
@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());
}
}
@Test
public void accountTestWithOption() throws SQLException {
Account account = new Account();
account.setUsername("saelobi");
account.setPassword("pass");
Account newAccount = accountRepository.save(account);
assertThat(newAccount).isNotNull();
Optional<Account> existingAccount = accountRepository.findByUsername(newAccount.getUsername());
assertThat(existingAccount).isNotEmpty();
Optional<Account> nonExistingAccount = accountRepository.findByUsername("superman");
assertThat(nonExistingAccount).isEmpty();
}
}
결과 화면
'Spring Framework > Spring boot #2' 카테고리의 다른 글
[Spring Boot #28] 스프링 부트 몽고DB(Mongo DB) 연동하기 (0) | 2021.03.25 |
---|---|
[Spring Boot #27] 스프링 부트 레디스(Redis) 연동하기 (0) | 2021.03.25 |
[Spring Boot #26] Flyway를 이용한 데이터 마이그레이션 (0) | 2021.03.25 |
[Spring Boot #25] 스프링 부트 데이터베이스 초기화 (0) | 2021.03.25 |
[Spring Boot #23] 스프링 부트 PostgreSQL 연동하기 (0) | 2021.03.25 |
[Spring Boot #22] 스프링 부트 DBCP 및 MySQL 연동해보기 (0) | 2021.03.25 |
[Spring Boot #21] 스프링 부트가 지원하는 인메모리 데이터베이스 (0) | 2021.03.25 |
[Spring Boot #20] 스프링 부트 CORS (0) | 2021.03.24 |