JAVA
Mybatis:설정하기
docc
2021. 7. 27. 16:23
Mybatis
참고사이트
https://www.baeldung.com/mybatis
이제 DAO 와 같이 DB 연결하는 Mapper 를 사용해 더 쉽게 연결하자! DAO는 JDBC API 였고, 이제 Mybatis API 를 설치한다. JDBC 를 위한 ConnectionPool API 도 필요 없다. |
||
DAO - 인터페이스를 만들고 그걸 구현한 클래스에 @Repository 를 걸어 @Autowired 시 인터페이스형으로 선언하여 사용함 Mapper - 인터페이스에 아예 @Mapper 를 걸면 @Autowired 시 알아서 상속받은 클래스가 있는것 처럼 선언되어 사용할 수 있다. (구현하는 클래스 필요가 없다.) |
||
DAO vs Mapper : 여러개를 한번에 저장시 DAO 는 중간에 실패해도 초반건 들어가버린다. Mapper는 전체를 롤백한다. (ex. board는 저장했는데 attach가 이상하다고 실패했을 때 전체를 실행 시키지 않는 롤백을 시켜야 좋다) |
Mybatis 설정하기! (아주중요!!!)
>> pom.xml
<!-- mybatis 를 위한 API 3개 -->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- db connectionpool 중 아래 2개는 필요없다. -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.7.0</version>
</dependency>
// <dependency>
// <groupId>org.apache.commons</groupId>
// <artifactId>commons-pool2</artifactId>
// <version>2.8.0</version>
// </dependency>
// <dependency>
// <groupId>commons-logging</groupId>
// <artifactId>commons-logging</artifactId>
// <version>1.2</version>
// </dependency>
>> web.xml
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
config.MvcConfig
config.BeanConfig
</param-value>
</init-param>
</servlet>
web.xml 에 DispatcherServlet 이
설정된 init-param 의 @Configuration 클래스들을 읽어 빈등록 한 것들을 반영해준다!
>> MybatisConfig.java
package config;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan(basePackages = {"com.stone"})
public class MyBatisConfig {
@Bean
public DataSource dataSource() {
String driverClassName = "com.mysql.cj.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/mvc2?useUnicode=true";
String userName = "root";
String password = "1234";
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(userName);
dataSource.setPassword(password);
return dataSource;
}
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource());
return sqlSessionFactoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate() throws Exception {
return new SqlSessionTemplate(sqlSessionFactory());
}
}
@MapperScan(basePackages = {"com.stone"}) : 패키지 아래에 mybatis 인 객체인 @Mapper 애들 스캔 @ComponentScan(basePackages = "com.stone") : 스프링객체인 @Controller 과 @Repository 인 애들 스캔, 자동으로 Bean등록 BeanConfig 에 @Configuration 을 달아 따로 @Bean 을 잡는 이유 ( 생성자에 셋팅을 해야 할 수 있으니까!) |
||
@Bean 등록이 많은 이유: 아래의 생성자에 설정을 해야하며 설정시 쓰는 객체를 개인의 판단에 따라 여러가지 사용 가능하기 때문이다. 아래의 3개 Bean 등록은 필수다!! |
||
1. DataSource : Connection 해준다. 그 중 BasicDataSource 객체를 선택했다. 대표적으로 DriverManagerDataSource() 와 BasicDataSource() 골라쓸 수 있다. 하지만 전자의 경우 계속 Connection 을 새로 생성하며 후자는 connection pooling 이라서 후자를 선택한다! (10개정도 만들어놓고 필요할때 꺼내쓰는방식)/connection pool 을 이제 안써도 됨! |
||
2. SqlSessionFactory : Connection 을 가져와 연결한 sqlFactory 를 만들어준다. 그 중 SqlSessionFactoryBean 객체를 선택했다. | ||
3. SqlSessionTemplate : sql문(select,insert등) 을 사용하게 하는 SqlSessionTemplate 객체를 Bean으로 올려 생성한다. |
>> MvcConfig.java (@Import 로 MyBatisConfig 추가)
@Import(value= {MyBatisConfig.class})
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.stone")
public class MvcConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/views/",".jsp");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// addResourceHandler = 모든 요청명 풀네임, addResourceLocations = 요청명 중 폴더명
registry.addResourceHandler("/img/**").addResourceLocations("/img/");
registry.addResourceHandler("/upload/**/**").addResourceLocations("/upload/");
registry.addResourceHandler("/js/*.js").addResourceLocations("/js/");
}
}
예시
@Select("select no,name from member where id=#{id} and password=#{password}")
HashMap<String, Object> isIn(@Param("id")String id, String password)
Member (회원)
>> IMemberMapper
package com.stone.mvc.dataservice_member;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import com.stone.mvc.common_member.Member;
@Mapper
public interface IMemberMapper {
@Select("select * from member;")
List<Member> selectAll();
@Select("select * from member where no=#{no}")
Member findByNo(@Param("no") int 회원번호);
@Insert("insert into member(name,post,address,detailaddress,tel,"
+"email,id,password,profile)"
+"values(#{name},#{post},#{address},#{detailaddress},"
+"#{tel},#{email},#{id},#{password},#{profile})")
void save(Member member);
@Select("select no,name from member where id=#{id} and password=#{password}")
//no=1 , name=나길동 이렇게 HashMap 으로 컬럼명과 컬럼값이 들어간다.
//만약 하나의 튜플이 아닌 여러개의 튜플이 나온다면 List<HashMap<String,String>> 으로 해주면 된다.
HashMap<String, String> isIn(String id, String password);
@Select("select if(count(*)=1,1,0) from member where id=#{id}")
boolean isIn2(String id);//숫자가0 은 false로 반환, 0외의 모든 수는 true 임을 기억! DB에는 true false 가 없음
}
@Mapper : JDBC 에서는 @Repository 였던 것처럼 DB연동하는 클래스라고 선언 (이제 MyBatisConfig 로 설정한 환경의 DB연결 설정이 알아서 셋팅된다.) |
||
select 문으로 db에 접근을 선언. 아래 매서드에 알아서 열이름=멤버변수이름을 매핑해서 넣어준다. | ||
@Param("#{no}이름과같은이름") : sql 문의 값에 매칭되는 매개변수 선언(변수명이 같다면 생략가능) |
>> MemberDAO ( DAO 의 일을 Mapper 로 돌리자 + Mapper 쪽에서 HashMap으로 바꿨으니 여기서도 반영하기 )
@Repository
public class MemberDAO implements IMemberDAO {
@Autowired
IMemberMapper memberMapper;
// 회원 저장하기
public void save(Member member) {
memberMapper.save(member);
}
// 존재하는 회원인지(로그인시 사용) 있으면 번호와 성명반환
public HashMap<String,Object> isin(String id, String password) {
return memberMapper.isIn(id, password);
}
// 존재하는 아이디인지(회원가입시 아이디중복체크로 사용)
public boolean isin(String id) {
return memberMapper.isIn2(id);
}
}
>> 회원등록컨트롤 (Mapper 쪽에서 HashMap으로 바꿨으니 여기서도 반영하기)
HashMap<String, Object> NoAndName = memberDAO.isin(member.getId(), member.getPassword());
session.setAttribute("no", NoAndName.get("no"));
session.setAttribute("name", NoAndName.get("name"));
Board (게시물)
>> IAttachMapper.java
package com.stone.mvc.dataservice_board;
import java.util.List;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import com.stone.mvc.common_board.Attach;
@Mapper
public interface IAttachMapper {
@Insert("insert into attach(name,size,board_no) values(#{name},#{size},#{board.no})")
void save(Attach 첨부);
@Insert({
"<script>",
"insert into attach(name,size,board_no) values",
"<foreach collection='attachs' item='attach' index='index' separator=','>",
"(#{attach.name},#{attach.size},#{attach.board.no})",
"</foreach>",
"</script>"
})
void saveAttachs(@Param(value="attachs")List<Attach> attachs);
@Select("select * from attach where board_no=#{board_no}")
List<Attach> selectByBoardNo(@Param("board_no") int 게시물번호);
@Select("select * from attach where no=#{no}")
@Results(value = {
@Result(property="no", column="no"),
@Result(property="name", column="name"),
@Result(property="size", column="size"),
@Result(property="board.no", column="board_no")})
Attach findByNo(@Param("no") int 첨부번호);
}
1. board.no : Attach 의 board 참조변수의 변수인 no 을 가져온다. | ||
2. 반복insert문 사용하기 : {} 로 감싸야함. 각 문장의 구분자는 '+' 가 아닌 ',' 이다. <script> : 반복되는 문장들이 바로바로 insert 되지 않고 한번에 모았다 가도록 잡아놓는다. <foreach> : 향상된 for문 - collection : 매개변수에서 가져오는 배열형태 - item : collection 에서 가져온 배열을 하나씩 빼서 넣기 - index : 반복수 - separator : 반복되는 문장 끝에오는 구분자 |
||
3. <ResultMap>(xml) = @Results(config) @Results - value : 여러 @Result 설정 @Result : property 와 column 을 매핑시켜줌 - property : 객체의 멤버변수명 - column : 테이블 컬럼명 //as 로 별칭만들 경우 그 별칭!(어디까지나 추출된 테이블의 컬럼명이다.) |
>> IBoardMapper
@Mapper
public interface IBoardMapper {
@Insert("insert into board(title,contents,writer) values(#{title},#{contents},#{writer.no})")
@SelectKey(statement="select last_insert_id()", keyProperty="no", before=false, resultType=int.class)
int saveOnlyBoard(Board board);//attach 가 저장이 안됨. insert 할 때의 one to many 관계
@Select("select * from board where no=#{no}")
@Results(value= {
@Result(property="no",column="no"),
@Result(property="title",column="title"),
@Result(property="contents",column="contents"),
@Result(property="wdate",column="wdate"),
@Result(property="writer",column="writer",one=@One(select="com.stone.mvc.dataservice_member.IMemberMapper.findByNo")),
@Result(property="views",column="views"),
@Result(property="attachs",javaType=List.class,column="no",many=@Many(select="com.stone.mvc.dataservice_board.IAttachMapper.selectByBoardNo"))
})
Board findByNo(int no);
@Update("update board set views=views+1 where no=#{no}")
void updateView(int no);
@Select("select * from board")
@Results(value= {
@Result(property="no",column="no"),
@Result(property="title",column="title"),
@Result(property="contents",column="contents"),
@Result(property="wdate",column="wdate"),
@Result(property="writer",column="writer",one=@One(select="com.stone.mvc.dataservice_member.IMemberMapper.findByNo")),
@Result(property="views",column="views"),
@Result(property="attachs",javaType=List.class,column="no",many=@Many(select="com.stone.mvc.dataservice_board.IAttachMapper.selectByBoardNo"))
})
List<Board> selectAll();
1. saveOnlyBoard() @SelectKey : 방금 insert 한 튜플의 key 값 가져오기 - statement : key 값 가져오는 sql 문 - keyProperty : key 값 컬럼이름, return 도 되고 board에 자동으로 이 이름의 멤버변수에 get된다. ( DAO 에서 따로 int no 로 받아 getNo(no) 하지 않아도 된다. ) - resultType : 나오는 key 값의 변수형 |
||
2. findByNo() 1) 게시물상세보기도 조회수update 문도 함께 날려야하지만 sql문은 여러번 날릴 수 없으므로 따로 만들어야 한다. (DAO 에서 두 sql 문을 실행하면 된다. ) 2) one to one 관계 : 게시물상세보기에 게시물 작성자 즉, Member 도 와야한다 @One : 자신(one도 되고 many도 될수 있음) to one 관계 column 값을 property에 바로 매핑하지 않고, 패키지명.클래스명.메서드명의 sql문에 column 값을 where에 넣어 결과값을 property에 매핑 3) @Many : 자신(one도 되고 many도 될수 있음) to many 관계 위와 동일 javaType : 여러개니까 List<Attach> 인 멤버변수에 List형으로 넣어준다고 표시 |
||
3. selectAll() 자신이 단일 객체던 List 형이던 위의 findByNo() 와 똑같이 매핑시켜 하나씩 List에 넣어준다. |
>> BoardDAO ( DAO 의 일을 Mapper 로 돌리자 )
@Repository
public class BoardDAO implements IBoardDAO {
@Autowired
private IBoardMapper boardMapper;
@Autowired
private IAttachMapper attachMapper;
//게시물 저장하기.
public int save(Board board) {
boardMapper.saveOnlyBoard(board);
attachMapper.saveAttachs(board.getAttachs());//for문 안쓰고 sql문으로 한번에 저장이 가능해졌다(script-foreach문)
return board.getNo();
}
// 게시물의 번호로 게시물의 상세내역 보기(+ 조회수 증가여부)
public Board findByNo(int no, boolean addView) {
if(addView) {boardMapper.updateView(no);}
return boardMapper.findByNo(no);
}
// 게시물전체 가져오기
public List<Board> selectAll() {
return boardMapper.selectAll();
}
}
>> ICommentMapper
@Mapper
public interface ICommentMapper {
@Insert("insert into comment(contents,writer,board_no) values(#{contents},#{writer.no},#{board.no}")
void save(Comment comment);
@Select("select * from comment where board_no=#{no} order by wdate desc limit 0,#{size}")
@Results(value= {
@Result(property="no",column="no"),
@Result(property="contents",column="contents"),
@Result(property="wdate",column="wdate"),
@Result(property="writer.no",column="writer"),
@Result(property="board.no",column="board_no")
})
List<Comment> selectByBoardNo(int no, int size);
@Select("select count(*) from comment where board_no=#{no}")
int totalSizeByBoardNo(int no);
}
1. board 에 달린 모든 Comment 객체와 댓글총수 구하는 메서드도 분리해야 한다.(sql문이 2개 였으니까) //굳이 board 객체를 다 채울 필요 없으니 no 만 셋팅했다. (만약 board 객체 다채울라면 one to one(댓글기준관계) 로 board의 메서드 끌어와야한다.) |
>> CommentDAO ( DAO 의 일을 Mapper 로 돌리자 )
@Repository
public class CommentDAO implements ICommentDAO {
@Autowired ICommentMapper commentMapper;
@Override
public void save(Comment comment) {
commentMapper.save(comment);
}
@Override
public List<Comment> selectByBoardNo(int no, int size, RefInteger totalSize) {
totalSize.value = commentMapper.totalSizeByBoardNo(no);
return commentMapper.selectByBoardNo(no, size);
}
}