반응형
목차
Mapper 클래스를 활용하여 자동 매핑
- 리소스 폴더 아래에 java 폴더아래와같은 경로, 이름으로 Mapper 클래스가 있을 시 SqlSessionTemplate 으로 경로를 명시해주지 않아도 매핑하여 사용 가능
- Mapper가 자동으로 해당 XML을 찾아 매핑 시켜줌
- Service는 Mapper인터페이스를 주입 받아 사용
- 주의 할 점은 파일명의 맨 앞이 대문자여야함!
- chap09 > StudentMapper 동일
- Config에는 @MapperScan 어노테이션을, 해당 매퍼 클래스에는 @Mapper를 사용하여 밝혀주어야 한다.

package chap09;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface StudentMapper {
List<StudentVO> index(StudentVO vo);
int count(StudentVO vo);
int insertStudent(StudentVO vo);
int insertHobby(HobbyVO vo);
StudentVO login(StudentVO vo);
}
package chap09;
import javax.sql.DataSource;
import org.apache.ibatis.annotations.Mapper;
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.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.zaxxer.hikari.HikariDataSource;
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "chap09") // 컴포넌트 스캔
@MapperScan(basePackages = "chap09", annotationClass = Mapper.class) // @Mapper 어노테이션이 있는 인터페이스만 Proxy개체로 생성
@EnableTransactionManagement // 트랜잭션 활성화
public class MvcConfig implements WebMvcConfigurer {
// 뷰리졸버 - 컨트롤러에서 포워딩할 경로(앞/뒤) 설정
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/views/", ".jsp");
}
// 비즈니스로직이 필요없는 페이지
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/home.do").setViewName("home");
}
// 정적리소스 처리(스프링이 아니라 톰캣이 처리하도록) 활성화
// img, css, js..
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer config) {
config.enable();
}
// HikariCP
@Bean
public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
// dataSource.setDriverClassName("oracle.jdbc.OracleDriver");
// dataSource.setDriverClassName("org.mariadb.jdbc.Driver");
dataSource.setDriverClassName("net.sf.log4jdbc.sql.jdbcapi.DriverSpy");
dataSource.setJdbcUrl("jdbc:log4jdbc:mariadb://127.0.0.1:3308/mysql");
dataSource.setUsername("root");
dataSource.setPassword("test1234");
return dataSource;
}
// MyBatis
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean ssf = new SqlSessionFactoryBean();
ssf.setDataSource(dataSource()); // 의존성주입 (DI)
// mapper파일 위치 설정
// PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// ssf.addMapperLocations(resolver.getResources("classpath:/mapper/**/*.xml"));
return ssf.getObject();
}
// DAO에 주입될 객체 (Mapper 에서는 필요 없음)
// sql실행하려고
// @Bean
// public SqlSessionTemplate sst() throws Exception {
// return new SqlSessionTemplate(sqlSessionFactory()); // 의존성주입(생성자방식)
// }
// 트랜잭션매니저 빈 등록
@Bean
public TransactionManager tm() {
TransactionManager tm =
new DataSourceTransactionManager(dataSource());
return tm;
}
}
// MyBatis
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean ssf = new SqlSessionFactoryBean();
ssf.setDataSource(dataSource()); // 의존성주입 (DI)
// mapper파일 위치 설정
// PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// ssf.addMapperLocations(resolver.getResources("classpath:/mapper/**/*.xml"));
return ssf.getObject();
}
// DAO에 주입될 객체 (Mapper 에서는 필요 없음)
// sql실행하려고
// @Bean
// public SqlSessionTemplate sst() throws Exception {
// return new SqlSessionTemplate(sqlSessionFactory()); // 의존성주입(생성자방식)
// }
트랜잭션
- 논리적인 하나의 작업 단위
- 전부 실행되거나 전부 안되거나 (All or Nothing)
- Config파일에 datasource를 넣어 사용을 명시
// 트랜잭션매니저 빈 등록
@Bean
public TransactionManager tm() {
TransactionManager tm =
new DataSourceTransactionManager(dataSource());
return tm;
}
- 어노테이션으로 활성화
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "chap09") // 컴포넌트 스캔
@MapperScan(basePackages = "chap09", annotationClass = Mapper.class) // @Mapper 어노테이션이 있는 인터페이스만 Proxy개체로 생성
**@EnableTransactionManagement // 트랜잭션 활성화 << 여기**
public class MvcConfig implements WebMvcConfigurer {
실습
- 실습 시나리오
- 학생 등록
- 두 개의 테이블 Insert
- 1 : 학생 테이블
- 2 : 취미 테이블
- 학생 테이블 insert → insert 후 pk 조회 → 취미 테이블 insert → 부모(학생)테이블에서 가져온 pk로 등록(반복)
로그인 + 인터셉터
- 인터셉터란?
- 스프링 MVC에서 요청(Request)이 컨트롤러에 도달하기 전/후에 동작하는 가로채기 기능입니다.
- 필터(Filter)와 유사하지만, 스프링 MVC에 더 밀접하게 통합되어 있음.
- 사용 예시
- 로그인 체크
- 권한 검사
- 로깅
- 요청 시간 측정
- 공통 처리 (예: 모든 요청에 공통적으로 적용되는 로직)
- 흐름
- 등록 (write.do)
- 주소창으로 write.do로 접속 시 get방식으로 controller에 요청 전달
- controller는 student/write를 반환하여 write.js페이지로 전달
- write.js 에서 form으로 등록한 post 형식으로 controller로 값 전달
- post 방식으로 controller로 요청 들어옴
- jsp의 폼에서 name과 같은 데이터를 VO에서 찾아 매개 변수로 넣어줌
- 서비스의 insert 함수 실행
- insert 함수는 mapper를 주입받고, mapper는 해당 mapper.xml에서 id값을 찾아 해당 쿼리문을 수행하고 값을 파싱해줌
- 이때, 취미 설정을 진행하는데 필요한 방금 들어온 studno의 키 가 필요, autoIncrement로 설정된 pk id값을 가져와 사용하기 위해 SELECT LAST_INSERT_ID() 로 가져온 키를 변수에 저장하여 세팅 후 db에 넣기
- redirect로 등록 처리가 되었으면 index.jsp로 보냄
- 로그인 (login.do)
- 주소창, 혹은 a태그의 href로 들어온 url로 이동 시 controller의 get매핑
- login.jsp로 이동됨
- post의 login.do로 form입력 시 데이터 전송
- login.do의 post로 controller에 요청이 들어옴
- HttpSession 은 세션 사용을 위해, vo은 데이터, RedirectAttributes 는 메세지 알림을 위한 1회성 리다이렉트 메세지로 전송
- service에서 login mapper를 통해 주입 받고, 해당 id를 mapper xml에서 찾아 매핑
- pwd=MD5(#{pwd}) 는 MD5 형식으로 암호화 하겠다는 것
- resultType으로 vo를 리턴하고, 컨트롤러에서 해당 vo를 login으로 저장
- 성공 시 세션에 vo 저장 후 index.jsp로 리다이렉션
- 실패 시 현재 페이지로 리다이렉트 후 1회성 alret를 띄움, addFlashAttribute 는 리다이렉션 시에도 데이터를 유지하고 싶을 때 사용, 세션에 저장, 1회성이며, 요청으로 사용 시 자동 삭제됨
- 등록 (write.do)
코드
package chap09;
import lombok.Data;
@Data
public class HobbyVO {
private int h_num;
private String h_name;
private int studno;
}
package chap09;
import javax.sql.DataSource;
import org.apache.ibatis.annotations.Mapper;
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.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.zaxxer.hikari.HikariDataSource;
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "chap09") // 컴포넌트 스캔
@MapperScan(basePackages = "chap09", annotationClass = Mapper.class) // @Mapper 어노테이션이 있는 인터페이스만 Proxy개체로 생성
@EnableTransactionManagement // 트랜잭션 활성화
public class MvcConfig implements WebMvcConfigurer {
// 뷰리졸버 - 컨트롤러에서 포워딩할 경로(앞/뒤) 설정
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/views/", ".jsp");
}
// 비즈니스로직이 필요없는 페이지
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/home.do").setViewName("home");
}
// 정적리소스 처리(스프링이 아니라 톰캣이 처리하도록) 활성화
// img, css, js..
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer config) {
config.enable();
}
// HikariCP
@Bean
public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
// dataSource.setDriverClassName("oracle.jdbc.OracleDriver");
// dataSource.setDriverClassName("org.mariadb.jdbc.Driver");
dataSource.setDriverClassName("net.sf.log4jdbc.sql.jdbcapi.DriverSpy");
dataSource.setJdbcUrl("jdbc:log4jdbc:mariadb://127.0.0.1:3308/mysql");
dataSource.setUsername("root");
dataSource.setPassword("test1234");
return dataSource;
}
// MyBatis
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean ssf = new SqlSessionFactoryBean();
ssf.setDataSource(dataSource()); // 의존성주입 (DI)
// mapper파일 위치 설정
// PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// ssf.addMapperLocations(resolver.getResources("classpath:/mapper/**/*.xml"));
return ssf.getObject();
}
// DAO에 주입될 객체 (Mapper 에서는 필요 없음)
// sql실행하려고
// @Bean
// public SqlSessionTemplate sst() throws Exception {
// return new SqlSessionTemplate(sqlSessionFactory()); // 의존성주입(생성자방식)
// }
// 트랜잭션매니저 빈 등록
@Bean
public TransactionManager tm() {
TransactionManager tm =
new DataSourceTransactionManager(dataSource());
return tm;
}
}
package chap09;
import java.util.Map;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Controller
public class StudentController {
@Autowired
private StudentService service;
// url 매핑 메서드, 파라미터받아, 서비스호출, 저장소에 저장, 포워딩
@GetMapping("/student/index.do")
public String index(Model model, StudentVO vo) {
log.info("vo:"+vo);
// log.info("s_major1:"+Arrays.toString(vo.getS_major1()));
Map<String ,Object> map = service.index(vo);
model.addAttribute("list", map); // request저장소에 list라는 이름으로 map객체를 저장
return "student/index";
}
// 등록폼
@GetMapping("/student/write.do")
public String write() {
return "student/write";
}
// 등록처리
@PostMapping("/student/write.do")
public String write(Model model,
StudentVO vo,
@RequestParam String[] h_name) {
boolean result = service.insert(vo, h_name);
// if (result) {
// return "";
// }
return "redirect:index.do";
}
@GetMapping("/student/login.do")
public void login() {
}
@PostMapping("/student/login.do")
public String login(HttpSession sess, StudentVO vo,
RedirectAttributes ra) { // 리다이렉트할때 전송할값
StudentVO login = service.login(vo);
if (login != null) { // 로그인 성공
sess.setAttribute("loginSession", login);
return "redirect:index.do";
} else {
// 값 저장(일회성)
ra.addFlashAttribute("msg", "로그인정보가 올바르지 않습니다.");
return "redirect:login.do";
}
}
}
package chap09;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface StudentMapper {
List<StudentVO> index(StudentVO vo);
int count(StudentVO vo);
int insertStudent(StudentVO vo);
int insertHobby(HobbyVO vo);
StudentVO login(StudentVO vo);
}
package chap09;
import java.util.Map;
public interface StudentService {
Map<String, Object> index(StudentVO vo);
boolean insert(StudentVO svo, String[] names);
StudentVO login(StudentVO vo);
}
package chap09;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentMapper mapper;
@Override
public Map<String, Object> index(StudentVO vo) {
System.out.println(mapper.getClass().getName());
List<StudentVO> list = mapper.index(vo);
Map<String, Object> map = new HashMap<>();
map.put("list", list);
map.put("count", mapper.count(vo));
return map;
}
@Transactional
@Override
public boolean insert(StudentVO svo, String[] names) {
// studno가 0
System.out.println("insert 전 studno:"+svo.getStudno());
int r = mapper.insertStudent(svo);
// studno가 PK값
System.out.println("insert 후 studno:"+svo.getStudno());
if (r > 0) {
// TODO 취미파라미터개수만큼 반복, student테이블에 방금 insert된 데이터의 PK도 가져와야돼, 취미명
for (String name : names) {
HobbyVO hvo = new HobbyVO();
hvo.setH_name(name);
// TODO student테이블의 PK
hvo.setStudno(svo.getStudno());
mapper.insertHobby(hvo);
}
}
return r > 0;
}
@Override
public StudentVO login(StudentVO vo) {
return mapper.login(vo);
}
}
package chap09;
import java.sql.Timestamp;
import lombok.Data;
@Data
public class StudentVO {
private int studno;
private String name;
private String id;
private String pwd;
private int grade;
private String jumin;
private Timestamp birthday;
private String tel;
private int height;
private int weight;
private int major1;
private int major2;
private int profno;
private String majorname;
// 검색용도
private int s_grade;
private String s_type;
private String s_word;
// private int[] s_major1;
private String s_major1;
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="chap09.StudentMapper">
<sql id="searchSql">
<where>
<!-- 학년 -->
<if test="s_grade > 0">
AND grade = #{s_grade}
</if>
<!-- 검색어 -->
<if test="s_word != null and s_word != ''"> <!-- 검색한 경우 -->
<!-- 전체 -->
<if test="s_type == ''">
AND (studno LIKE '%${s_word}%'
OR student.name LIKE '%${s_word}%'
OR id LIKE '%${s_word}%')
</if>
<!-- 번호/이름/아이디 -->
<if test="s_type != ''">
AND ${s_type} LIKE '%${s_word}%'
</if>
</if>
<!-- 전공 -->
<if test="s_major1 != null">
AND major1 IN
<!--
<foreach collection="s_major1" item="m" open="(" close=")" separator=",">
#{m}
</foreach>
-->
(${s_major1})
</if>
</where>
</sql>
<select id="index" parameterType="chap09.StudentVO" resultType="chap09.StudentVO">
SELECT
student.name,
student.studno,
student.id,
student.grade,
major.name majorname
FROM student LEFT JOIN major ON student.major1 = major.code
<include refid="searchSql"/>
ORDER BY studno DESC
</select>
<select id="count" parameterType="chap09.StudentVO" resultType="int">
SELECT COUNT(*) FROM student
<include refid="searchSql"/>
</select>
<!-- 학생테이블 insert -->
<insert id="insertStudent" parameterType="chap09.StudentVO">
INSERT INTO student (
name, id, pwd, grade, jumin
) VALUES (
#{name}, #{id}, md5(#{pwd}), #{grade}, #{jumin}
)
<selectKey keyProperty="studno" resultType="int" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
</insert>
<!-- 취미테이블 insert -->
<insert id="insertHobby" parameterType="chap09.HobbyVO">
INSERT INTO hobby (
h_name, studno
) VALUES (
#{h_name}, #{studno}
)
</insert>
<!-- 로그인 -->
<select id="login" parameterType="chap09.StudentVO" resultType="chap09.StudentVO">
SELECT * FROM student WHERE id=#{id} AND pwd=MD5(#{pwd})
</select>
</mapper>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="student">
<sql id="searchSql">
<where>
<!-- 학년 -->
<if test="s_grade > 0">
AND grade = #{s_grade}
</if>
<!-- 검색어 -->
<if test="s_word != null and s_word != ''"> <!-- 검색한 경우 -->
<!-- 전체 -->
<if test="s_type == ''">
AND (studno LIKE '%${s_word}%'
OR name LIKE '%${s_word}%'
OR id LIKE '%${s_word}%')
</if>
<!-- 번호/이름/아이디 -->
<if test="s_type != ''">
AND ${s_type} LIKE '%${s_word}%'
</if>
</if>
<!-- 전공 -->
<if test="s_major1 != null">
AND major1 IN
<!--
<foreach collection="s_major1" item="m" open="(" close=")" separator=",">
#{m}
</foreach>
-->
(${s_major1})
</if>
</where>
</sql>
<select id="index" parameterType="chap06.student.StudentVO" resultType="chap06.student.StudentVO">
SELECT
student.name,
student.studno,
student.id,
student.grade,
major.name majorname
FROM student JOIN major ON student.major1 = major.code
<include refid="searchSql"/>
ORDER BY studno DESC
</select>
<select id="count" parameterType="chap06.student.StudentVO" resultType="int">
SELECT COUNT(*) FROM student
<include refid="searchSql"/>
</select>
</mapper>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script>
let msg = '${msg}';
if (msg) {
alert(msg);
}
</script>
</head>
<body>
<h1>로그인</h1>
<form action="login.do" method="post">
아이디 : <input type="text" name="id"><br>
비밀번호 : <input type="password" name="pwd"><br>
<input type="submit" value="로그인">
</form>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>학생 등록</h1>
<form action="write.do" method="post">
아이디 : <input type="text" name="id"><br>
비밀번호 : <input type="text" name="pwd"><br>
이름 : <input type="text" name="name"><br>
학년 : <input type="text" name="grade"><br>
주민번호 : <input type="text" name="jumin"><br>
취미 :
<input type="checkbox" name="h_name" value="독서"> 독서
<input type="checkbox" name="h_name" value="여행"> 여행
<input type="checkbox" name="h_name" value="게임"> 게임
<input type="checkbox" name="h_name" value="영화"> 영화<br>
<input type="submit" value="등록">
</form>
</body>
</html>
메모
- Last_Insert_ID()
- MySQL, MariaDB에서 마지막에 넣은 키를 가져오는 함수
- jsp 전송폼 이름을 vo의 이름하고 통일시키기 (name)
728x90
반응형
'BackEnd' 카테고리의 다른 글
| CICD 구현해보기 (1) - GitAction (0) | 2025.11.06 |
|---|---|
| JWT + Security로 로그인 구현해보기 (2) | 2025.09.07 |
| 6/4 - MariaDB, Log4JDBC (0) | 2025.06.06 |
| 6/1 - 의존성 주입 (DI), 관점 지향 프로그래밍 (AOP) (0) | 2025.06.06 |
| Spring Lagacy Project, Spring MVC, MyBatis 인텔리제이에서 세팅하기 (0) | 2025.06.03 |