ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Spring] Spring Frameworks를 활용한 데이터 레이어(RDB) 접근 방법
    Backend/Spring 2022. 8. 19. 12:15

    Spring Frameworks를 활용한 데이터 레이어(RDB) 접근 방법

    • JDBC
    • JDBC Template
    • 쿼리매퍼(Mybatis)
    • ORM(JPA)

     

    JDBC

    자바 어플리케이션은 JDBC API를 이용하여 데이터 계층과 통신한다.

    • JDBC API 인터페이스는 각각 JDBC Driver라는 구현체를 통해서 동일한 인터페이스로 구현되어 있음
    • 각 Database에 대해 구현해놓은 Driver 클래스 객체들을 사용해서 JDBC API 인터페이스를 통해서 통신
    @Slf4j
    public class JDBCTest {
    
        static final String JDBC_DRIVER = "org.h2.Driver";
        static final String DB_URL = "jdbc:h2:~/test";
        static final String USER = "sa";
        static final String PASS = "";
    
        static final String DROP_TABLE_SQL = "DROP TABLE customers IF EXISTS";
        static final String CREATE_TABLE_SQL = "CREATE TABLE customers(id SERIAL, first_name VARCHAR(255), last_name VARCHAR(255))";
        static final String INSERT_SQL = "INSERT INTO customers (id, first_name, last_name) VALUES(1, 'honggu', 'kang')";
    
        @Test
        void jdbc_sample() {
            try {
                Class.forName(JDBC_DRIVER);
    
                Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);
                log.info("Connection 획득");
                Statement statement = connection.createStatement();
                log.info("Statement 획득");
    
                log.info("쿼리 실행");
                statement.executeUpdate(DROP_TABLE_SQL);
                statement.executeUpdate(CREATE_TABLE_SQL);
    
                statement.executeUpdate(INSERT_SQL);
                ResultSet resultSet = statement.executeQuery("SELECT id, first_name, last_name FROM customers WHERE id = 1");
    
                while(resultSet.next()) {
                    log.info(resultSet.getString("first_name"));
                }
    
                log.info("반납, 반납");
                statement.close();
                connection.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    • Connection 획득 - RDB와 연결
    • Statement를 이용한 질의
    • ResultSet을 이용한 질의 결과 사용
    • Statement, Connection 반납
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jdbc</artifactId>
    </dependency>

     

    JDBC Template

    JDBC Template을 이용해서, 데이터 계층에 접근이 가능하다. 기존 JDBC를 이용했을 때의 반복적인 작업(Connection 획득, Statement 생성, Connection과 Resultset 반납, exception 처리)을 JDBC Template이 대신 수행한다. 하지만 유지보수가 어려울 수 있다.

    @Repository
    public class CustomerRepository {
    
        private static final Logger log = LoggerFactory.getLogger(CustomerRepository.class);
    
        private final JdbcTemplate jdbcTemplate;
    
        public CustomerRepository(JdbcTemplate jdbcTemplate) {
            this.jdbcTemplate = jdbcTemplate;
        }
    
        public long save(Customer customer) {
            int insertCount = jdbcTemplate.update(
                    "INSERT INTO customers (id, first_name, last_name) VALUES(?, ?, ?)",
                    customer.getId(), customer.getFirstName(), customer.getLastName()
            );
            log.info("고객정보 {}건이 입력되었습니다.", insertCount);
            return customer.getId();
        }
    
        public long update(Customer customer) {
            int updateCount = jdbcTemplate.update(
                    "UPDATE customers SET first_name = ? , last_name = ? WHERE id = ?",
                    customer.getFirstName(), customer.getLastName(), customer.getId()
            );
            log.info("고객정보 {}건이 수정되었습니다.", updateCount);
            return customer.getId();
        }
    
        public Customer findById(long id) {
            Customer customer = jdbcTemplate.queryForObject(
                    "SELECT * FROM customers WHERE id = ?",
                    (resultSet, rowNum) -> new Customer(resultSet.getLong("id"), resultSet.getString("first_name"), resultSet.getString("last_name")),
                    id
            );
            log.info("아이다:{} 고객의 정보가 조회되었습니다." ,customer.getId());
            return customer;
        }
    
        public List<Customer> findAll() {
            List<Customer> customers = jdbcTemplate.query(
                    "SELECT * FROM customers",
                    (resultSet, rowNum) -> new Customer(resultSet.getLong("id"), resultSet.getString("first_name"), resultSet.getString("last_name"))
            );
            log.info("{}건의 고객정보가 조회되었습니다.", customers.size());
            return customers;
        }
    }
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jdbc</artifactId>
    </dependency>

     

    Mybatis (QueryMapper)

    JDBC의 반복적인 작업을 쿼리매퍼인 Mybatis가 대신 수행해준다. 자바 코드와 쿼리를 분리해주며, 쿼리 수정으로 자바 코드 수정이나 컴파일하는 작업을 하지 않아도 된다.

    방법은 Annotation을 이용하는 방식과 xml을 이용하는 방식 2가지가 존재한다.

    // Using Annotation
    @Mapper
    public interface CustomerMapper {
        @Insert("INSERT INTO customers (id, first_name, last_name) VALUES(#{id}, #{firstName}, #{lastName})")
        void save(Customer customer);
    
        @Update("UPDATE customers SET first_name=#{firstName}, last_name=#{lastName} WHERE id=#{id}")
        void update(Customer customer);
    
        @Select("SELECT * FROM customers")
        List<Customer> findAll();
    
        @Select("SELECT * FROM customers WHERE id = #{id}")
        Customer findById(@Param("id") long id);
    }
    ...
    // Using XML
    @Mapper
    public interface CustomerXmlMapper {
        void save(Customer customer);
        void update(Customer customer);
        Customer findById(long id);
        List<Customer> findAll();
    }
    
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.kdt.lecture.repository.CustomerXmlMapper">
        <insert id="save">
            INSERT INTO customers (id, first_name, last_name)
            VALUES (#{id}, #{firstName}, #{lastName})
        </insert>
    
        <update id="update">
            UPDATE customers
            SET first_name=#{firstName},
                last_name=#{lastName}
            WHERE id = #{id}
        </update>
    
        <select id="findById" resultType="customers">
            SELECT *
            FROM customers
            WHERE id = #{id}
        </select>
    
        <select id="findAll" resultType="customers">
            SELECT *
            FROM customers
        </select>
    </mapper>

     

    @SpringBootTest
    class CustomerXmlMapperTest {
    
        @Autowired
        JdbcTemplate jdbcTemplate;
    
        @Autowired
        CustomerXmlMapper customerXmlMapper;
    
        @BeforeEach
        void setUp() {
            jdbcTemplate.execute("DROP TABLE customers IF EXISTS");
            jdbcTemplate.execute("CREATE TABLE customers(" +
                    "id SERIAL, first_name VARCHAR(255), last_name VARCHAR(255))");
        }
    
        @AfterEach
        void tearDown() {
            jdbcTemplate.execute("TRUNCATE TABLE customers");
        }
    
    
        @Test
        void 고객정보가_저장되는지_확인한다() {
            // Given
            Customer customer = new Customer(1L, "honggu", "kang");
    
            // When
            customerXmlMapper.save(customer);
    
            // Then
            Customer guppy = customerXmlMapper.findById(1);
            assertThat(guppy.getFirstName()).isEqualTo("honggu");
        }
    }
    • 자바에서 쿼리를 쓰려면 String을 이용해야하지만, 구문 재활용이 가능하다.
    • xml 방식의 경우 인터페이스와 정의한 xmlMapper가 매핑됨
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.0</version>
    </dependency>

     

    JPA (Object Relation Mapper - ORM)

    자바 객체와 RDB의 테이블을 매핑을 시켜준다. 자바 객체의 변경이 있을 경우 RDB 테이블에도 변경이 이루어져 RDB의 테이블을 자바의 객체처럼 다룰 수 있게 해준다. 자바 객체와 RDB에 존재하는 패러다임, 브리지를 사용했다.

    • 생산성 증진
      • SQL에 의존적인 개발에서 탈피하여, 객체 중심으로 생산적인 개발이 가능하다. 
      • 객체 지향 프로그래밍(OOP)을 적극적으로 활용할 수 있다.
    • 객체와 관계형 테이블의 패러다임 불일치를 해결
      • 객체 지향 프로그래밍은 추상화, 캡슐화, 상속, 다형성 등을 제공한다.
      • 관계형 데이터베이스는 데이터 중심으로 구조화되어있으며, OOP의 특징을 지원하지 않는다.
    @SpringBootTest
    @Transactional
    class CustomerRepositoryTest {
    
        @Autowired
        CustomerRepository customerRepository;
    
        @AfterEach
        void tearDown() {
            customerRepository.deleteAll();
        }
    
        @Test
        void 고객정보가_저장되는지_확인한다() {
            // Given
            Customer customer = new Customer();
            customer.setId(1L);
            customer.setFirstName("honggu");
            customer.setLastName("kang");
    
            // When
            customerRepository.save(customer); // INSERT INTO ..
    
            // Then
            Customer selectedEntity = customerRepository.findById(1L).get(); // SELECT * FROM ..
            assertThat(selectedEntity.getId()).isEqualTo(1L);
            assertThat(selectedEntity.getFirstName()).isEqualTo(customer.getFirstName());
        }
    
        @Test
        void 고객정보가_수정되는지_확인한다() {
            // Given
            Customer customer = new Customer();
            customer.setId(1L);
            customer.setFirstName("honggu");
            customer.setLastName("kang");
            Customer entity = customerRepository.save(customer);
    
            // When
            entity.setFirstName("guppy");
            entity.setLastName("hong");
    
            // Then
            Customer selectedEntity = customerRepository.findById(1L).get();
            assertThat(selectedEntity.getId()).isEqualTo(1L);
            assertThat(selectedEntity.getFirstName()).isEqualTo(entity.getFirstName());
            assertThat(selectedEntity.getLastName()).isEqualTo(entity.getLastName());
        }
    
        @Test
        void 단건조회를_확인한다() {
            // Given
            Customer customer = new Customer();
            customer.setId(1L);
            customer.setFirstName("honggu");
            customer.setLastName("kang");
            customerRepository.save(customer);
    
            // When
            Customer selected = customerRepository.findById(customer.getId()).get();
    
            // Then
            assertThat(customer.getId()).isEqualTo(selected.getId());
        }
    
        @Test
        void 리스트조회를_확인한다() {
            // Given
            Customer customer1 = new Customer();
            customer1.setId(1L);
            customer1.setFirstName("honggu");
            customer1.setLastName("kang");
    
            Customer customer2 = new Customer();
            customer2.setId(2L);
            customer2.setFirstName("guppy");
            customer2.setLastName("hong");
    
    
    
            customerRepository.saveAll(Lists.newArrayList(customer1, customer2));
    
            // When
            List<Customer> selectedCustomers = customerRepository.findAll();
    
            // Then
            assertThat(selectedCustomers.size()).isEqualTo(2);
        }
    }
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

     

    댓글

Designed by Tistory.