[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/Spring Boot] - [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();
}
}


결과 화면



참고자료 : https://www.inflearn.com/course/스프링부트

이 글을 공유하기

댓글(0)

Designed by JB FACTORY