본문 바로가기

회고록(TIL&WIL)

TIL 2023.03.09 Spring framework 4.X (properties, junit, mybatis, jackson, session)

Spring framework 4.X

properties 파일 사용하여 JDBC

applicationContext.xml

<!-- JDBC -->
<bean 
  class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
  p:location="classpath:info.properties"/>
<bean 
  id="dataSource"
  class="org.springframework.jdbc.datasource.SimpleDriverDataSource"
  p:driverClass="com.mysql.cj.jdbc.Driver"
  p:url="${db.url}" p:username="${db.user}" p:password="${db.password}"/>

info.properties

db.url=jdbc:mysql://192.168.99.000:3306/lecture
db.user=scott
db.password=tiger

Junit

앞서 junit테스트를 사용할 때 어떤 xml을 읽어들일지에 대한 선택을 어노테이션으로 할 수 있다. 배열로 여러개 선택도 가능

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:/applicationContext.xml")
//@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
//@ContextConfiguration({
//		"classpath:/applicationContext.xml",
//		"file:src/main/webapp/WEB-INF/spring/root-context.xml"
//		})
public class ApplicationTest {
	@Autowired
	DataSource dataSource;
  
	@Test
	public void test() {
		assertNotNull(dataSource);
	}
}

mybatis

Spring version 3.X 최종버전 부터 안정적으로 사용가능하다 오류가 발생되었을 때 버전을 확인해보자!

mybatis-context.xml 파일 생성 후 스키마 복붙 및 기본 세팅

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <mappers>
    <mapper resource="mapper/dept-mapper.xml"/>
  </mappers>
</configuration>

mapper/dept-mapper.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>

</mapper>

applicationContext.xml

<!-- Mybatis -->
<bean 
  id="sqlSessionFactory"
  class="org.mybatis.spring.SqlSessionFactoryBean"
  p:configLocation="classpath:/mybatis-config.xml"
  p:dataSource-ref="dataSource"/>

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
  <constructor-arg ref="sqlSessionFactory"></constructor-arg>
</bean>

DeptVO, DeptDao(interface) 까지만 정의 후 dept-mapper.xml 추가 작성

<mapper namespace="com.bit.sts07.model.DeptDao">
	<select id="findAll" resultType="com.bit.sts07.model.entity.DeptVo">
		select * from dept
	</select>
</mapper>

Test

원래 namespace 작성이 필수가 아니었는데 ORM되도록 업데이트 되면서 namespace가 필수가 되었다.
DaoImpl을 작성하지 않았음에도 불구하고 DeptDao interface를 이용해서 select를 할 수 있게 된 결과값을 볼 수 있다.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:/applicationContext.xml")
public class DeptDaoTest {
	@Autowired
	SqlSession sqlSession;
	
	@Test
	public void findAllTest() {
//		for(DeptVo bean : sqlSession.getMapper(DeptDao.class).findAll()) {
//			System.out.println(bean);
//		}
		assertNotNull(sqlSession.getMapper(DeptDao.class).findAll());
	}
}

jackson - spring에서 List, Map같은 자료구조를 받아 return했을 때 자동으로 json형태로 변환하여 return해준다.

<!-- jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.3</version>
</dependency>

Controller

4.x버전부터는 메서드를 포함한 Mapping이 가능하다. (@GetMapping, @PostMapping, @PutMapping, @DeleteMapping)

@Inject의 경우는 @AutoWired랑 똑같으나 다른 프레임워크에서도 사용 가능한 주입 기능이다.

비동기 통신과 같이 return시 view대신 데이터를 리턴할때는 return 타입에 @ResponseBody를 붙여주면 된다.

package com.bit.sts07.controller;

import javax.inject.Inject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ...;

@Controller
@RequestMapping("/dept")
public class DeptController {
	Logger log = LoggerFactory.getLogger(getClass());
	
	@Inject
	DeptService deptService;
	
	@GetMapping("/")
	public String list(Model model) {
		model.addAttribute("list", deptService.selectAll());
		return "dept/list";
	}
	
	@PostMapping("/")
	public String add(@ModelAttribute("bean") DeptVo bean) {
		log.debug(bean.toString());
		deptService.insertOne(bean);
		return "redirect:./";
	}
	
//	public @ResponseBody String detail(@PathVariable("1") int deptno) {
//		return "msg";
//	}
	// 리스트, 객체를 리턴할 때 jackson 라이브러리가 있으면 자동으로 json형태로 리턴을 해준다.
//	public @ResponseBody List detail(@PathVariable("num") int deptno) {
//		return new ArrayList();
//	}
//	public @ResponseBody Map detail(@PathVariable("num") int deptno) {
//		return new HashMap();
//	}
	
	@GetMapping("/{num}")
	public @ResponseBody DeptVo detail(@PathVariable("num") int deptno) {
		return deptService.selectOne(deptno);
	}
	
	@PutMapping("/{num}")
	public @ResponseBody ResponseEntity<?> update(@RequestBody DeptVo bean) {
		System.out.println(bean);
		if(deptService.updateOne(bean)>0) {
			return new ResponseEntity(null, HttpStatus.OK);
		}
		return new ResponseEntity(null, HttpStatus.INTERNAL_SERVER_ERROR);
	}
	
	@DeleteMapping("/{deptno}")
	public @ResponseBody String delete(@PathVariable("deptno") int deptno) {
		System.out.println("delete : " + deptno);
		deptService.deleteOne(deptno);
		return "";
	}
}

Service - DeptDao를 구현하지 않고도 Mybatis에서 Dao를 참고하여 자동으로 메서드를 만들어준다.

package com.bit.sts07.service;

import ...;

@Service
public class DeptService {
	@Autowired
	SqlSession sqlSession;
	
	public List<DeptVo> selectAll() {
		return sqlSession.getMapper(DeptDao.class).findAll();
	}
	
	public DeptVo selectOne(int deptno) {
		return sqlSession.getMapper(DeptDao.class).findOne(deptno);
	}
	
	@Transactional
	public void insertOne(DeptVo bean) {
		sqlSession.getMapper(DeptDao.class).insertOne(bean);
	}
	
	@Transactional
	public int updateOne(DeptVo bean) {
		return sqlSession.getMapper(DeptDao.class).updateOne(bean);
	}
	
	@Transactional
	public int deleteOne(int deptno) {
		return sqlSession.getMapper(DeptDao.class).deleteOne(deptno);
	}
}

mybatis-config 에서 typeAlias를 지정해서 편하게 사용

<configuration>
  	<typeAliases>
  		<typeAlias type="com.bit.sts07.model.entity.DeptVo" alias="deptBean"/>
  	</typeAliases>
  	<mappers>
  		<mapper resource="mapper/dept-mapper.xml"/>
  	</mappers>
  </configuration>

mapper에 SQL 구문 작성 - namespace를 반드시 지정해줘야 Dao를 구현하지 않고도 Service에서 사용할 수 있게 된다.

<mapper namespace="com.bit.sts07.model.DeptDao">
	<select id="findAll" resultType="deptBean">
		select * from dept
	</select>
	<select id="findOne" parameterType="int" resultType="deptBean">
		select * from dept where deptno=#{val}
	</select>
	<insert id="insertOne" parameterType="deptBean">
		insert into dept value(#{deptno}, #{dname}, #{loc})
	</insert>
	<update id="updateOne" parameterType="deptBean">
		update dept set dname=#{dname}, loc=#{loc} where deptno=#{deptno}
	</update>
	<delete id="deleteOne" parameterType="int">
		delete from dept where deptno=#{val}
	</delete>
</mapper>

session을 이용한 간단 로그인/아웃 구현

Controller

@Controller
@RequestMapping("/join")
public class JoinController {
	@GetMapping("/")
	public String form() {
		return "join/login";
	}
	@PostMapping("/")
	public String login(@ModelAttribute("bean") DeptVo bean, HttpSession session, Model model) {
		if("user1".equals(bean.getDname()) 
				&& "test".equals(bean.getLoc())) {
			session.setAttribute("result", true);
			session.setAttribute("user", bean.getDname()+ "님 환영합니다.");
			return "redirect:../";
		}
		model.addAttribute("err", "로그인 정보가 정확하지 않습니다.");
		return "join/login";
	}
	@RequestMapping("/logout")
	public String logout(HttpSession session ) {
		session.invalidate();
		return "redirect:../";
	}
}

login.jsp

<%@ 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>
<%@ include file="../template/header.jspf" %>
</head>
<body>
<%@ include file="../template/menu.jspf" %>
<!--
menu.jspf의 내용중...
      ...
	<c:if test="${!sessionScope.result }">
        	<li><a href="${pageContext.servletContext.contextPath }/join/">Login</a></li>
        </c:if>
        <c:if test="${sessionScope.result }">
        	<li><a href="${pageContext.servletContext.contextPath }/join/logout">Logout</a></li>
        </c:if>
      </ul>
      <p class="navbar-text navbar-right">${sessionScope.user }</p>
      ...
-->
<div class="page-header">
  <h1>Login page<small>register</small></h1>
</div>

<div class="alert alert-danger" role="alert">${err }</div>

<form method="post">
	<div class="form-group">
		<input name="dname" placeholder="dname" class="form-control"/>
	</div>
	<div class="form-group">
		<input name="loc" placeholder="loc" class="form-control"/>
	</div>
	<div class="form-group">
		<button>로그인</button>
	</div>
</form>
<script type="text/javascript">
	if('${err}'=='')$('.alert').alert('close');
</script>
<%@ include file="../template/footer.jspf" %>
</body>
</html>