공부하는 입장이기 때문에 혹시 글에 잘못된 점이 있다면 댓글로 알려주시면 감사하겠습니다!
이번주에는 계속해서 DB와 관련해서 공부하고 있다. 처음에는 MySql을 사용하면서 쿼리문을 작성하는 연습을 했고,
오늘은 이제 자바와 MySQL을 연결해서 데이터를 가져와보려고 한다.
JDBC란?
JDBC란 Java DataBase Connectivity 의 약어로 Java와 DB연결을 위한 표준 API이다.
조금더 자세하게 설명하면 자바에서 DB를 연결하고, 그 안에 있는 데이터를 가져오기 위해서 사용하는 API이다.
이 글에서는 자바는 Eclipse, DB는 MySQL을 사용할 예정이다.
우선 전체적인 구조를 보면
크게 4가지로 나뉜다.
자바와 DB를 연결하기 위해서는 두 단계를 거쳐야한다.
JDBC API는 JDK에 포함되어있으며 DBMS에 상관없이 사용할 수 있는 API를 제공한다. JDK에 이미 포함이 되어있기 때문에 따로 다운로드하거나 설치할 필요는 없다.
대부분의 api는 java.sql 패키지나 javax.sql 패키지에 있는 api를 사용한다.
주요 클래스 및 인터페이스로
- DriverManger : JDBC 드라이버 로드
- Connectoin : DB와 연결하기 위한 인터페이스
- Statement / PreparedStatement / CallableStatement (SQL을 보내기 위한 통로)
- Statement : SQl을 보내기 위한 통로. 인자가 없음.
- PreparedStatement : Statement와 동일한데 차이점은 인자값으로 SQL을 받기 때문에 특정한 SQL에 대한 통로라고 생각하면 된다.
- CallableStatement : PL/SQL을 호출할 때 사용
- ResultSet : SQL문의 결과를 저장하는 객체
등이 있다.
JDBC Driver는 DBMS에 따라서 다르게 다운로드를 받아줘야한다. 각 DBMS측에서 제공해주는 라이브러리 압축 파일로 필자는 MySQL을 사용하기 때문에 MySQL에서 다운을 받아줬다.
JDBC를 하기 위한 환경 설정
- DBMS 서버 설치 : MySQL, Oracle, DB2 등
- JDBC Driver 설치 : 각 DBMS 페이지에서 제공하는 파일을 다운받기 (*.jar 파일)
- JDBC API : JDK에 포함
현재 DBMS와 자바가 설치되어있음을 가정으로 JDBC Driver 다운받는 것만 작성하려고 한다.
JDBC Driver (MySQL)
https://dev.mysql.com/downloads/connector/j/
MySQL :: Download Connector/J
MySQL Connector/J 8.0 is highly recommended for use with MySQL Server 8.0, 5.7 and 5.6. Please upgrade to MySQL Connector/J 8.0.
dev.mysql.com
이 페이지에 접속해서 Platform Independent로 변경해주고, 두번째 파일을 다운로드 받는다.
경로는 나중에 자바에서 import시킬 것이기 때문에 저장하고, 그 파일의 경로만 알고 있으면 된다.
Java에 JDBC Driver 로드
나는 시스템 전체가 아닌 특정 프로젝트에서 설정을 해줬다.
1. 프로젝트를 생성한다.
2. 프로젝트 오른쪽 클릭 -> Properites 클릭
3. Java Build Path 클릭
4. 오른쪽에 Add External JARs 클릭
5. 전에 다운 받았던 driver파일을 압축을 풀고, 폴더안에 들어가보면 jar파일이 하나 있다. 해당 파일을 선택 후 열기 클릭
6. 파일이 들어가있는거 확인
7. 프로젝트 폴더내에 다음과 같은 폴더가 새로 생성된다면 로드된 것이다.
JDBC 사용하기
우선 DB에 테이블과 데이터가 미리 존재한다는 가정하에 진행한다.
나는 testdb 라는 DB에다가 user 테이블을 만들어서 값을 넣어주고 진행했다.
JDBC을 위한 순서는 다음과 같다.
순서를 잘 보면 Connection -> Statemnet -> Result 순으로 객체를 생성하고 있으므로 해제할 때는 역순으로 해준다.
그림 중간에 Result생성하는 부분을 빠뜨렸다...ㅎ... Result 객체는 select문을 사용할 때만 필요하다. 그렇기 때문에 Result 객체를 생성해줬으면 해제도 해줘야하고, 만약 생성하지 않았다면 (executeUpdate문을 사용한 경우) 해제할 필요도 없다. 만들지 않았으니까
우선 나는 DAO, DTO 패턴을 사용해서 사용해볼 예정이다. Factory패턴을 사용하면 더 코드가 간결해지긴 하겠지만 우선을 기초적인 부분이니까 DAO, DTO, Main 이렇게 3개의 클래스를 사용할 예정이다.
간단하게 나는 사용자들의 정보를 저장하고, 조회하는 프로그램을 만들려고 한다.
DTO 클래스 형성 (User.java)
클래스명은 User로 생성해줬다. 변수들을 private으로 선언해주고, 생성자, getter, setter, tostring 함수 모두 만들어준다. (자바의 캡슐화) 오른쪽 클릭해서 자동완성 함수 이용하면 간편하다.
변수명은 DB에서 user_id라면 자바에서는 userId. 이런식으로 만들었다.
package test;
public class User {
private String userId;
private String userPw;
private String name;
private String phone;
private String grade;
private int age;
/**
* @param userId
* @param userPw
* @param name
* @param phone
* @param grade
* @param age
*/
public User() {};
public User(String userId, String userPw, String name, String phone, String grade, int age) {
this.userId = userId;
this.userPw = userPw;
this.name = name;
this.phone = phone;
this.grade = grade;
this.age = age;
}
/**
* @return the userId
*/
public String getUserId() {
return userId;
}
/**
* @param userId the userId to set
*/
public void setUserId(String userId) {
this.userId = userId;
}
/**
* @return the userPw
*/
public String getUserPw() {
return userPw;
}
/**
* @param userPw the userPw to set
*/
public void setUserPw(String userPw) {
this.userPw = userPw;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the phone
*/
public String getPhone() {
return phone;
}
/**
* @param phone the phone to set
*/
public void setPhone(String phone) {
this.phone = phone;
}
/**
* @return the grade
*/
public String getGrade() {
return grade;
}
/**
* @param grade the grade to set
*/
public void setGrade(String grade) {
this.grade = grade;
}
/**
* @return the age
*/
public int getAge() {
return age;
}
/**
* @param age the age to set
*/
public void setAge(int age) {
this.age = age;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(userId).append(" \t | ").append(userPw).append(" \t | ").append(name).append(" \t | ")
.append(phone).append(" \t | ").append(grade).append(" \t | ").append(age);
return builder.toString();
}
}
DAO 클래스 만들기 (UserDao.java)
이 부분에서 이제 JDBC를 직접적으로 사용하고 구현하는 부분이라고 할 수 있다.
우선 DB에 연결하기 위한 몇가지 변수들이 필요하다. 이 변수는 DBMS마다 다르기 때문에 나는 MySQL기준으로 설명할 예정이다.
총 4가지의 변수들이 필요한데, JDBC 드라이버 , JDBC URL, DB에 접속할 계정 id, 패스워드가 필요하다.
1. JDBC 드라이버 : DBMS에 맞는 드라이버를 로딩할 때 필요하다. 나는 MySQL을 사용하기 하므로
private String driver = "com.mysql.cj.jdbc.Driver";
2. JDBC URL : DBMS와 연결하기 위한 URL이다. 이것도한 DBMS에 따라서 형식이 다르기 때문에 DBMS에 따라서 입력값이 달라진다.
private String url = "jdbc:mysql://[host]:[포트]/[db이름]?serverTimezone=UTC&useUniCode=yes&characterEncoding=UTF-8";
3. 이제 db에 접속하기 위한 계정을 입력하는 두개의 변수가 필요하다.
내가 사용할 DB는 두번째였기 때문에 사용할 DB에 접속할 때 사용하는 user이름과 password를 입력해준다.
최종 코드
private String driver = "com.mysql.cj.jdbc.Driver";
private String url = "jdbc:mysql://127.0.0.1:3306/testdb?serverTimezone=UTC&useUniCode=yes&characterEncoding=UTF-8";
private String user = "[사용자이름]";
private String password = "[비밀번호]";
함수 작성
UserDao.java에는 총 2개의 함수를 만드려고 한다. 전체조회 하는 함수와 유저의 이름을 받아서 상세조회하는 함수.
예외처리를 많이해줘야하는데 우선 예외클래스를 따로 만들지 않고 모든 예외를 try-catch문으로 해결할 예정이다.
두 개의 함수 모두 어쨌든 드라이버를 호출하고 DB와 연결을 하는 코드가 동일하기 때문에 그 부분 먼저 설명하고 다른 부분만 따로 설명할 예정이다.
1. JDBC Driver 로딩
위에서 선언한 driver를 로딩해주는 코드를 작성한다.
//1. JDBC Driver 로딩
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Class.forName(드라이버) 함수를 사용해서 로드해준다.
2. DB서버 연결
Connection conn = DriverManager.getConnection(url, user, password);
위에서 선언한 변수들로 Connection 객체를 생성해준다.
3. SQL 실행 통로 형성
Statement stmt = conn.createStatement();
4. SQL 실행 요청 및 결과 받기
CUD (create, update, delete)는 excuteUpdate(sql) 함수를 사용하고 int 값을 반환한다.
R(read)은 excuteQuery(sql) 함수를 사용하고 ResultSet 객체를 반환한다.
String sql = "select * from user";
ResultSet rs = stmt.executeQuery(sql);
5. 결과 처리하기
ResultSet객체를 사용해서 결과를 처리해준다. 데이터를 받아서 객체를 생성한다던가, 출력을 한다던가 원하는 코드를 짜면 된다. 나는 전체 조회하는 SQL을 보냈으므로 결과값으로는 테이블의 모든 데이터가 ResultSet 객체에 담겨있다.
데이터가 담기는 순서는 다음과 같다.
ResultSet 객체에는 next()를 사용하면 다음에 가져올 데이터가 있는지 없는지 확인할 수 있다. 그러므로 데이터를 다 가져올 때까지 while문을 실행하면서 데이터를 저장을 했고, 하나의 행을 다 받아오면 User객체를 생성해줬다.
데이터를 가져올 때는 컬럼명을 사용할 수도 있고, 컬럼의 인덱스를 사용할 수도 있다. (컬럼의 인덱스는 1부터 시작 )
그리고 전체조회이기 때문에 객체가 없을 수도 있고, 여러개일 수도 있다. 그래서 그 전에 ArrayList를 선언해서 list안에 객체를 넣어준다.
//5. SQL 결과 처리
ArrayList<User> list = new ArrayList<>();
while(rs.next()) {
String userId = rs.getString("user_id"); // 컬럼명 (!!!!주의!!!! DTO의 이름과 DB의 컬럼명이 다름 )
//String userPw = rs.getString("user_pw"); // select inde 번호 : 1번부터
String userPw = rs.getString(2); // 연산결과인 경우 컬럼명 없음, 별명, 인덱스번호
String name = rs.getString("name");
String phone = rs.getString("phone");
String grade = rs.getString("grade");
int age = rs.getInt("age");
User dto = new User(userId, userPw, name, phone, grade,age);
list.add(dto);
}
여기까지하면 SQL문을 보내서 그 결과 데이터를 받고, 데이터를 원하는 대로 가공하는데까지 완료한 것이다.
해당부문에 대한 전체 코드는 다음과 같다.
ArrayList<User> list = null; //결과데이터를 담을 배열
try {
//2. DB서버 연결
Connection conn = DriverManager.getConnection(url, user, password);
//3. SQL 실행 통로 형성
Statement stmt = conn.createStatement();
//4. SQL 실행 요청 및 결과 받기
// CUD : excuteUpdate(sql) : int
// R : excuteQuery(sql) : ResultSet
String sql = "select * from user";
ResultSet rs = stmt.executeQuery(sql);
//5. SQL 결과 처리
list = new ArrayList<>();
while(rs.next()) {
String userId = rs.getString("user_id"); // 컬럼명 (!!!!주의!!!! DTO의 이름과 DB의 컬럼명이 다름 )
// String userPw = rs.getString("user_pw"); // select inde 번호 : 1번부터
String userPw = rs.getString(2)
String name = rs.getString("name");
String phone = rs.getString("phone");
String grade = rs.getString("grade");
int age = rs.getInt("age");
User dto = new User(userId, userPw, name, phone, grade,age);
list.add(dto);
}
} catch (SQLException e) {
e.printStackTrace();
}
6. 자원 해제
이제 위에서 만들었던 객체들을 자원 해제를 해준다. 자원 해제는 항상 실행되어야하기 때문에 finally 문 안에 넣어준다. 그리고 이제 객체를 담았던 list를 반환해주면 함수가 완성이 된다.
<함수 전체 코드 >
//회원 전체 조회
public ArrayList<User> selectList(){
//1. JDBC Driver 로딩
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
ArrayList<User> list = null; //결과데이터를 담을 배열
try {
//2. DB서버 연결
conn = DriverManager.getConnection(url, user, password);
//3. SQL 실행 통로 형성
stmt = conn.createStatement();
//4. SQL 실행 요청 및 결과 받기
// CUD : excuteUpdate(sql) : int
// R : excuteQuery(sql) : ResultSet
String sql = "select * from user";
rs = stmt.executeQuery(sql);
//5. SQL 결과 처리
list = new ArrayList<>();
while(rs.next()) {
String userId = rs.getString("user_id"); // 컬럼명 (!!!!주의!!!! DTO의 이름과 DB의 컬럼명이 다름 )
// String userPw = rs.getString("user_pw"); // select inde 번호 : 1번부터
String userPw = rs.getString(2); // 연산결과인 경우 컬럼명 없음, 별명, 인덱스번호
String name = rs.getString("name");
String phone = rs.getString("phone");
String grade = rs.getString("grade");
int age = rs.getInt("age");
User dto = new User(userId, userPw, name, phone, grade,age);
list.add(dto);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
//6. 자원 해제
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return list;
}
finally문에 있는 자원 해제 코드가 싫다면 try문 옆에 작성해서 알아서 자원해제가 되도록 하는 방법도 있다.
<자원 해제 코드를 따로 사용하지 않는 경우>
//회원 전체 조회
public ArrayList<User> selectList(){
//1. JDBC Driver 로딩
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
ArrayList<User> list = null; //결과데이터를 담을 배열
String sql = "select * from user"; //SQL문
try(Connection conn = DriverManager.getConnection(url, user, password); //2. DB서버 연결
Statement stmt = conn.createStatement(); //3. SQL 실행 통로 형성
ResultSet rs = stmt.executeQuery(sql);
) {
//5. SQL 결과 처리
list = new ArrayList<>();
while(rs.next()) {
String userId = rs.getString("user_id"); // 컬럼명 (!!!!주의!!!! DTO의 이름과 DB의 컬럼명이 다름 )
// String userPw = rs.getString("user_pw"); // select inde 번호 : 1번부터
String userPw = rs.getString(2);
String name = rs.getString("name");
String phone = rs.getString("phone");
String grade = rs.getString("grade");
int age = rs.getInt("age");
User dto = new User(userId, userPw, name, phone, grade,age);
list.add(dto);
}
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
Main함수 작성하기 (UserMain.java)
메인은 그냥 간단하게 Dao객체를 생성하고, 함수를 호출하는 코드를 작성해주면 된다.
package test;
import java.util.ArrayList;
public class UserMain {
public static void main(String[] args) {
UserDao dao = new UserDao();
System.out.println("=====전체조회=====");
//전체 조회해서 list에 담기
ArrayList<User> list = dao.selectList();
print(list);
}
private static void print(ArrayList<User> list) {
for (User dto : list) {
System.out.println(dto);
}
}
}
그러면 이제 결과 값이 다음과 같이 나오는 걸 확인할 수 있다.
전체 조회는 완성됐고, 이제 상세 조회는 반환 타입과 데이터를 가공해주는 부분만 약간 다르게 하면된다.
유저의 이름을 입력받아서 유저의 데이터를 가져오는 함수다.
//회원 상세 조회
public User selectUser(String userid){
//1. JDBC Driver 로딩
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
String sql = "select * from user where user_id ='"+userid+"'"; //SQL문
try(Connection conn = DriverManager.getConnection(url, user, password); //2. DB서버 연결
Statement stmt = conn.createStatement(); //3. SQL 실행 통로 형성
ResultSet rs = stmt.executeQuery(sql);
) {
//5. SQL 결과 처리
while(rs.next()) {
String userId = rs.getString("user_id");
//String userPw = rs.getString("user_pw");
String userPw = rs.getString(2);
String name = rs.getString("name");
String phone = rs.getString("phone");
String grade = rs.getString("grade");
int age = rs.getInt("age");
return new User(userId, userPw, name, phone, grade,age);
}
} catch (SQLException e) {
e.printStackTrace();
}
return null; //찾은 유저가 없는 경우 null반환
}
메인에서 테스트 해본다.
System.out.println("=====상세조회=====");
User user = dao.selectUser("user01");
System.out.println(user);}
결과
이렇게 하면 다 완성된 모습을 볼 수 있다.
완성된 전체 코드
<User.java>
package test;
public class User {
private String userId;
private String userPw;
private String name;
private String phone;
private String grade;
private int age;
/**
* @param userId
* @param userPw
* @param name
* @param phone
* @param grade
* @param age
*/
public User() {};
public User(String userId, String userPw, String name, String phone, String grade, int age) {
this.userId = userId;
this.userPw = userPw;
this.name = name;
this.phone = phone;
this.grade = grade;
this.age = age;
}
/**
* @return the userId
*/
public String getUserId() {
return userId;
}
/**
* @param userId the userId to set
*/
public void setUserId(String userId) {
this.userId = userId;
}
/**
* @return the userPw
*/
public String getUserPw() {
return userPw;
}
/**
* @param userPw the userPw to set
*/
public void setUserPw(String userPw) {
this.userPw = userPw;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the phone
*/
public String getPhone() {
return phone;
}
/**
* @param phone the phone to set
*/
public void setPhone(String phone) {
this.phone = phone;
}
/**
* @return the grade
*/
public String getGrade() {
return grade;
}
/**
* @param grade the grade to set
*/
public void setGrade(String grade) {
this.grade = grade;
}
/**
* @return the age
*/
public int getAge() {
return age;
}
/**
* @param age the age to set
*/
public void setAge(int age) {
this.age = age;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(userId).append("\t| ").append(userPw).append("\t| ").append(name).append("\t| ")
.append(phone).append("\t| ").append(grade).append("\t| ").append(age);
return builder.toString();
}
}
<UserDao.java>
package test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
public class UserDao {
private String driver = "com.mysql.cj.jdbc.Driver";
private String url = "jdbc:mysql://127.0.0.1:3306/testdb?serverTimezone=UTC&useUniCode=yes&characterEncoding=UTF-8";
private String user = "[username]";
private String password = "[pwd]";
//회원 전체 조회
public ArrayList<User> selectList(){
//1. JDBC Driver 로딩
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
ArrayList<User> list = null; //결과데이터를 담을 배열
String sql = "select * from user"; //SQL문
try(Connection conn = DriverManager.getConnection(url, user, password); //2. DB서버 연결
Statement stmt = conn.createStatement(); //3. SQL 실행 통로 형성
ResultSet rs = stmt.executeQuery(sql);
) {
//5. SQL 결과 처리
list = new ArrayList<>();
while(rs.next()) {
String userId = rs.getString("user_id"); // 컬럼명 (!!!!주의!!!! DTO의 이름과 DB의 컬럼명이 다름 )
// String userPw = rs.getString("user_pw"); // select inde 번호 : 1번부터
String userPw = rs.getString(2); // 연산결과인 경우 컬럼명 없음, 별명, 인덱스번호
String name = rs.getString("name");
String phone = rs.getString("phone");
String grade = rs.getString("grade");
int age = rs.getInt("age");
User dto = new User(userId, userPw, name, phone, grade,age);
list.add(dto);
}
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
//회원 상세 조회
public User selectUser(String userid){
//1. JDBC Driver 로딩
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
String sql = "select * from user where user_id ='"+userid+"'"; //SQL문
try(Connection conn = DriverManager.getConnection(url, user, password); //2. DB서버 연결
Statement stmt = conn.createStatement(); //3. SQL 실행 통로 형성
ResultSet rs = stmt.executeQuery(sql);
) {
//5. SQL 결과 처리
while(rs.next()) {
String userId = rs.getString("user_id");
//String userPw = rs.getString("user_pw");
String userPw = rs.getString(2);
String name = rs.getString("name");
String phone = rs.getString("phone");
String grade = rs.getString("grade");
int age = rs.getInt("age");
return new User(userId, userPw, name, phone, grade,age);
}
} catch (SQLException e) {
e.printStackTrace();
}
return null; //찾은 유저가 없는 경우 null반환
}
}
<UserMain.java>
package test;
import java.util.ArrayList;
public class UserMain {
public static void main(String[] args) {
UserDao dao = new UserDao();
System.out.println("=====전체조회=====");
//전체 조회해서 list에 담기
ArrayList<User> list = dao.selectList();
print(list);
System.out.println();
System.out.println("=====상세조회=====");
User user = dao.selectUser("user01");
print(user);
}
private static void print(User user) {
System.out.println(user);
}
private static void print(ArrayList<User> list) {
for (User dto : list) {
System.out.println(dto);
}
}
}
완성은 되었지만 위에서 작성한 코드가 좋은 코드라고 말할 수는 없다. 실행해보면 알겠지만, 메인을 실행하면 잠깐의 딜레이가 있다.
그리고 sql문을 보낼 때 string보다 Stringbuilder를 사용할 수도 있고, JDBC 드라이버 로딩하고 DB연결하는 것을 간편하게 해줄 Factroy패턴을 사용할 수도 있고, createStatement대신에 preparedStatement을 사용할 수도 있다.
여러가지 방법이 있지만 지금은 공부하려고 작성한 글이고, 처음 기초적인 것부터 해보기 위해서 위와 같이 작성했다.
이상 끗 !
'개발 공부 > Java' 카테고리의 다른 글
[JAVA] JDBC사용하기 (Factory 패턴과 PreparedStatement) (0) | 2022.03.23 |
---|