학습목표
JPA 소개
- ApplicationLayer 에서 Database Layer에 접근하는 방법에 대해 학습합니다.
- JDBC Template, 쿼리매퍼 (Mybatis) 소개
- ORM (JPA)
- JPA란 무엇인지 학습합니다.
- JPA의 필요성에 대해 학습합니다.
JPA 프로젝트 시작하기 (IntelliJ + Maven)
- JPA 실습을 위한 환경을 세팅합니다.
Spring Frameworks을 활용한 데이터 레이어(RDB) 접근 방법
JDBC
자바 어플레키에션은 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
획득Statement
를 이용한 질의ResultSet
을 이용한 질의결과 사용Statement
,Connection
반납
JDBC Template
JDBC Template를 이용해서, 데이터 계층에 접근이 가능합니다. 기존 JDBC를 이용했을때의 반복적인 작업을 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; } }
Mybatis (QueryMapper)
JDBC 의 반복적인 작업을 쿼리매퍼인 Mybatis가 대신 수행해줍니다. 자바 코드와 쿼리를 분리해줍니다. 쿼리 수정으로 자바 코드 수정 이나 컴파일 하는 작업을 하지 않아도 됩니다.
(자바에서 쿼리를 쓰려면 String을 이용해야하지만, 구문 재활용이 가능하다.)
// 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>
JPA (Object Relation Mapper - ORM)
@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); } }
JPA를 사용해야하는 이유
- 생산성 증진
- SQL에 의존적인 개발에서 탈피하여, 객체중심으로 생산적인 개발이 가능하다.
- 객체와 관계형테이블의 패러다임 불일치
- 객체지향 프로그래밍은 추상화, 캡슐화, 상속, 다형성 등을 제공한다.
- 관계형 데이터베이스 데이터 중심으로 구조화 되어있으며, OOP의 특징을 지원하지 않는다.
JPA 프로젝트 세팅
// POM.xml <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
// JAVA configuration @Configuration @EnableJpaRepositories(basePackages = "com.kdt.lecture.domain") public class DataSourceConfig { @Bean public DataSource dataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("org.h2.Driver"); dataSource.setUrl("jdbc:h2:~/test"); dataSource.setUsername("sa"); dataSource.setPassword(""); return dataSource; } @Bean public JpaVendorAdapter jpaVendorAdapter(JpaProperties jpaProperties) { AbstractJpaVendorAdapter adapter = new HibernateJpaVendorAdapter(); adapter.setShowSql(jpaProperties.isShowSql()); adapter.setDatabasePlatform(jpaProperties.getDatabasePlatform()); adapter.setGenerateDdl(jpaProperties.isGenerateDdl()); return adapter; } @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, JpaVendorAdapter jpaVendorAdapter, JpaProperties jpaProperties) { LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setDataSource(dataSource); em.setPackagesToScan("com.kdt.lecture.domain"); em.setJpaVendorAdapter(jpaVendorAdapter); Properties properties = new Properties(); properties.putAll(jpaProperties.getProperties()); em.setJpaProperties(properties); return em; } @Bean public PlatformTransactionManager transactionManager(LocalContainerEntityManagerFactoryBean entityManagerFactory) { JpaTransactionManager transactionManager = new JpaTransactionManager(); transactionManager.setEntityManagerFactory(entityManagerFactory.getObject()); return transactionManager; } }
과제
JPA 프로젝트를 세팅해본다.
세팅한 프로젝트를 이용해서 단일 엔티티를 이용한 CRUD를 구현한다.
- 고객(Customer) 엔티티는 ID(PK), 이름, 성을 가진다.
- 고객엔티티를 이용한 CRUD를 구현한다.