Spring 실습

Spring 실습 9일차(SQL 내 여러 테이블에 데이터 넣기)

choco2706 2024. 5. 2. 14:29

SQL 데이터베이스 member테이블에 데이터를 몇개 넣어보자

reg_date는 기본값을 sysdate로 입력해놨다.

 

address 테이블도 하나 만들어준다.

-- 회원 : 주소 = 1 : 1
CREATE TABLE ADDRESS(
    USER_ID VARCHAR2(20),
    POST_CODE VARCHAR2(10),
    LOCATION  VARCHAR2(300),
    CONSTRAINT PK_ADDRESS 
        PRIMARY KEY(USER_ID),
    CONSTRAINT FK_ADDRESS
        FOREIGN KEY(USER_ID)
        REFERENCES MEMBER(USER_ID)
);

 

user_id가 address 테이블의 기본키(PK)이면서 member테이블에서 참조하는 외래키(FK)이다.

 

만들었으면 왼쪽 상단 파일 -> Data Modeler -> 임포트 -> 데이터 딕셔너리를 들어가준다.

 

난 다수리 시스템이라는 곳에 테이블이 만들어져있다. 다음으로 넘어가면

 

다수리 시스템에 접속하고 있는 계정을 체크한 뒤 

 

임포트하려는 테이블을 체크하고 완료를 눌러주면 

두 테이블에 대한 관계를 확인할 수 있다.

 

이제 ADDRESS 테이블에도 데이터를 몇개 넣어주자

※ user_id부분은 member테이블과 맞춰야 한다.

 

address 테이블 데이터 입력

--왼쪽 외부조인(모든 회원정보 출력)
SELECT A.USER_ID, A.USER_NAME, A.PASSWORD, A.REG_DATE, A.COIN
             , B.USER_ID, B.POST_CODE, B.LOCATION
FROM     MEMBER A, ADDRESS B
WHERE A.USER_ID = B.USER_ID(+);

-- ANSI 표준(LEFT OUTER JOIN)
SELECT A.USER_ID, A.USER_NAME, A.PASSWORD, A.REG_DATE, A.COIN
            , B.USER_ID, B.POST_CODE, B.LOCATION
FROM MEMBER A
LEFT OUTER JOIN ADDRESS B
ON A.USER_ID = B.USER_ID;

 

쿼리 실행 결과

 

위 아래 코드 모두 동일한 결과를 출력한다.

더보기
조인 설명 사진

 

이번엔 Card 테이블과 VO를 만들어 Member와 연결해보자

Card

1명의 회원이 여러개의 카드를 소지할 수 있기 때문에 타입은 List<Card>로 만들어준다.

// MEMBER : Card = 1 : N
private List<Card> cardList;

 

중첩된 자바빈즈로 만들어주고 card라는 이름의 VO를 만들어주자.

package kr.or.ddit.vo;

import lombok.Data;

@Data
public class Card {
	// Member 자바빈 클래스의 프로퍼티(식별자)
	private String userId;
	private String no;
	private String validMonth;
}

 

이제 SQL 데이터베이스에 Card 테이블을 만들어준다.

CREATE TABLE CARD(
    USER_ID VARCHAR2(20),
    NO VARCHAR2(30),
    VALID_MONTH VARCHAR2(10),
    CONSTRAINT PK_CARD
        PRIMARY KEY(USER_ID, NO),
    CONSTRAINT FK_CARD
        FOREIGN KEY(USER_ID)
        REFERENCES MEMBER(USER_ID)
);

 

USER_ID와 NO가 복합키로써 기본키(PK) 이면서 MEMBER 테이블에서 참조하는 외래키(FK)이다.

 

복합키 설명 참고

ERD 클라우드에서 표현한 테이블간의 관계

 

Card테이블에 데이터를 몇개 넣어준다.

 

USER_ID와 NO가 같으면 데이터가 들어가지 않는다.

SELECT A.USER_ID, A.USER_NAME, A.PASSWORD, A.REG_DATE, A.COIN
             , B.USER_ID, B.NO, B.VALID_MONTH
FROM     MEMBER A LEFT OUTER JOIN CARD B
ON(A.USER_ID = B.USER_ID);

쿼리 결과문

 

이제 jsp에 카드 정보를 입력할 input요소를 만들어주자

 

<p>
      <!-- list타입으로 받기 때문에 인덱스가 필요하다. -->
      <input type="text" name="cardList[0].no" placeholder="카드1-번호" /><br />
      <input type="text" name="cardList[0].validMonth" placeholder="카드1-유효기간"/><br />
      <input type="text" name="cardList[1].no" placeholder="카드2-번호" /><br />
      <input type="text" name="cardList[1].validMonth" placeholder="카드2-유효기간"/><br />
   </p>

 

List<Card> 타입으로 받아주기 위해 name값에 인덱스를 표시해주어야 한다.

 

입력값
member출력 부분에 들어있다.

 

자기소개

jsp부분에 자기소개 입력할 수 있는 칸을 만들어주자

우선 VO에 introduction을 추가해주고

private String introduction;

 

jsp에 textArea로 추가해주자

<p>
  <textArea rows="6" cols="50" name="introduction" placeholder="자기소개"></textArea>
</p>

 

이제 CRUD를 진행해줄껀데 그 전에 컨트롤러에서 member log부분을 제외한 나머지 log들은 주석처리를 해주자

CRUD

이전에 많이 했기때문에 간략하게만 설명하겠다.

MemberService, MemverServiceImpl, MemberDao, member_SQL.xml을 어노테이션을 포함해 만들어준다.

 

@Autowired 어노테이션으로 의존성 주입까지 완료해주자.

 

@ResponseBody
	@PostMapping("/registerUserId")
	public String registerUserId(String userId, String password
			, String gender, String nationality, String cars
			, String[] carArray, ArrayList<String> carList
			, String hobbys, String[] hobbyArray
			, ArrayList<String> hobbyList, String developer
			, boolean foreigner, Address address
			, Member member) {
//		log.info("registerUserId");
//		log.info("userId : " + userId);
//		log.info("password : " + password);
//		log.info("gender : " + gender);
//		log.info("nationality : " + nationality);
//		log.info("cars : " + cars);
//		log.info("hobbys : " + hobbys);
//		log.info("developer : " + developer);
//		log.info("foreigner : " + foreigner);
//		log.info("address : " + address);
		log.info("member : " + member);
		
//		if(carArray != null) {
//			log.info("------------------");
//			for(String car : carArray) {
//				log.info("carArray[" + car + "]");
//			}
//			log.info("------------------");
//		}
//		
//		if(hobbyArray != null) {
//			log.info("------------------");
//			for(String hobby : hobbyArray) {
//				log.info("hobbyArray[" + hobby + "]");
//			}
//			log.info("------------------");
//		}
		
		// 얜 안됌ㅋㅋㅋㅋ
		if(carList != null) {
			log.info("------------------");
			for(int i = 0; i<carList.size(); i++) {
				log.info("carList[" + carList.get(i) + "]");
			}
			log.info("------------------");
		}
		// 얘도 안됌ㅋㅋㅋㅋㅋㅋㅋ
		if(hobbyList != null) {
			log.info("------------------");
			for(int i = 0; i<hobbyList.size(); i++) {
				log.info("hobbyList[" + hobbyList.get(i) + "]");
			}
			log.info("------------------");
		}
		
		// insert/update/delete는 Controller에서 직접 안한다.
		// Service 계층에서 비즈니스 로직을 수행한다.
		// Insert/Update/Delete의 리턴 타입은 int값
		int result = this.memberService.registerUserId(member);
		log.info("result : " + result);
		
		return "success";
	}

 

대충 이런 형식이 될것이다. 

 

이제 이전에 배웠던 대로 CURD를 진행하자

하기 전에

member안에 들어있는 card와 address값은 각 테이블에 넣어주어야 하기 때문에 Impl부터 조금 수정을 해주어야 한다.

 

일단 mybatisAlias.xml에 가서 alias를 VO마다 설정해주자

<typeAlias type="kr.or.ddit.vo.Member" alias="member" />
<typeAlias type="kr.or.ddit.vo.Address" alias="address" />
<typeAlias type="kr.or.ddit.vo.Card" alias="card" />

Impl

package kr.or.ddit.service.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import kr.or.ddit.dao.MemberDao;
import kr.or.ddit.service.MemberService;
import kr.or.ddit.vo.Address;
import kr.or.ddit.vo.Card;
import kr.or.ddit.vo.Member;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class MemberServiceImpl implements MemberService{
	
	@Autowired
	MemberDao memberDao;

	@Override
	public int registerUserId(Member member) {
		
		// 1) MEMBER
		int result = this.memberDao.registerUserId(member);
		log.info("registerUserId -> result: " + result);
		
		// 2) ADDRESS
		// member에서 address정보 빼오기
		Address address = member.getAddress();
		// address정보에서 비어있는 userId를 member에 있는 userId로 채워넣기
		address.setUserId(member.getUserId());
		log.info("registerUserId -> address : " + address);
		
		// DAO에서 address를 파라미터로 받는 insert 연결
		result += this.memberDao.insertAddress(address);
		log.info("registerUserId -> result(2) :" + result);

		// 3) Card
		// member에서 cardList정보 빼오기
		List<Card> cardList = member.getCardList();
		for (Card card : cardList) {
			// for문을 돌려 각 card에 비어있는 userId를 member에 있는 userId로 채워넣기
			card.setUserId(member.getUserId());
			log.info("registerUserId -> cardList2 :" + cardList);	

			// DAO에서 card를 파라미터로 받는 insert 연결
			result += this.memberDao.insertCard(card);
		}
		log.info("registerUserId -> result(3) :" + result);
		
		return result;
	}
}

DAO

package kr.or.ddit.dao;

import java.util.List;

import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import kr.or.ddit.vo.Address;
import kr.or.ddit.vo.Card;
import kr.or.ddit.vo.Member;

@Repository
public class MemberDao {
	@Autowired
	SqlSessionTemplate sqlSessionTemplate;

	// MEMBER 테이블에 INSERT
	public int registerUserId(Member member) {
		return this.sqlSessionTemplate.insert("member.registerUserId",member);
	}

	// ADDRESS 테이블에 INSERT
	public int insertAddress(Address address) {
		return this.sqlSessionTemplate.insert("member.insertAddress",address);
	}

	public int insertCard(Card card) {
		return this.sqlSessionTemplate.insert("member.insertCard",card);
	}
}

SQL.XML

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="member">
	<!-- INSERT, UPDATE, DELETE의 resultType은 생략(생략 시 int로 반환) -->
	<insert id="registerUserId" parameterType="member">
		INSERT INTO MEMBER(USER_ID, USER_NAME, PASSWORD, REG_DATE, COIN)
		VALUES(#{userId}, #{userName}, #{password}, SYSDATE, #{coin})
	</insert>
	
	<!-- address 테이블에 insert  -->
	<insert id="insertAddress" parameterType="address">
		INSERT INTO ADDRESS(USER_ID, POST_CODE, LOCATION)
     	VALUES(#{userId}, #{postCode}, #{location})
	</insert>
	
	<!-- CARD 테이블에 INSERT
   	Card{userId=c001,no=111,validMonth=24/03}
    -->
	<insert id="insertCard" parameterType="card">
		INSERT INTO ADDRESS(USER_ID, NO, VALID_MONTH)
     	VALUES(#{userId}, #{no}, #{validMonth})
	</insert>
</mapper>

 

위와 같이 작성하면 각 테이블에 데이터가 들어가게 된다.

lprodId 추출

갑자기 lprod로 넘어오게 되었다(이유는 몰?루)

lprod/create 페이지에 lprodId값이 들은 input에 SQL에서 lprodId의 최댓값+1을 해서 페이지가 실행되면 자동으로 들어가게 해보자

 

(ajax 활용)

기본 입력 창

 

// create.jsp
function getLprodId(){
	$.ajax({
		url:"getLprodId",
		type:"post",
		dataType:"text",
		success:function(result){
			console.log("result : ",result)
			$('input[name="lprodId"]').val(result);
		}
	})
}

$(function(){
	getLprodId() // 함수 호출
)

 

/*
	   요청URI : /lprod/getLprodId
	   요청파라미터 : 
	   요청방식 : post
	   
	   응답타입 : int
	   응답데이터 : LPROD 테이블의 MAX(LPROD_ID)+1
   */
   @ResponseBody
   @RequestMapping(value="/getLprodId", method=RequestMethod.POST)
   public int getLprodId() {
	   int result = this.lprodService.getLprodId();
	   log.info("result : " + result);
	   
	   return result;
   }

 

Service와 Impl, Dao 연결하는 방식은 스킵하겠다

 

<select id="getLprodId" resultType="Integer">
  SELECT MAX(LPROD_ID)+1 FROM LPROD
</select>

 

여기가 조금 다른데 INSERT, UPDATE, DELETE의 경우 기본 반환값이 int값이라 resultType을 적어주지 않아도 되지만

select는 resultType을 Integer라고 명시해야 int값으로 반환해준다.

$('input[name="lprodId"]').val(result);

 

위 스크립트의 경우 jQuery 문법으로 val()의 괄호 안에 return 받은 값을 넣어주면 value값으로 들어가게 된다

$('input[name="lprodId"]').val() = result;

위와 같이 쓰면 안된다는 소리

 

실행 결과

 

내 SQL안의 MAX값이 12이기 때문에 + 1이 된 값인 13이 자동으로 들어가있게 된다.

상품 코드 자동 생성

 

+ 버튼을 누르면 상품 아이디, 상품명, 가격이 한줄씩 추가되고

- 버튼을 누르면 한줄씩 지워지는 스크립트와

상품 분류 코드를 작성하고 상품 코드 자동 생성 버튼을 클릭 시 상품 아이디 란에 자동으로 값이 채워지게 해보자

 

ex) 상품 아이디 2줄, 상품 분류 코드 = P502

상품 아이디1 = P5021

상품 아이디2 = P5022

<body>
<h1>상품분류 등록</h1>

<!--
   요청URI : /lprod/create
   요청 파라미터 :{lprodId=14, lprodGu=P501, lprodNm=분식류}
   요청방식  :post
-->
<form action="/lprod/create" method="post">
   <p> 상품분류 아이디 : <input type="number" name="lprodId" placeholder="상품분류 아이디(ex.14)"> </p>
   <p> 
   		상품분류 코드 : <input type="text" name="lprodGu" required placeholder="상품분류 코드(ex.P501)">
   		<button type="button" class="btn btn-primary btn-xs col-sm-1">상품코드 자동생성</button>
   </p>
   <p> 상품분류 명 : <input type="text" name="lprodNm" placeholder="상품분류 명(ex. 분식류)"> </p>
   <p id="pFunc">
      <button type="button" class="btn btn-info btn-sm col-sm-1" id="btnPlus" >+</button>
      <button type="button" class="btn btn-danger btn-sm col-sm-1" id="btnMinus" >-</button>
    </p>
    <!-- 
      1) + : 영역이 하나 생김
      2) = : 마지막 영역 삭제
     -->
    <p class="pArea">
      <input type="text" name="productVOList[0].productId" id="productId0" class="form-control col-sm-3 float-left" placeholder="상품아이디">
      <input type="text" name="productVOList[0].pname" 	   id="pname0" class="form-control col-sm-3 float-left" placeholder="상품명">
      <input type="text" name="productVOList[0].unitPrice" id="unitPrice0" class="form-control col-sm-3 float-left" placeholder="가격">
    </p>
    <p style="clear: both;"></p>
    <p>
       <input type="button" id="btnSubmit" value="즉시삭제">
    </p>
</form>

</body>

 

/* 
      before : 선택한 요소의 앞에 내용 삽입
         after : 선택한 요소의 뒤에 내용 삽입
         prepend : 선택한요소의 자식요소 앞에 내용삽입
         append : 선택한요소의 자식요소 뒤에 내용삽입
   */
   //상품추가
   $("#btnPlus").on("click", function(){
   		// class 속성 값이 pArea인 요소의 갯수
		let pAreaLen = $(".pArea").length; 
		
        // name값과 id값을 중복되게 하지 않기 위해 pAreaLen값을 함께 부여해준다.
		let str ="<p class='pArea'>";
			str +="<input type='text' name='productVOList["+pAreaLen+"].productId' id='productId"+pAreaLen+"' class='form-control col-sm-3 float-left' placeholder='상품아이디' />";
			str +="<input type='text' name='productVOList["+pAreaLen+"].pname' 	 id='pname"+pAreaLen+"' class='form-control col-sm-3 float-left' placeholder='상품명' />";
			str +="<input type='text' name='productVOList["+pAreaLen+"].unitPrice' id='unitPrice"+pAreaLen+"' class='form-control col-sm-3 float-left clear-both' placeholder='가격' />";
			str +="</p>";
			str +="<p style='clear:both;'></p>";
            
            // id가 btnSubmit인 요소의 위에 str을 추가한다
			$("#btnSubmit").before(str);
   });

 	//next() 메서드는 선택한 요소의 다음 형제 요소를 반환함
   //   이 메서드는 DOM 요소의 다음 형제를 따라 이동함
   //next() 메서드와 유관한 메서드는 nextAll()과
   //   nextUntil() 메서드가 있음
   //nextAll() 메서드 : 선택한 요소의 다음 형제 요소를 모두 반환
   //nextUntil() 메서드 : 주어진 주 매개변수 사이에 있는 
   //   모든 다음 형제 요소를 반환함
   //<li> 요소 중에서 클래스 이름이 start인 요소의 형제 다음에 오는 요소를 선택함
   // - 버튼 클릭 시
   $("#btnMinus").on("click",function(){
   
   		// class 속성 값이 pArea인 요소의 갯수
	  	let pAreaLen = $(".pArea").length;
		console.log("pAreaLen",pAreaLen)

		// 갯수가 1개면 삭제가 안된다.
		if(pAreaLen < 2){
			alert("최소 하나는 존재해야 합니다.");
			return;
		}
		
        // 요소 삭제
	  	$(".pArea").last().next().remove();
      	$(".pArea").last().remove();

   })

 

// class 속성명이 btn-primary인 요소를 클릭했을 때
$('.btn-primary').on('click', function(){
      let pArea = $(".pArea"); 
      // input의 name값이 lprodGU인 요소의 value
      let lprodGu = $("input[name='lprodGu']").val();
	  
      // pArea의 갯수만큼 반복
      for(let i = 0; i<pArea.length; i++){
         console.log(lprodGu+(i+1));
         
         // id값이 productId + 1,2,3...인 요소의 value에 lprodGu(P502)+1,2,3...를 넣는다
         // JS에서는 문자 + 숫자(문자열로 자동 변경) = 문자형식이기 때문에
         // P502 + 1,2,3 ... = P5021, P5022, P5023... 이 된다.
         $("input[id='productId"+i+"']").val(lprodGu+(i+1));
      }   
   })

 

결과

 

9일차 종료~!