newlec Spring
31. Mapper 구현하기 #1 (getCount/getView)
mapper파일을 설정해서 구현해보자.
getViewList는 이미 구현해놧다.
나머지를 구현하자.
int getCount(String field, String query);
SELET구문 집계만하면되니 count(id)하고 쓰기 편하게 별칭도 달아주자
<select id="getCount">
SELECT count(id) count FROM notice
where ${field} like '%${query}%'
</select>Notice get(int id);
특정 id에 대한 것을 가져오기
<select id="get" resultType="com.newlecture.web.entity.NoticeView">
SELECT * FROM notice
WHERE id = #{id}
</select>32. Mapper 구현하기 #2 (getNext/getPrev)
getNext는 좀 특이하다. 3을 주고 3의 다음것을 얻고자 한다.
이것보다 큰것 크면서 가장 오래된것
일단 날짜를 비교해야한다.
3을 뽑는다면
SELECT ID FROM NOTICE
WHERE REGDATE > (SELECT REGDATE FROM NOTICE WHERE ID = 3)이걸 뒤집어서 가장 위에거를 가져오면된다.
SELECT * FROM notice
WHERE regdate > (SELECT REGDATE FROM NOTICE WHERE ID = 3)
LIMIT 1;하나만 가져오니 limit걸어주자.
mapper.xml에 추가해주면되는데 > 같은 경우에 태그에서 특수하게 사용하기때문에
html이나 xml에서 사용할수없다.
>해주면 된다. db에서 배웠던 내용으로 단어를 쓰고 & ;사이에 넣어주면된다.
prev는 작은걸 뽑아서 내림차순 정렬하면 가장 최근 것이 나오게 되는 것이다.
<select id ="getNext" resultType="com.newlecture.web.entity.Notice">
SELECT * FROM notice
WHERE regdate >
(SELECT regdate FROM notice WHERE id = #{id})
LIMIT 1;
</select>
<select id ="getprev" resultType="com.newlecture.web.entity.Notice">
SELECT * FROM notice
WHERE regdate < (SELECT regdate FROM notice where id = #{id})
ORDER BY regdate DESC
LIMIT 1;
</select>이런식으로 정의해주면 되는 것이다.
33. Mapper 구현하기 #3 (insert/update/delete)
update는 제목 내용 hit pub만 업데이트하면된다.
insert는 제목 내용 작성자 아이디만 넣으면된다.
delete는 id만 전달받아 삭제하면된다.
<update id="update" parameterType="com.newlecture.web.entity.Notice">
UPDATE
SET
title = #{title},
content = #{content},
hit = #{hit},
pub = #{pub}
WHERE id = #{id}
</update>
<insert id="insert" parameterType="com.newlecture.web.entity.Notice">
INSERT INTO Notice(title, content, memberId)
VALUES(#{title}, #{content}, #{memberId})
</insert>
<delete id="delete">
DELETE FROM notice
WHERE id = #{id}
</delete>34. Dynamic SQL Mapper 구현하기 #4 (deleteAll)
넘겨받기 위한 데이터가 배열이다. 동적으로 값이 들어와야한다.
DELETE FROM Notice WHERE id in (1,2,3,4) 이런식으로 담아야한다.
동적으로 배열이 펼쳐지도록 마이바티스가 동적으로 쿼리를 작성할 수 있도록 지원하고 잇다.
https://mybatis.org/mybatis-3/ko/dynamic-sql.html
forEach를 사용해보자.
ids가 collection에 들어간다.
item에 들어가서 id라는 이름으로 하나씩 꺼내게 된다.
jsp에서 forEach를 사용할때 items에 넣고 var로 하나씩 꺼내는 느낌이다.
반복되는데 앞부분엔 Open의 값으로 시작된다. ID in (
중간은 separator의 구분자가 들어가게 된다. ,
마지막으론 close의 값이 들어가게 된다 )
그래서 뭘담든 IN (1,2,3,4)의 모양이 들어가게 되는 것이다.
<delete id="deleteAll">
DELETE FROM Notice
WHERE id IN
<foreach item="id" index="index" collection="ids"
open="ID in ("
separator=","
close=")">
#{id}
</foreach>
</delete>35. Dynamic SQL Mapper 구현하기 #5 (updatePubAll)
pubIds 와 closeIds를 두가지의 동적인 값을 처리하는 것을 알아보자.
https://jin2rang.tistory.com/entry/MySQL-CASE-%EB%AC%B8%EB%B2%95
mysql의 case문법(swith와 비슷함)을 사용한다.
UPDATE Notice
SET
pub = CASE id
WHEN 14 THEN 0
WHEN 15 THEN 0
WHEN 21 THEN 1
WHEN 22 THEN 1
END
WHERE id IN (14,15,21,22);pub에 0또는 1로 바꾸는 건데 배열로 받은것을 펼쳐놓고 하는 것이다.
14 15 = closeid 이것을 두번에 걸쳐서 나눠서하면 된다.
int updatePubAll(int[] ids, boolean pub)로 할수도잇다.
int updatePubAll(int[] pubIds, int[] closeIds);
하지만 이렇게하는 이유는 ui를 보고 더 직관적으로 여러기법과 한번에 해야된다. 라고 할 수잇다.
when 14 then 0 when 15 then 0/ when 21 then 1 when 22 then 1 / 14,15 / 21,22
4번의 반복이 필요하다.
반복되는 과정에서 INDEX 가필요하면 사용해야하는데 필요없으면 없어도된다.
구분자를 반복해서 넣을게 없다면 그냥 없애고 넣으면된다.
반복되는 모습만 생각하면 된다.
<update id="updatePubAll">
UPDATE notice
SET
pub = CASE id
<foreach item="id" collection="openIds">
WHEN #{id} THEN 1
</foreach>
<foreach item="id" collection="closeIds">
WHEN #{id} THEN 0
</foreach>
END
WHERE id in (
<foreach item="id" collection="openIds">
#{id}
</foreach>
,
<foreach item="id" collection="closeIds">
#{id}
</foreach>
)
</update>36. Dynamic SQL Mapper 구현하기 #6 추가된 (updatePubAll)
위가 너무 복잡해보이니 더 쉽게 만들어보자.
마이바티스는 오버로드를 지원하지 않아서 이름을 바꿔써야한다.
int updatePubAll(int[] ids, boolean pub);
사용자가 어떤값을 전달함에따라서 공개할지 말지에 대해 담는다.
<update id="updatePubAll">
UPDATE Notice
SET
pub = #{pub}
WHERE id in
<foreach item="id" index="index" collection="ids"
open="ID in (" separator="," close=")">
#{id}
</foreach>
</update>pub를 받고 id에 따라 처리하는 것이다.
위는 한문장에 하는 것 이거는 여러번 실행해야한다.
닫을때, 오픈할때
37. Dynamic SQL (if, where, trim 태그)
동적 sql에서 자주 사용하는 것이 조건처리 WHERE절 이어붙이기 등 이다.
이미 데이터를 가져오는 getViewList를 완성햇다.
여기서 사용되는 쿼리에서 WHERE절이 간과하고잇는게잇다.
목록을 가져올때 관리자는 다봐야하는데 일반 사용자는 공개된것만 봐야한다.
List<Notice> getViewList(int offset, int size, String field, String query, boolean pub);pub를 인자로 하나 더 넣어줘야한다.
SELECT * FROM notice
WHERE
${field} LIKE '%${query}%'
and pub = #{pub}
ORDER BY regdate DESC
LIMIT #{offset}, #{size}쿼리에서도 pub를 하나 더 받아야한다.
만약에 query가 빈문자열이라면 특별히 검색할게 없으니 LIKE절을 빼주는게 좋다.
마이바티스의 동적 쿼리에서 if문을 사용할 수 있다.
빈문자가 아닐때 혹은 null값이 아닐때만 할수 잇게끔 포함시키게 해주자.
WHERE
<if test="query != null or query != ''">
${field} LIKE '%${query}%'
</if>
and pub = #{pub}근데 그냥 이렇게 써버리면 구분이 이상해진다.
없다면 and가 붕뜨게 되버려서 오류가 발생한다.
그래서 여기서 사용되는게 WHERE 태그이다.
WHERE태그로 조건절을 감싸면 조건이 하나라도 포함되면
WHERE가 포함되는 것이고 아니면 포함되지 않는다.
만약에 WHERE가 포함된것에서 포함안되면 and를 없애버린다.
<where>
<if test="query != null or query != ''">
${field} LIKE '%${query}%'
</if>
and pub = #{pub}
</where>if블럭이 사라진다고 하더라도 and를 지워버릴 수 잇다.
포함되면 자연스럽게 where가 포함된다.
만약 where가 잘 작동하지 않는다면 trim을 사용하면된다.
trim은 안쪽에서 구문이 포함되면 where가 붙는다.
구문에서 시작되는 첫글자가 and거나 or면 where로 바꾼다.
SELECT * FROM notice
<trim prefix="where" prefixOverrides = "AND | OR">
<if test="query != null or query != ''">
${field} LIKE '%${query}%'
</if>
and pub = #{pub}
</trim>
ORDER BY regdate DESC
LIMIT #{offset}, #{size}if문이 실행이안되면 WHERE PUB 가 되는 것이고
if문 실행되면 where ${field}~가 되는 것이다.
더 작동이 편하게 된다.
둘중하나로 더 잘되는 것을 사용하면 된다.
쿼리에서 컬럼 매핑이름이 잘되지않을 경우 문제 생길 수잇다. 그것을 알아보고자 한다.
38. Mapper 객체 단위 Test with jUnit5와 XML에 resultMap 사용하기
매핑해두고나면 정상적으로 잘 생성되는지 단위기능 되는지 테스트 해보고 싶다.
이것이 단위테스트고 여기서 사용하는 프레임워크가 jUnit5이다.
마이바티스의 기능을 수행하는 것이다.
마이바티스의 starter가 있다면 사용할 수있다.
@MybatisTest만 붙이면 간단하게 시험해볼 수잇다.
http://mybatis.org/spring-boot-starter/mybatis-spring-boot-test-autoconfigure/
폼파일에 추가해주자.
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>2.1.3</version>
<scope>test</scope>
</dependency>테스트하기 위한 클래스를 만들어야한다.
우리가 테스트하고 싶은것은 NoticeDao이기때문에
이 클래스를 오른쪽키 누르고 jUnit5 test파일 생성을 하면된다.
NoticeDaoTest클래스에서
우클릭 -> run as -> jUnitTest
test함수는
메인함수를 만드는게 귀찮아서 사용해보는 것이다.
NoticeDao를 테스트해야하니 NoticeDao를 주입해서 받아주자.
@MybatisTest어노테이션을 사용해야 테스트 할 수 있다.
@AutoConfigureTestDatabase(replace = Replace.NONE)
테스트하기 위한 데이터소스와 실제 소스와 다를 수잇는데 따로 마련햇느냐?
물어본다 저걸작성하면 다른걸 안쓴다는 것이다.
MVC다만들고 테스트하면 늦을 수잇으니 이렇게 하나씩 단위테스트를 해볼 필요가잇다.
MVC로작성하여서 통합테스트밖에 불가능하다면 junit을 사용하면 단위테스트를 사용할 수잇다.
그런데 만약 DB에는 MEMBER_ID이런식으로 구분을 햇다면 매핑을 어떻게 해야하는가.
어노테이션때는 설명을 햇다.
@Result(property = "memberName", column = "member_name")이런식으로 작성햇엇다.
<resultMap type="com.newlecture.web.entity.Notice" id="noticeViewMap">
<result property="memberName" column="member_name"/>
</resultMap>
<select id="getViewList" resultMap="noticeViewMap">resultMap을만들어서 결과집합을 이름을 바꿔주는 것을 명시하고
select할때 타입을 가져오는 것이아니라 Map을가져오면 된다.
@AutoConfigureTestDatabase(replace = Replace.NONE)
@MybatisTest
class NoticeDaoTest {
@Autowired
private NoticeDao noticeDao;
@Test
void test() {
List<Notice> list = noticeDao.getViewList(0, 1, "title", "", false);
System.out.println(list.get(0));
}
}39. Dao 구현체 직접 구현하기 SqlSession
mapper로 만들엇는데 이번엔 직접 만들어보도록하자.
원래는 <mapper> 등 태그를읽어내고 SqlSessionFactiry가 mapper container에 담는다.
원래 이것을 SqlSession을 사용해서 꺼내 읽어야한다.
스프링부트로 오면서 @Mapper어노테이션을 달면
이것을 꺼내지 않아도 알아서 IOC에 담아주었다.
사실 마이바티스는 dao를 구현하는데 편하게 해주는 것은 맞지만
dao를 대신하는 것은 아니다.
@Mapper는 dao인터페이스와 마이바티스가 너무 강하게 결합되는 느낌이 든다.
이것을 지우면 mapper container에서 스프링 IOC컨테이너로 값을 꺼내주지 못한다.
@Repsotiry가 mapper를 읽어낸다.
스프링부트에서는 dao를생성안해도 @Mapper를해서 그냥 담기만 하면됫던것이다.
인터페이스를 구현하기 위해서 그냥 @Mapper를 사용햇다. @Mapper가 dao와 붙어잇다면 마이바티스와 강하게 결합되어잇는 느낌이 든다.
다양한 방법으로 dao를 구현할 수잇는데 mapper를 지우고싶다.
@mapper가 IOC에 담아주는데 이것을 지우면 컨트롤러에서 사용할 수 없게된다. 그래서 직접 구현해보자.
@Component 중 @Controller @Service @Repository 의 3번째를 사용하는 것이다.
Mapper객체를 얻어낼땐 SqlSession객체를 얻어내서 할 수잇다.
IOC컨테이너에 담겨있으니 @autowired해서 사용하면된다.
sqlSession.getMapper(NoticeDao.class);
NoticeDao의 클래스 정보를 제공하고 이것의 구현체를 꺼내야한다.
@Repository
public class MybatisNoticeDao implements NoticeDao {
@Autowired
private SqlSession sqlSession;
@Override
public List<Notice> getViewList(int offset, int size, String field, String query, boolean pub) {
NoticeDao noticeDao = sqlSession.getMapper(NoticeDao.class);
return noticeDao.getViewList(offset, size, field, query, pub);
}
}Dao를 직접 구현햇을때 장점이 무엇인가?
인터페이스가 mybatis에 종속되는 것을 풀 수있다.
따라서 완전히 분리함으로써 mybatis가 아니어도 jpa등 다른 기술을 사용할수도 잇다.
구현체를 직접 구현할 수 잇다면 구현하는게 낫다.
마이바티스는 오버로드를 지원하지 않아서 인터페이스를 마이바티스에 맞춰 하나만 할 수잇다.
그런것도 별로 맞지 않아서 구분해서 나누면 오버로드 기능도 사용할 수 있다.
40. 나머지 Dao 구현체 직접 구현하기 Constructor Injection
sqlSession으로 매퍼를 얻어서 하는것이 문제가 발생할 수잇다.
코드가 계속 같기때문이다. 좀 줄일수잇는방법이 없을까?
반복을 줄이기 위해서 생성자에 넣어서 실행되도록하자.
NoticeDao의 추상메소드를 사용 -> 구현체인 MybatisNoticeDao가 생성됨 -> 생성하면서 mapper 필드를 초기화함 -> 리턴문의 mapper가 구현됨
@Repository
public class MybatisNoticeDao implements NoticeDao {
private NoticeDao mapper;
@Autowired
public MybatisNoticeDao(SqlSession sqlSession) {
mapper = sqlSession.getMapper(NoticeDao.class);
}
@Override
public List<Notice> getViewList(int offset, int size, String field, String query, boolean pub) {
return mapper.getViewList(offset, size, field, query, pub);
}
@Override
public Notice get(int id) {
return mapper.get(id);
}
@Override
public int getCount(String field, String query) {
return mapper.getCount(field, query);
}
@Override
public Notice getNext(int id) {
return mapper.getNext(id);
}
@Override
public Notice getPrev(int id) {
return mapper.getPrev(id);
}
@Override
public int insert(Notice notice) {
return mapper.insert(notice);
}
@Override
public int update(Notice notice) {
return mapper.update(notice);
}
@Override
public int delete(int id) {
return mapper.delete(id);
}
@Override
public int deleteAll(int[] ids) {
return mapper.deleteAll(ids);
}
@Override
public int updatePubAll(int[] ids, boolean pub) {
return mapper.updatePubAll(ids, pub);
}
}41. Service 계층 구현하기
직접 구현한 dao를 넣어보자.
pub같은 것이 추가되어서 고쳐야할 것이 생겻다.
@Override
public int updatePubAll(int[] pubIds, int[] closeIds) {
int result = 0;
result += noticeDao.updatePubAll(pubIds, true);
result += noticeDao.updatePubAll(pubIds, false);
return result;
}int updatePubAll(int[] ids, boolean pub);을 두번실행해서 결과에 더해주고 출력하자.
void test() {
System.out.println(service.getCount());
}
dao에서도 하면 또 문제가 발생하는것을 보면 dao에서 문제가 잇다는 것을 알 수잇다.
mapper를 보면 select문이 잘못되엇다는 것을 알수잇다.
<select id="getCount">
SELECT count(id) count FROM notice
where ${field} like '%${query}%'
</select>반환타입이 int인데 반환타입을 적어주지 않았다.
resultType="int"를 넣어주자
잘됫다라고 넘어가면안된다.
pub를 추가으니 count메소드에소드의 인자에도 boolean pub를 넣어주면서 고쳐야할 부분들을 모두 고쳐야한다.
mapper도 변경해준다.
2023.05.01
국비 공부를 하면서 인터페이스와 분리하는 것을 많이 안하고 있다.
결합력을 줄이는 방향이 확실히 미래를 봐서는 좋게 느껴진다.
하지만 프로젝트의 크기에 따라 그러지 않는 것도 편하다.
적절히 섞어서 사용하는 것을 중점으로 해야할 것 같다.
연습은 계속하기 위해 나누는 방법을 생각해보는 것도 좋아보인다.
'기초단계 > SPRING' 카테고리의 다른 글
| 2023.05.04 Spring (0) | 2023.05.09 |
|---|---|
| 2023.05.02 Spring 김영한 (0) | 2023.05.02 |
| 2023.04.27 Spring (0) | 2023.04.28 |
| 2023.04.24 Spring (0) | 2023.04.24 |
| 2023.04.22 Spring (0) | 2023.04.24 |