인프런 커뮤니티 질문&답변

Jong Hee Park님의 프로필 이미지
Jong Hee Park

작성한 질문수

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술

체크 박스 - 멀티

강사님 안녕하세요 질문이 있습니다!!

작성

·

1.1K

1

checkbox에 등록 지역을 추가하면요.

제가 DB는 따로 mariadb로 연결해서 진행하고 있습니다.

그래서 open 여부를 할때는 기존 테이블에 open 관련 컬럼을 하나 추가해줘서 해결했습니다.

근데 그 다음 등록 지역부분이 말입니다.

얘는 List형태로 DB 컬럼에 들어가야되잖아요.

그러면 테이블 설계를 어떻게 해야하는건가요?

만약 서울, 부산, 제주 이런 형식으로 등록 지역을 다중으로 선택 했을때 DB에는 다중으로 선택했으면 List형태로 들어가야되는데 DB 입장에선 그러면 테이블의 컬럼 데이터타입을 어떻게 줘야하나요?

답변 13

1

Jong Hee Park님의 프로필 이미지
Jong Hee Park
질문자

강사님 감사합니다.

DB에 저장할땐 List<String>이 들어 있는 item 객체를 쓰고

itemDto 객체를 따로 만들어서 select 해올땐 Dto객체를 써서 regions의 타입을 String으로 바꿔주었고

 

1. 저장할때는

<insert id="save" parameterType="com.itemservice.domain.item.Item">

insert into item(item_name, item_price, item_quantity, item_open, regions)

values(

#{itemName}, 

#{price}, 

#{quantity}, 

#{open},

concat_ws(',', <foreach collection="regions" item="region" separator=",">#{region}</foreach>)

)

 

  </insert>

-> 이런식으로 ,로 List<String>을 DB에 저장하구요

 

2.select 할땐

 

<select id="findByItem" resultType="com.itemservice.domain.item.ItemDto">

select

item_id as id,

item_name as itemName,

item_price as price,

item_quantity as quantity,

item_open as open,

regions

from item where item_id = #{id}

</select>

-> 단순히 String으로 ,이 포함된 문자열로 받은다음

 

3. 컨트롤러에서 

               ItemDto findItem = im.findByItem(itemId);

Item item = new Item();

item.setId(findItem.getId());

item.setItemName(findItem.getItemName());

item.setPrice(findItem.getPrice());

item.setQuantity(findItem.getQuantity());

item.setOpen(findItem.getOpen());

List<String> list = new ArrayList<String>(Arrays.asList(findItem.getRegions().split(",")));

item.setRegions(list);

model.addAttribute("item", item);

 

DB에 있는 값을 itemDto로 새로 받아서 DB에 있는 regions 값이 ','로 구분되어져서 String으로 들어갔으니

다시 List<String>형태로 변환한다음 새로운 item에 setting한 뒤 view로 보냈더니 

 

mapper에서도 강의처럼 동작 잘합니다.

 

으아 ㅠㅠ 감사합니다. 그래도 답변을 달아주신걸 참고해서 해결했습니다.

일단 mybatis사용한거랑 강의에서 진행한거

이렇게 두개로 강의 다시 시작하겠습니다

감사합니다.

 

 

 

안녕하세요 Jong Hee Park님!

결국 해결하셨네요! 대단합니다 :)

Jong Hee Park님의 프로필 이미지
Jong Hee Park
질문자

아 insert, select가 되닌까 새로운 문제에 봉착했습니다. ㅠㅠ

이게 insert할때 값이 모두 null일때는 예외가 터지네요.. 

그러면 이걸 mapper에서 조건을 줘야되나요? 컬렉션이 null일때는 예외가 터집니다.

 

SQL: insert into item(item_name, item_price, item_quantity, item_open, regions)    values(     ?,      ?,      ?,      ?,           concat_ws(',',  )         )

 

이렇게 뜬다면 concat_ws 문법 사용한쪽에 null처리를 해줘야하나요? 

안녕하세요!

 

SELECT할 때 DTO를 활용하고 있습니다.

그러니 이 DTO를 적극 활용하는게 좋을것 같습니다.

저라면 이렇게 시도해볼것 같습니다.

SELECT 뿐 아니라 INSERT때도 DTO를 이용하도록 변경할 것 같습니다.

 

맵퍼에서 foreach와 concat_ws 부분을 삭제하고, DTO에서 콜렉션->문자열, 문자열->콜렉션 을 처리하도록 getter와 setter를 변경한다면, 맵퍼 부분이 많이 간결해질것 같습니다.

 

그리고 마이바티스는 null에 대한 조건을 체크하는 로직을 제공하기에 null 에 대한 처리만 추가하면 되겠지요.마이바티스에서 if 문으로 null 체크하는 방법은 아래의 링크를 참조해주세요.

https://showbang.github.io/typistShow/2017/04/11/nullCheck/

 

감사합니다.

Jong Hee Park님의 프로필 이미지
Jong Hee Park
질문자

으아 ㅠㅠ 무슨 말씀이신지는 이해가 되는데 코드로 구현할려닌까 막막하네요 ㅠㅠ

이번에 경험하셧듯,

비지니스 로직에 사용한 데이터와 엔티티가 정확히 일치하는 경우는 거의 없습니다.

그래서 이런 불일치를 해결하기 위한 방법 중 하나가 DTO를 이용하는 방법입니다.

이 외에도 DTO는 각 서비스간의 커플링을 낮추는(loose coupling) 역할도 합니다.

'DTO를 왜 사용해야 하는가'에 대한 좋은 경험이 되셨지 않을까 생각해요. 지금 고민하셨던 부분을 기록해 두시고 틈틈이 복습하시면 후에 큰 도움이 될거라 생각합니다.

 

일단 form에서 받아온 값을 DTO로 변환하는 부분을 작성하시고 이 DTO로 insert가 문제없이 처리되도록 변경하는 지점부터 시작하면 될것 같습니다 :)

Jong Hee Park님의 프로필 이미지
Jong Hee Park
질문자

일단 save하는 메소드쪽에서 로직 주석처리하고 값을 찍어보닌까

지역 값 = [] 

System.out.println("지역값 = " + addItem.getRegions());

이런식으로 나옵니다.

저건 빈 배열이라는 뜻인데 저럴때는 예외처리를 어떻게 해줘야될까요?

// ItemDto 의 regions의getter/setter 의사코드
class ItemDto {
  private String regions; // 필드 regions

  public void setRegions(List<String regions) {
      if (regions가 null이라면/혹은 빈 값이라면) {
        this.regions = null;
        return;
    }
    this.regions = region의 모든 내용을 ,(콤마)로 묶기;
  }

  public List<String> getRegions() {
    if (regions == null) {
          return null반환;
    }
    return 구분자(콤마)를 기준으로 String을 분해한뒤 List<String>로 변환해서 반환;
  }
}

이와같은 코드가 될것 같습니다.

Jong Hee Park님의 프로필 이미지
Jong Hee Park
질문자

강사님 감사합니다.

살짝 응용해서 컨트롤러 단에 추가해서 처리했습니다.

System.out.println("지역값 = " + addItem.getRegions());

if(addItem.getRegions().size() == 0) {

List<String> result = new ArrayList<String>();

result.add(null);

addItem.setRegions(result);

}

System.out.println("지역값 = " + addItem.getRegions());

 

요렇게 컨트롤러에서 값이 없으면 null로 바꿔서 넣어주닌까 에러가 안납니다.

 

근데 예외처리를 컨트롤러단에서 하는건 좋은 코드가 아닌건가요? 서비스단에서 해줘야하나요?

1

Jong Hee Park님의 프로필 이미지
Jong Hee Park
질문자

mariadb를 사용안하고 강의에서 진행하는대로 진행해야겠습니다. 일단 다만들어보고 실DB로 바꿀때 고민해봐야될거 같습니다.

안녕하세요!

 

아래의 링크를 참조한번 해보시면 좋을것 같습니다 ^^

http://daplus.net/java-jpa%EC%97%90%EC%84%9C-list-string-%EC%9C%A0%ED%98%95%EC%9D%98-%EC%86%8D%EC%84%B1%EC%9D%84-%EC%9C%A0%EC%A7%80%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9E%85/

1

Jong Hee Park님의 프로필 이미지
Jong Hee Park
질문자

일단 문제점을 다시 한번 정리하겠습니다.

 

강의 대로 item 도메인을

        private Long id;  // 3. pk

private String itemName;  // 4. 상품 이름

private Integer price;  // 5. 상품 가격

private Integer quantity;  // 6. 상품 수량

private Boolean open; // 7. 판매 여부

private List<String> regions; // 8. 등록 지역

private ItemType itemType;  // 9. 상품 종류

private String deliveryCode;  // 10. 배송 방식

 

--> 이렇게 정의했습니다.

 

여기서 7번 open 속성 까지는 db에 새로운 컬럼을 추가하여 약간의 수정후 db에 checkbox 값이 잘 저장이 됩니다. 7번은 single checkbox 였습니다.

 

문제점이 생긴게 8번 regions라는 List 타입의 속성입니다. 얘는 다중 체크 박스 값을 여기다 저장하기 위해 List로 만들어 준거 같습니다. 그래서 mapper를 수정하기 전 checkbox값이 잘 컨트롤러로 넘어오나 확인을 해보면 log에 item.regions=[SEOUL, BUSAN, CHEONGJU] 이런식으로 잘 가지고 옵니다.

 

저는 여기서 저 regions 값을 db에 저장하려고 하는데 여기서 문제점이 발생합니다.

테이블의 단일 속성에다가 List<String>값을 한꺼번에 넣으려니 문제가 발생하는거 같은데

 

위에서 알려주신 방법대로 해도 이게 제가 이해를 잘 못하는건가 해결이 잘 안됩니다.ㅠㅠ

 

 

안녕하세요! 

여러가지로 테스트해보았는데 아래의 샘플 코드를 참조하서 작성해보시기 바랍니다.

foreach를 사용하여 리스트를 순회하고, 이 결과를 마리아DB의 내장함수 CONCAT_WS() 로 감싸서 하나의 문자열로 변환하였습니다.

 

하지만 코드가 그리 깔끔해 보이지는 않습니다.

이 방법보다는 getter를 직접 작성하셔서 List인 값을 String으로 반환하도록 한 뒤 이값을 DB에 전달하는 것이

더 좋은 방법일것 같습니다.

 

감사합니다.

 

<select id="findByBatis" resultMap="getUserEntityMap">
        select
            m.user_id, m.name, m.age, m.sex,
            c.company_id, c.name as companyName
        from
            user m
            inner join company c on m.company_id = c.company_id
        where 1=1
        AND m.user_id = concat_ws(',', <foreach collection="members" item="item" separator=",">#{item}</foreach>)
    </select>

0

Jong Hee Park님의 프로필 이미지
Jong Hee Park
질문자

근데 위에 데이터 처럼 지역이 2개이상인건 못가져오는거 같아요.

핫팩은 보시는것처럼 3개 선택을 했는데 못가져옵니다.

 

 

0

Jong Hee Park님의 프로필 이미지
Jong Hee Park
질문자

 

db에 이렇게 들어가거든요. 여기서 지역이 하나씩인건 보기에서 잘떠요. 

0

Jong Hee Park님의 프로필 이미지
Jong Hee Park
질문자

또 하나 발견했습니다. 

<!-- multi checkbox  -->

    <div>

    <div>등록 지역</div>

        <div th:each="region : ${regions}" class="form-check form-check-inline" style="display: inline-block; padding-right: 10px;">

        <input type="checkbox" th:field="${item.regions}" th:value="${region.key}" class="form-check-input">

            <label th:for="${#ids.prev('regions')}"

                   th:text="${region.value}" class="form-check-label">서울</label>

        </div>

    </div>   

item.html에서 체크가 하나만 되었을 경우는 체크 값을 화면에 잘 뿌려주고  있습니다.

itemDto라는 객체를 따로 만들어 아예 String으로 가져왔습니다.

근데 체크값이 2개 이상일때는 체크가 안된 상태로 상세보기가 뜹니다. 1개만 체크해서 DB에 저장했을때는 그 위치에 잘 보여줍니다.. ㅠㅠ

 

 

0

Jong Hee Park님의 프로필 이미지
Jong Hee Park
질문자

아 그 저장은 된다고 말씀드렸잖아요. 근데 저장된 값을 못불러오는거 같습니다. 다른 데이터는 정상적으로 뜨는데 List<String> 값만 매핑이 안되는거 같아요 데이터가 null로 찍힙니다.

그러면 mapper를 어떻게 수정해줘야 할까요?

현재 mapper는

<select id="findById" resultType="com.itemservice.domain.item.Item">

select

item_id as id,

item_name as itemName,

item_price as price,

item_quantity as quantity,

item_open as open,

regions

from item where item_id = #{id}

</select>

이렇게 되어있습니다. 아이템 하나 보는 기능입니다.

 

0

Jong Hee Park님의 프로필 이미지
Jong Hee Park
질문자

<insert id="save" parameterType="com.itemservice.domain.item.Item">

insert into item(item_name, item_price, item_quantity, item_open, regions)

values(

#{itemName}, #{price}, #{quantity}, #{open},

concat_ws(',', <foreach collection="regions" item="region" separator=",">#{region}</foreach>)

)

</insert>

이렇게 하면 insert는 정상적으로 됩니다.

근데 select할때 오류가 납니다 ㅠㅠ 제 생각으론 select할때 반대로 변환해서 가지고와야 되는거 같은데 어렵네요 ㅠㅠ

0

Jong Hee Park님의 프로필 이미지
Jong Hee Park
질문자

mybatis를 사용하면 안되는건가요 ㅠㅠ   근데 기존 소스랑 뭐 별다른 차이가 없는거 같은데 왜 mybatis만 사용하면 이런 에러가 나는거죠?

0

Jong Hee Park님의 프로필 이미지
Jong Hee Park
질문자

서포터즈님 ㅠㅠ 저장은 되는데요 select할때 못불러옵니다...

concat_ws해서 저장은 했는데 select할때 dto객체랑 미스매칭이라 에러가나는거 같습니다... 

0

Jong Hee Park님의 프로필 이미지
Jong Hee Park
질문자

강사님 ㅠㅠ

 

insert into item(item_name, item_price, item_quantity, item_open, regions)

values(

#{itemName}, #{price}, #{quantity}, #{open},

<foreach collection="list" item="item" separator=" , ">

#{item}

</foreach>

)

요런 형식으로 적어 보닌까 오류메시지가 달라졌는데요

일단 

<foreach collection="list" item="item" separator=" , ">

#{item}

</foreach>

요렇게 하면 html에서 체크박스로 선택한 세개는 잘 가져오는거 같아요. 근데

이게 

3개를 가져온 값을 통채로 한 컬럼에 String으로 넣어야 하는거 아닌가요?

 

3개를 가져와서 어디다 저장을 해서 regions라는 컬럼에 넣어야될거 같은데 아닌가요? ㅠㅠ

 

0

Jong Hee Park님의 프로필 이미지
Jong Hee Park
질문자

item.open=true

item.regions=[SEOUL, BUSAN, CHEONGJU]

 

-> 저장할때 로그를 찍으면 체크 박스를 선택하면 이런식으로 넘어오거든요?

그래서 open은 해당 컬럼을 추가해주닌까 해결이 됬어요.

근데 regions 같은 경우는 저거 배열 아닌가요? 배열로 넘어온거면 DB에 insert할때

 

제거 프로그램이 현재 mapper 로직으로 CRUD가 동작하거든요?

<insert id="save" parameterType="com.itemservice.domain.item.Item">

insert into item(item_name, item_price, item_quantity, item_open)

values(#{itemName}, #{price}, #{quantity}, #{open})

</insert>

 

저렇게 했을때 open 여부 체크해서 db에 insert는 잘되는데요

private Long id;  // 3. pk

private String itemName;  // 4. 상품 이름

private Integer price;  // 5. 상품 가격

private Integer quantity;  // 6. 상품 수량

private Boolean open; // 7. 판매 여부

private List<String> regions; // 8. 등록 지역

private ItemType itemType;  // 9. 상품 종류

private String deliveryCode;  // 10. 배송 방식

 

7번까지는 단일 값이라 잘 들어가는거 같은데 8번은 List잖아요

로그에 찍히는거 보면 [SEOUL, BUSAN, CHEONGJU] 배열인데

이걸 분해해서 하나하나씩 넣거나 저걸 통채로 넣으라는 말씀이신거 같아서 

한번 넣어봤는데 오류가 납니다. ㅠㅠ

안녕하세요!

 

맵퍼에서 배열을 처리하기 위해선, 마이바티스 문법중 foreach를 활용하셔야 합니다. 아래 링크의 foreach 문법을 참조해주세요.

https://java119.tistory.com/85 

 

혹은 단일 필드로 사용하고자 하시면,regions의 getter를 직접 구현하는 방법으로 시도해보시면 어떨가요?

public String regions() {

    return // 리스트를 스트링으로 변환//

}

 

감사합니다.

0

안녕하세요, Jong Hee Park 님. 공식 서포터즈 codesweaver 입니다.
.
배열로 넘어온 값을 분해해서 각각의 DB필드에 저장하는 방식도 가능하고,
혹은 ,(콤마)등의 구분자를 기준으로 한 문자열로 하나의 필드에 저장하는 방법도 가능합니다.

.

지역별로 검색을 할 일이 많은 서비스라면 별도의 필드로 분리해서 관리하는것이 유리할 것이고, 자주 사용하지 않는 값이라면 하나의 필드에 문자열로 저장하는것도 큰 문제는 없다고 생각합니다.
.
감사합니다.

Jong Hee Park님의 프로필 이미지
Jong Hee Park

작성한 질문수

질문하기