I. Mybatis
1. 들어가며
(1) yml 정리
1)
그전 경로문제로 yml을 만들었는데, 이것을 제외한 application.properties 만 둔다
spring.servlet.multipart.max-file-size=100MB
spring.servlet.multipart.max-request-size=200MB
spring.datasource.url=jdbc:mariadb://db.newlecture.com:3306/rlanddb
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.username=rland
spring.datasource.password=20220823
mybatis.mapper-locations=classpath*:mapper/*Mapper.xml
2. XML방식
(1) 기본준비
Mybatis 쿼리문만 작성하면 자동으로 Dao를 작성해주는 프레임워크이다
그런데 인자가 100개인데, 목록을 100개 다 만들어야하는가? 사람은 반복적인거 싫어해!
페이지에 검색어, 필터링조건 등 다양한게 많아!
overload방식으로 인수를 넣는거를 할수있을까 싶지만, Mybatis에서는 overload를 지원하지않아
왜냐 구현(implement)은 메소드 오버로드로 하나를 하는건데, 마이바티스가 이미 구현해주는거기 때문이야 (????)
특히 모든 case 마다 구현하기는 어려운데, mybatis는 모든 case 가 on된 경우를 기준으로 하기위해서는 mybatis가 바람직해!
일반적인 implement는 모든 case마다 구현해야해서 번거로워!
나는 추가하고싶으면? 클래스를 만들어서 하면돼!
2)
마이바티스는 자기가 만든 mapper는 스프링의 콩자루(bean)가 아니라 자기(Mybatis)가 관리하는 콩자루야
스프링, 마이바티스 콩자루 별개라서 같이 못썼어 왜냐 스프링에 있어야 오토와이어 돼! 그런데 자루 따로라 못해
마이바티스 컨테이너가 스프링과 연결할수 있는 연결체 라이브러리를 만들었다(마이바티스가)
sqlSession은 Mybatis의 콩자루 꺼내오기위한 객체야!
이게 오토와이어 되는건 라이브러리로 포함되서 그래
<MBMenuRepository.java>
@Repository
public class MbMenuResponsitory implements MenuRepository {
@Autowired
private SqlSession session;
@Override
public List<Menu> findAll() {
// TODO Auto-generated method stub
return null;
}
3)
mapper 콩자루에 있는것 꺼내서 내가 원하는 오버로드에 구현하면 돼!
@Override
public List<Menu> findAll() {
// TODO Auto-generated method stub
MenuRepository menuDao = session.getMapper(MenuRepository.class);
return menuDao.findAll();
}
오버로드 지원하지않지만, 하고싶으면 이렇게 하면된다는 뜻이야!
음 우리는 이렇게 하지않을꺼야
4)
@Mapper
public interface MenuRepository {
//jdbc 와 mybatis로 구현한게 두개야!
// @Select("select * from menu")
List<Menu> findAll(int offset, int size, String query, int category);
페이지 조회할 때, 시작은어디서, 몇페이지를 볼지?, 뭐를검색할거야?, 카테고리있어?
생각해보자
i) 이렇게 인자가 있을때도 없을때 (null여부) 있다. 즉 4개 변수가 늘 받을수 있는게 아냐!
ii) offset 자료형은 정수형이야 값이, 무조건 전달되어야하는데, 원래 전달여부는 null이어야해! 그냥 int외 null이 가능한 Interger를 하자!
=> 동적쿼리를 하자
2. XML방식 : 동적쿼리
(1) - select
maria DB의 페이지쿼리를 보자! (https://mybatis.org/mybatis-3/dynamic-sql.html)
1)
<MenuRepository.java>
@Mapper
public interface MenuRepository {
//jdbc 와 mybatis로 구현한게 두개야!
// @Select("select * from menu")
List<Menu> findAll( Integer offset,
Integer size,
String query,
Integer price,
Integer category,
String orderField,
String orderDir
);
}
여기 적용할수 있는 모든 인수를 넣자! (인터페이스임 : mybatis 본래 취지대로 implemnet 가 없음!)
<MenuRepositoryMapper.xml>
<!-- 아까는 한줄에 구현해야했는데, 이제 내려쓰기도 해도 되고부담이 없어! 주석도 돼! -->
<select id="findAll" resultType="kr.co.rland.web.entity.Menu">
<!-- resultType이 무엇이냐 modle을 넣어주자 -->
select *
from menu
where
name like '%${query}%'
and price > #{price}
and categoryId=#{categoryId}
order by ${orderField} ${orderField}
limit #{size} offset #{offset}
</select>
여기에다가 쿼리문에 각 내용을 넣을 수있게끔 하자!
limit #{size} offset #{offset} |
여기서 size 는 한번에 몇개를 보여줄지
offset은 어디서부터 시작할지를 의미한다
문자열인 경우
# 은 " " 가 있어야한다. (정적 / 보안 : prevent for sql injection) (ex: id)
$ 는 " " 가 없어도 된다는 의미이고 (동적 : ' ' 가 붙지않아서 동적으로 결정가능 )
결국 정수형 # 이 바람직하고, $는 문자열이 바람직하다
2)
이렇게 한것에서 동적으로 어떤경우는 쓰고 안쓰는것을 구분해보자
trim 과 if를 이용해서 가능하다
<select id="findAll" resultType="kr.co.rland.web.entity.Menu">
<!-- resultType이 무엇이냐 modle을 넣어주자 -->
select *
from menu
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="query != null">
name like '%${query}%'
</if>
<if test="price != null">
and price > #{price}
</if>
<if test="categoryId != null">
and categoryId=#{categoryId}
</if>
</trim>
order by ${orderField} ${orderField}
limit #{size} offset #{offset}
</select>
만약 첫번째 구문에서 query가 null이면 실행이 안되는데
이 경우 price 에서 쿼리문 앞부분 and가 문제시되는데 이를 where로 바꿔줄 수있다.
즉 trim 해주는데, 서두에 where을 and/or일때 overrides 해주어서 정상적인 쿼리문으로 만들어줘!
3)
이후 service에 가서 필요한 인자를 넣어보자
@Service
public class DefaultMenuService implements MenuService {
@Autowired
private MenuRepository repository;
public void setRepository(MenuRepository repository) {
this.repository = repository;
}
@Override
public List<Menu> getList() {
return repository.findAll(0,10,"",1,3000,"regDate","desc");
}
}
0번 게시물부터 / 10개씩 / 모든 내용들을 / 카테고리1을 /3000원부터 / regDate를 기준으로 / 내림차순으로 조회한다
그리고 다른 구현체들의 내용들을 업데이트하고, 실행하자
이경우 오류가 개인적으로 발생했다
order by ${orderField} ${orderDir}
limit #{size} offset #{offset}
orderDir 이 아니라 orderField로 바꾸어주어야한다.
4)
이 경우 sql 을 제대로 했는지를 확인하는 방법으로 log가 있어!
log 는 라이브러리나 프레임웍 만드는 쪽에서 준비한는거야!
log로 sql쿼리가 어떻게 만들어서 어떻게 전달되는지 살펴보자!
- debug 하면
logging.level.kr.co.rland.web.repository=debug
이렇게 나온다
어떤 쿼리문이 전달됐는지 / 어떤 parameter가 전달됐는지를 알 수있다
- 한편 debug -> trace로 하면
logging.level.kr.co.rland.web.repository=debug
이미지와 같이 조회된 columns와 row를 볼 수있다.
(2) XML방식 : 동적쿼리 - update
1)
그런데 update를 할 때 번거로움이 커!
특정 컬럼만 바꾸고 싶고, 나머지 부분은 두고싶은데 그 부분은 Null로 바뀌어져!
다행히 Mybatis 장점은 update insert를 도와주는 기능이 있어
이렇게 하면 데이터가 있는거만 딱 update를 해줘!
2)
반드시 먼저 workbench에서 쿼리문을 실행하고나서 mybatis에다가 등록하자!
3)
<update id="update">
update menu
set
name=#{name},
price=#{price},
img=#{img}
where id=#{id}
</update>
이렇게 해주자!
이경우 name에 $가 아닌데 '' 를 붙여줘야하기 때문이다(???)
3)
<update id="update">
update menu
<trim prefix="SET" suffixOverrides=",">
<if test="name != null">name=#{name},</if>
<if test="price != null">price=#{price},</if>
<if test="img != null">img=#{img}</if>
where id=#{id}
</trim>
</update>
마찬가지로 if문을 넣어주고, 쉼표(,)방지를 위해서 trim을 이용한다!
한편 여기서 if문의 "name"은 menu entity 에서 get으로 가져오는 것이고
name = #{name}은 입력을 위해서 set으로 이해하면 좋다
(3) Junit
1)
업데이트를 만들었는데 잘 확인하고 싶은데, controller, service 만들기도 번거롭고, 권한 때문에 손을 못댈수도 있어
이 부분만 테스트 하는방법이 있어 서비스 만드는 사람은 서비스만 테스트! 같이 가능해!
그런데 문제가 있어 mybatis하려면 설정을 다해야해서 어려워 그래서 junit 을 이용해보자!
package kr.co.rland.web.repository;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
class MenuRepositoryTest {
@Test
void testFindAll() {
fail("Not yet implemented");
}
@Test
void testUpdate() {
fail("Not yet implemented");
}
}
repository 에서 new해서 만들어서 하자!
junit을 하면 설정을 할 필요가 없어!
2)
Mapper가 일해서 담아줘야하고, 스프링이 들고올수있게끔 하자
우선 마이바티스가 제공하는 기능을 이용해야해!
( http://mybatis.org/spring-boot-starter/mybatis-spring-boot-test-autoconfigure/ )
그리고 pom.xml에다가 추가하자
3)
package kr.co.rland.web.repository;
import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
import kr.co.rland.web.entity.Menu;
//DB에 대해서 실제로 존재하는것을 이용하겠다
//다른것으로 리플레이스 하지않겠다는 의미이다
@AutoConfigureTestDatabase(replace = Replace.NONE)
@MybatisTest
class MenuRepositoryTest {
//이거를 쓰려면 스프링의 IoC 컨테이너를 load해야해!
//컨테이너에 담긴적 없으면 본 repo는 null이야
@Autowired
private MenuRepository repository;
@Test
void testFindAll() {
// assertNull(repository);
List<Menu> list= repository.findAll(0, 10, null, null, null, "regDate", "desc");
System.out.println(list);
}
@Test
void testUpdate() {
fail("Not yet implemented");
}
}
어노테이션 두개 추가해주어야한다
@Autowired를 하려면 스프링 IoC컨테이너에 load를 해야하는데 선술한바 load하지 못한다
결국 @MybatisTest 를 추가해준다
그럼에도 불구하고 오류가 발생하는데 테스트용 DB가 아니라 실존하는 DB를 이용하겠다는 의미로
@AutoConfigureTestDatabase 를 추가해서 테스트용 DB로 대체하지 않는다는 의미에서 NONE을 한다
그리고 추가한 list 에다가 담아서 콘솔로 출력하면 결과를 볼 수있다.
4)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="kr.co.rland.web.repository.MenuRepository">
<!-- 아까는 한줄에 구현해야했는데, 이제 내려쓰기도 해도 되고부담이 없어! 주석도 돼! -->
<select id="findAll" resultType="kr.co.rland.web.entity.Menu">
<!-- resultType이 무엇이냐 modle을 넣어주자 -->
select *
from menu
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="query != null">
name like '%${query}%'
</if>
<if test="price != null">
and price > #{price}
</if>
<if test="categoryId != null">
and categoryId=#{categoryId}
</if>
</trim>
order by ${orderField} ${orderDir}
limit #{size} offset #{offset}
</select>
<select id="findById" resultType="kr.co.rland.web.entity.Menu">
select *
from menu
where id = #{id}
</select>
<update id="insert">
update menu set
Where
</update>
<update id="update" parameterType="kr.co.rland.web.entity.Menu">
update menu
<trim prefix="SET" suffixOverrides=",">
<if test="name != null">name=#{name},</if>
<if test="price != null">price=#{price},</if>
<if test="img != null">img=#{img}</if>
</trim>
where id=#{id}
</update>
<delete id="delete">
</delete>
</mapper>
그런데 이 경우 update에 resultType대신에 parameterType을 설정해주자!
resultType : list처럼 출력을 Menu 담아두는 것으로것으로 지정하는것이라면
즉
parameterType: 은 입력을 위해 Menu에 담아두는 것이야!
model 에서 menu도 null이 허용되게끔 하자!
1. 보충
(1) resultType VS parameterType
1) resultType이란 select된 데이터를 반환할 틀(model)을 정해주는것으로서 위 xml에서는 Menu객체에 담긴다
2) parameterType 이란 자바에 Menu형식의 값이 들어올것이라는것을 인식시키고, 쿼리에서는 name,price 등을 사용한다
2. 회고
1) 오랜만에 수업을 따라가고, 이해하고, 질문하고 해결해서 속이 시원하다
2) 실제로 구현하는데 많은 문제가있었다
우선 xml방식으로 하더라도, 생성자 방식으로 구현하는 것이 있고, / 서비스함수에서 불러오는 방법 두가지가있다
특히 xml 방식에서 써줄 때 namespace 는 repository interface 를 넣어주어야한다 (model 객체가 아님)
그리고 id값이 중복되면 안된다 -> 왜냐하면 오버라이딩을 할 수없기 때문이다.
'배움 __IL > TIL 1기' 카테고리의 다른 글
TIL : 65번째- 230309 [3-1-목] (0) | 2023.03.09 |
---|---|
TIL : 64번째- 230308 [3-1-수] (0) | 2023.03.08 |
TIL : 62번째- 230304 [3-1-월] (0) | 2023.03.06 |
TIL : 61번째- 230302 [2-4-목] (0) | 2023.03.02 |
TIL : 60번째- 230228 [2-4-화] (0) | 2023.02.28 |