[7/24 TIL] JPA(RDB 접근 방법 4가지, JPA 관련 Bean 설정)

yumyeonghan·2023년 7월 24일
0

🍃프로그래머스 백엔드 데브코스 4기 교육과정을 듣고 정리한 글입니다.🍃

RDB 접근 방법 4가지

JDBC

그림 출처

  • 자바 애플리케이션은 JDBC API를 이용해서 DB와 통신한다.
@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 users IF EXISTS";
    static final String CREATE_TABLE_SQL = "CREATE TABLE users(id SERIAL, first_name VARCHAR(255), last_name VARCHAR(255))";
    static final String INSERT_SQL = "INSERT INTO users (id, first_name, last_name) VALUES(1, 'myeonghan', 'yu')";

    @Test
    void jdbc_sample() {
        try {
            Class.forName(JDBC_DRIVER);

            Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);
            Statement statement = connection.createStatement();

            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 users WHERE id = 1");

            while(resultSet.next()) {
                log.info(resultSet.getString("first_name"));
            }

            statement.close();
            connection.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 처리해야 할 Exception이 많다.
  • 쿼리 실행 전, DriverManager를 통해 Connection을 가져온다.
  • Statement를 이용해서 쿼리를 실행한다.
  • ResultSet으로 조회 결과를 받아 사용한다.
  • 다 사용한 Statement, Connection을 반납한다.
  • 위와 같이 공통적인 작업이 반복된다.

JDBC Template

@Slf4j
@SpringBootTest
public class JDBCTemplateTest {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    // 데이터베이스 결과를 매핑할 클래스 (DTO)
    private static class User {
        private Long id;
        private String firstName;
        private String lastName;

        // 생성자, Getter, Setter 생략
    }

    @Test
    void jdbc_template_sample() {
        // 테이블 생성
        jdbcTemplate.execute("DROP TABLE IF EXISTS users");
        jdbcTemplate.execute("CREATE TABLE users(id SERIAL, first_name VARCHAR(255), last_name VARCHAR(255))");

        // 데이터 삽입
        jdbcTemplate.execute("INSERT INTO users (id, first_name, last_name) VALUES(1, 'myeonghan', 'yu')");

        // 단일 값 조회 및 로깅 (RowMapper 활용)
        User user = jdbcTemplate.queryForObject(
                "SELECT id, first_name, last_name FROM users WHERE id = 1",
                (resultSet, rowNum) -> {
                    User u = new User();
                    u.setId(resultSet.getLong("id"));
                    u.setFirstName(resultSet.getString("first_name"));
                    u.setLastName(resultSet.getString("last_name"));
                    return u;
                }
        );

        log.info("User: {}", user);
    }
}
  • 기존 JDBC를 이용할때의 반복적인 작업을 생략 가능하다.
  • 하지만 자바 코드상에 String 타입으로 쿼리문을 직접 다루기 때문에 유지보수가 힘들다.

Mybatis (QueryMapper)

# yml 파일 설정
mybatis:
  type-aliases-package: com.example.model # User 클래스의 패키지 경로
  mapper-locations: classpath:mapper/*Mapper.xml # MyBatis XML 매퍼 파일 경로
  • yml 파일에 Mybatis 설정을 한다.
//xml 맵퍼
@Mapper
public interface UserXmlMapper {
    void save(User user);
    void update(User user);
    User findById(long id);
    List<User> findAll();
}
  • xml 파일과 매핑될 인터페이스를 구현한다.
<!-- 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">
<mapper namespace="com.example.repository.UserXmlMapper">
    <insert id="save">
        INSERT INTO users (id, first_name, last_name)
        VALUES (#{id}, #{firstName}, #{lastName})
    </insert>

    <update id="update">
        UPDATE users
        SET first_name=#{firstName},
            last_name=#{lastName}
        WHERE id = #{id}
    </update>

    <select id="findById" resultType="User">
        SELECT *
        FROM users
        WHERE id = #{id}
    </select>

    <select id="findAll" resultType="User">
        SELECT *
        FROM users
    </select>
</mapper>
  • xml 설정을 통해 쿼리를 작성한다.
  • 쿼리를 User POJO의 필드 값을 가져와서 생성한다.
@Mapper
public interface UserAnnotationMapper {

    @Insert("INSERT INTO users (id, first_name, last_name) VALUES(#{id}, #{firstName}, #{lastName})")
    void save(User user);

    @Update("UPDATE users SET first_name=#{firstName}, last_name=#{lastName} WHERE id=#{id}")
    void update(User user);

    @Select("SELECT * FROM users WHERE id = #{id}")
    User findById(@Param("id") long id);

    @Select("SELECT * FROM users")
    List<User> findAll();
}
  • 참고로 xml이 아닌, 어노테이션을 활용해서 사용할 수 있다.
//Mybatis 사용
@Slf4j
@SpringBootTest
public class MyBatisTest {

    @Autowired
    private UserXmlMapper userXmlMapper;

    @Test
    void mybatis_test() {
        // 데이터 삽입
        User user = new User();
        user.setId(1L);
        user.setFirstName("myeonghan");
        user.setLastName("yu");
        userXmlMapper.save(user);

        // 데이터 업데이트
        user.setFirstName("update");
        userXmlMapper.update(user);

        // 모든 데이터 조회
        List<User> users = userXmlMapper.findAll();
        for (User u : users) {
            log.info("User: {}", u);
        }

        // ID로 데이터 조회
        User foundUser = userXmlMapper.findById(1L);
        log.info("Found User: {}", foundUser);
    }
}
  • JDBC Template와 달리, 쿼리문을 자바 코드와 독립적으로 관리하기 때문에 유지보수가 용이하다.

앞서 다룬 3가지 방법(JDBC, JDBC Template, Mybatis)들은 한계가 있다.

  • 자바 객체와 RDB 테이블이 각각 갖고 있는 패러다임에 대한 불일치가 발생
  • 패러다임이란 자바 또는 RDB같은 어떠한 분야에 사용되는 사고방식이나 모델을 의미
  • 자바 패러다임은 객체지향 프로그래밍을 기반으로 함
    • 클래스와 객체를 중심으로 코드를 구성하며, 클래스는 데이터와 해당 데이터를 처리하는 메서드를 포함
    • 상속, 캡슐화, 다형성 등의 객체지향 개념을 활용
  • RDB 패러다임은 관계형 데이터베이스 관리 시스템을 기반으로 함
    • 데이터는 행과 열로 구성된 테이블에 저장되며, 쿼리를 통해 작업을 수행함
    • 객체지향 개념을 사용하지 않음

JPA (Object Relation Mapper, ORM)

@Getter
@Setter
@Entity
public class User {
    @Id
    private Long id;
    private String firstName;
    private String lastName;
    
    //따로 생성자를 만들경우 반드시 기본 생성자를 정의해야한다. 
}
  • Entity를 설정한다.
  • @Id로 Pk값 필드를 명시한다.
  • Jpa는 기본적으로 Entity의 getter, setter, 기본 생성자로 동작한다.
public interface UserJpaRepository extends JpaRepository<User, Long> {
}
  • 엔티티와 맵핑되는 JpaRepository 인터페이스를 사용한다.
  • 제네릭 타입으로 엔티티 타입과, pk값 타입을 설정해준다.
//JPA 사용
@Slf4j
@SpringBootTest
@Transactional
public class JpaTest {

    @Autowired
    private UserJpaRepository userJpaRepository;

    @Test
    void jpa_test() {
        // 데이터 삽입
        User user = new User();
        user.setId(1L);
        user.setFirstName("myeonghan");
        user.setLastName("yu");
        userJpaRepository.save(user);

        // 데이터 업데이트
        user.setFirstName("update");
        userJpaRepository.save(user);

        // 모든 데이터 조회
        List<User> users = userJpaRepository.findAll();
        for (User u : users) {
            log.info("User: {}", u);
        }

        // ID로 데이터 조회
        User foundUser = userJpaRepository.findById(1L).orElse(null);
        log.info("Found User: {}", foundUser);
    }
}
  • RDB 테이블을 마치 자바 객체처럼 다룰수 있다.
  • SQL에 의존적인 개발이 아닌, 객체 중심으로 생산적인 개발이 가능하다.
  • 따라서 객체와 RDB 테이블의 패러다임 불일치 문제가 해결된다.

JPA 관련 Bean 설정

DataSource

	@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;
    }
  • 사용할 DataSource를 설정해준다.

JpaVendorAdapter

	@Bean
    public JpaVendorAdapter jpaVendorAdapter(JpaProperties jpaProperties) {
        AbstractJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
        adapter.setShowSql(jpaProperties.isShowSql());
        adapter.setDatabasePlatform(jpaProperties.getDatabasePlatform());
        adapter.setGenerateDdl(jpaProperties.isGenerateDdl());
        return adapter;
    }
  • 인터페이스인 JPA의 구현체를 등록해준다.
  • 구현체는 대부분 하이버네이트를 사용한다.
  • JPA의 properties를 설정해준다.

EntityManagerFactory

	@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;
    }
  • RDB 테이블과 맵핑될 엔티티를 관리하는 객체가 EntityManager이다.
  • 이러한 EntityManager를 만드는 객체가 EntityManagerFactory이고, 이를 빈으로 등록한다.
  • 이때 DataSource, JpaVendorAdapter, JpaProperties를 사용한다.

TransactionManager

	@Bean
    public PlatformTransactionManager transactionManager(LocalContainerEntityManagerFactoryBean entityManagerFactory) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory.getObject());

        return transactionManager;
    }
  • 만들어진 EntityManagerFactory 빈을 주입받아 TransactionManager 빈을 등록한다.
  • TransactionManager 빈은 @Transactional와 함께 사용되어 트랜잭션의 시작과 종료, 롤백, 커밋을 자동으로 처리한다.
  • 즉, RDB 트랜잭션을 관리해준다.
  • 참고로 트랜잭션 안에서, EntityManager에 의해 Entity의 라이프 사이클이 관리되며, 변경 감지등의 작업을 수행한다.

Spring Boot의 auto-configuration은 JPA를 사용하기 위한 위 4가지 빈들을 yml 파일을 읽어 내부적으로 자동 생성해준다.

profile
웹 개발에 관심 있습니다.

0개의 댓글