<entry key="updateMenu">
UPDATE
tbl_menu
SET
menu_name = ?
, menu_price = ?
WHERE
menu_code = ?
</entry>
<entry key="deleteMenu">
DELETE
FROM tbl_menu
WHERE menu_code = ?
</entry>
쿼리 매핑 파일에 플레이스홀더와 함께 쿼리를 작성해주자.
public static void main(String[] args) {
Connection conn = getConnection();
Scanner sc = new Scanner(System.in);
PreparedStatement pstmt = null;
int result = 0;
Properties prop = new Properties();
try {
prop.loadFromXML(new FileInputStream("src/main/java/com/ino/mapper/menu-query.xml"));
String query = prop.getProperty("deleteMenu");
pstmt = conn.prepareStatement(query);
System.out.print("input menuCode: ");
int menuCode = sc.nextInt();
pstmt.setInt(1, menuCode);
result = pstmt.executeUpdate();
} catch (IOException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(pstmt);
close(conn);
}
}
실행부는 이전과 똑같다
쿼리에서 값을 꺼낼때, ALIAS 도 가능하며 함수를 사용할 경우 함수까지 모두 기재해주어야한다.
<entry key="selectLastMenuCode">
SELECT
MAX(menu_code) AS last_menu_code
FROM
tbl_menu
</entry>
// 예시
lastMenuCode = rset1.getInt("last_menu_code");
// 구현
DAO
Data Access Object
public class MenuDAO {
// 마지막 메뉴 번호 조회용
private Properties prop = new Properties();
public MenuDAO() {
try {
prop.loadFromXML(new FileInputStream("src/main/java/com/ino/mapper/menu-query.xml"));
} catch (IOException e) {
e.printStackTrace();
}
}
public int selectLastMenuCode() {
Connection conn = getConnection();
PreparedStatement pstmt1 = null;
ResultSet rset1 = null;
int lastMenuCode = 0;
String query1 = prop.getProperty("selectLastMenuCode");
try {
pstmt1 = conn.prepareStatement(query1);
rset1 = pstmt1.executeQuery();
if(rset1.next()) {
lastMenuCode = rset1.getInt("last_menu_code");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(rset1);
close(pstmt1);
close(conn);
}
return lastMenuCode;
}
// 카테고리 전체 정보 조회용
public List<CategoryDTO> getCategory(Connection conn) {
PreparedStatement pstmt2 = null;
ResultSet rset2 = null;
List<CategoryDTO> categoryDTOList = new ArrayList<>();
String query2 = prop.getProperty("selectCategoryList");
try {
pstmt2 = conn.prepareStatement(query2);
rset2 = pstmt2.executeQuery();
while(rset2.next()) {
CategoryDTO c = new CategoryDTO(rset2.getInt("category_code"), rset2.getString("category_name"));
categoryDTOList.add(c);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(rset2);
close(pstmt2);
close(conn);
}
for(CategoryDTO c: categoryDTOList) {
System.out.println(c.toString());
}
return categoryDTOList;
}
public int insertMenu(Connection conn, MenuDTO menu) {
menu.setMenuCode(selectLastMenuCode() + 1);
PreparedStatement pstmt3 = null;
int result = 0;
String query3 = prop.getProperty("insertMenu");
try {
pstmt3 = conn.prepareStatement(query3);
pstmt3.setInt(1, menu.getMenuCode());
pstmt3.setString(2, menu.getMenuName());
pstmt3.setInt(3, menu.getMenuPrice());
pstmt3.setInt(4, menu.getCategoryCode());
pstmt3.setString(5, menu.getOrderableStatus());
result = pstmt3.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(pstmt3);
close(conn);
}
return result;
}
}
DB와의 연결에 대한 책임만을 담당하게 해주자.
PreparedStatement를 쿼리 변경을 통해 재사용하지 않는 이유
쿼리 변경과 재사용
쿼리 변경:PreparedStatement는 생성 시에 SQL 쿼리가 컴파일되므로, 쿼리를 변경하려면 기존의 PreparedStatement를 닫고 새로운 것을 생성해야 합니다.
즉, 쿼리 구조가 변경되면 PreparedStatement를 재사용하는 것이 아니라, 새로 생성하는 것이 좋습니다.
이유:성능: 쿼리 구조가 변경되면, 기존의 쿼리는 더 이상 유효하지 않습니다. 기존의 PreparedStatement를 계속 사용하면 성능이 저하될 수 있습니다.
보안: 쿼리를 변경하면서 매개변수의 바인딩이 올바르게 이루어지지 않을 위험이 있습니다.
예시
아래는 쿼리를 변경하고 새로운 PreparedStatement를 생성하는 예시입니다:java import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; public class DynamicPreparedStatementExample { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/mydatabase"; String user = "username"; String password = "password"; try (Connection conn = DriverManager.getConnection(url, user, password)) { // 첫 번째 쿼리 String sql1 = "INSERT INTO users (username, email) VALUES (?, ?)"; try (PreparedStatement pstmt = conn.prepareStatement(sql1)) { pstmt.setString(1, "user1"); pstmt.setString(2, "user1@example.com"); pstmt.executeUpdate(); } // 두 번째 쿼리 String sql2 = "UPDATE users SET email = ? WHERE username = ?"; try (PreparedStatement pstmt = conn.prepareStatement(sql2)) { pstmt.setString(1, "new_email@example.com"); pstmt.setString(2, "user1"); pstmt.executeUpdate(); } System.out.println("사용자가 성공적으로 추가 및 업데이트되었습니다."); } catch (SQLException e) { e.printStackTrace(); } }
}
결론
쿼리 변경 시 새로운 PreparedStatement 생성: 쿼리 구조가 변경될 경우, 기존의 PreparedStatement를 재사용하지 않고 새로운 것을 생성하는 것이 가장 바람직합니다.
효율성 유지: 이를 통해 성능을 최적화하고, SQL 인젝션과 같은 보안 문제를 예방할 수 있습니다.
자원 관리: PreparedStatement 사용 후에는 항상 close() 메서드를 호출하여 자원을 해제하는 것을 잊지 마세요. try-with-resources 문을 사용하면 자동으로 관리할 수 있습니다.Connection 객체를 파라미터로 넘기는 이유
트랜잭션 관리
트랜잭션의 일관성: 여러 DAO 메서드가 하나의 트랜잭션으로 묶여야 하는 경우, 동일한 Connection 객체를 사용하여 트랜잭션의 일관성을 유지할 수 있습니다. 예를 들어, 여러 데이터베이스 작업이 모두 성공해야만 트랜잭션을 커밋하고, 하나라도 실패할 경우 롤백할 수 있습니다.자원 관리
Connection의 재사용: Connection 객체는 생성하는 데 비용이 크기 때문에, DAO 메서드 간에 동일한 Connection을 재사용함으로써 성능을 향상시킬 수 있습니다. Connection Pool을 사용하면, 미리 생성된 연결을 효율적으로 관리할 수 있습니다.유연한 설계
테스트 용이성: Connection을 파라미터로 받는 방식은 테스트 코드에서 모의 객체(mock object)를 사용하기 용이합니다. 실제 데이터베이스에 연결하지 않고도 DAO 메서드를 테스트할 수 있습니다.의존성 주입
의존성 관리: DAO 메서드가 Connection을 직접 생성하는 대신 외부에서 주입받도록 설계함으로써, 의존성 관리가 더 용이해집니다. 이는 객체 간의 결합도를 낮추고 코드의 유지보수성을 높입니다.일관된 API 제공
API의 일관성: DAO 메서드가 Connection 객체를 인자로 받으면, 호출하는 측에서 트랜잭션 관리나 연결 관리를 명확하게 할 수 있습니다. 이는 코드의 가독성을 높여줍니다.
예시
다음은 Connection을 파라미터로 받는 DAO 메서드의 간단한 예시입니다:결론
DAO에서 Connection을 파라미터로 넘기는 방식은 트랜잭션 관리, 자원 효율성, 유연한 설계, 테스트 용이성 등 여러 가지 장점을 제공합니다. 이는 코드의 가독성을 높이고, 유지보수를 용이하게 하며, 성능을 개선하는 데 기여합니다.import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; public class UserDAO { public void addUser(Connection conn, String username, String email) throws SQLException { String sql = "INSERT INTO users (username, email) VALUES (?, ?)"; try (PreparedStatement pstmt = conn.prepareStatement(sql)) { pstmt.setString(1, username); pstmt.setString(2, email); pstmt.executeUpdate(); } } }
autocommit여부
conn.getAutoCommit()
을 통해 알 수 있고,
끈 상태로 작업을 하는게 좋다.
DriverManager를 통해 커넥션을 가져올때, conn.setAutoCommit(false)
를 설정하여 오토커밋을 해제 가능하다.
int result = dao.insertMenu(conn, menu);
if (result > 0) {
try {
if (conn != null && !conn.isClosed()) {
conn.commit();
}
} catch (SQLException e) {
e.printStackTrace();
}
} else {
try {
if (conn != null && !conn.isClosed()) {
conn.rollback();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
DAO로부터 쿼리 결과를 받아 정상적으로 삽입이 될 경우에만 commit, 아니면 rollback을 진행해주자.
Service
비즈니스 로직 처리 및 트랜잭션 관리 담당
사용자의 요청에 따라 순차적으로 실행해야 되는 작업들을 하나로 묶어 관리
중간과정에 문제 발생시 rollback이 필요하므로 하나의 트랜잭션으로 묶어 관리
처리 과정
Connection
생성순차적으로 작업 실행
트랜잭션 처리가 필요할 경우 트랜잭션 처리
Connection 반납
비즈니스 로직 : db - UI 간 정보교환을 위한 규칙이나 알고리즘 의미
'TIL' 카테고리의 다른 글
TIL 3/25 - CI (0) | 2025.03.25 |
---|---|
TIL 3/24 - MVC (0) | 2025.03.24 |
TIL - 3/20 - JDBC (0) | 2025.03.20 |
TIL 3/19 - 정규화, JDBC (0) | 2025.03.19 |
TIL 3/18 - DB 모델링, 이상(Anomaly), 정규화 (0) | 2025.03.18 |