🍃프로그래머스 백엔드 데브코스 4기 교육과정을 듣고 정리한 글입니다.🍃
Embedded Database
- 애플리케이션과 함께 동작하는 데이터베이스 시스템
- 사용자가 별도의 데이터베이스 서버를 설치하거나 설정할 필요가 없음
- 외부 환경이 테스트의 성공 여부를 결정하는것을 방지
- 테스트는 실제 데이터베이스 말고, 임베디드 데이터베이스를 사용해서 작성하는 것이 CI 관점에서 좋음
- H2를 사용하거나, 테스트 로직에 특정 데이터베이스 방언이 많다면 Embedded Mysql같이 특정 데이터베이스의 임베디드 버전을 사용
NamedParameterJdbcTemplate
- 기존 JdbcTemplate의 플레이스 홀더("?")를 이름 기반으로 설정 가능
- 바인딩 순서를 신경쓰지 않아도 되는 이름 기반의 파라미터 바인딩 지원
예시 코드
public class NamedParameterJDBCTemplateCrudExample {
public static void main(String[] args) {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
return;
}
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/sampledb");
dataSource.setUsername("username");
dataSource.setPassword("password");
NamedParameterJdbcTemplate jdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
UUID id = UUID.randomUUID();
String name = "유명한";
int age = 25;
insertData(jdbcTemplate, id, name, age);
UUID searchId = UUID.fromString("your-id");
UserData userData = retrieveDataById(jdbcTemplate, searchId);
if (userData != null) {
System.out.println("Data Found:");
System.out.println("ID: " + userData.getId() + ", Name: " + userData.getName() + ", Age: " + userData.getAge());
} else {
System.out.println("Data Not Found");
}
System.out.println("All Data:");
retrieveData(jdbcTemplate);
String newName = "김명한";
int newAge = 30;
updateData(jdbcTemplate, id, newName, newAge);
deleteData(jdbcTemplate, id);
retrieveData(jdbcTemplate);
}
private static void insertData(NamedParameterJdbcTemplate jdbcTemplate, UUID id, String name, int age) {
String sql = "INSERT INTO users (id, name, age) VALUES (:id, :name, :age)";
Map<String, Object> params = new HashMap<>();
params.put("id", toBytes(id));
params.put("name", name);
params.put("age", age);
jdbcTemplate.update(sql, params);
}
private static UserData retrieveDataById(NamedParameterJdbcTemplate jdbcTemplate, UUID id) {
String sql = "SELECT * FROM users WHERE id = :id";
Map<String, Object> params = new HashMap<>();
params.put("id", toBytes(id));
UserData userData = jdbcTemplate.queryForObject(sql, params, (ResultSet rs, int rowNum) -> {
UUID userId = toUUID(rs.getBytes("id"));
String name = rs.getString("name");
int age = rs.getInt("age");
return new UserData(userId, name, age);
});
return userData;
}
private static void retrieveData(NamedParameterJdbcTemplate jdbcTemplate) {
String sql = "SELECT * FROM users";
jdbcTemplate.query(sql, (ResultSet rs) -> {
while (rs.next()) {
UUID id = toUUID(rs.getBytes("id"));
String name = rs.getString("name");
int age = rs.getInt("age");
}
});
}
private static void updateData(NamedParameterJdbcTemplate jdbcTemplate, UUID id, String name, int age) {
String sql = "UPDATE users SET name = :name, age = :age WHERE id = :id";
Map<String, Object> params = new HashMap<>();
params.put("id", toBytes(id));
params.put("name", name);
params.put("age", age);
jdbcTemplate.update(sql, params);
}
private static void deleteData(NamedParameterJdbcTemplate jdbcTemplate, UUID id) {
String sql = "DELETE FROM users WHERE id = :id";
Map<String, Object> params = new HashMap<>();
params.put("id", toBytes(id));
jdbcTemplate.update(sql, params);
}
private static UUID toUUID(byte[] bytes) {
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
return new UUID(byteBuffer.getLong(), byteBuffer.getLong());
}
private static byte[] toBytes(UUID uuid) {
ByteBuffer byteBuffer = ByteBuffer.allocate(16);
byteBuffer.putLong(uuid.getMostSignificantBits());
byteBuffer.putLong(uuid.getLeastSignificantBits());
return byteBuffer.array();
}
}
DataAccessException
그림 출처
- 스프링 프레임워크에서 제공하며, 데이터 접근중에 발생할 수 있는 예외이고, RuntimeException을 상속 받음
- 데이터베이스 액세스 작업 중 발생하는 SQLException을 래핑하여 전달해서 데이터베이스와 관련된 예외 처리를 표준화 함
- 다양한 데이터 액세스 기술(JDBC, JPA, Hibernate)이나 DBMS(Oracle, MySQL)에서 발생하는 예외들을 DataAccessException 하위 예외로 두어 쉽게 처리할 수 있음
트랜잭션
그림 출처
- 원자성 (Atomicity): 모든 작업은 전부 수행되거나 전혀 수행되지 않아야 함
- 일관성 (Consistency): 트랜잭션 전후에 데이터베이스가 일관된 상태를 유지해야 함
- 격리성 (Isolation): 하나의 트랜잭션은 다른 트랜잭션의 연산에 영향을 미치지 않아야 함
- 지속성 (Durability): 트랜잭션을 성공적으로 완료한 후에는 해당 트랜잭션에 의한 모든 변경이 영구적으로 저장되어야 함
예시 코드
public void insertDataWithTransaction(UUID id, String name, int age) {
String sql = "INSERT INTO users (id, name, age) VALUES (:id, :name, :age)";
Map<String, Object> params = new HashMap<>();
params.put("id", toBytes(id));
params.put("name", name);
params.put("age", age);
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
try {
jdbcTemplate.update(sql, params);
transactionManager.commit(transactionStatus);
} catch (Exception ex) {
transactionManager.rollback(transactionStatus);
throw ex;
}
}
- 직접 트랜잭션 매니저를 통해 트랜잭션 정의
- try-catch 블록을 사용하여 트랜잭션의 성공 여부에 따라 커밋 또는 롤백을 수행