Spring 실습

Spring 실습 15일차

choco2706 2024. 5. 13. 17:42

ERwin에 오라클 테이블을 연동

 

File - New 를 통해 새로운 모델을 만들어주고

 

상단바의 Tools -> Reverse Engineer 클릭

 

이후 사진과 같이 진행

 

 

가져올 데이터베이스의 계정명, 비밀번호를 작성하고 Connect를 눌러준다.

 

※ 오류가 아마 뜰 수도 있는데 무시하고 확인을 계속 눌러주면 된다.

 

<설계3단계>
개념적 설계 : 개체(Entity), 관계(Relationship)설정 후 그린것까지
논리적 설계 : 개념적 설계 후 속성(Attribute), P.K/F.K제약사항, 정규화
---------------------------------------- 설계 완료----------------------------------------
물리적 설계 : 속성의 영문명, 자료형, 크기. 역정규화

 

 

위 설계 3단계는 정처기에도 나올 수 있으니 외워두자.

 

식별 비식별 관계

 

 

이렇게 O 표시가 되어있는 것들이 있을텐데 zero의 의미이기 때문에 지워주는게 낫다

저 선을 더블클릭해보자.

수정 부분

 

위 사진과 같이 수정해줘야 나중에 회사에서 감리에 걸리지 않을 수 있다.

수정 후

 

 

국적을 공통 코드로 가져와보기

지난 시간에 만들어놓은 notionalityCodeMap이라는 맵 안에 있는 국적을

이번에는 테이블의 공통 코드로 받아와보자.

 

그 전에 ComCodeVO와 ComCodeDetailVO를 만들어주고, myBatisAlias에도 추가해주자.

package kr.or.ddit.vo;

import java.util.List;

import lombok.Data;

@Data
public class ComCodeVO {
	private String comCode;
	private String comCodeNm;
	private List<ComCodeDetailVO> comCodeDetailVOList;
}
package kr.or.ddit.vo;

import lombok.Data;

@Data
public class ComCodeDetailVO {
	private String comCodeDetail;
	private String comCodeDetailNm;
	private String comCode;
}

 

그 다음 mapper라는 패키지를 만들어준다.

패키지 경로

 

그 안에 ComCodeMapper라는 이름의 Interface를 하나 만들어준다.

 

이제 MemberController에서 Dao를 쓰지 않고 mapper로 다이렉트로 접근한다.

@Autowired
ComCodeMapper comCodeMapper;
//	      국적을 공통코드로부터 가져와보자
	   ComCodeVO comCodeVO = this.comCodeMapper.getComCode("NATN");
	   log.info("comCodeVO : " + comCodeVO);
	   
	   model.addAttribute("comCodeVO",comCodeVO);

 

package kr.or.ddit.mapper;

import kr.or.ddit.vo.ComCodeVO;

public interface ComCodeMapper {
//    국적을 공통코드로부터 가져와보자
	public ComCodeVO getComCode(String comCode);

}

 

아까 만들어놓은 Interface에는 이런식으로 만들어진다.

 

comCode_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="kr.or.ddit.mapper.ComCodeMapper">

   <!-- COM_CODE (1) -->
   <resultMap type="comCodeVO" id="comCodeMap">
      <result property="comCode" column="COM_CODE"/>
      <result property="comCodeNm" column="COM_CODE_NM"/>
      <collection property="comCodeDetailVOList" resultMap="comCodeDetailMap"></collection>
   </resultMap>

   <!-- COM_CODE_DETAIL (N) -->
   <resultMap type="comCodeDetailVO" id="comCodeDetailMap">
      <result property="comCodeDetail" column="COM_CODE_DETAIL"/>
      <result property="comCodeDetailNm" column="COM_CODE_DETAIL_NM"/>
      <result property="comCode" column="COM_CODE"/>
   </resultMap>

   <!-- 
      // 국적을 공통코드로부터 가져와보자
      public ComCodeVO getComCode(String comCode);
    -->
   <select id="getComCode" parameterType="String"  resultMap="comCodeMap">
      SELECT A.COM_CODE, A.COM_CODE_NM
      ,  B.COM_CODE_DETAIL, B.COM_CODE_DETAIL_NM, B.COM_CODE
      FROM     COM_CODE A, COM_CODE_DETAIL B
      WHERE  A.COM_CODE = B.COM_CODE
      AND A.COM_CODE = #{comCode}
      ORDER BY B.COM_CODE_DETAIL
   </select>

</mapper>

 

※ 체크할 점

namespace는 ComCodeMapper의 경로 전체를 적어주어여하고, select의 id값은 interface의 변수명과 동일해야 한다.

 

1:N의 관계이기 때문에 resultMap으로 작성해주어야 한다.

 

다만. 어노테이션 없이 사용하기 위해선 root-context.xml을 수정해주어야 한다.

<!-- Mapper 인터페이스 설정 
   개발자가 직접 DAO를 설정하지 않아도
   자동으로 Mapper 인터페이스를 활용하는 객체를 생성하게 됨
   .**. -> (중첩된)패키지 하위의 모든 것 
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  <property name="basePackage" value="kr.or.ddit.**.mapper" />
</bean>

 

위 코드는 DAO를 사용하지 않아도 mapper를 사용할 수 있게 해주는 역할을 한다.

 

이제 registerForm05.jsp로 넘어가서 아래 코드를 추가해주자.

<!-- 국적을 공통코드로부터 가져와보자 -->
<select id="nationality" name="nationality">
    <c:forEach var="comCodeDetailVO" items="${comCodeVO.comCodeDetailVOList}" varStatus="stat">
        <option value="${comCodeDetailVO.comCodeDetail}">${comCodeDetailVO.comCodeDetailNm}</option>
    </c:forEach>
</select>

jsp 실행 결과

 

위에서 model에 담긴 comCodeVO에서 c:forEach를 이용해 comCodeVODetailVOList만큼 반복하여 꺼내고 그 이름을 comCodeDetailVO로 하기로 한다.

 

반복하여 꺼낸 comCodeDetailVO 에서 comCodeDetail과 DetailNm을 꺼내 option태그의 속성으로 사용한다.

 

 

입력과 검증

스프링 MVC는 Bean Validation 기능을 이용해 요청 파라미터 값이 바인딩된
도메인 클래스(또는 커맨드 클래스)의 입력값 검증을 함

 Bean Validation이 제공하는 제약 애너테이션
  - NotNull : 빈 값 체크(int타입)
  - NotBlank : null 체크, trim후 길이가 0인지 체크(String타입)
  - Size : 글자 수 체크
  - Email : 이메일 주소 형식 체크
  - Past : 오늘보다 과거 날짜(ex. 생일)
  - Future : 미래 날짜 체크(ex. 예약일)
  - AssertFalse : false 값만 통과 가능
  - AssertTrue : true 값만 통과 가능
  - DecimalMax(value=) : 지정된 값 이하의 실수만 통과 가능
  - DecimalMin(value=) : 지정된 값 이상의 실수만 통과 가능
  - Digits(integer=,fraction=) : 대상 수가 지정된 정수와 소수 자리수보다 적을 경우 통과 가능
  - Future : 대상 날짜가 현재보다 미래일 경우만 통과 가능
  - Past : 대상 날짜가 현재보다 과거일 경우만 통과 가능
  - Max(value) : 지정된 값보다 아래일 경우만 통과 가능
  - Min(value) : 지정된 값보다 이상일 경우만 통과 가능
  - NotNull : null 값이 아닐 경우만 통과 가능
  - Null : null일 겨우만 통과 가능
  - Pattern(regex=, flag=) : 해당 정규식을 만족할 경우만 통과 가능
  - Size(min=, max=) : 문자열 또는 배열이 지정된 값 사이일 경우 통과 가능
  - Valid : 대상 객체의 확인 조건을 만족할 경우 통과 가능

 

환경 설정

pom.xml

<!-- 입력값을 검증하기 위한 라이브러리 의존 관계 정의 시작 
  스프링 
  M(Model) : Service, ServiceImple, Mapper
  V(View) : JSP
  C(Controller) : Controller 
  Bean(자바빈 클래스, ArticleVO) Validation(유효성검사) 기능을 이용해 
  요청 파라미터 값이 바인딩된(멤버변수에 세팅된) 도메인 클래스(ArticleVO)의 입력값 검증을 함
  요청 파라미터 : ?articleNo=112&title=개똥이
  public String write(골뱅이ModelAttribute ArticleVO articleVO)
  -->
  <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-validator -->
  <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>5.2.5.Final</version>
  </dependency>

 

 <dependencies> 안쪽에 넣어준다.

 

※ pom.xml을 수정하면 maven build를 해주어야 한다.

 

// 입력값 검증 규칙을 지정함
	@NotBlank 
	private String userId;
	// 여러 개의 입력값 검증 규칙을 지정할 수 있음
	@NotBlank 
	@Size(max=3)
	private String userName = "hongkd";

 

userId에 아무것도 입력되지 않았을 때 @NotBlack에서 걸러준 뒤 다시 돌아가게 할 수 있다.

userName의 최대 크기는 3(한글 기준 1글자)으로 지정한다

 

@PostMapping("/registerForm01Post")
public String registerForm01Post(Member member) {
   log.info("registerForm01Post -> member : " + member);

   return "registerForm05";
}

 

아무것도 등록하지 않고 데이터를 전송했을 때 오류가 나게 된다.

이클립스 콘솔

 

 

오류가 나지 않게 @Validated, @ModelAttribute, BindingResult를 추가해준 코드이다.

@PostMapping("/registerForm01Post")
public String registerForm01Post(@Validated @ModelAttribute("user") Member member
       , BindingResult brResult
       , Model model) {
   log.info("registerForm01Post -> member : " + member);
   log.info("registerForm01Post -> brResult : " + brResult);
   /*----*/
  //폼 객체의 프로퍼티에 값을 지정

  member.setCoin(1000);
  member.setPassword("java");
  member.setIntroduction("저는 개똥이 입니다.\n왜일까요?");

  //모델에 Map 타입의 데이터를 생성하여 추가한 후에 화면(jsp)에 전달
  Map<String,String> hobbyMap = 
        new HashMap<String, String>();
  hobbyMap.put("Music", "음악");
  hobbyMap.put("FootBall", "축구");
  hobbyMap.put("Book", "도서");
  hobbyMap.put("Travel", "여행");

  member.setHobbyMap(hobbyMap);

  //Music을 미리 체크 처리
  String[] hobbyArray = {"kmj"};
  member.setHobbyArray(hobbyArray);

  member.setHobby("kmj");

  //보유자동차
  Map<String,String> carMap = 
        new HashMap<String, String>();      
  carMap.put("qm5", "qm5");
  carMap.put("sm6", "sm6");
  carMap.put("volvo", "volvo");

  member.setCarMap(carMap);

  //volvo를 미리 체크 처리
  String[] carArray = {"volvo"};
  member.setCarArray(carArray);

  //성별
  Map<String,String> genderCodeMap = 
        new HashMap<String, String>();
  genderCodeMap.put("Male", "남성");
  genderCodeMap.put("Female", "여성");
  genderCodeMap.put("etc", "기타");

  member.setGenderCodeMap(genderCodeMap);

  //미리 선택 처리
  member.setGender("Female");

  //11. 셀렉트 박스 요소
  //국적 선택
  Map<String,String> notionalityCodeMap = 
        new HashMap<String, String>();
  notionalityCodeMap.put("Korea", "대한민국");
  notionalityCodeMap.put("Germany", "독일");
  notionalityCodeMap.put("Australia", "오스트레일리아");
  notionalityCodeMap.put("Canada", "캐나다");

  member.setNotionalityCodeMap(notionalityCodeMap);
  //국적 미리 선택
  member.setNationality("Korea");

  //국적을 공통코드로부터 가져와보자
  ComCodeVO comCodeVO = this.comCodeMapper.getComCode("natn");
  log.info("registerForm05->comCodeVO : " + comCodeVO);

  model.addAttribute("comCodeVO", comCodeVO);
  /*----*/

  return "registerForm05";
}

 

 

실행해보면 NotBlack로 걸리는 걸 볼 수 있다.

 

입력값 검증을 하기 위해서는 메서드 매개변수에 도메인 클래스를 정의하고 골뱅이Validated를 지정

입력값 검증 대상의 도메인 클래스 직후에 BindingResult를 정의함

 

BindingResult에는 요청 데이터의 바인딩 오류와 입력값 검증 오류 정보가 저장됨

 

※ 주의

@validated와 BindingResult은 서로 연관되어있는 요소들이기 때문에 붙혀서 선언해야 하며, Model은 웬만하면 최대한 맨 뒤에 선언을 하는게 낫다(괜히 꼬일 수 있음).

 

입력값 검증 결과

입력값 검증 대상의 도메인 클래스 직후에 BindingResult를 정의함
BindingResult에는 요청 데이터의 바인딩 에러와 입력값 검증 에러 정보가 저장됨

 

 

/* 
2. 입력값 검증 결과
입력값 검증 대상의 도메인 클래스 직후에 BindingResult를 정의함
BindingResult에는 요청 데이터의 바인딩 에러와 입력값 검증 에러 정보가 저장됨
*/

if (brResult.hasErrors()) { // true
    // 검사 결과 오류 확인
    List<ObjectError> allErrors = brResult.getAllErrors(); // 모든 에러 꺼내기
    for (ObjectError objectError : allErrors) {
        log.info("allError : " + objectError);
    }

    // 객체와 관련된 오류
    List<ObjectError> globalErrors = brResult.getGlobalErrors();
    for (ObjectError objectError : globalErrors) {
        log.info("globalErrors : " + objectError);
    }
    // 멤버 변수와 관련된 오류
    List<FieldError> fieldErrors = brResult.getFieldErrors();
    for (FieldError fieldError : fieldErrors) {
        log.info("fieldError : " + fieldError);
    }

    /*----*/
    // 폼 객체의 프로퍼티에 값을 지정

    member.setCoin(1000);
    member.setPassword("java");
    member.setIntroduction("저는 개똥이 입니다.\n왜일까요?");

    // 모델에 Map 타입의 데이터를 생성하여 추가한 후에 화면(jsp)에 전달
    Map<String, String> hobbyMap = new HashMap<String, String>();
    hobbyMap.put("Music", "음악");
    hobbyMap.put("FootBall", "축구");
    hobbyMap.put("Book", "도서");
    hobbyMap.put("Travel", "여행");

    member.setHobbyMap(hobbyMap);

    // Music을 미리 체크 처리
    String[] hobbyArray = { "kmj" };
    member.setHobbyArray(hobbyArray);

    member.setHobby("kmj");

    // 보유자동차
    Map<String, String> carMap = new HashMap<String, String>();
    carMap.put("qm5", "qm5");
    carMap.put("sm6", "sm6");
    carMap.put("volvo", "volvo");

    member.setCarMap(carMap);

    // volvo를 미리 체크 처리
    String[] carArray = { "volvo" };
    member.setCarArray(carArray);

    // 성별
    Map<String, String> genderCodeMap = new HashMap<String, String>();
    genderCodeMap.put("Male", "남성");
    genderCodeMap.put("Female", "여성");
    genderCodeMap.put("etc", "기타");

    member.setGenderCodeMap(genderCodeMap);

    // 미리 선택 처리
    member.setGender("Female");

    // 11. 셀렉트 박스 요소
    // 국적 선택
    Map<String, String> notionalityCodeMap = new HashMap<String, String>();
    notionalityCodeMap.put("Korea", "대한민국");
    notionalityCodeMap.put("Germany", "독일");
    notionalityCodeMap.put("Australia", "오스트레일리아");
    notionalityCodeMap.put("Canada", "캐나다");

    member.setNotionalityCodeMap(notionalityCodeMap);
    // 국적 미리 선택
    member.setNationality("Korea");

    // 국적을 공통코드로부터 가져와보자
    ComCodeVO comCodeVO = this.comCodeMapper.getComCode("natn");
    log.info("registerForm05->comCodeVO : " + comCodeVO);

    model.addAttribute("comCodeVO", comCodeVO);
    /*----*/

    return "registerForm05";
}

 

for문으로 오류를 찍고, 그 이후 에러를 막기 위해 뒷 코드를 복사해서 넣어줬다.

NotBlank를 감지해서 잡아주고 return해서 본래 페이지로 돌아온다.

 

이제 jsp에서 위 오류가 났을 때를 처리해보자

 

 VO에 출력할 메세지를 입력해주고

@NotBlank(message = "아이디를 입력해주세요") 
private String userId;
// 여러 개의 입력값 검증 규칙을 지정할 수 있음
@NotBlank(message = "이름를 입력해주세요") 
@Size(max=3, message = "최대 3글자(한글 기준 1글자)까지만 작성해주세요")
private String userName = "hongkd";

 

jsp에 아래와 같이 작성해준다.

<p><form:label path="userId">유저 ID : </form:label>  
  <form:input path="userId" placeholder="아이디"/>   
  <font color="red"><form:errors path="userId" /> </font>
</p>
<p><form:label path="userName">이름 : </form:label>  
  <form:input path="userName" placeholder="이름"/>
  <font color="red"><form:errors path="userName" /> </font>
</p>
 <font color="red"><form:errors path="userId" /> </font>

 

이 부분이 에러 메세지를 출력해주는 부분이다. path를 각각 맞춰주어야 한다.

실행 결과

 

Email과 날짜

@NotBlank(message = "이메일을 작성해주세요")
@Email(message = "올바른 이메일을 작성해주세요")
private String email;
@DateTimeFormat(pattern="yyyy-MM-dd")
@Past(message = "과거 날짜를 입력해주세요")
private Date dateOfBirth;
<p>
    <form:label path="email">이메일 : </form:label> 
    <form:input path="email" />
    <font color="red"><form:errors path="email" /></font>
</p>
<p>
    <form:label path="dateOfBirth">생일 : </form:label> 
    <form:input path="dateOfBirth" placehold="ex)2020-03-01" />
    <font color="red"><form:errors path="dateOfBirth" /></font>
</p>

 

이메일은 형식에 맞게 작성되었고, 날짜는 과거 날짜만 입력할 수 있기 때문에 등록버튼을 누르면 날짜만 오류 메세지를 띄워주는 것을 볼 수 있다.

 

 

중첩된 자바빈즈 검증

중첩된 자바빈즈와 자바빈즈의 컬렉션에서 정의한 프로퍼티에 대해 입력값 검증을 할 때는 골뱅이 Valid를 지정함

@Valid
private Address address;

@Valid
private List<Card> cardList;

 

Address.java

@Data
public class Address {
	// Member 자바빈 클래스의 프로퍼티(식별자)
	private String userId;
	@NotBlank(message = "우편번호를 입력해주세요")
	private String postCode; // 우편 변호
	@NotBlank(message = "주소를 입력해주세요")
	private String location; // 주소
}

 

Card.java

@Data
public class Card {
	// Member 자바빈 클래스의 프로퍼티(식별자)
	private String userId;
	@NotBlank(message = "카드 번호를 입력해주세요")
	private String no;
	@Future(message = "미래의 날짜를 입력해주세요")
	@DateTimeFormat(pattern = "yyyyMMdd")
	private Date validMonth;
}
<p>
    우편번호 : <form:input path="address.postCode"/><br />
    <font color="red"><form:errors path="address.postCode" /></font><br />
    주소 : <form:input path="address.location"/><br />
    <font color="red"><form:errors path="address.location" /></font><br />
</p>
<p>
    1. 카드 번호 : <form:input path="cardList[0].no" placeholder="카드 번호" /><br />
    <font color="red"><form:errors path="cardList[0].no" /></font><br />
    1. 유효 기간: <form:input path="cardList[0].validMonth" placeholder="유효 기간" /><br />
    <font color="red"><form:errors path="cardList[0].validMonth" /></font><br />
    2. 카드 번호 : <form:input path="cardList[1].no" placeholder="카드 번호" /><br />
    <font color="red"><form:errors path="cardList[1].no" /></font><br />
    2. 유효 기간 : <form:input path="cardList[1].validMonth" placeholder="유효 기간" /><br />
    <font color="red"><form:errors path="cardList[1].validMonth" /></font><br />
</p>

 

@Future은 Date타입에서만 사용할 수 있으며, 현재시간 기준 미래의 시간을 작성해야 한다.

 

혹시나 유효기간 부분에 123, qwe와 같은 날짜 형식과 다르게 타이핑을 하면 

 

오류를 잡긴 하지만 원하는 메세지를 출력할 수 없기 때문에 날짜의 경우 정해놓은 형식에 맞춰야 한다.