이번 예제에서는 role id와 description을 column으로 가지는 table인 Role을 select, insert, delete하는 코드를 짜보고 올바르게 동작하는지 확인해 보자. JDBC 코드를 작성하기 전에 먼저 데이터베이스에서 가져올 객체를 정의해야 하므로 아래와 같이 Role 클래스를 정의한다.
package jtbcexample.dto;
public class Role {
private Integer roleId;
private String description;
public Role() {
}
public Role(Integer roleId, String description) {
super();
this.roleId = roleId;
this.description = description;
}
public Integer getRoleId() {
return this.roleId;
}
public void setRoleId(Integer roleId) {
this.roleId = roleId;
}
public String getDescription() {
return this.description;
}
public void setDescription(String description) {
this.description=description;
}
@Override
public String toString() {
return "Role [roleId=" + roleId + ", description=" + description + "]";
}
}
Role class는 Role 생성자와 roleId와 desciption의 getter/setter 그리고 오버라이딩 하기 위한 toString() 메서드를 정의한다. 이후 Role 객체를 다루기 위한 JDBC 프로그래밍을 위해 RoleDao.java 파일을 새로 생성한다. 먼저 RoleDao 클래스의 첫 부분을 살펴보자.
public class RoleDao {
private static String dbUrl = "jdbc:mysql://localhost:3306/connectdb?autoReconnect=true";
private static String dbUser = "connectuser";
private static String dbPasswd = "connect123!@#";
dbUrl, dbUser, dbPasswd는 mysql driver의 getConnection() 메서드에 전달할 인자다. 이때 dbUrl을 살펴보면 3306 부분에는 mysql이 작동하는 port 넘버를 넣어주면 된다. 일반적으로 mysql은 3306으로 작동하기 때문에 포트를 다르게 설정하지 않았다면 이 설정을 사용하면 된다. 추가로 뒤에 붙어있는 "?autoReconnect=true"를 사용하지 않으면 아래와 같은 에러가 발생할 수 있다.
이제 select를 위한 getRole() 메서드를 살펴보자.
public Role getRole(Integer roleId) {
Role role = null;
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection(dbUrl, dbUser, dbPasswd);
String sql = "SELECT description, role_id FROM role WHERE role_id = ?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, roleId); // 첫 번째 ?에 roleId를 넣으라는 의미.
resultSet = preparedStatement.executeQuery();
if(resultSet.next()) {
String description = resultSet.getString(1);
Integer id = resultSet.getInt("role_id");
role = new Role(id, description);
}
}catch (Exception e) {
e.printStackTrace();
}finally {
if(resultSet != null) {
try {
resultSet.close();
} catch(SQLException e) {
e.printStackTrace();
}
}
if(preparedStatement != null) {
try {
preparedStatement.close();
} catch(SQLException e) {
e.printStackTrace();
}
}
if(connection != null) {
try {
connection.close();
} catch(SQLException e) {
e.printStackTrace();
}
}
}
return role;
}
select 문의 반환형은 우리가 이전에 선언했던 Role 객체를 반환하도록 선언한다. 먼저 처음에 필요한 객체들을 모두 선언해 준다. 이전에 JDBC 이론에서 살펴본 바와 같이 Connection, Statement, ResultSet 객체를 선언하며 추가로 반환할 객체 Role 객체를 선언해야 한다. 이후 선언한 객체들을 할당해 주면 된다. java와 데이터베이스를 연결하여 작업을 처리하는 과정에서 갑작스럽게 연결이 끊기는 등의 문제가 발생할 수 있다. 따라서 이러한 작업들을 수행할 때는 try-catch 구문을 사용하여 작성해야 한다. 또한 에러가 발생한 경우에도 생성된 객체들을 close하도록 해야 하므로 finally문에 모든 객체를 close하는 명령을 수행해야 한다. 이때 어떠한 문제로 resultSet과 같은 객체들이 할당되지 않아 null값이 들어있을 수 있기 때문에 이 역시 try-catch 구문으로 작성해야 하며 추가적으로 if문을 활용해 null 값인 경우 close를 수행하지 않도록 사전에 방지해준다.
이제 try문 내부를 살펴보자. 먼저 mysql driver를 로딩하기 위해 "Class.forName("com.mysql.jdbc.Driver");"를 수행한다. 만약 다른 DBMS로 mysql이 아니라 oracle과 같이 다른 것을 사용한다면 위 문자열을 해당 DBMS에서 제공하는 형식에 맞게 수정하면 된다고 한다. 로딩을 완료했다면 Connection 객체를 생성해야 한다. 따라서 DriverManage의 getConnection 메서드를 사용해 connection 인스턴스를 할당한다. 이후 실행할 Query 문을 sql 문자열 변수에 담아 connection 객체의 prepareStatement() 메서드에 넘겨준다. 이때 Query 문을 보면 물음표(?)로 작성된 부분이 있다. 우리는 select를 수행할 때 getRole 함수 호출 시 전달되는 roleId에 해당하는 행을 가져와야 한다. 따라서 getRole 함수 수행마다 Query 문이 달라져야 하는데 이를 좀 더 쉽게 해결해 주는 것이 바로 물음표(?)이다. 해당 부분은 아래의 "preparedStatement.setInt(1, roleId);"를 통해 채워진다. 해당 문장은 "첫 번째 ?에 roleId를 넣어라"와 같은 의미라고 생각하면 된다. 이후 resultSet에 Query문의 결과를 넣어주기 위해 preparedStatement 객체의 executeQuery() 메서드를 수행한다. 이후 resultSet 객체에서 결괏값을 꺼내오면 된다. 하지만 roleID에 해당하는 결과 값이 없을 수도 있다. 이런 경우 결괏값을 꺼내는 과정을 수행하면 안 되므로 resultSet 객체의 next() 메서드를 사용하면 된다. next() 메서드는 결괏값이 있는 경우 첫 번째 레코드로 코드를 이동시키고 true를 리턴하며 호출될 때마다 이를 반복한다. 만약 다음 레코드가 없다면 false를 리턴한다. 따라서 next() 메서드에서 true를 리턴했다면 값을 읽기만 하면 된다. 이때 값을 읽으려면 자료형에 맞는 get 함수에 select 문에서 작성한 column의 번호 혹은 column의 이름을 넘겨주면 된다.
이번에는 모든 데이터를 가져오는 getRoles() 메서드를 살펴보자.
public List<Role> getRoles() {
List<Role> list = new ArrayList<>();
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
String sql = "SELECT description, role_id FROM role order by role_id";
try (Connection connection = DriverManager.getConnection(dbUrl, dbUser, dbPasswd);
PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
try (ResultSet resultSet = preparedStatement.executeQuery()) {
while (resultSet.next()) {
String description = resultSet.getString(1);
int id = resultSet.getInt("role_id");
Role role = new Role(id, description);
list.add(role);
}
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
위 코드는 getRole() 메서드와 비슷하게 동작한다. 단, 코드 상의 가장 큰 차이는 이전에는 finally 문에 모든 객체를 하나씩 close 했지만 해당 코드는 close 코드들을 생략하여 훨씬 간단해졌다. 이는 try-with resource라는 문법으로 try문에 자원 객체를 전달하여 try 문이 끝나면 자동으로 자원을 close하는 기능을 가지고 있다. 위에서는 첫 번째 try문 괄호안에 Connection, PreparedStatement 객체, 두 번째 try문 괄호 안에는 ResultSet 객체를 생성했으므로 각 try문이 끝나면 해당 객체들을 자동으로 close해준다.
이번에는 insert 문을 수행하는 insertRole 메서드를 살펴보자.
public int insertRole(Role role) {
int insertCount = 0;
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
String sql = "INSERT INTO role (role_id, description) VALUES ( ?, ? )";
try (Connection connection = DriverManager.getConnection(dbUrl, dbUser, dbPasswd);
PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
preparedStatement.setInt(1, role.getRoleId());
preparedStatement.setString(2, role.getDescription());
insertCount = preparedStatement.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
return insertCount;
}
insert 문의 경우 select와 같이 Query 문의 결과가 없다. 따라서 insert 문은 ResultSet 객체는 선언할 필요가 없으며 Connection과 Statement 객체만 선언하면 된다. 단, Query문 실행시 select와 같이 "preparedStatement.executeQuery()"를 수행하지 않고 executeUpdate() 메서드를 실행시킨다. executeUpdate() 메서드는 insert, delete, update 관련 구문에서 반영된 레코드의 건수를 반환한다. 단, create와 drop 관련 구문은 -1을 반환한다고 한다.
아래는 delete 문을 위해 선언한 메서드다.
public int deleteRole(Integer roleId) {
int deleteCount = 0;
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (Exception e) {
e.printStackTrace();
}
String sql = "DELETE FROM role WHERE role_id=?";
try (Connection connection = DriverManager.getConnection(dbUrl, dbUser, dbPasswd);
PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
preparedStatement.setInt(1, roleId);
deleteCount = preparedStatement.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
return deleteCount;
}
아래는 update 문을 위해 선언한 메서드다.
public int updateRole(Role role) {
int updateCount = 0;
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (Exception e) {
e.printStackTrace();
}
String sql = "UPDATE role SET description=? WHERE role_id=?";
try (Connection connection = DriverManager.getConnection(dbUrl, dbUser, dbPasswd);
PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
preparedStatement.setString(1, role.getDescription());
preparedStatement.setInt(2, role.getRoleId());
updateCount = preparedStatement.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
return updateCount;
}
References
'BackEnd > JDBC' 카테고리의 다른 글
[JDBC] MySQL Datetime 타입 저장 방법 (0) | 2023.01.31 |
---|---|
[JDBC] DTO/DAO/VO 간단한 정리 (0) | 2023.01.26 |
[JDBC] like문 사용법 (0) | 2023.01.18 |
JDBC란? (0) | 2022.12.26 |