-
Mybatis:설정하기JAVA 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); } }
'JAVA' 카테고리의 다른 글
3Tier: 예외처리,@Qualifier (0) 2021.07.27 Spring:interceptor,3Tier(Presentation,Business,Dataservice) (0) 2021.07.27 BLOB 이미지파일업로드 (0) 2021.07.27 Spring:댓글더보기(ajax.ver) (0) 2021.07.26 Spring:댓글(ajax.ver) (0) 2021.07.26