본문 바로가기
2023 활동 - 4학년/[1월 ~ 4월] sw 아카데미 백엔드 과정

[2023.03.09 / CNU SW 아카데미] SpringBoot Part2 D-23

by 은행장 노씨 2023. 3. 9.

String JDBC(1)

이번에는 스프링에서 JDBC를 어떻게 만드는 지 살펴볼 것이다. 

  • 매번 커넥션을 생성하고 close 한다. => 많은 리소스가 소비
  • Connection Pool
    - DataSource를 이용해서 Connection을 가져올 수 있다.
    - 풀에서 커넥션을 썼다가 반납하는 개념이다. 

출처 : 프로그래머스 캠퍼스

 

HikariCP

: 매우 가볍고 매우 빠른 JDBC 풀이다.

- 상당히 빠르고 안정적이다.  

// pom.xml
<dependency>
	<groupId> org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

구조

- database 속성 이름을 지을 때는 _ 를 많이 쓰고

- 자바나 엔티티 클래스 필드에서는 카멜 케이스를 많이 쓴다. 

 

처음 클래스를 만들 때, final을 고민해서 붙이는 것이 중요하다. 

//Customer.java

public class Customer {
	private final UUID customerId;
    private String name;
    private final String email;
    private LocalDateTime lastLoginAt;
    private final LocalDateTime createdAt;
    
    public Customer(UUID customerId, String email, LocalDateTime createdAt) {
    	this.customerId = customerId;
        this.email = email;
        this.createdAt = createdAt;
    }
    
    public Customer(UUID customerId, String name, String email, LocalDateTime lastLoginAt, LocalDateTime createdAt) {
    	this.customerId = customerId;
        this.name = name;
        this.email = email;
        this.lastLoginAt= lastLoginAt;
        this.createdAt = createdAt;
    }
    
    
}

 

필수적인 것과, null 이 들어갈 수 있는 것을 따로 생각하여 생성자를 만든다. 

- setter를 무분별하게 쓰면 안된다. 실제 엔티티 상태를 바꾸는 것이다. 

> 이름을 바꾸는 메소드
- setName (x)
- changeName (o)
// Customer.java

public void changeName(String name) {
	if (name.isBlank()) {
    	throw new RuntimeException("error : Name should not be blank");
    }
    this.name = name;
}

- validate을 입력시에 생성해준다. 

 

package : customer > Customer, CustomerRepository

// CustomerRepository.java

public interface CustomerRepository {
    Customer insert(Customer customer);
    Customer update(Customer customer);
    // insert, update를 묶어서 save로 처리하기도 한다.

    List<Customer> findAll();
    Optional<Customer> findById(UUID customerId);
    Optional<Customer> findByName(String name);
    Optional<Customer> findByEmail(String email);

    void deleteAll();
}

 


JdbcTemplate

- 커넥션을 맺는 부분

- 예외 처리

위의 두 부분이 계속 반복된다. 

 

데이터를 가지고 와서 원하는 부분과 매핑하는 것도 비슷하다. 

jdbc 템플릿은 기본적으로 데이터 소스만 있으면 된다. 

 

// CustomerJdbcRepository.java

public class CustomerJdbcRepository implements CustomerRepository{
    private static final Logger logger = LoggerFactory.getLogger(CustomerJdbcRepository.class);
    private final DataSource dataSource;
    private final JdbcTemplate jdbcTemplate;
    
    private static final RowMapper<Customer> customerRowMapper = (resultSet, i) -> {
        var customerName = resultSet.getString("name");
        var email = resultSet.getString("email");
        var customerId = toUUID(resultSet.getBytes("customer_id"));
        var lastLoginAt = resultSet.getTimestamp("last_login_at").toLocalDateTime() == null ?
                resultSet.getTimestamp("last_login_at").toLocalDateTime() : null;
        var createdAt = resultSet.getTimestamp("create_at").toLocalDateTime();
        return new Customer(customerId, customerName, email, lastLoginAt, createdAt);
    };

    public CustomerJdbcRepository(DataSource dataSource, JdbcTemplate jdbcTemplate) {
        this.dataSource = dataSource;
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public List<Customer> findAll() {
        return jdbcTemplate.query("select * from customers", customerRowMapper);
    }
    
    private static final UUID toUUID(byte[] bytes) {
        var byteBuffer = ByteBuffer.wrap(bytes);
        return new UUID(byteBuffer.getLong(), byteBuffer.getLong());
    }
    //...
}

코드가 훨씬 깔끔해졌다. 

@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
    return new JdbcTemplate(dataSource);
}

아래의 jdbc 템플릿을 정리해야 한다.

 

jdbcTemplate의 query가 어떻게 구현되어 있는지 살펴보자. callback

query를 사용하면 list가 반환된다. 

 

// CustomerJdbcRepository.java

@Override
    public Optional<Customer> findById(UUID customerId)  {
        return Optional.ofNullable(jdbcTemplate.queryForObject("select * from customers WHERE customer_id = UUID_to_BIN(?)", customerRowMapper, customerId.toString()));
    }

 

count 함수를 만들어보자. 

// CustomerJdbcRepository.java

@Override
    public int count() {
        return jdbcTemplate.queryForObject("select count(*) from customers", Integer.class);
    }

 

JDBC Template - DML

- DML 부분도 바꿔보자. 

- jdbcTemplate.update() 를 사용해야 한다. 

 

- insert, update, delete all

// CustomerJdbcRepository.java

	@Override
    public Customer insert(Customer customer) {
        var update = jdbcTemplate.update("INSERT INTO customers(customer_id, name, email, created_at) VALUES (UUID_TO_BIN(?), ?, ?, ?)",
                customer.getCustomerId().toString().getBytes(),
                customer.getName(),
                customer.getEmail(),
                Timestamp.valueOf(customer.getCreateAt()));

        if (update != 1) {
            throw new RuntimeException("Nothing was inserted");
        }
        return customer;
    }
    
    @Override
    public Customer update(Customer customer) {
        var update = jdbcTemplate.update("UPDATE customers SET name = ?, email = ?, last_login_at = ? WHERE  customer_id = UUID_TO_BIN(?)",
                customer.getName(),
                customer.getEmail(),
                customer.getLastLoginAt() != null ? Timestamp.valueOf(customer.getLastLoginAt()) : null;
                customer.getCustomerId().toString().getBytes(),
        if (update != 1) {
            throw new RuntimeException("Nothing was updated");
        }
        return customer;
    }
    
    @Override
    public void deleteAll(){
        jdbcTemplate.update("DELETE FROM customers");
    }

코드가 훨씬 짧아졌다. 

디버그를 걸어놓고 내부적으로 어떻게 동작하는지 한번 해보자.