자바에서 제공하는 JDBC를 보다 편리하게 사용할 수 있도록 해주는 프레임워크
- SQL 쿼리문을 코드 내에서 쓰지 않고 Mapper 파일에서 관리함으로써 코드와 SQL 쿼리를 분리
- SQL 쿼리 수정 시 코드를 직접 수정하지 않아도 돼 유지보수 시 안정성을 높임
- 코드를 간소화하여 가시성을 높여줌
=>간단히 자바와 스프링에서 DB를 다루는 JDBC를 좀 더 편하고 깔끔하게 다루기 위한 DB 연동 프레임워크 또는 라이브러리
특히 스프링에서는 Mybatis를 사용하기 위한 모듈을 제공하므로 더욱 편하게 사용할 수 있습니다. DAO를 대신할 mapper를 만들 때 매우 편리합니다.
출처블로그
ORM(Object Relation Mapping) : 객체와 관계형 데이터 베이스 간의 매핑을 지원하는 것
개발자가 지정한 SQL, 저장프로시저,몇가지 고급 매핑을 지원하는 프레임워크
기존 JDBC를 이용하여 프로그래밍을 하면 소스안에 SQL문을 작성했지만, Mybatis에서는 SQL을 XML 파일에 작성하기 때문에 SQL의 변환이 자유롭고 가독성이 좋다는 장점이 있다.
출처블로그
1. 이클립스와 연동하기위한 DB작성(스키마+user와board테이블)
2. DB를 객체화 하기 위한 dto클래스와 mapper작성(mapper클래스+mapper.xml)
3. mapper를 mybatis가 알아들을 수 있게 서로 연동해주는 MybatisConfig클래스 작성
spring initializer에서 spring web, mySql framework, mybatis 선택 후 생성
yml 파일 작성해주고 폴더 구조 다음 사진과 같이
(위에는 BEFORE 이건 AFTER
)
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.01:3306/demo_schema
username: {mysql 아이디}
password: {mysql 비번}
mybatisdemo :
mapper-locations: "classpath:mybatis/mappers/*.xml"
configuration:
map-underscore-to-camel-case : true
- 메이븐 "pom.xml"에
DB연동에 필요한 라이브러리 의존 설정
추가
- MySQL Connector
- Mybatis
- Mybatis-spring (스프링에서 Mybatis 연동을 위한 모듈)
- spring-jdbc (기본 자바 JDBC가 아닌 스프링의 JDBC)
- common-dbcp2 (톰캣에서 커넥션풀을 이용할 수 있도록 아파치에서 제공해주는 라이브러리)
- spring-test (스프링에 Mybatis가 정상적으로 연동되었는지 확인 용도)
<?xml version="1.0" encoding="UTF-8"?> <!--xml 버전 명시-->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mb.mybatisdemo.mapper.PostMapper">
</mapper>
dao
: data access object
=> 데이터를 주고 받게 해주는 스프링의 레포지토리 기능, 객체 클래스를 불러오는 것
=> 스프링의 다른 애플리케이션과 소통하기 위한 클래스
dto
: 데이터를 실제로 담기위한 것
mapper
: mapper.xml에서 작성한 애들을 postmapper, boardmapper의 interface 역할
=> mapper 폴더 안 정의된 애들은 우리가 xml에서 정의해준 것에 따라서 작동이 되는 방식
출처
Mybatis XML을 사용해서 코딩 순서
테이블 생성 및 설정
도코메인 객체의 설계 및 클래스 작성
DAO 인터페이스 / 실행 기능 인터페이스 정의
XML Mapper 생성 및 SQL문 작성
XML 작성
MyBatis에 작성한 XML Mapper 인식 설정
DAO 인터페이스 구현한 클래스 작성
스프링에 DAO 등록
- DAO(Data Access Object)
- 데이터베이스의 data에 접근하기 위한 객체이며 데이터베이스 접근을 하기 위한 로직과 비즈니스 로직을 분리하기 위해 사용한다.
- 사용자는 자신이 필요한 Interface를 DAO에게 던지고 DAO는 이 Interface를 구현한 객체를 사용자에게 편리하게 사용할 수 있도록 반환한다.
DAO는 데이터베이스와 연결할 Connection까지 설정되어 있는 경우가 많다.- 그래서 현재 쓰이는 MyBatis 등을 사용할 경우 커넥션풀까지 제공되고 있기 때문에 DAO를 별도로 만드는 경우는 드물다.
- DTO(Data Transfer Object)
- VO라고도 표현하며 계층 간 데이터 교환을 위한 자바 빈즈(Java Beans)이다.
- 데이터베이스 레코드의 데이터를 매핑하기 위한 데이터 객체를 말한다. DTO는 보통 로직을 가지고 있지 않고 data와 그 data에 접근을 위한 getter, setter만 가지고 있다.
- 정리하면 DTO는 Database에서 Data를 얻어 Service나 Controller 등으로 보낼 때 사용하는 객체를 말한다.
public class PersonDTO { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
- 위의 클래스를 보면 getter/setter가 존재한다. 여기서 중요한 건 Property(프로퍼티) 개념인데 자바는 Property가 문법적으로 제공되지 않는다.
- 자바에서 Property라는 개념을 사용하기 위해 지켜야 할 약속이 있다.
- setter/getter에서 set과 get 이후에 나오는 단어가 Property라고 약속하는 것이다.
- 그래서 위 클래스에서 프로퍼티는 name과 age이다.
- 중요한 것은 프로퍼티가 멤버 변수 name, age로 결정되는 것이 아닌 getter/setter에서의 name과 age 임을 명심해야 한다.
- 즉 멤버 변수는 아무렇게 지어도 영향이 없고 getter/setter로 프로퍼티(데이터)를 표현한다는 것이다.
- 자바는 다양한 프레임워크에서 데이터 자동화 처리를 위해 리플렉션 기법을 사용하는데, 데이터 자동화 처리에서 제일 중요한 것은 표준 규격이다. 예를 들어 위 클래스 DTO에서 프로퍼티가 name, age라면 name, age의 키값으로 들어온 데이터는 리플렉션 기법으로 setter를 실행시켜 데이터를 넣을 수 있다.
- 중요한 것은, 우리가 setter를 요청하는 것이 아닌 프레임워크 내부에서 setter가 실행된다는 점이다.
- 그래서 layer 간(특히 서버 => 뷰로 이동 등)에 데이터를 넘길 때 DTO를 쓰면 편하다는 것이 이런 이유 때문이다. 뷰에 있는 form에서 name 필드 값을 프로퍼티에 맞춰 넘겼을 때 받아야 하는 곳에서 일일이 처리하는 것이 아니라 name 속성의 이름과 매칭되는 프로퍼티에 자동적으로 DTO가 인스턴스화되어 PersonDTO를 자료형으로 값을 받을 수 있다.
DTO(Data Tranfer Object) 란 ? - 풀 네임에서 유추할 수 있듯이 데이터를 객체로 만들어주는 클래스이다.
- 우리가 Mysql에 테이블의 형태로 만든 데이터들을
- import com.example.demo.dto.BoardDto로 import하고
- BoardDto board 이렇게 객체를 선언해주면
- board.setTitle("제목") 이런식으로 객체화하여 사용할 수 있게 된다.
- DAO(Data Access Object) 란 ? - 데이터 베이스에 접속해서 데이터 추가, 삭제, 수정 등의 작업을 하는 클래스
- 일반적인 JSP 혹은 Servlet 페이지내에 위의 로직을 함께 기술할 수 도 있지만, 유지보수 및 코드의 모듈화를 위해 별도의 DAO클래스를 만들어 사용 한다. 실제로 DB에 insert,update,delete등의 작업을 해주는 클래스라고 보면 될듯하다.
- Mybatis나 JDBC나 모두 웹 프로그램을 DB와 접속시켜주는 기능을 하는데
JDBC의 복잡성을 Mapping으로 개선하여 쉽게 사용하게 만들어주는게 Mybatis의 기능이다.
postmapper.xml
<?xml version="1.0" encoding="UTF-8"?> <!--xml 버전 명시-->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mb.mybatisdemo.mapper.PostMapper">
<insert id="createPost" parameterType="mb.mybatisdemo.dto.PostDto">
insert into POST(title, content, title, writer,board)
values (title, content, writer, board)
</insert>
</mapper>
-> PostMapper에서 id createPost에 이 SQL문을 적용해라
-> Parameter type은 PostMapper안에서 createPost가 받아들일 + 사용할 parameter 값을 지정해주는 것
package mb.mybatisdemo.mapper;
import mb.mybatisdemo.dto.PostDto;
public interface PostMapper {
int createPost(PostDto dto);
}
-> xml에서 아이디값 일치 / 인터페이스 가리키는 것 일치/ param 일치 -> xml 적용하게 됨
package mb.mybatisdemo.dto;
/*
id int
title varchar
content varchar
writer varchar
board varchar
*/
public class PostDto {
private int id;
private String title;
private String content;
private String writer;
private String board;
public PostDto() {
}
public PostDto(int id, String title, String content, String writer, String board) {
this.id = id;
this.title = title;
this.content = content;
this.writer = writer;
this.board = board;
}
public int getId() {
return id;
}
public String getContent() {
return content;
}
public String getTitle() {
return title;
}
public String getWriter() {
return writer;
}
public String getBoard() {
return board;
}
public void setId(int id) {
this.id = id;
}
public void setTitle(String title) {
this.title = title;
}
public void setContent(String content) {
this.content = content;
}
public void setWriter(String writer) {
this.writer = writer;
}
public void setBoard(String board) {
this.board = board;
}
@Override
public String toString() {
return "PostDto{" +
"id=" + id +
", title='" + title + '\'' +
", content='" + content + '\'' +
", writer='" + writer + '\'' +
", board='" + board + '\'' +
'}';
}
}
insert into POST(title, content, title, writer,board)
values (title, content, writer, board)
이렇게 되어있는데 이렇게 되어있으면 postdto의 변수가 가는게 아니라 title이라는 문자열 그자체가 전달이 되는 것이다 이를 변수로 인식되게 하려면 아래처럼 코드 수정 needed
=> 이런 식으로 이제 CRUD 작성 (XML & MAPPER)
xml
<?xml version="1.0" encoding="UTF-8"?> <!--xml 버전 명시-->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mb.mybatisdemo.mapper.PostMapper">
<insert id="createPost" parameterType="mb.mybatisdemo.dto.PostDto">
insert into POST(title, content, title, writer,board)
values (#{title}, #{content}, #{writer}, ${board})
<!--문자열의 경우는 #{dto의변수명} 이거는 따옴표를 추가해줌-->
<!--board는 조인 테이블을 위한 id 값 , 얘는 따옴표 추가 안함-${}-->
</insert>
<select id="readPost"
parameterType="int"
resultType="mb.mybatisdemo.dto.PostDto">
select * from post where id = ${id}
</select><!--하나만 가져오는 경우-->
<select id="readPostAll"
resultType="mb.mybatisdemo.dto.PostDto">
select * from post <!--list의 구현체로 반환하게 됨-->
</select>
<update id="updatePost"
parameterType="mb.mybatisdemo.dto.PostDto">
update post set
title=#{title},
content=#{content},
writer=#{writer},
board=${board}
where id=${id}
</update>
<delete id="deletePost" parameterType="int">
delete from post where id=${id}
</delete>
</mapper>
postmapper
package mb.mybatisdemo.mapper;
import mb.mybatisdemo.dto.PostDto;
import java.util.List;
public interface PostMapper {
int createPost(PostDto dto);
PostDto readPost(int id);
List<PostDto> readPostAll();
int updatePOST(PostDto dto);
int deletePOST(PostDto dto);
}
=> mapper에서 insert, delete, update return 값으론 int 써주기, 결과로 몇개의 row가 영향 받았는지 알려주기 떄문에
postdao
package mb.mybatisdemo.dao;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
@Repository
public class PostDao {
private final SqlSessionFactory sessionFactory;
public PostDao(
@Autowired SqlSessionFactory sessionFactory
){
this.sessionFactory=sessionFactory;
}
}
=> 이떄 sessionfactory 사용 위해서는 spring ioc 관리하에 있어야 하니깐 따라서 @Componenet 중에서도 데이터 주고받는 클래스, 컴포넌트라는 것을 명시해줄 @Repository 사용
PostDao {
private final SqlSessionFactory sessionFactory;
public PostDao(
@Autowired SqlSessionFactory sessionFactory
){
this.sessionFactory=sessionFactory;
}
public int createPost(PostDto dto){
SqlSession session = sessionFactory.openSession();
//이제 xml과 mapping된 PostMapper 엮어주기
PostMapper mapper = session.getMapper(PostMapper.class);
//Postmapper 할당해줄 건데,얘를 세션에서
// PostMapper랑 동일한 객체, 구현체를 달라하면
// PostMapper 구현한 구현체를 주게됨
int rowAffected= mapper.createPost(dto);
//DB유지하기 위해선 세션 유지, 이 세션 유지 안하고
//한번 통신하고 닫아주는 것이 아래
session.close(); //이렇게 해주면 다음에 또 이 닫은 세션 열어서 활용 가능
return rowAffected;
위와 같은 createPost를 아래와 같이 변경하기도 ㄱㄴ
public int createPost(PostDto dto){
try (SqlSession session = sessionFactory.openSession()){
PostMapper mapper=session.getMapper(PostMapper.class);
return mapper.createPost(dto);
}
}
=> open , close 알아서 해줌 (close가 IOException임)
public PostDto readPost(int id){
try (SqlSession session = sessionFactory.openSession()){
PostMapper mapper=session.getMapper(PostMapper.class);
//왜 굳이 sessionFactory에서 세션을 열고 닫지?
//걍 매퍼만 바로 사용하면 안됨?이라는 의문들기 가능
//mapperisntance는 threadsafe하지 않음
return mapper.readPost(id);
}
}
public List<PostDto> realPostAll(){
try (SqlSession session = sessionFactory.openSession()){
PostMapper mapper=session.getMapper(PostMapper.class);
return mapper.readPostAll();
}
}
public int updatePost(PostDto dto) {
try (SqlSession session = sessionFactory.openSession()) {
PostMapper mapper = session.getMapper(PostMapper.class);
return mapper.updatePost(dto);
}
}
public int deletePost(int id) {
try (SqlSession session = sessionFactory.openSession()) {
PostMapper mapper = session.getMapper(PostMapper.class);
return mapper.deletePost(id);
}
}
package mb.mybatisdemo;
import mb.mybatisdemo.dao.PostDao;
import mb.mybatisdemo.dto.PostDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class TestComponent {
private final PostDao postdao;
public TestComponent(
@Autowired PostDao postdao) {
this.postdao=postdao;
PostDto newPost = new PostDto();
newPost.setTitle("From mybatis");
newPost.setContent("Hello batis");
newPost.setWriter("shucream");
newPost.setBoard(0);
this.postdao.createPost(newPost);
List<PostDto> postDtoList=this.postdao.readPostAll();
System.out.println(postDtoList.size()-1);
PostDto firstPost = postDtoList.get(0);
firstPost.setContent("content updated by batis");
postdao.updatePost(firstPost);
System.out.println(this.postdao.readPost(firstPost.getId()));
}
}
=> 근데 자꾸 내가 아래 명시해 둔 에러 뜨며 작동이 안되네 일단 파일 삭제시켜두고 보류..
(+) xml에 sql 문 하나더 추가
<select id="readPostQuery"
parameterType="mb.mybatisdemo.dto.PostDto"
resultType="mb.mybatisdemo.dto.PostDto">
select * from post
where title = #{title}
<if test="writer!=null">
and writer = #{writer}
</if>
</select>
public interface PostMapper{
int createPost(PostDto dto);
PostDto readPost(int id);
List<PostDto> readPostAll();
PostDto readPostQuery(PostDto dto);//추가
int updatePost(PostDto dto);
int deletePost(int id);
}
<insert id="createPostAll"
parameterType="mb.mybatisdemo.dto.PostDto">
insert into POST(title, content, title, writer,board)
values
<foreach collection="list" item="item" separator=",">
<!--separator 기준 : , 기준으로 나누기
& 하나의 콜렉션에서 , 으로 나뉜 각각 애들을 item으로 부르겠삼-->
(#{item.title}, #{item.content}, #{item.writer}, ${item.board})
</foreach>
</insert>
public interface PostMapper{
int createPostAll(List<PostDto> dtoList);
int createPost(PostDto dto);
PostDto readPost(int id);
List<PostDto> readPostAll();
PostDto readPostQuery(PostDto dto);//추가
int updatePost(PostDto dto);
int deletePost(int id);
}
boarddto
package mb.mybatisdemo.dto;
public class BoardDto {
private int id;
private String name;
public BoardDto(){
}
public BoardDto(int id, String name){
this.id=id;
this.name=name;
}
public int getId() {
return id;```
}
public String getName() {
return name;
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "BoardDto{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
boardxml
<?xml version="1.0" encoding="UTF-8"?> <!--xml 버전 명시-->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mb.mybatisdemo.mapper.BoardMapper">
<insert id="createBoard"
useGeneratedKeys="true"
keyProperty="id"
parameterType="mb.mybatisdemo.dto.BoardDto"
>
insert into board(name) values (#{name})
</insert>
BoardMapper
package mb.mybatisdemo.mapper;
import mb.mybatisdemo.dto.BoardDto;
public interface BoardMapper {
int createBoard(BoardDto dto);
}
boarddao
package mb.mybatisdemo.dao;
import mb.mybatisdemo.dto.BoardDto;
import mb.mybatisdemo.mapper.BoardMapper;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
public class BoardDao {
private final SqlSessionFactory sessionFactory;
public BoardDao(@Autowired SqlSessionFactory sessionFactory)
{
this.sessionFactory=sessionFactory;
}
public int createBoard(BoardDto dto){
try(SqlSession session = sessionFactory.openSession()){
BoardMapper mapper = session.getMapper(BoardMapper.class);
return mapper.createBoard(dto);
}
}
}
코드를 입력하세요
db에서는 fk가 하나의 pk를 바라본다
=> fk, pk에 대한 개념
board.xml에서
<insert id="createBoard"
useGeneratedKeys="true"
keyProperty="id"
parameterType="mb.mybatisdemo.dto.BoardDto"
> <insert id="createBoard"
useGeneratedKeys="true"
keyProperty="id"
parameterType="mb.mybatisdemo.dto.BoardDto"
>
-> id property를 가진 키를 useGeneratedKeys를 통해 생성하라는 것
(+)mybatis useGeneratedKeys를 이용해서 auto_increment 값을 얻기
출처블로그
Caused by: org.apache.ibatis.binding.BindingException: Type interface mb.mybatisdemo.mapper.PostMapper is not known to the MapperRegistry.
- testcomponent를 만들고 돌려보니 이런 에러 발생
=> 보통은 xml에서 지정한 인터페이스 함수와 내가 만든 인터페이스 함수명이 일치하지 않을 때 발생
=> 하지만 나는 이름이 아주 똑같았다.
Execution failed for task ':MybatisdemoApplication.main()'.
Process 'command 'C:/Users/DONGYUN/.jdks/corretto-11.0.14/bin/java.exe'' finished with non-zero exit value 1
package mb.mybatisdemo;
import mb.mybatisdemo.dao.BoardDao;
import mb.mybatisdemo.dao.PostDao;
import mb.mybatisdemo.dto.BoardDto;
import mb.mybatisdemo.dto.PostDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class TestComponent {
private final PostDao postdao;
//private final BoardDao boardDao;
public TestComponent(
@Autowired PostDao postdao, BoardDao boardDao) {
this.postdao = postdao;
//this.boardDao = boardDao;
//
// BoardDto boarddto = new BoardDto();
// boarddto.setName("new board");
// this.boardDao.createBoard(boarddto);
// System.out.println(boarddto.getId());
PostDto newPost = new PostDto();
newPost.setTitle("From mybatis");
newPost.setContent("Hello batis");
newPost.setWriter("shucream");
newPost.setBoard(0);
this.postdao.createPost(newPost);
List<PostDto> postDtoList=this.postdao.readPostAll();
System.out.println(postDtoList.size()-1);
PostDto firstPost = postDtoList.get(0);
firstPost.setContent("content updated by batis");
postdao.updatePost(firstPost);
System.out.println(this.postdao.readPost(firstPost.getId()));
}
}
=> 저녁먹고 반드시 에러원인찾아낸다
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [mb.mybatisdemo.TestComponent]: Constructor threw exception; nested exception is org.apache.ibatis.binding.BindingException: Type interface mb.mybatisdemo.mapper.PostMapper is not known to the MapperRegistry.
-이번엔 빈 주입에러
package mb.mybatisdemo;
import mb.mybatisdemo.dao.BoardDao;
import mb.mybatisdemo.dao.PostDao;
import mb.mybatisdemo.dto.BoardDto;
import mb.mybatisdemo.dto.PostDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class TestComponent {
private final PostDao postdao;
//private final BoardDao boardDao;
public TestComponent(
@Autowired PostDao postdao, BoardDao boardDao) {
this.postdao = postdao;
//this.boardDao = boardDao;
//
// BoardDto boarddto = new BoardDto();
// boarddto.setName("new board");
// this.boardDao.createBoard(boarddto);
// System.out.println(boarddto.getId());
PostDto newPost = new PostDto();
newPost.setTitle("From mybatis");
newPost.setContent("Hello batis");
newPost.setWriter("shucream");
newPost.setBoard(0);
this.postdao.createPost(newPost);
List<PostDto> postDtoList=this.postdao.readPostAll();
System.out.println(postDtoList.size()-1);
PostDto firstPost = postDtoList.get(0);
firstPost.setContent("content updated by batis");
postdao.updatePost(firstPost);
System.out.println(this.postdao.readPost(firstPost.getId()));
}
}