TIL - 15주차 코드

 

1. Mybatis : 230306

1) 이전 내용 복습 :


a. 리액티브 개념 :

  • 모델을 이용해서 View와 Controller로 나눈 것으로 MVC 패턴의 다른말이다.


  • 리액티브 플랫폼 :
    • react, angular, vue.js가 있다. model을 view가 소비한다.
    • 컨트롤러 개입 없이 model이 view를 바꾸고 view가 model을바꾸는 개념이 리액티브 개념이다. 그래서 re-active이다.



c. Jaesper :

  • 기존 코드를 서블릿 코드로 만들어주는 도구이다.



d. JSTL과 EL을 이용한 View의 코드

  • 태그 라이브러리란?
    • 태그 모양으로 행위를 가지는 라이브러리


  • 사용 용도
    • View 단에서 자바 코드를 드러내기 위해 사용한다.
    • 분업화를 위해서 자바 코드가 사용되면 단순한 제어를 위해 자바를 잘 아는 사람이 참여해야 하는 것이 비효율적이기도 하고 수 많은 코드 블록의 삽입이 코드를 복잡하고 유지보수하기에 힘든 문제가 있어서


  • JSTL 제어를 위해 사용하는 데이터는 어디에 저장된 데이터인가? 지역변수인가?
    • 서버 4대저장소(page, request, session, application)
  • EL을 이용할 때의 장점은 무엇인가?
    • 자바 코드 없이 4대 저장소의 값을 쉽고 간결하게 꺼내올 수 있다.


  • 데이터를 저장하기 위해서 지역변수가 아닌 4대 저장소를 활용하는 이유?**
    • 출력데이터는 기본 지역 데이터와 분리해서 사용할 필요가 있다. MVC는 코드를 나누는 방식을 사용하기 때문에 View의 데이터는 최소 다른 서블릿과 데이터를 공유하는 것이 기본이다. 뿐만 아니라 단일한 데이터 소비를 위해서라도 지역변수를 범주에 두는 것은 전체 변수 사용에 충돌이나 복잡한 문제를 가질 수 있다.



e. 쿠키

  • 쿠키에는 빈공백이나 특수문자 등은 들어갈 수 없어요. 따라서 URL 방식으로 인코딩될 수 있는 문자열만 가능합니다. URL에 사용할 수 있는 형태로만 저장이 가능하다고 보면 된다.

  • 클라이언트 환경이 너무 다양하기 때문에 그것을 신뢰해서 사용하기는 힘들 수도 있습니다. 필수 데이터를 위해서 사용하기 보다는 부가적으로 필요한 데이터로 사용하는 것이 좋을 것 같습니다. 예를 들어서 그 데이터 없이 페이지를 만들 수 없다거나 그것이 아니라 사용할 수 없음 말고 식으로 가볍게 사용할 데이터여야 합니다.(세선 ID, 인증 관련 토큰 등**)




2) Mybatis


a. 실습 코드 :

  • MenuRepository.java
package kr.co.rland.web.repository;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import kr.co.rland.web.entity.Menu;

// @Repository
// ibatis가 원래 이름인데 버전이 업되면서 mybatis로 바뀌었다. mapper로 매핑
// @Mapper가 구현체를 알아서 repository에 붙여준다.
// 하지만, 이렇게 되면 
@Mapper
public interface MenuRepository {
	
	// SQL문은 모든 메서드 중에서 쓸 거만 구현해도 된다.
	@Select("select * from menu")
	List<Menu> findAll();
	
	Menu findById(long id);
	
	Menu insert(Menu menu);
	
	Menu update(Menu menu);
	
	void delete(long id);
}


b. Mybatis 사용 방법 :

  • SQL문에 데이터 대입할 때, id가 문자열 타입이면 #을 쓰면 ““가 포함되고 $를 쓰면 ““가 포함되지 않고 값을 그대로 쓴다.(정수와 문자열을 비교)


c. Mybatis 문제점 :

  • SQL문에 내려쓰기가 불가능하다. 복잡하게 SQL문을 “+”로 이어줘야 한다.
  • 또한, 인터페이스에 어노테이션을 붙여서 문제가 많다.
  • SQL문이 길어지면 길어질수록 가독성이 안 좋아진다. 원래, 인터페이스가 무엇인지 알아보기 힘들다.

package kr.co.rland.web.repository;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import kr.co.rland.web.entity.Menu;

// @Repository
// ibatis가 원래 이름인데 버전이 업되면서 mybatis로 바뀌었다. mapper로 매핑
// @Mapper가 구현체를 알아서 repository에 붙여준다.
// 하지만, 이렇게 되면 
@Mapper
public interface MenuRepository {
	
	// SQL문은 모든 메서드 중에서 쓸 거만 구현해도 된다.
	@Select("select * from menu")
	List<Menu> findAll();
	@Select("select * from menu where id=#{id}")
	Menu findById(long id);
	
	Menu insert(Menu menu);
	
	Menu update(Menu menu);
	
	void delete(long id);
}



d. 해결 방법 :

  • @Mapper만 남기고 SQL문에 해당하는 어노테이션은 다 지우자! 그래서, resources 폴더에 Mapper 폴더를 만들고 xml 파일을 만들자. 또한, Entity 폴더로 갈 수도 있다.
  • 그래서, 다이나믹 프로그래밍을 하자.


e. 해결 과정 :


1) XML 파일에 Mybatis 초기 설정 구문 참고 사이트에 들어가서 Exploring Mapped SQL Statements 부분을 추가해주자
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.BlogMapper">
  <select id="selectBlog" resultType="Blog">
    select * from Blog where id = #{id}
  </select>
</mapper>


2) XML 파일에 SQL문 매핑에 관한 실습 코드
<?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">
		select * from menu
	</select>
	
	<select id="findById" resultType="kr.co.rland.web.entity.Menu">
		select * from menu where id=#{id}
	</select>
	
<!-- 	<insert id="insert">
	
	</insert>
	
	<update id="update">
	
	</update>
	
	
	<delete id="delete">
	</delete> -->
</mapper>


3) 추가 설정
  • 우리는 이제 내가 이 SQL문을 매핑했다고 Mybatis에게 알려줘야 한다.


  • 원래, application.properties가 root 설정이다. 그래서, 모든 페이지에서 쓰이는 공통적인 부분만 설정해준다.

  • 그래서 우리는 application-dev.yml(application-prod.yml)로 설정할 것인지 application.properties로 설정할 것인지 판단하여 프로필 설정만 해준다.
    • 둘 중 하나만 설정하므로 application-dev.yml에서 설정하고 application.properties의 DB 연결 설정을 모두 지우자.
  • application.properties

spring.servlet.multipart.max-file-size=100MB	
#각각의 파일 용량 
spring.servlet.multipart.max-request-size=200MB 
#전체 용량

  • application-dev.yml

spring:
  datasource:
    url: jdbc:mariadb://db.newlecture.com:3306/rlanddb
    driver-class-name: org.mariadb.jdbc.Driver
    username: rland
    password: 20220823
    


4) yml에 classpath 설정하기
  • 해당 경로에서 xml을 사용하기 위해서 ClassPathXmlApplicationContext을 설정을 해주고 설정을 따로 안해주면 root 경로가 기본이라서 root를 기준으로 xml 파일을 찾아서 읽는다.
  • 추가로 Maven 프로젝트는 Java 폴더가 root 경로이다.
  • 그래서, xml 파일에서 SQL문의 반환 타입을 설정해주기 위해서 resultType 속성의 경로를 /rlandboot3/src/main/java/kr/co/rland/web/entity/Menu.java에서 java부터 시작 경로를 설정해준다.
    • 그래서, kr.co.rland.web.entity.Menu로 설정해준다.


  • 그리고 yml 설정 파일에서 모든 클래스를 매핑하려고 마이바티스 설정의 mapper-location 속성을 *Mapper.xml라고 설정하면 Mapper로 끝나는 모든 파일에 대하여 매핑이 가능하다.
    • 추가로 .yml파일은 모두 빈 공백도 약속이라서 매우 주의해서 사용하자!
  • application.yml

spring:
  profiles:
    active:
    - dev

mybatis:
  mapper-location:
  - classpath:mapper/*Mapper.xml


5) Mybatis SQL문의 반환 타입인 resultType 설정
  • Mybatis에서 xml 설정에서 SQL문을 실행 후 반환할때, Entity를 담는 그릇도 경로 설정도 resultType으로 해주어야 한다.

  • 경로는 Copy Qualified Name에서 가져오고 경로에서 절대경로인 슬래쉬 “/”는 모두 지운다.

<?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">
		select * from menu
	</select>
	
	<select id="findById" resultType="kr.co.rland.web.entity.Menu">
		select * from menu where id=#{id}
	</select>

<!-- 	<insert id="insert">
	
	</insert>
	
	<update id="update">
	
	</update>
	
	
	<delete id="delete">
	</delete> -->
</mapper>



2. Mybatis 페이저 사용 방법 : 230307

  • 오버로드 사용하는 방법


1) Mybatis 페이저 방법 1:


a. 예전 방법 :

  • 원래, 마이바티스에서는 오버로드 메서드 생성이 불가능하다.


  • 그런데, 마이바티스의 또 다른 저장소가 있다. 마이바티스는 자기만의 Mapper 빈 객체가 따로 있었다.


  • 마이바티스를 스프링에서 사용하기 위해서 IOC컨테이너 담아줘야 한다. 그래서, 스프링이 관리하는 빈 객체를 따로 만들어 줘야 했다.


  • 그래서, 마이바티스에서 스프링과 연계해주는 IOC 커넥터를 만들어줘서 사용하게 되었다. 마이바티스에서 그 만들어진 IOC 컨테이너에 빈 객체를 꺼내기 위한 것이 SqlSession이다.


  • 원래는 오버로드를 지원하지 않아서 SqlSession을 통해 Mapper해줘야 했다. 하지만 이것은 코드가 매우 복잡해진다.


  • 실습 코드 :
package kr.co.rland.web.repository.mybatis;

import java.util.ArrayList;
import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.springframework.stereotype.Repository;

import kr.co.rland.web.entity.Menu;
import kr.co.rland.web.repository.MenuRepository;

@Repository
public class MbMenuRepository implements MenuRepository{. 
	
	@Autowired
	private SqlSession session;
	
	@Override
	public List<Menu> findAll() {
		
		MenuRepository menuDao = session.getMapper(MenuRepository.class);
		
		return menuDao.findAll(1, 20, 1);
	}
	
	@Override
	public List<Menu> findAll(int page) {
		
		MenuRepository menuDao = session.getMapper(MenuRepository.class);
		
		return menuDao.findAll(page, 20, 1);
	}

	@Override
	public Menu findById(long id) {
		return null;
	}

	@Override
	public Menu insert(Menu menu) {
		return null;
	}

	@Override
	public Menu update(Menu menu) {
		return null;
	}

	@Override
	public void delete(long id) {
		
	}

}



2) Mybatis 페이저 방법 2

a. Mybatis에서 이용 : offset, size 이용

  • offset을 이용하자.

  • 매개변수 인자들이 모두 전달 되는 것이 아니라 아닌 것도 있다.

  • 하지만, offset은 무조건 전달 되도록 해야 한다. 그래서 null값이 올수 있도록 int형이 아니라 Integer 타입으로 offset과 size를 설정해준다.


  • 실습 코드 :
package kr.co.rland.web.repository;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import kr.co.rland.web.entity.Menu;

// @Repository
// ibatis가 원래 이름인데 버전이 업되면서 mybatis로 바뀌었다. mapper로 매핑
// @Mapper가 구현체를 알아서 repository에 붙여준다.

@Mapper
public interface MenuRepository {
	
	List<Menu> findAll(Integer offset, Integer size, String query, int page);
	
	Menu findById(long id);
	
	Menu insert(Menu menu);
	
	Menu update(Menu menu);
	
	void delete(long id);
}



b. MySQL paging 이용 : limit, offset

  • limit : 몇 개씩 끊어서 주세요.

  • offset : 어디서 부터? 데이터를 가져다 주세요.

  • MySQL에서 테스트하고 Spring으로 가져가자.

select * from menu
order by regDate desc
limit 10 offset 0;


c. MySQL의 paging을 MYbatis 이용 : if 절 이용

  • SQL문에서 조건절이 null 값이 나올 때, 에러 방지용


a) 수정 전 코드


  • 실습코드 :
    • MenuRepositoryMapper.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">
<mapper namespace="kr.co.rland.web.repository.MenuRepository">
	<select id="findAll" resultType="kr.co.rland.web.entity.Menu">
		select * 
		from menu
		where 
			name like '%${query}%' 
		    and price > #{price} 
		    and categoryId=#{categoryId}
		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>

<!-- 	<insert id="insert">
	
	</insert>
	
	<update id="update">
	
	</update>
	
	
	<delete id="delete">
	</delete> -->
</mapper>
  • MenuRepository.java
package kr.co.rland.web.repository;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import kr.co.rland.web.entity.Menu;

// @Repository
// ibatis가 원래 이름인데 버전이 업되면서 mybatis로 바뀌었다. mapper로 매핑
// @Mapper가 구현체를 알아서 repository에 붙여준다.

@Mapper
public interface MenuRepository {
	
	List<Menu> findAll(Integer offset,
					Integer size, 
					String query,
					Integer categoryId,
					Integer price,
					String orderField,
					String orderDir
					);
	
	Menu findById(long id);
	
	Menu insert(Menu menu);
	
	Menu update(Menu menu);
	
	void delete(long id);
}



b) 수정 후 코드
  • 위의 칼럼을 다 넣어줘야 에러가 안나서 null 값을 처리해주는 if절이나 where절 그리고 trim절을 사용해줘야 한다.
  • where 절은 null 값이 반환되어도 where이 사라지지 않고 where이 남아 있는다. 하지만, 처음이 null 값이라면 문제가 있다.
  • 그래서, trim은 null 값이 반환되어도 where을 AND나 OR로 바꿔 준다.


  • 실습코드 :
    • MenuRepositoryMapper.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">
<mapper namespace="kr.co.rland.web.repository.MenuRepository">
	<select id="findAll" resultType="kr.co.rland.web.entity.Menu">
		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>

<!-- 	<insert id="insert">
	
	</insert>
	
	<update id="update">
	
	</update>
	
	
	<delete id="delete">
	</delete> -->
</mapper>


  • MenuRepository.java
package kr.co.rland.web.repository;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import kr.co.rland.web.entity.Menu;

// @Repository
// ibatis가 원래 이름인데 버전이 업되면서 mybatis로 바뀌었다. mapper로 매핑
// @Mapper가 구현체를 알아서 repository에 붙여준다.

@Mapper
public interface MenuRepository {
	
	List<Menu> findAll(Integer offset,
					Integer size, 
					String query,
					Integer categoryId,
					Integer price,
					String orderField,
					String orderDir
					);
	
	Menu findById(long id);
	
	Menu insert(Menu menu);
	
	Menu update(Menu menu);
	
	void delete(long id);
}


  • DefaultMenuService.java
package kr.co.rland.web.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import kr.co.rland.web.entity.Menu;
import kr.co.rland.web.repository.MenuRepository;

// @Component 어노테이션 설정해서 IOC에서 빈객체를 만들 수 있다. 
@Service
public class DefaultMenuService implements MenuService {
	
	@Autowired
	private MenuRepository repository;
	

	// 객체를 만들어서 결합해준다! 
	// setter가 있어야 xml에서 bean 태그에서 property 속성을 이용할 수 있다. 
	public void setRepository(MenuRepository repository) {
		this.repository = repository;
	}

	@Override
	public List<Menu> getList() {
		return repository.findAll(0,10,"",1,3000,"regDate","desc");
	}

}



3) 추가 : 로깅 찍기**

  • 보통, 디버그에서 쓰인다.
  • level을 설정해주면 단계별로 로깅을 할수 있다.
  • 클래스별로 해준다.


  • 나중에, 로그를 찍는 이유는 라이브러리를 만드는 경우에 사용한다! 그것을 나중에 사용하는 사람들이 확인하게 하려고 로깅을 찍어주자.**
    • 보통, 경력이 쌓이면 만들 것이다.
    • 리팩토링에서 쓰일 수 있다.


  • 설정 과정 :
    • 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

# 로깅 찍기
logging.level.kr.co.rland.web.repository=debug


4) Mybatis에서 update

  • MySQL, MariaDB은 Auto-Commit이 된다.
  • prefix : 실행될 쿼리의 문 안에 쿼리 가장 앞에 붙여준다.
  • suffixOverrides : 실행될 쿼리의 문 안에 쿼리 가장 앞에 해당하는 문자들이 있으면 자동으로 지워준다.
  • update SQL 절에 #{name}은 getter이다. 사용자 입력을 받아서 return 값으로 name을 반환하는 get이 필요하다! 그 값으로 DB에서 그 레코드의 값으로 업데이트 시킨다!!**


  • 실습 코드 :
    • MenuRepositoryMapper.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">
<mapper namespace="kr.co.rland.web.repository.MenuRepository">
	<select id="findAll" resultType="kr.co.rland.web.entity.Menu">
		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>

	<!-- <insert id="insert">
	
	</insert>
	 -->
	<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>
		</trim>
		where id=#{id}
	</update>
	
	
<!-- 	<delete id="delete">
	</delete> -->
</mapper>


5) SpringBoot 단위 테스트


a. JUnit 테스트 설정 : Mybatis

  • 먼저 라이브러리 추가해주기!!

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter-test</artifactId>
    <version>3.0.1</version>
    <scope>test</scope>
</dependency>


b. 테스트 실습 코드 1 : Mybatis


  • JUnit 테스트용 DB가 있을 수 있다. 그래서, DB가 바뀔 수 도 있어서 그것을 하지 말라고 설정을 해줘야 한다.
    • @AutoConfigureTestDatabase 속성의 설정에서 replace=Replace.NONE을 추가 해주기!
      • @AutoConfigureTestDatabase(replace=Replace.NONE)


  • 실습 코드 :
  • MenuRepositoryTest.java
package kr.co.rland.web.repository;

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;


// IOC 컨테이너에 담아줘야하기 때문에 테스트 어노테이션을 붙여줘야 한다.
// 2가지 옵션이 있다.
// 1) @MybatisTest 2) @SpringBootTest

@AutoConfigureTestDatabase(replace=Replace.NONE)
@MybatisTest
class MenuRepositoryTest {

	@Autowired
	private MenuRepository repository;
	
	@Test
	void testFindAll() {
		List<Menu> list = repository.findAll(0,10,null,null,null,"regDate","desc");
		System.out.println(list);
	}

	@Test
	void testUpdate() {
	}

}


  • application.properties
spring.servlet.multipart.max-file-size=100MB	
#각각의 파일 용량 
spring.servlet.multipart.max-request-size=200MB 
#전체 용량

# JUnit 테스트용 db가 있다. 그래서, DB가 바뀔 수도 있어서 그것을 하지마라고 설정을 해줘야 한다.
# replace none 설정!!
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

# 로깅 찍기
logging.level.kr.co.rland.web.repository=debug


c. 테스트 실습 코드 2 : Mybatis

  • update는 parameterType으로 반환한다. 그 이유는 업데이트를 하기 위해서 id같은 인자가 필요하기 때문이다.
  • parameterType는 진짜 반환하는 그 객체 타입이 맞는지 확인한다. 또한, 그 객체에 맞는 getter를 가져온다 .


  • 주의!! : parameterType을 안쓰면, 기존의 파라미터를 쓰고 parameterType은 그 정해진 객체만 전달해줘야 한다.
  • 정리하면, parameterType은 입력용이고 resultType은 출력용이다.


  • update 하기 위해서 테이블의 모든 칼럼을 Entity에 넣어줘야 한다.
  • 테이블의 칼럼 값이 null도 허용하게 Entity를 바꿔줘서 테스트를 해줘야 햔다.


  • 실습코드 :
    • MenuRepositoryTest.java
package kr.co.rland.web.repository;

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;


// IOC 컨테이너에 담아줘야하기 때문에 테스트 어노테이션을 붙여줘야 한다.
// 2가지 옵션이 있다.
// 1) @MybatisTest 2) @SpringBootTest

@AutoConfigureTestDatabase(replace=Replace.NONE)
@MybatisTest
class MenuRepositoryTest {
	
	// @Autowired만 쓰면 안 된다. IOC에 담아줘야 테스트가 돌아간다..
	@Autowired
	private MenuRepository repository;
	
	@Test
	void testFindAll() {
		List<Menu> list = repository.findAll(0,10,null,null,null,"regDate","desc");
		System.out.println(list);
	}

	@Test
	void testUpdate() {
		// update 하기 위해서 모든 칼럼을 넣어줘야 한다.
		
		// 칼럼의 값이 null도 허용해서 테스트를 해줘야 햔다.
		
		Menu menu = new Menu();
		menu.setId(856);
		menu.setName("아마아마리카노");
		
		repository.update(menu);
	}

}


  • MenuRepositoryMapper.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">
<mapper namespace="kr.co.rland.web.repository.MenuRepository">
	
	<resultMap id="menuResultMap" type="Menu">
	  <result property="regDate" column="reg_date"/>
	</resultMap>
	
	<select id="findAll" resultMap="menuResultMap">
		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" resultMap="menuResultMap">
		select * 
		from menu 
		where id=#{id}
	</select>
	
	<select id="findAllByIds" resultMap="menuResultMap">
		select *
		from menu
		<where>
		  <foreach item="id" collection="ids"
		      open="id in (" separator="," close=")">
		        #{id}
		  </foreach>
  		</where>
	</select>

	<!-- <insert id="insert">
	
	</insert>
	 -->
	<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>
		</trim>
		where id=#{id}
	</update>
	
	
<!-- 	<delete id="delete">
	</delete> -->
</mapper>


  • Menu.java
package kr.co.rland.web.entity;

import java.util.Date;

public class Menu {
	private long id;
	private String name;
	private Integer price;
	private String img;
		
	@Column("reg_date")	// 새로 만든 어노테이션 적용!
	private Date regDate;
	
	private Integer categoryId;
	private long regMemberId;

	
	public Menu() {
	}

	
	public Menu(long id, String name, Integer price, String img, Date regDate, Integer categoryId, long regMemberId) {
		super();
		this.id = id;
		this.name = name;
		this.price = price;
		this.img = img;
		this.regDate = regDate;
		this.categoryId = categoryId;
		this.regMemberId = regMemberId;
	}


	public long getId() {
		return id;
	}


	public void setId(long id) {
		this.id = id;
	}


	public String getName() {
		return name;
	}


	public void setName(String name) {
		this.name = name;
	}


	public Integer getPrice() {
		return price;
	}


	public void setPrice(Integer price) {
		this.price = price;
	}


	public String getImg() {
		return img;
	}


	public void setImg(String img) {
		this.img = img;
	}


	public Date getRegDate() {
		return regDate;
	}


	public void setRegDate(Date regDate) {
		this.regDate = regDate;
	}


	public Integer getCategoryId() {
		return categoryId;
	}


	public void setCategoryId(Integer categoryId) {
		this.categoryId = categoryId;
	}


	public long getRegMemberId() {
		return regMemberId;
	}


	public void setRegMemberId(long regMemberId) {
		this.regMemberId = regMemberId;
	}


	@Override
	public String toString() {
		return "Menu [id=" + id + ", name=" + name + ", price=" + price + ", img=" + img + ", regDate=" + regDate
				+ ", categoryId=" + categoryId + ", regMemberId=" + regMemberId + "]";
	}

	
	
}




3. Mybatis JUnit 테스트, SqlSession : 230308

1) Mybatis의 foreach절 :

  • Mybatis의 JUnit 테스트
  • SQL의 in 구문 대신!


  • 실습코드 :


MenuRepository.java

package kr.co.rland.web.repository;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import kr.co.rland.web.entity.Menu;

// @Repository
// ibatis가 원래 이름인데 버전이 업되면서 mybatis로 바뀌었다. mapper로 매핑
// @Mapper가 구현체를 알아서 repository에 붙여준다.

@Mapper
public interface MenuRepository {
	
	List<Menu> findAll(Integer offset,
					Integer size, 
					String query,
					Integer categoryId,
					Integer price,
					String orderField,
					String orderDir
					);
	
	Menu findById(long id);
	
	// 타입 주의!! 리스트들을 모두 출력하기 때문이다. 
	List<Menu> findAllByIds(List<Long> ids);
	
	
	Menu insert(Menu menu);
	
	Menu update(Menu menu);
	
	void delete(long id);
}


MenuRepositoryMapper.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">
<mapper namespace="kr.co.rland.web.repository.MenuRepository">
	<select id="findAll" resultType="kr.co.rland.web.entity.Menu">
		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>
	
	<select id="findAllByIds" resultType="kr.co.rland.web.entity.Menu">
		select *
		from menu
		<where>
		  <foreach item="id" collection="ids"
		      open="id in (" separator="," close=")">
		        #{id}
		  </foreach>
  		</where>
	</select>

	<!-- <insert id="insert">
	
	</insert>
	 -->
	<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>
		</trim>
		where id=#{id}
	</update>
	
	
<!-- 	<delete id="delete">
	</delete> -->
</mapper>


MenuRepositoryTest.java

package kr.co.rland.web.repository;

import java.util.ArrayList;
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;


// IOC 컨테이너에 담아줘야하기 때문에 테스트 어노테이션을 붙여줘야 한다.
// 2가지 옵션이 있다.
// 1) @MybatisTest 2) @SpringBootTest

@AutoConfigureTestDatabase(replace=Replace.NONE)
@MybatisTest
class MenuRepositoryTest {
	
	// @Autowired만 쓰면 안 된다. IOC에 담아줘야 테스트가 돌아간다..
	@Autowired
	private MenuRepository repository;
	
	// 여러개의 테스트 메서드 중에서 하나만 테스트하고 싶으면 나머지는 @Test만 주석처리하자! 
	//@Test
	void testFindAll() {
		List<Menu> list = repository.findAll(0,10,null,null,null,"regDate","desc");
		System.out.println(list);
	}
	
	@Test
	void testFindAllByIds() {
		List<Long> ids = new ArrayList<>();
		ids.add(616L);
		ids.add(617L);
		ids.add(713L);
		ids.add(737L);
		
		List<Menu> list = repository.findAllByIds(ids);
		System.out.println(list);
	}

	//@Test
	void testUpdate() {
		// update 하기 위해서 모든 칼럼을 넣어줘야 한다.
		
		// 칼럼의 값이 null도 허용해서 테스트를 해줘야 햔다.
		
		Menu menu = new Menu();
		menu.setId(856);
		menu.setName("아마아마리카노");
		
		repository.update(menu);
	}

}




2) resultType 경로 간단히

  • resultType의 경로를 간단하게 매핑할 수 있다.


  • application.properties
spring.servlet.multipart.max-file-size=100MB	
#각각의 파일 용량 
spring.servlet.multipart.max-request-size=200MB 
#전체 용량

# JUnit 테스트용 db가 있다. 그래서, DB가 바뀔 수도 있어서 그것을 하지마라고 설정을 해줘야 한다.
# replace none 설정!!
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
# resultType의 경로 간단히!!
mybatis.type-aliases-package=kr.co.rland.web.entity


# 로깅 찍기
logging.level.kr.co.rland.web.repository=debug



<?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="Menu">
		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="Menu">
		select * 
		from menu 
		where id=#{id}
	</select>
	
	<select id="findAllByIds" resultType="Menu">
		select *
		from menu
		<where>
		  <foreach item="id" collection="ids"
		      open="id in (" separator="," close=")">
		        #{id}
		  </foreach>
  		</where>
	</select>

	<!-- <insert id="insert">
	
	</insert>
	 -->
	<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>
		</trim>
		where id=#{id}
	</update>
	
	
<!-- 	<delete id="delete">
	</delete> -->
</mapper>



3) 칼럼이름 매핑하기


  • resultMap : 서로 칼럼명이 달라지면 서로 다른 데이터를 가지고 있는 맵객체를 설정해줘야 한다.
    • resultType이 아니다. 타입이 아니라 맵객체를 설정해줘야 한다.
    • 초기 설정시, 타입을 기반으로 하고 달라지는 속성만 하위 태그에 넣는다!


  • 설정 방법 :
    • Mapper.xml 파일에 resultMap 태그로 초기 설정해주고
    • Mybatis 각 SQL문의 resultType이 아니라 resultMap으로 속성을 바꿔서 매핑해주자.


  • 실습 코드 :
    • MenuRepositoryMapper.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">
<mapper namespace="kr.co.rland.web.repository.MenuRepository">
	
	<resultMap id="menuResultMap" type="Menu">
	  <result property="regDate" column="reg_date"/>
	</resultMap>
	
	<select id="findAll" resultMap="menuResultMap">
		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" resultMap="menuResultMap">
		select * 
		from menu 
		where id=#{id}
	</select>
	
	<select id="findAllByIds" resultMap="menuResultMap">
		select *
		from menu
		<where>
		  <foreach item="id" collection="ids"
		      open="id in (" separator="," close=")">
		        #{id}
		  </foreach>
  		</where>
	</select>

	<!-- <insert id="insert">
	
	</insert>
	 -->
	<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>
		</trim>
		where id=#{id}
	</update>
	
	
<!-- 	<delete id="delete">
	</delete> -->
</mapper>



4) Mybatis - 멀티 DB 사용


  • 실습 코드 :
    • databaseIdProvider 부분
<databaseIdProvider type="DB_VENDOR">
  <property name="SQL Server" value="sqlserver"/>
  <property name="DB2" value="db2"/>
  <property name="Oracle" value="oracle" />
</databaseIdProvider>


  • mappers
public interface DatabaseIdProvider {
  default void setProperties(Properties p) { // Since 3.5.2, changed to default method
    // NOP
  }
  String getDatabaseId(DataSource dataSource) throws SQLException;
}



  • mappers
<!-- Using classpath relative resources -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- Using url fully qualified paths -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- Using mapper interface classes -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- Register all interfaces in a package as mappers -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>




5) @Mapper를 사용하지 않는 방법


  • 초기 Mybatis 버전 :
    • 원래 순수 자바로 Mybatis를 사용하기 위해서 Config 파일로 기본 설정을 했어야 했다. 그래서, config xml파일에서 JDBC 연결을 해줘서 팩토리를 설정을 완성시킨다. mappers 태그로 설정해줘야 한다.
    • 이제, config xml 파일에서 build를 하면 class파일이 생기고
    • 그리고 나서, mapper xml 파일에서 mapper 태그로 SQL 매핑을 할 파일을 설정한다.
    • 그리고나서 자바 파일에서 openSession()이 실행되면 SessionFactory에서 팩토리의 인스턴스가 생긴다. 이것은 sqlSession이다.
    • 그래서 이것은 IOC 컨테이너(Mapper 컨테이너)에서 사용할 수 있으므로 getMapper로 꺼내서 쓸 수 있다.


  • 중간 과정의 SessionFactory :
    • @Mapper를 사용하지 않고 또 다른 방법인 순수 자바 코드로 매핑시킨다. 인터페이스에 mapper하여 얻는 구현체는 Factory를 얻어서 사용한다. Factory에서 기간이 있는 session을 통해서 구현체를 얻어서 사용한다.
    • 구현체를 꺼내서 사용할 때, session이라는 유효기간이 존재하는데 이러한 유효 기간이 있는 메서드로 바로 꺼내서 쓸 수 있거나 sqlSession으로 꺼내서 사용할 수 있다. 그리고 이렇게 session으로 유효 기간이 유지되는 곳은 “Mapper 컨테이너”이다.


  • 스프링 부트에서 Mybatis를 순수 자바로 사용하는 방법 정리 1:
    • 원래, 스프링 부트에서 이러한 팩토리 설정을 해놔서 Factory가 있었다. 그 Factory는 “IOC container”이며 자료형은 “ApplicationContext”이고 “bean container”이다.
    • Mybatis에서 “SessionFactory”가 보따리이다.
    • Mybatis의 보따리는 “Mapper 컨테이너”이다. Mybatis의 “Mapper 컨테이너” 실제 자료형은 “SQLSessionFactory”이다.


  • 스프링 부트에서 Mybatis를 순수 자바로 사용하는 방법 정리 2:
    • 스프링에서 사용하기 위해서 Mybatis와 IOC 컨테이너와 연결을 해줘야 한다.
    • 그래서, IOC 컨테이너에서 SQLSessionFactory나 SQLSession를 얻어서 연결할 수 있다.
    • 사용 방법 : SQLSessionFactory을 설정하면 객체를 직접 꺼내는 과정을 내가 해야하는데 SQLSession만 설정해주면 편하게 설정없이 그냥 꺼내 쓸 수 있다.
    • 또한, 또 다른 기능인 Mybatis에서 오버로드 메서드를 생성할 때, xml 파일에서 제외하고 sqlSession을 이용한다.



6) 오버로드 메서드를 사용

  • 5)에서 설명한 sqlSession을 이용한다.
  • 코드 수정하기 : 완전하지는 않다.


  • 실습 코드 :


MenuRepository.java

package kr.co.rland.web.repository;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

import kr.co.rland.web.entity.Menu;

// ibatis가 원래 이름인데 버전이 업되면서 mybatis로 바뀌었다. mapper로 매핑
// @Mapper가 구현체를 알아서 repository에 붙여준다.

@Mapper
public interface MenuRepository {
	
	List<Menu> findAll();
	List<Menu> findAll(Integer offset, Integer size);
	List<Menu> findAll(Integer offset,
					Integer size, 
					String query,
					Integer categoryId,
					Integer price,
					String orderField,
					String orderDir
					);
	
	Menu findById(long id);
	
	// 타입 주의!! 리스트들을 모두 출력하기 때문이다. 
	List<Menu> findAllByIds(List<Long> ids);
	
	
	Menu insert(Menu menu);
	
	Menu update(Menu menu);
	
	void delete(long id);
}


mbMenuRepository.java
package kr.co.rland.web.repository.mybatis;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import kr.co.rland.web.entity.Menu;
import kr.co.rland.web.repository.MenuRepository;

@Repository
public class MbMenuRepository implements MenuRepository{

//  [페이저 지원 방법] ! : 오버로드 사용하는 방법 1
//	원래, 마이바티스에서는 오버로드가 불가능하다. 

//	그런데, 마이바티스의 또 다른 저장소가 있다.
//	마이바티스는 자기만의 Mapper 빈 객체가 따로 있었다. 
	
//	그래서, 스프링이 관리하는 빈 객체를 따로 만들어 줘야 했다.
//	마이바티스를 스프링에서 사용하기 위해서 IOC컨테이너 담아줘야 한다. 
	
//	그래서 마이바티스에서 연계해주는 IOC커넥터를 만들어줘서 사용한다. 
//	마이바티스에서 빈 객체를 꺼내기 위한 것이 SqlSession이다.

//	오버로드를 지원하지 않아서 SqlSession을 통해 Mapper 해줘야 한다. 
	
	@Autowired
	private SqlSession session;

	@Override
	public List<Menu> findAll() {
		MenuRepository repository = session.getMapper(MenuRepository.class);
		return repository.findAll(0, 10, null, null, null, null, null);
	}
	@Override
	public List<Menu> findAll(Integer offset, Integer size) {
		MenuRepository repository = session.getMapper(MenuRepository.class);
		return repository.findAll(offset, size, null, null, null, null, null);
	}
	
	@Override
	public List<Menu> findAll(Integer offset, Integer size, String query, Integer categoryId, Integer price,
			String orderField, String orderDir) {
		MenuRepository repository = session.getMapper(MenuRepository.class);
		return repository.findAll(offset, size, query, categoryId, price, orderField, orderDir);
	}

	@Override
	public Menu findById(long id) {
		return null;
	}

	@Override
	public List<Menu> findAllByIds(List<Long> ids) {
		MenuRepository repository = session.getMapper(MenuRepository.class);
		
		return repository.findAllByIds(ids);
	}

	@Override
	public Menu insert(Menu menu) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Menu update(Menu menu) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void delete(long id) {
		// TODO Auto-generated method stub
		
	}
	
	

}


MenuRepositoryMapper.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">
<mapper namespace="kr.co.rland.web.repository.MenuRepository">
	
	<resultMap id="menuResultMap" type="Menu">
	  <result property="regDate" column="reg_date"/>
	</resultMap>
	
	<select id="findAll" resultMap="menuResultMap">
		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" resultMap="menuResultMap">
		select * 
		from menu 
		where id=#{id}
	</select>
	
	<select id="findAllByIds" resultMap="menuResultMap">
		select *
		from menu
		<where>
		  <foreach item="id" collection="ids"
		      open="id in (" separator="," close=")">
		        #{id}
		  </foreach>
  		</where>
	</select>

	<!-- <insert id="insert">
	
	</insert>
	 -->
	<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>
		</trim>
		where id=#{id}
	</update>
	
	
<!-- 	<delete id="delete">
	</delete> -->
</mapper>


  • Test 코드 :


MenuRepositoryTest.java
package kr.co.rland.web.repository;

import java.util.ArrayList;
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;


// IOC 컨테이너에 담아줘야하기 때문에 테스트 어노테이션을 붙여줘야 한다.
// 2가지 옵션이 있다.
// 1) @MybatisTest 2) @SpringBootTest

@AutoConfigureTestDatabase(replace=Replace.NONE)
@MybatisTest
class MenuRepositoryTest {
	
	// @Autowired만 쓰면 안 된다. IOC에 담아줘야 테스트가 돌아간다..
	@Autowired
	private MenuRepository repository;
	
	// 여러개의 테스트 메서드 중에서 하나만 테스트하고 싶으면 나머지는 @Test만 주석처리하자! 
	@Test
	void testFindAll() {
		// List<Menu> list = repository.findAll(0,10,null,null,null,"regDate","desc");
		List<Menu> list = repository.findAll(0,10,null,null,null,null,null);
		System.out.println(list);
	}
	
	//@Test
	void testFindAllByIds() {
		List<Long> ids = new ArrayList<>();
		ids.add(616L);
		ids.add(617L);
		ids.add(713L);
		ids.add(737L);
		
		List<Menu> list = repository.findAllByIds(ids);
		System.out.println(list);
	}

	//@Test
	void testUpdate() {
		// update 하기 위해서 모든 칼럼을 넣어줘야 한다.
		
		// 칼럼의 값이 null도 허용해서 테스트를 해줘야 햔다.
		
		Menu menu = new Menu();
		menu.setId(856L);
		menu.setName("아마아마리카노");
		
		repository.update(menu);
	}

}




4. Mybatis sqlSession : 230309


1) @Mapper 사용하지 않고 Repository를 직접 구현하기


a. 현재 문제점 : 위의 코드처럼 sqlSession을 사용하면 에러가 발생한다!


b. 해결 방법 1 : 현재는 필요한 부분만 테스트하는데, 해결하기 위해서 스프링 전체 통합 테스트를 해야 한다.

  • @SpringBootTest : 나머지들도 다 읽어 들일 수 있게 테스트를 설정해야 한다.


2) 첫 번째 실습 과정 : @MybatisTest vs @SpringBootTest


MenuRepository.java
package kr.co.rland.web.repository;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

import kr.co.rland.web.entity.Menu;

// ibatis가 원래 이름인데 버전이 업되면서 mybatis로 바뀌었다. mapper로 매핑
// @Mapper가 구현체를 알아서 repository에 붙여준다.

// @Mapper
public interface MenuRepository {
	
	List<Menu> findAll();
	List<Menu> findAll(Integer offset, Integer size);
	List<Menu> findAll(Integer offset,
					Integer size, 
					String query,
					Integer categoryId,
					Integer price,
					String orderField,
					String orderDir
					);
	
	Menu findById(long id);
	
	// 타입 주의!! 리스트들을 모두 출력하기 때문이다. 
	List<Menu> findAllByIds(List<Long> ids);
	
	
	Menu insert(Menu menu);
	
	Menu update(Menu menu);
	
	void delete(long id);
}


MbMenuRepository.java
package kr.co.rland.web.repository.mybatis;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import kr.co.rland.web.entity.Menu;
import kr.co.rland.web.repository.MenuRepository;

@Repository
public class MbMenuRepository implements MenuRepository{

//  [페이저 지원 방법] ! : 오버로드 사용하는 방법 1
//	원래, 마이바티스에서는 오버로드가 불가능하다. 

//	그런데, 마이바티스의 또 다른 저장소가 있다.
//	마이바티스는 자기만의 Mapper 빈 객체가 따로 있었다. 
	
//	그래서, 스프링이 관리하는 빈 객체를 따로 만들어 줘야 했다.
//	마이바티스를 스프링에서 사용하기 위해서 IOC컨테이너 담아줘야 한다. 
	
//	그래서 마이바티스에서 연계해주는 IOC커넥터를 만들어줘서 사용한다. 
//	마이바티스에서 빈 객체를 꺼내기 위한 것이 SqlSession이다.

//	오버로드를 지원하지 않아서 SqlSession을 통해 Mapper 해줘야 한다. 
	
	
	private SqlSession session;
	private MenuRepository repository;
	
	
	public MbMenuRepository() {
	}
	
	// 반복되는 것을 줄여주기 위해서 생성자 Injection을 해줘서 초기화해주면 된다!
	@Autowired
	public MbMenuRepository(SqlSession session) {
		this.session = session;
		this.repository = session.getMapper(MenuRepository.class);
		// sqlSession에서 getMapper로 객체를 꺼낸다!
	}

	@Override
	public List<Menu> findAll() {
		// 반복적인 이것을 간략히 써주기 위해서 생성자 Injection을 해준다!
		// MenuRepository repository = session.getMapper(MenuRepository.class);

		return repository.findAll(0, 10, null, null, null, null, null);
	}
	
	@Override
	public List<Menu> findAll(Integer offset, Integer size) {
		
		return repository.findAll(offset, size, null, null, null, null, null);
	}
	
	@Override
	public List<Menu> findAll(Integer offset, Integer size, String query, Integer categoryId, Integer price,
			String orderField, String orderDir) {
		
		return repository.findAll(offset, size, query, categoryId, price, orderField, orderDir);
	}

	@Override
	public Menu findById(long id) {
		
		return null;
	}

	@Override
	public List<Menu> findAllByIds(List<Long> ids) {
		
		return repository.findAllByIds(ids);
	}

	@Override
	public Menu insert(Menu menu) {
		return null;
	}

	@Override
	public Menu update(Menu menu) {
		return null;
	}

	@Override
	public void delete(long id) {
		
	}

}


MenuRepositoryTest.java
package kr.co.rland.web.repository;

import java.util.ArrayList;
import java.util.List;

import org.junit.jupiter.api.Test;
import org.mybatis.spring.boot.test.autoconfigure.AutoConfigureMybatis;
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 org.springframework.boot.test.context.SpringBootTest;

import kr.co.rland.web.entity.Menu;


// IOC 컨테이너에 담아줘야하기 때문에 테스트 어노테이션을 붙여줘야 한다.
// 2가지 옵션이 있다.
// 1) @MybatisTest 2) @SpringBootTest

@AutoConfigureTestDatabase(replace=Replace.NONE)
// @MybatisTest
@AutoConfigureMybatis
@SpringBootTest
class MenuRepositoryTest {
	
	// @Autowired만 쓰면 안 된다. IOC에 담아줘야 테스트가 돌아간다..
	@Autowired
	private MenuRepository repository;
	
	// 여러개의 테스트 메서드 중에서 하나만 테스트하고 싶으면 나머지는 @Test만 주석처리하자! 
	@Test
	void testFindAll() {
		// List<Menu> list = repository.findAll(0,10,null,null,null,"regDate","desc");
		//List<Menu> list = repository.findAll(0,10,null,null,null,null,null);
		List<Menu> list = repository.findAll();
		System.out.println(list);
	}
	
	//@Test
	void testFindAllByIds() {
		List<Long> ids = new ArrayList<>();
		ids.add(616L);
		ids.add(617L);
		ids.add(713L);
		ids.add(737L);
		
		List<Menu> list = repository.findAllByIds(ids);
		System.out.println(list);
	}

	//@Test
	void testUpdate() {
		// update 하기 위해서 모든 칼럼을 넣어줘야 한다.
		
		// 칼럼의 값이 null도 허용해서 테스트를 해줘야 햔다.
		
		Menu menu = new Menu();
		menu.setId(856L);
		menu.setName("아마아마리카노");
		
		repository.update(menu);
	}

}


MenuRepositoryTest.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">
<mapper namespace="kr.co.rland.web.repository.MenuRepository">
	
	<resultMap id="menuResultMap" type="Menu">
	  <result property="regDate" column="reg_date"/>
	</resultMap>
	
	<select id="findAll" resultMap="menuResultMap">
		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>
		<if test="orderField != null and offset != null">
			order by ${orderField} ${orderDir}
		</if>
		<if test="size != null and offset != null">
			limit #{size} offset #{offset};
		</if>
	</select>
	
	<select id="findById" resultMap="menuResultMap">
		select * 
		from menu 
		where id=#{id}
	</select>
	
	<select id="findAllByIds" resultMap="menuResultMap">
		select *
		from menu
		<where>
		  <foreach item="id" collection="ids"
		      open="id in (" separator="," close=")">
		        #{id}
		  </foreach>
  		</where>
	</select>

	<!-- <insert id="insert">
	
	</insert>
	 -->
	<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>
		</trim>
		where id=#{id}
	</update>
	
	
<!-- 	<delete id="delete">
	</delete> -->
</mapper>



3) 두번째 실습 과정 - 수동 옵션 :


MenuRepository.java
package kr.co.rland.web.repository;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

import kr.co.rland.web.entity.Menu;

// ibatis가 원래 이름인데 버전이 업되면서 mybatis로 바뀌었다. mapper로 매핑
// @Mapper가 구현체를 알아서 repository에 붙여준다.

@Mapper
public interface MenuRepository {
	
	List<Menu> findAll();
	List<Menu> findAll(Integer offset, Integer size);
	List<Menu> findAll(Integer offset,
					Integer size, 
					String query,
					Integer categoryId,
					Integer price,
					String orderField,
					String orderDir
					);
	
	Menu findById(long id);
	
	// 타입 주의!! 리스트들을 모두 출력하기 때문이다. 
	List<Menu> findAllByIds(List<Long> ids);
	
	
	Menu insert(Menu menu);
	
	Menu update(Menu menu);
	
	void delete(long id);
}


MbMenuRepository.java
package kr.co.rland.web.repository.mybatis;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import kr.co.rland.web.entity.Menu;
import kr.co.rland.web.repository.MenuRepository;

// @Repository
public class MbMenuRepository implements MenuRepository{

//  [페이저 지원 방법] ! : 오버로드 사용하는 방법 1
//	원래, 마이바티스에서는 오버로드가 불가능하다. 

//	그런데, 마이바티스의 또 다른 저장소가 있다.
//	마이바티스는 자기만의 Mapper 빈 객체가 따로 있었다. 
	
//	그래서, 스프링이 관리하는 빈 객체를 따로 만들어 줘야 했다.
//	마이바티스를 스프링에서 사용하기 위해서 IOC컨테이너 담아줘야 한다. 
	
//	그래서 마이바티스에서 연계해주는 IOC커넥터를 만들어줘서 사용한다. 
//	마이바티스에서 빈 객체를 꺼내기 위한 것이 SqlSession이다.

//	오버로드를 지원하지 않아서 SqlSession을 통해 Mapper 해줘야 한다. 
	
	
	private SqlSession session;
	private MenuRepository repository;
	
	
	public MbMenuRepository() {
	}
	
	// 반복되는 것을 줄여주기 위해서 생성자 Injection을 해줘서 초기화해주면 된다!
	@Autowired
	public MbMenuRepository(SqlSession session) {
		this.session = session;
		this.repository = session.getMapper(MenuRepository.class);
		// sqlSession에서 getMapper로 객체를 꺼낸다!
	}
	
	// 여기서 인자들은 SQL문에서 사용하려고 쓰는 것이다. 
	@Override
	public List<Menu> findAll() {
		// 반복적인 이것을 간략히 써주기 위해서 생성자 Injection을 해준다!
		// MenuRepository repository = session.getMapper(MenuRepository.class);

		// return repository.findAll(0, 10, null, null, null, null, null);
		return session.selectList("kr.co.rland.web.repository.MenuRepository.findAll");
	}
	
	@Override
	public List<Menu> findAll(Integer offset, Integer size) {
		
		return repository.findAll(offset, size, null, null, null, null, null);
	}
	
	@Override
	public List<Menu> findAll(Integer offset, Integer size, String query, Integer categoryId, Integer price,
			String orderField, String orderDir) {
		
		return repository.findAll(offset, size, query, categoryId, price, orderField, orderDir);
	}

	@Override
	public Menu findById(long id) {
		
		return null;
	}

	@Override
	public List<Menu> findAllByIds(List<Long> ids) {
		
		return repository.findAllByIds(ids);
	}

	@Override
	public Menu insert(Menu menu) {
		return null;
	}

	@Override
	public Menu update(Menu menu) {
		return null;
	}

	@Override
	public void delete(long id) {
		
	}
	
	

}


MenuRepository.java
package kr.co.rland.web.repository;

import java.util.ArrayList;
import java.util.List;

import org.junit.jupiter.api.Test;
import org.mybatis.spring.boot.test.autoconfigure.AutoConfigureMybatis;
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 org.springframework.boot.test.context.SpringBootTest;

import kr.co.rland.web.entity.Menu;


// IOC 컨테이너에 담아줘야하기 때문에 테스트 어노테이션을 붙여줘야 한다.
// 2가지 옵션이 있다.
// 1) @MybatisTest 2) @SpringBootTest

@AutoConfigureTestDatabase(replace=Replace.NONE)
// @MybatisTest
@AutoConfigureMybatis
@SpringBootTest
class MenuRepositoryTest {
	
	// @Autowired만 쓰면 안 된다. IOC에 담아줘야 테스트가 돌아간다..
	@Autowired
	private MenuRepository repository;
	
	// 여러개의 테스트 메서드 중에서 하나만 테스트하고 싶으면 나머지는 @Test만 주석처리하자! 
	@Test
	void testFindAll() {
		// List<Menu> list = repository.findAll(0,10,null,null,null,"regDate","desc");
		//List<Menu> list = repository.findAll(0,10,null,null,null,null,null);
		List<Menu> list = repository.findAll();
		System.out.println(list);
	}
	
	//@Test
	void testFindAllByIds() {
		List<Long> ids = new ArrayList<>();
		ids.add(616L);
		ids.add(617L);
		ids.add(713L);
		ids.add(737L);
		
		List<Menu> list = repository.findAllByIds(ids);
		System.out.println(list);
	}

	//@Test
	void testUpdate() {
		// update 하기 위해서 모든 칼럼을 넣어줘야 한다.
		
		// 칼럼의 값이 null도 허용해서 테스트를 해줘야 햔다.
		
		Menu menu = new Menu();
		menu.setId(856L);
		menu.setName("아마아마리카노");
		
		repository.update(menu);
	}

}




4) delete, insert 절 추가

  • DB인 mariaDB에서 테스트하고 springboot에서 적용시키자!


  • 실습 코드:


MenuRepository.java
package kr.co.rland.web.repository;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

import kr.co.rland.web.entity.Menu;

// ibatis가 원래 이름인데 버전이 업되면서 mybatis로 바뀌었다. mapper로 매핑
// @Mapper가 구현체를 알아서 repository에 붙여준다.

@Mapper
public interface MenuRepository {
	
//	List<Menu> findAll();
//	List<Menu> findAll(Integer offset, Integer size);
	List<Menu> findAll(Integer offset,
					Integer size, 
					String query,
					Integer categoryId,
					Integer price,
					String orderField,
					String orderDir
					);
	
	Menu findById(long id);
	
	// 타입 주의!! 리스트들을 모두 출력하기 때문이다. 
	List<Menu> findAllByIds(List<Long> ids);
	
	// 마지막으로 필요한 count 함수로 마지막 페이지를 구분할 수 있다!!
	int count(
			String query,
			Integer categoryId,
			Integer price);
	
	Menu insert(Menu menu);
	
	Menu update(Menu menu);
	
	void delete(long id);
}


MenuRepositoryTest.java
package kr.co.rland.web.repository;

import java.util.ArrayList;
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;


// IOC 컨테이너에 담아줘야하기 때문에 테스트 어노테이션을 붙여줘야 한다.
// 2가지 옵션이 있다.
// 1) @MybatisTest 2) @SpringBootTest

@AutoConfigureTestDatabase(replace=Replace.NONE)
@MybatisTest
//@AutoConfigureMybatis
//@SpringBootTest
class MenuRepositoryTest {
	
	// @Autowired만 쓰면 안 된다. IOC에 담아줘야 테스트가 돌아간다..
	@Autowired
	private MenuRepository repository;
	
	// 여러 개의 테스트 메서드 중에서 하나만 테스트하고 싶으면 나머지는 @Test만 주석처리하자! 
//	@Test
//	void testFindAll() {
//		// List<Menu> list = repository.findAll(0,10,null,null,null,"regDate","desc");
//		//List<Menu> list = repository.findAll(0,10,null,null,null,null,null);
//		List<Menu> list = repository.findAll();
//		System.out.println(list);
//	}
	
	//@Test
	void testFindAllByIds() {
		List<Long> ids = new ArrayList<>();
		ids.add(616L);
		ids.add(617L);
		ids.add(713L);
		ids.add(737L);
		
		List<Menu> list = repository.findAllByIds(ids);
		System.out.println(list);
	}

	//@Test
	void testUpdate() {
		// update 하기 위해서 모든 칼럼을 넣어줘야 한다.
		
		// 칼럼의 값이 null도 허용해서 테스트를 해줘야 햔다.
		
		Menu menu = new Menu();
		menu.setId(856L);
		menu.setName("아마아마리카노");
		
		repository.update(menu);
	}
	
	@Test
	void testCount() {
		int count = repository.count(null,null,null);
		System.out.println(count);
	}

}


MenuRepositoryMapper.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">
<mapper namespace="kr.co.rland.web.repository.MenuRepository">
	
	<resultMap id="menuResultMap" type="Menu">
	  <result property="regDate" column="reg_date"/>
	  <result property="categoryId" column="category_id"/>
	  <result property="regMemberId" column="reg_member_id"/>
	</resultMap>
	
	<select id="findAll" resultMap="menuResultMap">
		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>
		<if test="orderField != null and offset != null">
			order by ${orderField} ${orderDir}
		</if>
		<if test="size != null and offset != null">
			limit #{size} offset #{offset};
		</if>
	</select>
	
	<select id="findById" resultMap="menuResultMap">
		select * 
		from menu 
		where id=#{id}
	</select>
	
	<select id="findAllByIds" resultMap="menuResultMap">
		select *
		from menu
		<where>
		  <foreach item="id" collection="ids"
		      open="id in (" separator="," close=")">
		        #{id}
		  </foreach>
  		</where>
	</select>
	
	<!-- count관련 select SQL문의 타입도 매우 중요하다!! -->
	<select id="count" resultType="Integer">
		select count(id) count
		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>
	</select>

	<insert id="insert">
		insert into menu(name, price, img, category_id, reg_member_id)
		values(#{name}, #{price}, #{img}, #{categoryId}, #{regMemberId})
	</insert>
	 
	<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>
		</trim>
		where id=#{id}
	</update>
	
	
    <delete id="delete">
    	delete from menu where id=${id}
	</delete>
</mapper>



5) MariaDB의 데이터베이스 모델링


a. DB의 view 설계 시 :

  • 관계가 있는 것은 Join을 이용한다.
  • 관계가 없는 것은 서브쿼리로 한다. 하지만, 서브쿼리를 성능이 안 좋아져서 튜닝을 해야한다. 인덱스 정책?


  • DB 모델링 : DB 설계!!
  • MariaDB 계정 : 사용자 계정 명과 비밀번호를 입력해서 계정 만들자!
    • ex) 사용자 계정명은 “Test”, DB명은 “TestDB”이 될 것이다, 접속하는 주소는 ‘’ 가 된다.


b. 데이터베이스 모델링 개념 :

  • 주체와 대상을 의미하며, key “Entity”라고 한다.


  • 역할자? : 역할에 따라서 권한이 달라지고 해야할 것이 달라진다!
    • ex) 관리자, 회원 등등
    • 관리자(역할자)가 메뉴를 “등록”하는 관계가 있다.
    • 관리자가 글을 등록한다면, 등록 시, 게시글의 공개를 바로 한다면 그것도 써주자!


  • 행위는 “Relation”이라고 하고 한다. 예를 들어, 회원과 영화의 연결고리가 Relation이다.

  • 뽑아내는 산출물을 “Entity”와 “Relation”이 있다.


c. DB 개념 :

  • 개념 파악 하는 것을 개념 설계라고 하며 ERD라고 한다.
  • “조회” 내역(검색 내역)은 DB에 넣어야하는가? 적재해야하는지?


d. NoSQL 개념 :

  • DB에 넣으려면 수정하고 삭제하는 경우에 사용한다.
  • 그래서 조회 내역은 DB에 넣지 않고 빅데이터라서 적재해야하므로 비정규화데이터베이스에 저장한다.
  • DB는 원래 무결성을 모델링해서 NoSQL에 집어 넣는다. 이러한 DB 중에서 하나가 “MongoDB”이다. 문서 데이터라고 한다.
  • 그러나, 그냥 파일로 저장해도 된다. 그래서, NAS에 저장한다. 용량은 1T의 1K배인 1P이다. 용량이 1년 정도 모아서 분석하자! 용량은 엄청 많이 쌓이고 컴퓨터를 분산해서 분석하여 처리하도록 한다. Hadoop으로 여러개의 컴퓨터를 묶어서 처리해서 병렬로 처리해서 클러스터링해서 데이터를 분석한다.
  • 비정형 데이터는 개인정보와 검색하는 것에 대해 관련있다.
  • 사용할 때는 모집단 등 공부하고 통계 관련 공부한다. 추후에는 인공지능까지 찾아서 공부가 된다.


e. ERD 그리는 방법 :


a) 일반적으로 사람끼리 관계 맺기
  • 역할자인 사람끼리 관계를 맺는다.


b) 다른 방식 : 물건끼리 관계 맺기
  • 역할자가 특이하게 물건끼리도 관계를 맺을수 있다.
    • 카테고리랑 메뉴 관계 : 구성 관계이다.


c) 포함 관계
  • 회원과 관리자가 포함 관계 일 수 있다. 분리가 되지 않은 형태
  • 학생과 선생이 있을 때, 선생도 수업을 들을 수 있을 때!
  • 이럴때는 상속 받는 것처럼 ERD에서 그려준다.
  • 회원이 부모이고 관리자가 자식이다. 관리자가 꼬리이다. 회원이 머리이다. 관리자가 역할이 더 많기때문이다.


이미지




5. DB 설계 : 230310


1) DB 모델링

  • 행위(마름모칸)할 때마다 수정이 되면, DB에 쌓이게 된다.

  • 마름모 모양이 서로 선으로 연결되어 있으면, 참조한다는 의미이다.

  • 행위(마름모)가 있는 이유는 있어야 한다. 행위가 벌어질 때, 등록 날짜 같은 것이 포함되어야 한다.

  • 관리자와 회원 계정이 하는 일이 다를 수 도 있고 같을 수 도 있다. 관리자 계정도 결제할 수 있다.



2) 테이블의 키 설정


  • 식별키로 두 개의 키를 합쳐서 복합키(슈퍼키)로 쓸 수 있다. : 추천하는 개념에 회원 아이디, 메뉴 아이디를 합치면 아이디가 짧아진다. 아이디가 조합형이다.
    • 추천 하는 개념에 회원 아이디 하나로 식별키로 쓰면 아이디가 엄청 길어진다. 아이디가 엄청 누적형이다.
    • 이렇게 만드는 것이 대학교의 학번이 그렇게 만들어진다.


  • 복합키의 단점이 비교할 때, 항상 2개를 비교해야 한다.
  • 그래서, 단일키로 사용하는 경우가 많다.


  • 점선은 식별자로 참조하지 않는 참조이다.
  • 실선은 식별자로 참조하는 참조이다.


  • “다대다” 관계는 1대다 관계와 1대다 관계로 풀어줘야 한다.
  • “다대다” 관계 : 또, 등록할 수 있으면 식별 관계가 안 된다. 여행상품을 한 사람이 구매하고 똑같은 것을 또 구매할 수 있는지.. 또 구매할 수 있으면 실선이 아니라 점선으로 연결이 되고 PK가 아니라 FK로 쓰자!**


3) 논리적 테이블 작성

  • 기본적으로 DB 테이블에는 PK가 무조건 있어야 한다.
  • 1대 N 관계이면 N쪽에 FK가 있고
  • N대 N관계이면 테이블(엔티티) or 액션 테이블(엔티티)이 만들어진다.


  • 관리 테이블이 있으려면 DB에 기록을 남겨야햔다.
  • 회원아이디를 삭제하면 회원 날짜라던지 삭제된 회원 정보 등등


  • 우리가 생각하는 그것은 “신고” 테이블이다!!(신고된 회원 관리, 신고된 게시글, 신고된 채팅 내용)


  • 채팅방 테이블이 필요한 이유는 채팅 내용을 남겨놔야 채팅방을 나갔다가 들어가도 채팅 내용을 다시 읽을수 있다.