[Spring Boot #24] 스프링 부트 Spring-Data-JPA 연동

2021. 3. 25. 01:04 Spring Framework/Spring boot2

| 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();
    }
}

 

결과 화면

 

 

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



출처: https://engkimbs.tistory.com/790?category=767865 [새로비]