git repository : https://github.com/zezeg2/java-practice/tree/main/jdbc-practice
create table member (
id varchar(10) primary key,
pw varchar(10) not null,
name varchar(10),
phone char(11) check(phone like '010%'),
email varchar(30) unique,
address varchar(50),
indate datetime
);
├── pom.xml
├── main
│ ├── java
│ │ └── template
│ │ ├── MemberRunner.java
│ │ ├── connection
│ │ │ ├── ConnectionInform.java
│ │ │ └── JDBCConnection.java
│ │ ├── domain
│ │ │ └── member
│ │ │ ├── dao
│ │ │ │ └── MemberDAO.java
│ │ │ ├── dtos
│ │ │ │ ├── AuthorizeMemberDTO.java
│ │ │ │ ├── InfoMemberDTO.java
│ │ │ │ ├── MemberDTO.java
│ │ │ │ └── UpdateMemberDTO.java
│ │ │ ├── exceptions
│ │ │ │ ├── IncorrectPasswordException.java
│ │ │ │ └── MemberNotFoundException.java
│ │ │ └── view
│ │ │ ├── CreateMemberViewImpl.java
│ │ │ ├── DeleteMemberViewImpl.java
│ │ │ ├── GetAllMemberInfoViewImpl.java
│ │ │ ├── GetMemberInfoViewImpl.java
│ │ │ ├── UpdateMemberViewImpl.java
│ │ │ └── View.java
│ │ └── tools
│ │ └── GlobalScanner.java
│ └── resources
...
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>template</groupId>
<artifactId>jdbc-practice</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.mariadb.jdbc/mariadb-java-client -->
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>3.1.0</version>
</dependency>
</dependencies>
</project>
ConnectionInform.java
JDBC 커넥션을 위한 Constants
public class ConnectionInform {
public final static String DRIVER_CLASS = "org.mariadb.jdbc.Driver";
public final static String JDBC_URL = "jdbc:mariadb://localhost:3306/memberdb";
public final static String USERNAME = "jdbc";
public final static String PASSWORD = "jdbc";
}
JDBCConnaction.java
public class JDBCConnection {
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(JDBC_URL, USERNAME, PASSWORD);
}
}
GlobalScanner.java
public class GlobalScanner implements Closeable {
private static GlobalScanner instance = null;
private final Scanner scanner;
private GlobalScanner() {
this.scanner = new Scanner(System.in);
}
public static GlobalScanner getInstance() {
if (instance == null) instance = new GlobalScanner();
return instance;
}
public Scanner getScanner() {
return scanner;
}
public int nextNum(String comment) {
System.out.print(comment);
while (!scanner.hasNextInt()) {
scanner.next();
System.err.print("올바른 값을 입력해주세요. 재 선택 > ");
}
return scanner.nextInt();
}
public String nextString(String comment) {
System.out.print(comment);
return scanner.next();
}
public int nextNumOrCheckReplace(String comment, String check, int replace) {
System.out.print(comment);
while (!scanner.hasNextInt()) {
if (scanner.next().equalsIgnoreCase(check)) return replace;
System.err.printf("올바른 값을 입력해주세요 (종료 : '%s') 재 선택 > ", check);
}
return scanner.nextInt();
}
public String nextStringOrReplace(String comment, String check, String replace){
System.out.print(comment);
String input = scanner.next();
return input.equals(check) ? replace : input;
}
@Override
public void close() {
scanner.close();
}
}
MemberRunner.java
View
인터페이스 구현체createMember(MemberDTO dto)
를 호출한다getMember(AuthorizeMemberDTO dto)
실행 후 조회 성공시(인증O) 수정하고자 하는 데이터를 입력받아 레코드를 수정하는 DAO 메서드deleteMember(String id)
호출getMember(AuthorizeMemberDTO dto)
실행 후 조회 성공시(인증O) 해당 레코드를 삭제하는 DAO메서드updateMember(UpdateMemberDTO dto)
호출InfoMemberDTO
로 Mapping 하여 이를 콘솔에 출력한다COUNT_PER_PAGE
개 조회) InfoMemberDTO
로 Mapping 한 List로 반환하는 DAO 메서드getAllMemberInfo(int page)
를 실행한다. 회원 정보들을 사용자가 입력한 페이지 번호별로 출력한다.MemberNotFoundException
, IncorrectPasswordException
등) 의도된 예외 이므로 에러 메세지를 출력하고 다른 처리는 하지 않는다public class MemberRunner {
public static void main(String[] args) {
try (GlobalScanner sc = GlobalScanner.getInstance()) {
System.out.println("===== 회원 관리 프로그램 =====");
System.out.println("\n1. 회원정보 입력\n2. 회원정보 수정\n3. 회원 탈퇴\n4. 회원 정보 조회\n5. 전체 회원 조회\n6. 메뉴보기\n'q' => 프로그램 종료");
while (true) {
int select = sc.nextNumOrCheckReplace("\n메뉴 입력 > ", "q", 0);
if (select == 0) {
System.out.println("====== 프로그램 종료 ======");
break;
}
try {
switch (select) {
case 1 -> CreateMemberViewImpl.getInstance().run();
case 2 -> UpdateMemberViewImpl.getInstance().run();
case 3 -> DeleteMemberViewImpl.getInstance().run();
case 4 -> GetMemberInfoViewImpl.getInstance().run();
case 5 -> GetAllMemberInfoViewImpl.getInstance().run();
case 6 -> System.out.print("\n1. 회원정보 입력\n2. 회원정보 수정\n3. 회원 탈퇴\n4. 회원 정보 조회\n5. 전체 회원 조회\n6. 메뉴보기\n'q' => 프로그램 종료");
default -> System.err.print("올바른 값을 입력해주세요. 재 선택 > ");
}
} catch (SQLException e) {
e.printStackTrace();
break;
} catch (RuntimeException e) {
System.out.println(e.getMessage());
}
}
}
}
}
MemberDTO.java
// 데이터베이스의 member record와 일치하는 dto
public record MemberDTO(String id, String pw, String name, String email, String phone, String address, String indate) {}
AuthorizeMemberDTO.java
//id, pw가 일치하는지 확인하기 위해 id, pw 필드만을 가짐
public record AuthorizeMemberDTO(String id, String pw) { }
InfoMemberDTO.java
// 조회시 필요한 정보만을 노출하기 위한 dto
public record InfoMemberDTO(String id, String name, String email, String phone, String address, String indate) {
@Override
public String toString() {
return "InfoMemberDTO{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", email='" + email + '\'' +
", phone='" + phone + '\'' +
", address='" + address + '\'' +
", indate='" + indate + '\'' +
'}';
}
}
UpdateMemberDTO.java
// update 를 위해 id와 일치하는 레코드의 정보를 업데이트 하기 위한 dto
public record UpdateMemberDTO(String id, String pw, String email, String phone, String address) {}
MemberDAO.java
try-with-resources
로 정상적으로 자원을 반납하도록 보장한다COUNT_PER_PAGE
를 멤버로 가진다public class MemberDAO {
private static MemberDAO instance;
public static MemberDAO getInstance() {
if (instance == null) instance = new MemberDAO();
return instance;
}
private final int COUNT_PER_PAGE = 3;
private MemberDAO() {
}
// id는 unique 컬럼이므로 예외를 방지하기위해 중복확인이 요구된다.
// sql의 exist() 함수를 통해 효율적으로 테이블 내 id 존재유무를 확인하여 boolean 값 리턴
public boolean isExistId(String id) throws SQLException {
String sql = "SELECT EXISTS(SELECT 1 FROM member WHERE id = ?)";
try (Connection con = JDBCConnection.getConnection();
PreparedStatement pt = con.prepareStatement(sql)) {
pt.setString(1, id);
ResultSet rs = pt.executeQuery();
rs.next();
return rs.getInt(1) == 1;
}
}
// email는 unique 컬럼이므로 예외를 방지하기위해 중복확인이 요구된다.
// id와 동일한 방법으로 존재유무 확인 후 boolan value return
public boolean isExistEmail(String email) throws SQLException {
String sql = "SELECT EXISTS(SELECT 1 FROM member WHERE email = ?)";
try (Connection con = JDBCConnection.getConnection();
PreparedStatement pt = con.prepareStatement(sql)) {
pt.setString(1, email);
ResultSet rs = pt.executeQuery();
rs.next();
return rs.getInt(1) == 1;
}
}
// insert 쿼리 실행, 리턴 void
public void createMember(MemberDTO dto) throws SQLException {
String sql = "INSERT INTO member (id, pw, name, email, phone, address, indate) VALUES (?,?,?,?,?,?,now())";
try (Connection con = JDBCConnection.getConnection();
PreparedStatement pt = con.prepareStatement(sql)) {
pt.setString(1, dto.getId());
pt.setString(2, dto.getPw());
pt.setString(3, dto.getName());
pt.setString(4, dto.getEmail());
pt.setString(5, dto.getPhone());
pt.setString(6, dto.getAddress());
pt.execute();
}
}
// member 테이블 내 전체 레코드 수를 카운팅하여 전체 페이지 리턴
public int countPage() throws SQLException {
String sql = "SELECT COUNT(*) FROM member";
try (Connection con = JDBCConnection.getConnection();
Statement st = con.createStatement()) {
ResultSet rs = st.executeQuery(sql);
rs.next();
int totalCnt = rs.getInt(1);
return (totalCnt - 1) / COUNT_PER_PAGE + 1;
}
}
// limit -> 조회쿼리의 조회레코드 수를 제한
// offset -> 조회시 offset 값 이후부터의 데이터를 가져온다
// limit, offset을 이용하여 페이지네이션 구현
public List<InfoMemberDTO> getAllMemberInfo(int page) throws SQLException {
List<InfoMemberDTO> memberList = new ArrayList<>();
String sql = "SELECT * FROM member ORDER BY indate LIMIT ? OFFSET ?";
try (Connection con = JDBCConnection.getConnection();
PreparedStatement pt = con.prepareStatement(sql)) {
int startIndex = COUNT_PER_PAGE * (page - 1);
pt.setInt(1, COUNT_PER_PAGE);
pt.setInt(2, startIndex);
ResultSet rs = pt.executeQuery();
while (rs.next()) {
String id = rs.getString("id");
String name = rs.getString("name");
String email = rs.getString("email");
String phone = rs.getString("phone");
String address = rs.getString("address");
String indate = rs.getString("indate");
memberList.add(new InfoMemberDTO(id, name, email, phone, address, indate));
}
return memberList;
}
}
// 회원의 AuthorizeMemberDTO를 인수로 받아 getMember메소드 실행 ->
// 반환된 MemberDTO 객체로부터 필드값을 InformMemberDto와 매핑하여InformMemberDto를 리턴한다
public InfoMemberDTO getMemberInfo(AuthorizeMemberDTO dto) throws SQLException {
MemberDTO member = getMember(dto);
return new InfoMemberDTO(member.getId(), member.getName(), member.getEmail(), member.getPhone(), member.getAddress(), member.getIndate());
}
// UpdateMemberDTO 를 인수로 받아 dto의 id에 해당하는 레코드에 대해 업데이트 쿼리를 실행한다.
public void updateMember(UpdateMemberDTO dto) throws SQLException {
String sql = "UPDATE member SET pw = ?, email = ?, phone = ?, address = ? WHERE id = ?";
try (Connection con = JDBCConnection.getConnection();
PreparedStatement pt = con.prepareStatement(sql)) {
pt.setString(1, dto.pw());
pt.setString(2, dto.email());
pt.setString(3, dto.phone());
pt.setString(4, dto.address());
pt.setString(5, dto.id());
pt.executeQuery();
}
}
// id를 인수로 받아 해당 id에 해당하는 레코드를 삭제하는 쿼리를 실행한다
public void deleteMember(String id) throws SQLException {
String sql = "DELETE FROM member WHERE id = ?";
try (Connection con = JDBCConnection.getConnection();
PreparedStatement pt = con.prepareStatement(sql)) {
pt.setString(1, id);
pt.executeQuery();
}
}
// AuthorizeMemberDTO 를 인수로 받고 id, pw 모두 일치하는지 확인하고 일치 할시 MemberDTO 리턴
// id 조회 안되면 throw MemberNotFoundException (extends RuntimeException)
// pw 일치하지 않으면 throw IncorrectPasswordException (extends RuntimeException)
public MemberDTO getMember(AuthorizeMemberDTO dto) throws SQLException {
String sql = "SELECT id, pw, name, phone, email, address, indate FROM member WHERE id = ?";
try (Connection con = JDBCConnection.getConnection();
PreparedStatement pt = con.prepareStatement(sql)) {
pt.setString(1, dto.id());
ResultSet rs = pt.executeQuery();
if (rs.next()) {
if (rs.getString("pw").equals(dto.pw())) {
String id = rs.getString("id");
String pw = rs.getString("pw");
String name = rs.getString("name");
String email = rs.getString("email");
String phone = rs.getString("phone");
String address = rs.getString("address");
String indate = rs.getString("indate");
return new MemberDTO(id, pw, name, email, phone, address, indate);
} else throw new IncorrectPasswordException();
} else throw new MemberNotFoundException();
}
}
}
View.java
(Interface)MemberDAO 및 GlobalScanner를 멤버로 가진다.
GlobalScanner로 사용자 입력을 받고, 입력받은 데이터를 DAO 메서드를 실행하는 인자로 전달한다.
DAO를 실행하고 반환값이 있다면 콘솔에 출력한다.
public interface View {
MemberDAO dao = MemberDAO.getInstance();
GlobalScanner sc = GlobalScanner.getInstance();
void run() throws SQLException;
}
CreateMemberViewImpl.java
public class CreateMemberViewImpl implements View {
private static CreateMemberViewImpl instance;
private CreateMemberViewImpl() {
}
public static CreateMemberViewImpl getInstance() {
if (instance == null) instance = new CreateMemberViewImpl();
return instance;
}
@Override
public void run() throws SQLException {
String id = sc.nextString("아이디 입력 > ");
if (dao.isExistId(id)) {
System.out.println("이미 존재하는 id 입니다.");
return;
}
String email = sc.nextString("이메일 입력 > ");
if (dao.isExistEmail(email)) {
System.out.println("이미 존재하는 이메일 입니다.");
return;
}
String pw = sc.nextString("패스워드 입력 > ");
String name = sc.nextString("이름 입력 > ");
String phone = sc.nextString("휴대전화 입력 > ");
String address = sc.nextString("주소 입력 > ");
dao.createMember(new MemberDTO(id, pw, name, email, phone, address, null));
System.out.println("회원 등록 완료");
}
}
DeleteMemberViewImpl.java
public class DeleteMemberViewImpl implements View {
private static DeleteMemberViewImpl instance;
private DeleteMemberViewImpl() {
}
public static DeleteMemberViewImpl getInstance() {
if (instance == null) instance = new DeleteMemberViewImpl();
return instance;
}
@Override
public void run() throws SQLException {
AuthorizeMemberDTO member = new AuthorizeMemberDTO(sc.nextString("아이디 입력 > "), sc.nextString("패스워드 입력 > "));
MemberDTO authMember = dao.getMember(member);
dao.deleteMember(authMember.id());
System.out.println("유저정보가 삭제되었습니다.");
}
}
GetAllMemberInfoViewImpl.java
public class GetAllMemberInfoViewImpl implements View {
private static GetAllMemberInfoViewImpl instance;
private GetAllMemberInfoViewImpl() {
}
public static GetAllMemberInfoViewImpl getInstance() {
if (instance == null) instance = new GetAllMemberInfoViewImpl();
return instance;
}
@Override
public void run() throws SQLException {
List<InfoMemberDTO> result = dao.getAllMemberInfo(sc.nextNum(String.format("페이지 번호 입력 (1 - %d 페이지) > ", dao.countPage())));
result.forEach(System.out::println);
}
}
GetMemberInfoViewImpl.java
public class GetMemberInfoViewImpl implements View {
private static GetMemberInfoViewImpl instance;
private GetMemberInfoViewImpl() {
}
public static GetMemberInfoViewImpl getInstance() {
if (instance == null) instance = new GetMemberInfoViewImpl();
return instance;
}
@Override
public void run() throws SQLException {
AuthorizeMemberDTO member = new AuthorizeMemberDTO(sc.nextString("아이디 입력 > "), sc.nextString("패스워드 입력 > "));
InfoMemberDTO result = dao.getMemberInfo(member);
System.out.println(result);
}
}
UpdateMemberViewImpl.java
public class UpdateMemberViewImpl implements View {
private static UpdateMemberViewImpl instance;
private UpdateMemberViewImpl() {
}
public static UpdateMemberViewImpl getInstance() {
if (instance == null) instance = new UpdateMemberViewImpl();
return instance;
}
public void run() throws SQLException {
AuthorizeMemberDTO member = new AuthorizeMemberDTO(sc.nextString("아이디 입력 > "), sc.nextString("패스워드 입력 > "));
MemberDTO origin = dao.getMember(member);
System.out.println("재설정할 항목을 입력해주세요 (enter 'p'-> 다음항목)");
String tempEmail = sc.nextString("새로운 이메일 입력 > ");
if (dao.isExistEmail(tempEmail)) {
System.out.println("이미 존재하는 이메일 입니다.");
return;
}
String email = tempEmail.equals("p") ? origin.email() : tempEmail;
String pw = sc.nextStringOrReplace("새로운 패스워드 입력 > ", "p", origin.pw());
String phone = sc.nextStringOrReplace("새로운 휴대전화 입력 > ", "p", origin.phone());
String address = sc.nextStringOrReplace("새로운 주소 입력 > ", "p", origin.address());
dao.updateMember(new UpdateMemberDTO(origin.id(), pw, email, phone, address));
System.out.println("유저정보 업데이트에 성공했습니다.");
}
}
서비스 로직 중 정상적으로 일어날 수 있는 Exceptions, → 런타임예외를 상속한다
IncorrectPasswordException.java
public class IncorrectPasswordException extends RuntimeException{
public IncorrectPasswordException() {
super("비밀번호가 일치하지 않습니다.");
}
}
MemberNotFoundException.java
public class MemberNotFoundException extends RuntimeException{
public MemberNotFoundException() {
super("존재하지 않는 아이디입니다.");
}
}
유저생성 → 유저조회 실패 확인 (MemberNotFoundException
, IncorrectPasswordException
) → 유저 조회 성공
유저 정보 업데이트 → 업데이트 정보로 로그인 → 유저삭제 → 삭제확인
메뉴 다시보기 → 전체 유저 조회 → 메뉴 이외 입력값 선택 → 종료