Backend/Spring
[Spring] Spring Frameworks를 활용한 데이터 레이어(RDB) 접근 방법
mirae.kwak
2022. 8. 19. 12:15
728x90
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>
728x90