풀스택개발자_대학교 정규과정/JSP_Example
JSP 간단 회원관리 과제: 마기창
- -
db의 데이터를 처리하는 클래스 : user_info_dao.java
package com.example.jspexam;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
public class user_info_dao {
private static user_info_dao instance = new user_info_dao();
// 2. 외부에서 생성자를 호출할 수 없도록 생성자에 private제한을 붙임
private Connection conn;
private PreparedStatement pstmt;
private DataSource ds;
public user_info_dao() {
try {
Context init = new InitialContext();
ds = (DataSource) init.lookup("java:comp/env/jdbc/OracleDB");
} catch (NamingException e) {
throw new RuntimeException(e);
}
}
public static user_info_dao getInstance() {
return instance;
}
public user_info Member_info(String selectId) {
user_info info = new user_info();
String sql = "select * from user_info where inputId = ?";
try {
// conn 생성
conn = ds.getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, selectId);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
info.setInputId(rs.getString(1));//로그인 유저의 이메일
info.setInputPW(rs.getString(2));//로그인 유저의 이메일
info.setInputEm(rs.getString(3));//로그인 유저의 이메일
info.setInputName(rs.getString(4));//로그인 유저의 이름
info.setDatepicker(rs.getString(5));//로그인 유저의 이름
info.setSelf(rs.getString(6));//로그인 유저의 이름
info.setStrCheckHoby(rs.getString(7));//로그인 유저의 이름
}
pstmt.close();
} catch (SQLException e) {
throw new RuntimeException(e);
} catch (Exception e) {
e.printStackTrace();
}
return info;
}
public user_info Member_info(String inputId, String inputPW) {
System.out.println("Member_info id+pw 실행");
user_info info = null;
String sql = "select * from user_info where inputId = ? AND inputPW = ?";
try {
// conn 생성
conn = ds.getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, inputId);
pstmt.setString(2, inputPW);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
info = new user_info();
info.setInputId(rs.getString(1));//로그인 유저의 이메일
info.setInputPW(rs.getString(2));//로그인 유저의 이메일
info.setInputEm(rs.getString(3));//로그인 유저의 이메일
info.setInputName(rs.getString(4));//로그인 유저의 이름
info.setDatepicker(rs.getString(5));//로그인 유저의 이름
info.setSelf(rs.getString(6));//로그인 유저의 이름
info.setStrCheckHoby(rs.getString(7));//로그인 유저의 이름
}
pstmt.close();
} catch (SQLException e) {
throw new RuntimeException(e);
} catch (Exception e) {
e.printStackTrace();
}
return info;
}
public Map<String, user_info> selectUserInfo() {
System.out.println("selectUserInfo 실행");
String sql = "select * from user_info";
Map<String, user_info> user_info_map = new HashMap<String, user_info>();
try {
// conn 생성
conn = ds.getConnection();
pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
user_info info = new user_info();
info.setInputId(rs.getString(1));
info.setInputPW(rs.getString(2));
info.setInputEm(rs.getString(3));
info.setInputName(rs.getString(4));
info.setDatepicker(rs.getString(5));
info.setSelf(rs.getString(6));
info.setStrCheckHoby(rs.getString(7));
user_info_map.put(info.getInputId(), info);
}
pstmt.close();
} catch (SQLException e) {
throw new RuntimeException(e);
} catch (Exception e) {
e.printStackTrace();
}
return user_info_map;
}
public boolean deleteUser(String removeId) {
System.out.println("deleteUser 실행");
String sql = "DELETE FROM user_info WHERE inputId = ?";
boolean flag = false;
try {
// conn 생성
conn = ds.getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, removeId);
if (pstmt.executeUpdate() != 0)//삭제 성공시
flag = true;
pstmt.close();
} catch (SQLException e) {
throw new RuntimeException(e);
} catch (Exception e) {
e.printStackTrace();
}
return flag;
}
public boolean signUp(user_info userInfo) {
System.out.println("deleteUser 실행");
String sql = "INSERT INTO user_info (inputId,inputPW,inputEm,inputName,datepicker,self,checkHoby) VALUES (?,?,?,?,?,?,?)";
boolean flag = true;
try {
// conn 생성
conn = ds.getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, userInfo.getInputId());
pstmt.setString(2, userInfo.getInputPW());
pstmt.setString(3, userInfo.getInputEm());
pstmt.setString(4, userInfo.getInputName());
pstmt.setString(5, userInfo.getDatepicker());
pstmt.setString(6, userInfo.getSelf());
pstmt.setString(7, userInfo.getStrCheckHoby());
pstmt.executeUpdate();
} catch (SQLIntegrityConstraintViolationException e) {
flag = false;
} catch (Exception e) {
e.printStackTrace();
}
return flag;
}
}
유저의 dto(데이터클래스).java
package com.example.jspexam;
import java.util.Arrays;
public class user_info {
private String inputId;
private String inputPW;
private String inputEm;
private String inputName;
private String datepicker;
private String self;
private String[] checkHoby;
private String strCheckHoby;
public String getInputId() {
return inputId;
}
public void setInputId(String inputId) {
this.inputId = inputId;
}
public String getInputPW() {
return inputPW;
}
public void setInputPW(String inputPW) {
this.inputPW = inputPW;
}
public String getInputEm() {
return inputEm;
}
public void setInputEm(String inputEm) {
this.inputEm = inputEm;
}
public String getInputName() {
return inputName;
}
public void setInputName(String inputName) {
this.inputName = inputName;
}
public String getDatepicker() {
return datepicker;
}
public void setDatepicker(String datepicker) {
this.datepicker = datepicker;
}
public String getSelf() {
return self;
}
public void setSelf(String self) {
this.self = self;
}
public String[] getCheckHoby() {
return checkHoby;
}
public void setCheckHoby(String[] checkHoby) {
this.checkHoby = checkHoby;
System.out.println(Arrays.toString(checkHoby));
}
public String getStrCheckHoby() {
if (checkHoby != null) {
String strCheckHoby = "";
for (int i = 0; i < checkHoby.length; i++) {
strCheckHoby += checkHoby[i];
}
System.out.println(strCheckHoby);
return strCheckHoby;
}
return strCheckHoby;
}
public void setStrCheckHoby(String strCheckHoby) {
this.strCheckHoby = strCheckHoby;
}
}
loginForm.jsp(로그인)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<jsp:useBean id="dao" class="com.example.jspexam.user_info_dao" scope="request"/>
<html>
<head>
<title>Title</title>
</head>
<link rel="stylesheet" href="/css/loginFm.css">
<link rel="stylesheet" href="/css/bootstrap.css">
<!-- CSS code를 복사해 붙여넣은 PersonInfo, 본인 파일명과 root를 작성해주세요 --!>
<script src="/JS/bootstrap.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet">
<body>
<div class="login-container animated fadeInDown bootstrap snippets bootdeys">
<div class="loginbox bg-white">
<div class="loginbox-title">SIGN IN</div>
<div class="loginbox-social">
<div class="social-title ">Connect with Your Social Accounts</div>
</div>
<div class="loginbox-or">
<div class="or-line"></div>
<div class="or">OR</div>
</div>
<form method="post" action="/exam_1011/loginProcess.jsp">
<div class="loginbox-textbox">
<input type="text" class="form-control" placeholder="ID" name="inputId">
</div>
<div class="loginbox-textbox">
<input type="text" class="form-control" placeholder="Password" name="inputPW">
</div>
<div class="loginbox-submit">
<input type="submit" class="btn btn-primary btn-block" value="Login" STYLE="width: 100%">
</div>
</form>
<div class="loginbox-signup">
<a href="/exam_1011/joinForm.jsp">Sign Up With Email</a>
</div>
</div>
<div class="logobox">
</div>
</div>
</body>
</html>
loginProcess.jsp(로그인 로직)
<%@ page import="com.example.jspexam.user_info_dao" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<jsp:useBean id="userInfo" class="com.example.jspexam.user_info" scope="request"/>
<jsp:setProperty name="userInfo" property="inputId" param="inputId"/>
<jsp:setProperty name="userInfo" property="inputPW" param="inputPW"/>
<%
userInfo = user_info_dao.getInstance().Member_info(userInfo.getInputId(), userInfo.getInputPW());
if (userInfo != null) {
out.println("<script>");
out.println("alert('로그인이 성공되었습니다');");
out.println("location.href = '/exam_1011/Main.jsp';");
out.println("</script>");
session.setAttribute("user_info",userInfo);
} else {
out.println("<script>");
out.println("alert('유저 정보가 없습니다');");
out.println("location.href = '/exam_1011/loginForm.jsp';");
out.println("</script>");
}
%>
<html>
<head>
<title>Title</title>
</head>
<body>
</body>
</html>
joinForm.jsp(회원가입)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="http://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
</head>
<style>
body {
background-image: url("/img/back.png");
}
table {
border-spacing: 0px;
border: 1px solid grey;
}
td, th {
width: 500px;
border: 1px solid black;
padding: 10px;
}
tr td:nth-child(1) {
background-color: lightgrey;
font-style: inherit;
text-align: center;
}
tr td:nth-child(2) {
background-color: white;
}
select {
width: 30px;
}
.errorPattern {
border: 5px solid red;
}
.checkedPattern {
border: 5px solid green;
}
</style>
<body>
<!--<audio controls autoplay loop style="visibility: hidden">-->
<!-- <source src="음악.mp3" type="audio/mp3">-->
<!--</audio>-->
<form action="/exam_1011/joinProcess.jsp" method="post" id="goForm" accept-charset="UTF-8">
<table align="center">
<thead>
<th style="background-color: lightblue" colspan="2">회원 기본 정보</th>
</thead>
<tbody>
<tr>
<td style="width: 20%"><label><strong>아이디:</strong></label></td>
<td><input type="text" maxlength="12" id="inputId" name="inputId" required> 4~12자의 영문 대소문자와 숫자로만 입력</td>
</tr>
<tr>
<td style="width: 20%"><label><strong>비밀번호:</strong></label></td>
<td><input type="password" id="inputPW" name="inputPW" maxlength="12" title="4~12자의 영문 대소문자와 숫자로만 입력하시오"
required> 4~12자의
영문
대소문자와
숫자로만 입력
</td>
</tr>
<tr>
<td style="width: 20%"><label><strong>비밀번호확인:</strong></label></td>
<td><input type="password" maxlength="12" id="inputPWcheck" name="inputPWcheck" required></td>
</tr>
<tr>
<td style="width: 20%"><label><strong>메일주소:</strong></label></td>
<td><input type="email" id="inputEm" name="inputEm" required> 예) id@domain.com</td>
</tr>
<tr>
<td style="width: 20%"><label><strong>이름:</strong></label></td>
<td><input type="text" id="inputName" name="inputName" required></td>
<tr>
<tr>
<th style="background-color: lightblue" colspan="2">개인 신상 정보</th>
</tr>
<%-- <tr>--%>
<%-- <td style="width: 20%"><label><strong>주민등록번호:</strong></label></td>--%>
<%-- <td><input type="number"> 예) 1234561234567</td>--%>
<%-- <tr>--%>
<tr>
<td style="width: 20%"><label><strong>생일:</strong></label></td>
<td><input type="text" id="datepicker" name="datepicker"></td>
</tr>
<tr>
<td style="width: 20%"><label><strong>관심분야:</strong></label></td>
<td>
<input type="checkbox" name="checkHoby" value="computer">
컴퓨터 <input type="checkbox" name="checkHoby" value="net">인터넷 <input
type="checkbox" name="trip" value="travel">여행 <input type="checkbox" name="checkHoby"
value="movie">영화감상 <input
type="checkbox"
name="checkHoby" value="music">음악감상
</td>
<tr>
<tr style="height: 200px">
<td style="width: 20%"><label><strong>자기소개:</strong></label></td>
<td>
<textarea style="width: 80%; height:180px" required name="self"></textarea>
</td>
<tr>
</tbody>
</table>
<div align="center">
<br>
<input type="submit" value="회원 가입" id="sbButton">
<input type="reset" value="다시 입력">
</div>
</form>
</body>
<script type="text/javascript">
//비밀번호 영문자+숫자+특수조합(8~25자리 입력) 정규식
const inputId = document.getElementById("inputId");
const inputPW = document.getElementById("inputPW");
const inputPWcheck = document.getElementById("inputPWcheck");
const inputEm = document.getElementById("inputEm");
const inputName = document.getElementById("inputName");
const inputYear = document.getElementById("inputYear");
const regexId = /[a-zA-Z0-9]{4,12}/;
const regEmail = /([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.) |(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/;
const regName = /^[a-zA-Z]{2,}|[가-힣]{2,}$/;
const regBornYear = /19[0-9][0-9]|20\d{2,4}/;
inputId.addEventListener("input", function (event) {
checkForm(regexId, inputId);
})
inputPW.addEventListener("input", function (event) {
checkForm(regexId, inputPW);
})
inputEm.addEventListener("input", function (event) {
checkForm(regEmail, inputEm);
})
inputName.addEventListener("input", function (event) {
checkForm(regName, inputName);
})
// inputYear.addEventListener("input", function (event) {
// checkForm(regBornYear, inputYear);
// })
inputPWcheck.addEventListener("input", function (event) {
if ((inputPWcheck.value).length === 0) {
inputPWcheck.removeAttribute('class')
} else {
if (!(inputPW.value === inputPWcheck.value)) {//패턴과 일치하지 않는다면
inputPWcheck.classList.remove('checkedPattern');
inputPWcheck.classList.add('errorPattern');
} else {
inputPWcheck.classList.remove('errorPattern');
inputPWcheck.classList.add('checkedPattern');
}
}
})
function checkForm(pattern, element) {
if ((element.value).length === 0) {
element.removeAttribute('class')
} else {
if (!pattern.test(element.value)) {//패턴과 일치하지 않는다면
element.classList.remove('checkedPattern');
element.classList.add('errorPattern');
element.setCustomValidity('[양식에 맞게 입력하시오]');
} else {
element.classList.remove('errorPattern');
element.classList.add('checkedPattern');
element.setCustomValidity('');
}
}
}
$(function () {
//input을 datepicker로 선언
$("#datepicker").datepicker({
dateFormat: 'yy-mm-dd' //달력 날짜 형태
, showOtherMonths: true //빈 공간에 현재월의 앞뒤월의 날짜를 표시
, showMonthAfterYear: true // 월- 년 순서가아닌 년도 - 월 순서
, changeYear: true //option값 년 선택 가능
, changeMonth: true //option값 월 선택 가능
, showOn: "both" //button:버튼을 표시하고,버튼을 눌러야만 달력 표시 ^ both:버튼을 표시하고,버튼을 누르거나 input을 클릭하면 달력 표시
, buttonImage: "http://jqueryui.com/resources/demos/datepicker/images/calendar.gif" //버튼 이미지 경로
, buttonImageOnly: true //버튼 이미지만 깔끔하게 보이게함
, buttonText: "선택" //버튼 호버 텍스트
, yearSuffix: "년" //달력의 년도 부분 뒤 텍스트
, monthNamesShort: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'] //달력의 월 부분 텍스트
, monthNames: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'] //달력의 월 부분 Tooltip
, dayNamesMin: ['일', '월', '화', '수', '목', '금', '토'] //달력의 요일 텍스트
, dayNames: ['일요일', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일'] //달력의 요일 Tooltip
, minDate: "-5Y" //최소 선택일자(-1D:하루전, -1M:한달전, -1Y:일년전)
, maxDate: "+5y" //최대 선택일자(+1D:하루후, -1M:한달후, -1Y:일년후)
});
//초기값을 오늘 날짜로 설정해줘야 합니다.
$('#datepicker').datepicker('setDate', 'today'); //(-1D:하루전, -1M:한달전, -1Y:일년전), (+1D:하루후, -1M:한달후, -1Y:일년후)
});
</script>
</html>
joinProcess.jsp(회원가입 로직)
<%@ page import="com.example.jspexam.user_info_dao" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%request.setCharacterEncoding("UTF-8");%>
<jsp:useBean id="userInfo" class="com.example.jspexam.user_info" scope="request"/>
<jsp:setProperty name="userInfo" property="*"/>
<%
if (user_info_dao.getInstance().signUp(userInfo)) {
out.println("<script>");
out.println("alert('회원가입이 성공되었습니다');");
out.println("location.href = '/exam_1011/loginForm.jsp';");
out.println("</script>");
} else {
//userid가 중복될 경우
out.println("<script>");
out.println("alert('존재하는 아이디입니다..');");
out.println("location.href = '/exam_1011/joinForm.jsp';");
out.println("</script>");
}
%>
<html>
<head>
<title>Title</title>
</head>
<body>
</body>
</html>
Main.jsp(메인화면)
<%@ page import="com.example.jspexam.user_info" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
user_info info = (user_info) session.getAttribute("user_info");
%>
<html>
<head>
<title>Title</title>
</head>
<link rel="stylesheet" href="/css/profile.css.css">
<link rel="stylesheet" href="/css/bootstrap.css">
<!-- CSS code를 복사해 붙여넣은 PersonInfo, 본인 파일명과 root를 작성해주세요 --!>
<script src="/JS/bootstrap.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet">
<body>
<section class="section about-section gray-bg" id="about">
<div class="container">
<div class="row align-items-center flex-row-reverse">
<div class="col-lg-6">
<div class="about-text go-to">
<h3 class="dark-color">About Me</h3>
<h3><%=info.getSelf()%>
</h3>
<div class="row about-list">
<div class="col-md-6">
<div class="media">
<label>Birthday</label>
<p><%=info.getDatepicker()%>></p>
</div>
</div>
<div class="col-md-6">
<div class="media">
<label>E-mail</label>
<p><%=info.getInputEm()%>
</p>
</div>
<div class="media">
<label>Name</label>
<p><%=info.getInputName()%>
</p>
</div>
<div class="media">
<label>관심분야</label>
<p><%=info.getStrCheckHoby()%>
</p>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="about-avatar">
<img src="https://bootdey.com/img/Content/avatar/avatar7.png" title="" alt="">
</div>
</div>
</div>
</div>
<%
if (info.getInputId().equals("admin")) {
%>
<a href="/exam_1011/Member_list.jsp">회원목록</a>
<%}%>
</section>
</body>
</html>
Member_list.jsp(관리자가 유저들의 목록을 확인하는 페이지)
<%@ page import="java.util.*" %>
<%@ page import="com.example.jspexam.user_info" %>
<%@ page import="com.example.jspexam.user_info_dao" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
Map<String, user_info> user_info_map = user_info_dao.getInstance().selectUserInfo();
%>
<html>
<head>
<title>Title</title>
</head>
<body>
<h2>회원 목록</h2>
<Hr>
<%
if (user_info_map != null)
for (Map.Entry<String, user_info> entry : user_info_map.entrySet()) {
%>
<h2><%=entry.getKey()%> * <%=entry.getValue().getInputName()%>
<a href="./Member_Info.jsp?selectId=<%=entry.getKey()%>">상세보기</a>
<a href="./Member_delete.jsp?removeId=<%=entry.getKey()%>">삭제</a>
</h2>
<%}%>
<a href="./Main.jsp">프로필로 돌아가기</a>
</body>
</html>
Member_Info.jsp(유저의 상세정보)
<%@ page import="com.example.jspexam.user_info_dao" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<jsp:useBean id="userInfo" class="com.example.jspexam.user_info" scope="request"/>
<%
userInfo = user_info_dao.getInstance().Member_info(request.getParameter("selectId"));
%>
<html>
<head>
<title>Title</title>
<link rel="stylesheet" href="/css/profile.css.css">
<link rel="stylesheet" href="/css/bootstrap.css">
<!-- CSS code를 복사해 붙여넣은 PersonInfo, 본인 파일명과 root를 작성해주세요 --!>
<script src="/JS/bootstrap.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet">
</head>
<body>
<section class="section about-section gray-bg" id="about">
<div class="container">
<div class="row align-items-center flex-row-reverse">
<div class="col-lg-6">
<div class="about-text go-to">
<h3 class="dark-color">About Me</h3>
<h3><%=userInfo.getSelf()%>
</h3>
<div class="row about-list">
<div class="col-md-6">
<div class="media">
<label>Birthday</label>
<p><%=userInfo.getDatepicker()%>></p>
</div>
</div>
<div class="col-md-6">
<div class="media">
<label>E-mail</label>
<p><%=userInfo.getInputEm()%>
</p>
</div>
<div class="media">
<label>Name</label>
<p><%=userInfo.getInputName()%>
</p>
</div>
<div class="media">
<label>관심분야</label>
<p><%=userInfo.getStrCheckHoby()%>
</p>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="about-avatar">
<img src="https://bootdey.com/img/Content/avatar/avatar7.png" title="" alt="">
</div>
</div>
</div>
</div>
<a href="/exam_1011/Member_list.jsp">회원목록으로 돌아가기</a>
</section>
</body>
</html>
Member_delete.jsp(유저정보 삭제)
<%@ page import="com.example.jspexam.user_info_dao" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
if (user_info_dao.getInstance().deleteUser(request.getParameter("removeId"))) {
response.sendRedirect("/exam_1011/Member_list.jsp");
} else {
out.println("<H3>DB삭제실패</h3>");
}
%>
<html>
<head>
<title>Title</title>
</head>
<body>
</body>
</html>
'풀스택개발자_대학교 정규과정 > JSP_Example' 카테고리의 다른 글
MVC 2패턴 흐름과 게시판 흐름도 (0) | 2022.10.14 |
---|---|
session으로 장바구니 기능 구현 (0) | 2022.09.20 |
forward 사용하지 않고 값 넘겨주기 (0) | 2022.09.19 |
jsp foward를 사용하여 parameter 값 전송 (0) | 2022.09.19 |
jsp:include 예제 (0) | 2022.09.16 |
Contents
소중한 공감 감사합니다