[Spring Boot] 스프링 데이터 - Spring Data JPA

dsunni·2020년 8월 9일
0
post-custom-banner

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를 부가적으로 사용한다.

스프링 데이터 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>

JPA 사용하기

Account.java

@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을 사용해도된다.

어노테이션

  • @Entity : 클래스를 엔티티로 사용
  • @Id : id를 id로 사용
  • @GeneratedValue : auto increment 추가

AccountRepository

public interface AccountRespository extends JpaRepository<Account, Long> {
}
  • extends JpaRepository<Account, Long>
    • <entity의 타입, id의 타입>

AccountRepositoryTest

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

슬라이싱 테스트 (@DataJpaTest)

슬라이싱 테스트란 Respository 포함해서 관련된 Bean들만 등록해서 테스트만드는 테스트이다.

  • @DataJpaTest : 슬라이싱 테스트 애노테이션

    • 슬라이싱 테스트는 인메모리 데이터베이스(h2)를 사용한다.
  • Connection으로 출력하면

    jdbc:h2:mem:b962ded7-8750-4182-939e-ffea4fb83049
    H2 JDBC Driver
    SA

    위와 같이 슬랑싱 테스트에서 h2디비를 사용함을 확인할 수 있다


Integration 테스트 (@SpringBootTest)

Integration 테스트란 메인 애플리케이션을 찾아 모든 Bean을 등록해서 테스트하는 테스트이다.

따라서 application.properties파일이 적용되기 때문에 등록한 PostgreSQL DB를 사용한다.

권장은 안함

하지만.. 테스트할 때는 속도를 위해 인메모리 데이터베이스를 사용하는 편이 좋다.

또한 테스트에서 DB를 바꾸면 application context에서 설정한 DB가 변경되기 때문에 테스트용 DB 를 만들어서 사용한다.

  • @SpringBootTest(properties = " spring.datasource.url=' ' ")
    • url이 오버라이딩됨
@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


Account 테스트해보기

@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=?


PostgreSQL 생성

docker run -p 5432:5432 -e POSTGRES_PASSWORD=pass -e POSTGRES_USER=dsunni -e POSTGRES_DB=springboot --name psql_boot -d postgres

applciation.properties

아무런 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
  • java.lang.reflect.InvocationTargetException : null 에러가 뜬다면 마지막 한 줄을 추가
profile
https://dsunni.tistory.com/ 이사갑니답
post-custom-banner

0개의 댓글