๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Back-end ๋ฐ๋ธŒ์ฝ”์Šค/week 08 TIL (Jpa)

[TIL] 221205 - JPA : JPA ์†Œ๊ฐœ

by young-ji 2022. 12. 7.

SpringBoot Part4 (1) 

 

 

Spring Frameworks์„ ํ™œ์šฉํ•œ ๋ฐ์ดํ„ฐ ๋ ˆ์ด์–ด(RDB) ์ ‘๊ทผ ๋ฐฉ๋ฒ•

H2Database

: In-memory ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค ( ์„œ๋ฒ„๊ฐ€ ๋‚ด๋ ค๊ฐ€๋ฉด ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ์ „๋ถ€ ๋‚ ๋ผ๊ฐ )

: ๋ณดํ†ต ์ƒ˜ํ”Œ์ฝ”๋“œ๋‚˜ ํ…Œ์ŠคํŠธ๋ฅผ ํ• ๋•Œ ์‚ฌ์šฉ.

 

JDBC

์ž๋ฐ” ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์€ JDBC API๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ ๊ณ„์ธต๊ณผ ํ†ต์‹ ํ•œ๋‹ค.

@Slf4j
public class JDBCTest {

    // H2DB์˜ url๊ณผ default userName, pass
    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 {
            // jdbc driver ์„ค์ •
            Class.forName(JDBC_DRIVER);

            // DB ์—ฐ๊ฒฐ
            Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);
            log.info("Connection ํš๋“");

            // ํ†ต์‹ ์„ ํ•˜๊ธฐ ์œ„ํ•œ statement ๊ฐ์ฒด - ์ฟผ๋ฆฌ๋ฅผ ๋‚ ๋ฆด ์ˆ˜ ์žˆ๋‹ค.
            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 resultSet = statement.executeQuery("SELECT id, first_name, last_name FROM customers WHERE id = 1");

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

            // ์…์„ฑ๋œ connection ๋ฐ˜๋‚ฉ
            log.info("๋ฐ˜๋‚ฉ, ๋ฐ˜๋‚ฉ");
            statement.close();
            connection.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

  • JDBC template

๊ธฐ์กด JDBC๋ฅผ ์ด์šฉํ–ˆ์„๋•Œ์˜ ๋ฐ˜๋ณต์ ์ธ ์ž‘์—…์„ JDBC Template์ด ๋Œ€์‹  ์ˆ˜ํ–‰ํ•ด์ค€๋‹ค.

JDBC template์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„  SpringApplicationContext๊ฐ€ ๋– ์•ผํ•œ๋‹ค.

@Slf4j
@SpringBootTest
public class JDBCTest {

    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')";

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Test
    void jdbcTemplate_sample(){
        jdbcTemplate.update(DROP_TABLE_SQL);
        jdbcTemplate.update(CREATE_TABLE_SQL);
        log.info("CREATED TABLE USING JDBC TEMPLATE");

        jdbcTemplate.update(INSERT_SQL);
        log.info("CREATED CUSTOMER RECORD JDBC TEMPLATE");

        String fullName = jdbcTemplate.queryForObject(
                "SELECT * FROM customers WHERE id = 1",
                (resultSet,i)-> resultSet.getString("first_name")+ " "+ resultSet.getString("last_name"));
        log.info("FULL NAME : {}",fullName);
    }
}

๋‹จ์  → java code ์ƒ์— JDBC ๊ตฌ๋ฌธ์„ ์ž‘์„ฑํ•˜์—ฌ ์ฟผ๋ฆฌ ๊ด€๋ฆฌ๊ฐ€ ์–ด๋ ค์›Œ์ง„๋‹ค.

 

MyBatis

JDBC์˜ ๋ฐ˜๋ณต์ ์ธ ์ž‘์—…์„ ์ฟผ๋ฆฌ๋ฉ”ํผ์ธ Mybatis๊ฐ€ ๋Œ€์‹  ์ˆ˜ํ–‰ํ•˜๋ฉฐ ์ž๋ฐ”์ฝ”๋“œ์™€ ์ฟผ๋ฆฌ๋ฅผ ๋ถ„๋ฆฌํ•ด์ค€๋‹ค.

์ฟผ๋ฆฌ ์ˆ˜์ •์œผ๋กœ ์ž๋ฐ” ์ฝ”๋“œ๋ฅผ ์ˆ˜์ • ํ•˜๊ฑฐ๋‚˜ ์ปดํŒŒ์ผ ํ•˜๋Š” ์ž‘์—…์„ ํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค.

 

(spring-boot-starter ํŒจํ‚ค์ง€๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ auto configuration ์œผ๋กœ ์„ค์ •์ด ๊ฐ€๋Šฅ )

mybatis:
  type-aliases-package: com.kdt.lecture.repository.domain  # ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ(resultSet)๋ฅผ ์–ด๋–ค package์— mapping ํ• ์ง€
  configuration:
    map-underscore-to-camel-case: true # ์–ธ๋”๋ฐ” ํ˜•ํƒœ ํ•„๋“œ๋ฅผ -> ์นด๋ฉœ์ผ€์ด์Šค ํ˜•ํƒœ๋กœ ๋งค์นญ
    default-fetch-size: 100 # ํ•œ๋ฒˆ์— ๋ช‡๊ฐœ์”ฉ ๊ฐ€์ ธ ์˜ฌ๊ฒƒ์ธ๊ฐ€.
    default-statement-timeout: 30  # statement ๊ฐ์ฒด๊ฐ€ ํ†ต์‹ ํ•˜๋Š” time limit
  mapper-locations: classpath:mapper/*.xml # ์ฟผ๋ฆฌ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์œ„์น˜

 

์ฟผ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•

 

1. Annotiation ์ด์šฉ

// 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);
}

 

2. 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.kdt.lecture.repository.CustomerMapper">
    <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>

 

com.kdt.lecture.repository.CustomerMapper

@Mapper
public interface CustomerMapper {

    void save(Customer customer);
    Customer findById(long id);
}

 

์‚ฌ์šฉํ•˜๊ธฐ

customerMapper.save(new Customer(1L, "youngji", "jang"));
Customer customer = customerMapper.findById(1L);

⇒ RDB์™€ java ๊ฐ์ฒด๊ฐ€ ๊ฐ–๋Š” ํŒจ๋Ÿฌ๋‹ค์ž„์˜ ๋ถˆ์ผ์น˜๊ฐ€ ์ƒ๊ธฐ๊ฒŒ ๋˜๋Š”๋ฐ JPA๋ฅผ ์ด์šฉํ•ด ๋ณด์™„ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

JPA (Object Relation Mapper)

Spring :
  datasource:
    hikari:
      driver-class-name: org.h2.Driver
      url: jdbc:h2:~/test
      username: sa
      password:
  jpa:
    generate-ddl: true  #ddl option
    open-in-view: false
    show-sql: true    #sql ์ฟผ๋ฆฌ ํ™•์ธ

 

๊ฐ์ฒด ์ƒ์„ฑ

@Entity // ์‹ค์ œ RDB ํ…Œ์ด๋ธ”๊ณผ mapping์ด ๋˜๋Š” ๊ฐ์ฒด ๋ผ๋Š” ๊ฒƒ์„ ๋ช…์‹œ
@Table(name = "customer") // table ใ…‡์–ด๋…ธํ…Œ์ด์…˜์ด ์—†์œผ๋ฉด ๊ธฐ๋ณธ์ ์œผ๋กœ class ๋ช…๊ณผ ๋™์ผํ•œ ํ…Œ์ด๋ธ”์„ ์ฐพ๋Š”๋‹ค.
public class CustomerEntity {

    @Id // pk ๋ช…์‹œ
    private long id;
    private String firstName;
    private String lastName;

    //getter setter๋ฅผ ํ†ตํ•ด entity ์ž‘๋™
    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

 

 

๊ฐ์ฒด์™€ ๋งคํ•‘๋  repository interface - JpaRepository ๊ตฌํ˜„์ฒด ์ƒ์†

public interface CustomerRepository extends JpaRepository<CustomerEntity, Long> {
}

 

์‚ฌ์šฉํ•˜๊ธฐ

@Slf4j
@SpringBootTest
public class JPATest {

    @Autowired
    CustomerRepository customerRepository;

    @BeforeEach
    void setUp() {
    }

    @AfterEach
    void tearDown() {
        customerRepository.deleteAll();
    }

    @Test
    void INSERT_TEST() {

        //Given
        CustomerEntity customer = new CustomerEntity();
        customer.setId(1L);
        customer.setFirstName("youngji");
        customer.setLastName("jang");

        //When
        customerRepository.save(customer);

        //then
        CustomerEntity entity = customerRepository.findById(1L).get();
        log.info("{} {}", entity.getFirstName(), entity.getLastName());
    }

    @Test
    @Transactional // ์˜์†์„ ์ปจํ…์ŠคํŠธ ์•ˆ์—์„œ ๊ด€๋ฆฌ๋ฅผ ํ•˜๊ฒ ๋‹ค.
    void UPDATE_TEST(){
        //Given
        CustomerEntity customer = new CustomerEntity();
        customer.setId(1L);
        customer.setFirstName("youngji");
        customer.setLastName("jang");
        customerRepository.save(customer);

        //When
        CustomerEntity entity = customerRepository.findById(1L).get();

        //then
        entity.setFirstName("guppy");
        entity.setLastName("kang");
        // ๋”ฐ๋กœ ์ €์žฅ์„ ํ•˜์ง€ ์•Š์•„๋„ ์˜์†์„ ์ปจํ…์ŠคํŠธ์— ์˜ํ•ด ๋”ํ‹ฐ ์ฒดํ‚นํ•˜๊ณ  ์žˆ์–ด ๊ฐ์ง€๋ฅผ ํ•˜๊ณ  ์†์„ฑ์ด ๋ณ€๊ฒฝ๋˜๋ฉด ์ž๋™์ ์œผ๋กœ update ์ฟผ๋ฆฌ๊ฐ€ ๋‚ ๋ผ๊ฐ„๋‹ค.
        CustomerEntity updated = customerRepository.findById(1L).get();
        log.info("{} {}", updated.getFirstName(), updated.getLastName());
    }
}

: entity ๊ฐ์ฒด๋งŒ ์ˆ˜์ •ํ•˜์—ฌ๋„ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์— ์˜ํ•ด ๋ณ€๊ฒฝ์ด ๊ฐ์ง€๋˜์„œ ์ž๋™์ ์œผ๋กœ ์ฟผ๋ฆฌ๊ฐ€ ์‹คํ–‰๋œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

: ์ปฌ๋Ÿผ ์ถ”๊ฐ€๋‚˜ ๋ณ€๊ฒฝ์ด ์žˆ์„๋•Œ๋„ ๊ฐ์ฒด๋งŒ ๋ณ€๊ฒฝํ•˜๋ฉด ์ฟผ๋ฆฌ๋ฅผ ์†๋Œ€์ง€ ์•Š๊ณ  ์†์‰ฝ๊ฒŒ ํ…Œ์ด๋ธ” ๋ณ€๊ฒฝ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

 

 

JPA ์‚ฌ์šฉ ์ด์œ  (ORM ํ”„๋ ˆ์ž„์›Œํฌ → ๊ฐ์ฒด์™€ ๊ด€๊ณ„ํ˜• ํ…Œ์ด๋ธ”์„ ๋งคํ•‘ํ•ด์ค€๋‹ค.)

  1. ์ƒ์‚ฐ์„ฑ ์ฆ์ง„
    • → SQL์— ์˜์กด์ ์ธ ๊ฐœ๋ฐœ์—์„œ ํƒˆํ”ผํ•˜์—ฌ, ๊ฐ์ฒด์ค‘์‹ฌ์œผ๋กœ ์ƒ์‚ฐ์ ์ธ ๊ฐœ๋ฐœ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
  2. ๊ฐ์ฒด์™€ ๊ด€๊ณ„ํ˜• ํ…Œ์ด๋ธ”์˜ ํŒจ๋Ÿฌ๋‹ค์ž„ ๋ถˆ์ผ์น˜๋ฅผ ๋ณด์™„
    • → ๊ฐ์ฒด ์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋žจ์€ ์ถ”์ƒํ™”, ์บก์Šํ™”, ์ƒ์†, ๋‹คํ˜•์„ฑ ๋“ฑ์„ ์ œ๊ณตํ•œ๋‹ค.
    • → ๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” ๋ฐ์ดํ„ฐ ์ค‘์‹ฌ์œผ๋กœ ๊ตฌ์กฐํ™” ๋˜์–ด์žˆ์œผ๋ฉฐ, OOP์˜ ํŠน์ง•์„ ์ง€์›ํ•˜์ง€ ์•Š๋Š”๋‹ค.

 

JPA ํ”„๋กœ์ ํŠธ ์„ธํŒ…

  • configration ์„ธํŒ…

→ spring boot์˜ auto configration์œผ๋กœ ์„ค์ • ๊ฐ€๋Šฅ. ๋‚ด๋ถ€์ ์œผ๋กœ ์–ด๋–ป๊ฒŒ ์„ธํŒ… ๋˜๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ~

@Configuration
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;
    }

    // ์–ด๋–ค ๊ตฌํ˜„์ฒด๋ฅผ ์“ธ์ง€ ์„ค์ •
    // jpa ๊ตฌํ˜„์ฒด๋“ค ์ค‘์— hibernate๋ฅผ ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉํ•œ๋‹ค.
    @Bean
    public JpaVendorAdapter jpaVendorAdapter(JpaProperties jpaProperties){ //yaml์„ ํ†ตํ•ด ์„ค์ •ํ•œ JpaProperties Bean์— ์ฃผ์ž…๋จ
        AbstractJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
        jpaVendorAdapter.setShowSql(jpaProperties.isShowSql());
        jpaVendorAdapter.setDatabasePlatform(jpaProperties.getDatabasePlatform());
        jpaVendorAdapter.setGenerateDdl(jpaProperties.isGenerateDdl());

        return jpaVendorAdapter;
    }

    // entity manager factory - ํ…Œ์ด๋ธ”๊ณผ ๋งคํ•‘๋  entity๋ฅผ ๊ด€๋ฆฌ
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(DataSource dataSource,JpaVendorAdapter jpaVendorAdapter,
                                                                           JpaProperties jpaProperties) {

        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource); // ์–ด๋–ค ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค๋ฅผ ์“ธ๊ฒƒ์ธ์ง€
        em.setPackagesToScan("com.example.kdtjpapractice.domain"); // entity๋ฅผ ์–ด๋–ค ํŒจํ‚ค์ง€์— ๋‘๊ณ  ๊ด€๋ฆฌํ•  ๊ฒƒ์ธ์ง€.
        em.setJpaVendorAdapter(jpaVendorAdapter); // ์œ„์— ๋ช…์‹œํ•œ hibernate adapter๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋‹ค.

        // ์ถ”๊ฐ€ hibernate ํŠœ๋‹ - yaml์— ์„ค์ •
        Properties properties = new Properties();
        properties.putAll(jpaProperties.getProperties());
        em.setJpaProperties(properties);

        return em;
    }

    // transaction manger - RDB์˜ ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ
    @Bean
    public PlatformTransactionManager transactionManager(LocalContainerEntityManagerFactoryBean entityManagerFactoryBean){
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactoryBean.getObject());
        
        return transactionManager;
    }

}

 

yaml JpaProperties ์„ธํŒ…

Spring :
  h2:
    console:
      enabled: true
  jpa:
    generate-ddl: true  #ddl option
    hibernate:
      ddl-auto:
    database: H2
    open-in-view: false
    show-sql: true    #sql ์ฟผ๋ฆฌ ํ™•์ธ
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL5InnoDBDialect
        query.in_clause_parameter_padding: true
        hbm2ddl:
          auto: create-drop

 

 

properties.hibernate.dialect ์„ค์ •์‹œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

 

https://2dongdong.tistory.com/66

ํ•ด๋‹น ๊ตฌ๋ฌธ์„ ์ง€์šฐ๊ณ  ์–ด๋–จ dialect๋ฅผ ์‚ฌ์šฉํ•˜๋‚˜ ๋ณด์•˜๋‹ค.

 

 

 

https://github.com/jojoldu/freelec-springboot2-webservice/issues/67

์•„๋ž˜์™€ ๊ฐ™์ด ์„ค์ •ํ•˜๋‹ˆ๊นŒ ์ž˜ ๋™์ž‘ํ•œ๋‹ค.

dialect: org.hibernate.dialect.MySQL57Dialect

์ž๋™์œผ๋กœ ์„ค์ •๋˜๋Š” ๊ฒƒ๊ณผ ์–ด๋–ค ์ฐจ์ด๊ฐ€ ์žˆ๋Š”์ง€ ์•Œ์•„๋ด์•ผ ํ• ๊ฒƒ๊ฐ™๋‹ค.

 

 

 

 

์ถœ์ฒ˜ - backend dev course ๊ฐ•ํ™๊ตฌ๋‹˜

๋Œ“๊ธ€