# ★★★ MyBatis에서 Mapper.xml인식시켜주기 위해서 위치 밝히는 것 ★★★ 1. 이렇게 Mapper하나하나를 직접 지정해줘도 되지만 mybatis.mapper-locations=mappers/MenuRepositoryMapper.xml
2. *를 사용하면 모든 Mapper를 한 번에 다 불러올 수 있음 (※단, 모든 파일명이 다 Mapper로 끝나야 함 주의) mybatis.mapper-locations=mappers/*Mapper.xml
# ★★★ MyBatis에서 패키지에 대한 내용도 미리 설정 가능 ★★★ mybatis.type-aliases-package=kr.co.rland.web.entity
#Mapper.xml 파일 설정
MyBatis 연결 및 자잘한 설정을 모두 처리했으니
Mapper.xml 파일에서는 뭐가 달라졌는지 살펴보자.
1. 구분자 설정 빠짐
application.properties에서 모두 처리했기 때문에
구분자 설정 부분이 빠졌다.
💡구분자 설정이 사라졌다.
application.properties에서 이미 다 설정을 해주었기 때문에
Mapper.xml 파일에는 해당 설정이 빠졌다.
★★★ 변경 전 MenuRepositoryMapper.xml 설정 ★★★
<!-- mybits-config에서 typeAlias로 패키지.클래스명을 Menu라는 이름으로 줄였다. -->
<resultMap id="MenuMapper" type="Menu">
<!-- <id property="id" column="user_id" /> -->
<result property="korName" column="kor_name" />
<result property="engName" column="eng_name" />
<result property="regDate" column="reg_date" />
<result property="memberId" column="member_id" />
</resultMap>
★★★ 변경 후 MenuRepositoryMapper.xml 설정 ★★★
<!-- application.properties에 패키지명 다 적어놔서 -->
<!-- 아예 해당 태그블록이 사라짐 -->
2. 리턴타입이 resultMap이었던 것이
모두 다 returnType으로 바뀌었다.
💡 resultMap이 returnType으로 바뀌었다.
객체 반환시 resultMap이라고 구분지었던 것이
모두 returnType으로 바뀌었다.
(이전에는 객체 반환시에는 resultMap, 값 반환시에만 returnType 이었음)
★★★ 변경 전 MenuRepositoryMapper.xml 설정 ★★★
<!-- 객체 반환 시에는 구분자 내용을 포함한 resultMap으로 썼다. -->
<resultMap id="MenuMapper" type="Menu">
... 구분자 설정 중략...
</resultMap>
<select id="findAll" resultMap="MenuMapper">
select * from menu
</select>
★★★ 변경 후 MenuRepositoryMapper.xml 설정 ★★★
<!-- resultMap에서 resultType으로 리턴타입이 바뀌었음 -->
<!-- 참고로 application.properties에서 entity 패키지를 밝혀주었기 때문에 -->
<!-- 여기서는 returnType으로 클래스명만 작성하면 된다. -->
<select id="findAll" resultType="Menu">
select * from menu
</select>
최종 변경된 MenuRepositoryMapper.xml 파일을
코드로 확인해보자.
💡 Mapper.xml 파일 설정
<?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">
<!-- 얘는 그대로 패키지명부터 다 적어주기 -->
<!-- application.properties의 mapper-locations에 적으니까 오류남 -->
<mapper namespace="kr.co.rland.web.repository.MenuRepository">
<!-- typealiases에 패키지명 다 적어놔서 resultMap에 Menu쓸 때 패키지부터 안써도 됨
그리고 resultMap에서 resultType으로 바꿔줘야 함!!!!!!! -->
<select id="findAll" resultType="Menu">
select * from menu
</select>
<select id="findViewAll" resultType="MenuView">
select * from menu_view
</select>
<!-- null값이 포함되면 Integer라고 type쓴다. -->
<select id="count" resultType="int">
select count(id) count from menu
</select>
<select id="findById" resultType="Menu">
select * from menu
where id = #{id}
</select>
<insert id="save" parameterType="Menu">
insert into menu(kor_name, eng_name, price, img, member_id)
values(#{korName}, #{engName}, #{price}, #{img}, #{memberId})
</insert>
<update id="update" parameterType="Menu">
update menu set img = #{img}
where kor_name = #{korName};
</update>
<delete id="delete">
delete from menu
where id = #{id};
</delete>
</mapper>
#Lombok 라이브러리
getter/setter/toString과 같은
boilerplate를 자동으로 처리해주는
Lombok 라이브러리를 설치해보자.
💡 boilerplate란?
계속 사용되는 반복적인 업무를 의미한다. 대표적인 boilerplate 코드로 자바에서 사용하는 getter, setter를 꼽을 수 있다.
java에서는 생성자를 따로 지정하지 않은 경우 기본 생성자(파라미터가 하나도 없는)를 자동으로 추가해주지만,
오버로드한 생성자가 하나라도 있을 경우에는 (파라미터가 들어간) 기본 생성자를 자동으로 만들어주지 않는다.
그렇다면 객체를 new할 때마다 반드시 파라미터를 넣어야만 오류가 나지 않기 때문에 번거로운 일이 발생한다.
따라서 오버로드 생성자를 추가하는 경우에는 기본 생성자를 필수로 만들어주는 습관을 들이자.
#@Builder 클래스 vs 생성자
Lombok을 통해서 처리할 수 있는
Annotation 중에 Builder는
setter의 기능을 더욱 단순화 해준다.
이때 특정 생성자위에도 붙일 수 있고
클래스 위에도 붙일 수 있는데 두 개의 차이가 있다.
💡 @Builder 사용 주의점 (클래스와 생성자)
1. Builder를 쓰려면 class에 @AllArgsConstructor를 꼭 추가해줘야 한다. ( @AllArgsConstructor를 해줘야지만 속성을 추가할 수 있기 때문 )
2. Builder를 class가 아니라 특정 컬럼만 가지고 있는 생성자를 만들고 그 위에 붙이면, 필수 매개변수에 대해서만 builder할 수 있게 해준다.
3. 클래스에 붙이면 만들어 놓은 모든 오버로드 생성자 전체 다 커버가능! 오버로드 생성자처럼 꼭 순서를 지키지 않아도 되고, 원하는 컬럼에 대해서만 setter할 수 있으니 훨씬 편리!
class에 @Builder를 붙였다.
아울러 @AllArgsConstructor가 있으니
원하는 field만 뽑아서 setter할 수 있다.
💡 @Builder 어노테이션 붙이기
@NoArgsConstructor
@AllArgsConstructor
@Getter //getter를 직접 만들거나 아니면 lombok(라이브러리)의 지시어 @getter를 쓴다.
@Setter
@ToString
@Builder
/*
* Builder
*
* 1. Builder를 쓰려면 @AllArgsConstructor를 꼭 추가해줘야 한다.
* ( @AllArgsConstructor를 해줘야지만 속성을 추가할 수 있기 때문 )
* 2. Builder를 class가 아니라 특정 컬럼만 가지고 있는 생성자를 만들고 그 위에 붙이면,
* 필수 매개변수에 대해서만 builder할 수 있게 해준다.
* 3. 클래스에 붙이면 기본 생성자부터 모든 매개변수 생성자까지 다 가능!
* (단 기본생성자, 매개변수 생성자 모두 어노테이션 있어야 함!)
*
**/
public class Menu {
//1. 데이터베이스 테이블 속성 그대로
//2. 구분자 형태만 바꾸기(하이픈->대소문자)
private long id;
private String korName;
private String engName;
private int price;
private String img;
private Date regDate;
private int hit;
private long memberId;
//이제는 생성자 오버로드나 getter/setter/toString 같은거 안만들어도 된다.
}//class ends
#.builder()로 속성 추가하기
메소드에서 .builder()를 사용할 때
Builder는 static이기 때문에
객체를 생성한 뒤에 붙이지 않고
class에 바로 붙여서 사용한다.
출처: 뉴렉처(https://www.youtube.com/@newlec1)
#(번외)Entity는 개별적으로 가져가는 것이다.
@Builder를 하려다보니
부모 클래스와 중복되는 컬럼때문에
오류가 생긴다.
때문에 View를 위한 Entity를 따로 추가할 때
중복된 컬럼이 다수 있다고 하더라도
상속을 받지 않고, 개별적으로 만든다.
💡 중복되는 속성까지 포함한 View 만들기
@NoArgsConstructor
@AllArgsConstructor
@Getter //getter를 직접 만들거나 아니면 lombok(라이브러리)의 지시어 @getter를 쓴다.
@Setter
@ToString
/*
* ToString + EqualsAndHashCode + Getter + Setter + RequiredArgsConstructor를 다 포함한
* @Data를 써도 됨
*
* */
@Builder
/*
* Builder를 부모에서 이미 만든 경우에는 중복되어서 오류가 난다.
*
* 1. Builder를 쓰려면 @AllArgsConstructor를 꼭 추가해줘야 한다.
* ( @AllArgsConstructor를 해줘야지만 속성을 추가할 수 있기 때문 )
* 2. Builder를 class가 아니라 특정 컬럼만 가지고 있는 생성자를 만들고 그 위에 붙이면,
* 필수 매개변수에 대해서만 builder할 수 있게 해준다.
* 3. 클래스에 붙이면 기본 생성자부터 모든 매개변수 생성자까지 다 가능!
* (단 기본생성자, 매개변수 생성자 모두 어노테이션 있어야 함!)
*
**/
//★★★★★중복되는 속성있다고 상속받으면 오류나니까 상속받지마★★★★★
public class MenuView{
/* Member Variables */
//1. 데이터베이스 테이블 속성 그대로
//2. 구분자 형태만 바꾸기(하이픈->대소문자)
/* Menu에 있던 속성 */
private long id;
private String korName;
private String engName;
private int price;
private String img;
private Date regDate;
private int hit;
private long memberId;
/* MenuView에서 추가된 속성 */
private int likeCount;
}
#DAO 인터페이스 구현체가 필요없다 (@Repository → @Mapper)
스프링에서는 Mapper를 꺼내 쓸 수가 없었다.
MyBatis에서 관리하는 것이었기 때문에 인터페이스를 구현하는 Imp클래스를 따로 만들어서 getMapper로 다시 IOC 컨테이너에 담아줬었다. (즉, Mapper.xml이 IOC 컨테이너에 들어가지 않았던 것)
이 때Imp클래스 위에@Repository라는 어노테이션을 활용했다.
💡 Spring에서 사용했던 방법
@Repository
public class MbMenuRepository implements MenuRepository {
//생성자보다 어노테이션이 늦게 실행되기 때문에 생성자에서 SqlSession을 만들 수는 없다.
//(아직 연결 안돼서 객체 없으니 만들 수가 없음)
//때문에 field에 DI한다.
@Autowired
private SqlSessionFactory sqlSessionFactory;
//DAO함수 하나하나 연결을 위해서 개별로 openSession()을 해주었다.
@Override
public List<Menu> findAll() {
SqlSession sqlSession = sqlSessionFactory.openSession();
MenuRepository repository = sqlSession.getMapper(MenuRepository.class);
List<Menu> list = repository.findAll();
return list;
}
@Override
public int count() {
// TODO Auto-generated method stub
return 0;
}
@Override
public Menu findById(long id) {
// TODO Auto-generated method stub
return null;
}
}//class ends
그러나 스프링부트에 오면서 이것이 가능해졌다.
@Mapper라는 어노테이션을 활용해서
Mapper를 바로 사용할 수 있게 되었다.
💡 왜 SpringBoot에서는 되는거지?
스프링 부트에서 제공하는 Starter라는 라이브러리를 통해서 가능해졌다. (Starter: 설정을 포함하고 있는 라이브러리임)
별도의 Imp 클래스(구현체)를 만들 필요 없이
인터페이스에 직접 @Mapper를 달아서 만든다.
일반적으로 인터페이스는 구현할 때
모든 메소드를 다 Override하게 되어 있지만,
@Mapper 어노테이션을 붙인 객체들은
필요한 메소드에 대해서만 구현해도
오류가 나지 않는다..!
💡 인터페이스에 @Mapper붙이고 바로 구현하기
//일반적으로 인터페이스는 만들어 놓은 것에 대해서 다 구현체를 만들어놓게끔 되어 있지만
//Mapper 객체는 인터페이스의 모든 것을 다 구현하지 않아도 되게 해준다.
@Mapper
public interface MenuRepository {
// @Select("select * from menu")
//어노테이션으로 이거를 다 쓰면 너무 길어지기 때문에 Mapper.xml 다시 쓸거임
//.xml읽어올 수 있도록 application.properties에 mapper-locations 세팅하기
List<Menu> findAll();
// @Select("select * from menu where id = #{id}")
Menu findById(long id);
int count(); // select count(id) from ..
int save(Menu menu);
int update(Menu menu);
int delete(long id);
}//interface ends