- 문장 그대로 Java 앱과 DB 를 연결시켜주기 위해 만들어진 기술이다.
- 그렇기 때문에 JPA 도 이 기술을 사용하여 구현되어 있다.
- JDBC Driver 는 여러타입의 DB 와 연결할 수 있는 기능을 제공한다.
JDBC Driver Manager
는 런타임 시점에
-Connection(연결)
을 생성하여 쿼리를 요청할 수 있는 상태를 만들어주고
-Statement(상태)
를 생성하여 쿼리를 요청하게 해주고
-ResultSet(결과셋)
을 생성해 쿼리 결과를 받아올 수 있게 해준다.
- 꼭 사용후에는 각각close()
를 호출해서 자원 해제를 시켜줘야 한다 (자원 관리)
- JDBC를 통해서 INSERT와 SELECT를 해보았다 (JPA를 사용했을때 보다 상당히 귀찮다)
public class JDBCTest { @Test @DisplayName("JDBC DB 연결 실습") void jdbcTest() throws SQLException { // given String url = "jdbc:postgresql://localhost:5432/messenger"; String username = "tablenext"; String password = "pass"; // when try (Connection connection = DriverManager.getConnection(url, username, password)) { try { String creatSql = "CREATE TABLE ACCOUNT (id SERIAL PRIMARY KEY, username varchar(255), password varchar(255))"; try (PreparedStatement statement = connection.prepareStatement(creatSql)) { statement.execute(); } } catch (SQLException e) { if (e.getMessage().equals("ERROR: relation \"account\" already exists")) { System.out.println("ACCOUNT 테이블이 이미 존재합니다."); } else { throw new RuntimeException(); } } } // then // DB 확인 } @Test @DisplayName("JDBC 삽입/조회 실습") void jdbcInsertSelectTest() throws SQLException { // given String url = "jdbc:postgresql://localhost:5432/messenger"; String username = "tablenext"; String password = "pass"; // when try (Connection connection = DriverManager.getConnection(url, username, password)) { System.out.println("Connection created: " + connection); String insertSql = "INSERT INTO ACCOUNT (id, username, password) VALUES ((SELECT coalesce(MAX(ID), 0) + 1 FROM ACCOUNT A), 'user1', 'pass1')"; try (PreparedStatement statement = connection.prepareStatement(insertSql)) { statement.execute(); } // then String selectSql = "SELECT * FROM ACCOUNT"; try (PreparedStatement statement = connection.prepareStatement(selectSql)) { var rs = statement.executeQuery(); while (rs.next()) { System.out.printf("%d, %s, %s", rs.getInt("id"), rs.getString("username"), rs.getString("password")); } } } } @Test @DisplayName("JDBC DAO 삽입/조회 실습") void jdbcDAOInsertSelectTest() throws SQLException { // given AccountDAO accountDAO = new AccountDAO(); // when var id = accountDAO.insertAccount(new AccountVO("new user", "new password")); // then var account = accountDAO.selectAccount(id); assert account.getUsername().equals("new user"); } }
- JDBC 로 직접 SQL을 작성했을때의 문제
- SQL 쿼리 요청시 중복 코드 발생
- DB별 예외에 대한 구분 없이 Checked Exception (SQL Exception) 처리
- Connection, Statement 등.. 자원 관리를 따로 해줘야함
- 안해주면 메모리 꽉차서 서버가 죽음
- 이 문제 해결을 위해 처음으로
Persistence Framework
등장
Persistence Framework
는 2가지가 있다.
- SQL Mapper :
JDBC Template
,MyBatis
👈 요게 먼저나옴- ORM :
JPA
,Hibernate
- SQL Mapper (QueryMapper)
- SQL ↔ Object
- SQL 문과 객체(Object)의 필드를 매핑하여 데이터를 객채화
- SQL Mapper 첫번째 주자로
JDBCTemplate
탄생
- 쿼리 수행 결과와 객채 필드 매핑
- RowMapper 로 응답필드 매핑코드 재사용
- Connection, Statement, ResultSet 반복적 처리 대신 해줌
- But, 결과값을 객체 인스턴스에 매핑하는데 여전히 많은 코드가 필요함
@JdbcTest // Jdbc Slice Test @AutoConfigureTestDatabase(replace = Replace.NONE) // 테스트용 DB 쓰지 않도록 @Rollback(value = false) // Transactional 에 있는 테스트 변경은 기본적으론 롤백 하도록 되어있다. public class JDBCTemplateTest { @Autowired private JdbcTemplate jdbcTemplate; @Test @DisplayName("SQL Mapper - JDBC Template 실습") void sqlMapper_JDBCTemplateTest() { // given var accountTemplateDAO = new AccountTemplateDAO(jdbcTemplate); // when var id = accountTemplateDAO.insertAccount(new AccountVO("new user2", "new password2")); // then var account = accountTemplateDAO.selectAccount(id); assert account.getUsername().equals("new user2"); } }
- AccountTemplateDAO (Data Access Object)
package me.whitebear.jpastudy.jdbc.template; import me.whitebear.jpastudy.jdbc.vo.AccountVO; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; @Repository // Java 패키지에 있었다면 JdbcTemplate 생성자 주입받음 public class AccountTemplateDAO { private final JdbcTemplate jdbcTemplate; public AccountTemplateDAO(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } //SQL 관련 명령어 private final String ACCOUNT_INSERT = "INSERT INTO account(ID, USERNAME, PASSWORD) " + "VALUES((SELECT coalesce(MAX(ID), 0) + 1 FROM ACCOUNT A), ?, ?)"; // coalesce 은 Postgresql 용 IFNULL private final String ACCOUNT_GET = "SELECT * FROM account WHERE ID = ?"; //CRUD 기능의 메소드 구현 //계정 등록 public Integer insertAccount(AccountVO vo) { // 기본 코드 (응답값은 생성된 갯수) // return jdbcTemplate.update(ACCOUNT_INSERT, vo.getUsername(), vo.getPassword()); // id 값을 받아오기 위한 코드 KeyHolder keyHolder = new GeneratedKeyHolder(); jdbcTemplate.update(connection -> { var ps = connection .prepareStatement(ACCOUNT_INSERT, new String[]{"id"}); ps.setString(1, vo.getUsername()); ps.setString(2, vo.getPassword()); return ps; }, keyHolder); return (Integer) keyHolder.getKey(); } //계정 조회 public AccountVO selectAccount(Integer id) { return jdbcTemplate.queryForObject(ACCOUNT_GET, new AccountRowMapper(), id); } }
- SQL Mapper 두번째 주자로
MyBatis
탄생
- 반복적인 JDBC 프로그래밍을 단순화
- SQL 쿼리들을 XML 파일에 작성하여 코드와 SQL 을 분리!
- But, 결국 SQL을 직접 작성하는것은 피곤하다…(DB 기능에 종속적)
- But, 테이블마다 비슷한 CRUD 반복, DB타입 및 테이블에 종속적이다.
@SpringBootTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @Import(DBConfiguration.class) public class MyBatisTest { // Mapper 클래스를 받으려면 mapper.xml 빌드 해야하고, 그러려면 main 으로 옮겨서 해야함... @Autowired AccountMapper accountMapper; @Autowired AccountMapperV2 accountMapperV2; @Test @DisplayName("SQL Mapper - MyBatis 실습") void sqlMapper_MyBatisTest() { // given // when accountMapper.insertAccount(new AccountMyBatisVO("new user3", "new password3")); var account = accountMapper.selectAccount(1); // then assert !account.getUsername().isEmpty(); } @Test @DisplayName("SQL Mapper - MyBatis V2 실습") void sqlMapper_MyBatisV2Test() { // given // when accountMapperV2.insertAccount(new AccountMyBatisVO("new user4", "new password4")); var account = accountMapperV2.selectAccount(1); // then assert !account.getUsername().isEmpty(); } }
- AccountMapper.xml
<?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"> <!-- [템플릿 설명] - 해당 파일은 SQL 문을 작성하는 곳입니다. --> <mapper namespace="me.whitebear.jpastudy.mybatis.mapper.AccountMapper"> <select id="selectAccount" resultType="me.whitebear.jpastudy.mybatis.vo.AccountMyBatisVO"> SELECT id, username, password FROM account WHERE id = #{id} </select> <insert id="insertAccount" parameterType="me.whitebear.jpastudy.mybatis.vo.AccountMyBatisVO"> INSERT INTO account(username, password) VALUES (#{username}, #{password}); </insert> </mapper>
QueryMapper
의 DB의존성 및 중복 쿼리 문제점ORM
은 DB의 주도권을 뺏어왔다고 표현해도 과언이 아니다.ORM
은DAO
또는Mapper
를 통해서 조작하는것이 아니라 테이블을 아예 하나의 객체(Object)와 대응시켜 버린다.- 말이 쉽지….
객체지향(Object)
을관계형 데이터베이스(Relation)
에매핑(Mapping)
한다는건 정말 많은 난관이 있다.
JDBC, JDBC Template, MyBatis를 통해 평소에 JPA를 사용하여 했던 DB실습을 해보았는데,
하면 할 수 록 JPA가 굉장히 편하다는걸 느꼈다.
하지만 JPA를 사용하지 않는 기업들이 많기에..
쓰는법을 익히는 김에 등장 배경도 알아보았다.
(꼭 JPA를 사용하는 기업에 취업해야지)