본문 바로가기

회고록(TIL&WIL)

TIL 2023.03.06 Spring Framework 3.X (annotation, ibatis)

Spring 3.0

eclipse perspective 설정을 spring으로 변경


프로젝트 생성하자마자 실행하여 Hello, world 볼 수 있는데 한글이 깨지는 것은 JSP 문제다. 아래 코드를 jsp에 추가하면된다.

<%@ page pageEncoding="utf-8" %>

servlet-context.xml

기본적으로 handller mapping은 따로 되어있지 않고 어노테이션을 이용할 수 있도록 해두었고 view resolver 되어있음
그래서 매핑을 하려고 servlet-context를 수정할 필요 없이 어노테이션을 이용하여 매핑을 해야함
최대한 손댈 필요 없도록 만들었던 것이 특징이다.

<!-- Enables the Spring MVC @Controller programming model -->
	<annotation-driven />
  ...
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
	<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<beans:property name="prefix" value="/WEB-INF/views/" />
		<beans:property name="suffix" value=".jsp" />
	</beans:bean>

Controller

@org.springframework.stereotype.Controller
public class Ex01Controller implements Controller {

	@RequestMapping("/ex01.do")
	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		return new ModelAndView("ex01");
	}
}

Spring 3.0은 POJO 지향으로 개발되었기 때문에 개발자 입장에서도 편하고 spring 입장에서도 가벼운 장점이 있다.

어노테이션을 이용하면 Controller 인터페이스를 구현하지 않아도 사용이 가능하다.
@RequestMapping을 메소드에 달기 때문에 메소드명도 자유롭게 사용 가능.
그러므로 하나의 컨트롤러에 여러개의 URL매핑을 할 수 있게 된다.
return 타입도 자유, 파라미터도 자유

@Controller
public class Ex02Controller{

	@RequestMapping("/ex02.do")
	public ModelAndView ex02(HttpServletRequest request, HttpServletResponse response) throws Exception {
		return new ModelAndView("ex02");
	}
  @RequestMapping("/ex03.do")
	public ModelAndView ex03(HttpServletRequest request, HttpServletResponse response) throws Exception {
		return new ModelAndView("ex03");
	}
  @RequestMapping("/ex04.do")
	public String ex04(HttpServletRequest request, HttpServletResponse response) throws Exception {
		return "ex04";
	}
  @RequestMapping("/ex05.do")
	public String ex05() {
		return "ex05";
	}
}

단, 아무 클래스에나 어노테이션을 단다고해서 컨트롤러가 되는 것은 아니다.
servlet-context.xml에서 설정되어있는 package의 하위 경로의 객체들을 scan한다.

<context:component-scan base-package="com.bit.sts04" />

클래스에도 @RequestMapping을 달 수 있는데 추가적으로 경로를 하나 더 달 수 있다.
단 클래스에 달았다고해서 메서드에 안달면 안된다. 메서드에는 필수.

@Controller
@RequestMapping("/lec01")
public class Ex03Controller {
	@RequestMapping("/ex06.do")
	public String ex06() {
		return "ex01";
	}
}
>>> /lec01/ex06.do 로 호출 가능

모든 요청이 DispatcherSevlet을 타도록 되어있기 때문에 jsp에서 css,img 등을 가져오기 위해선
IO를 통해서 직접 가져오는 방법도 있겠지만 servlet-context.xml에서 처리를 할 수 있다.
기본 설정은 직접 불러올 파일들을 webapp의 resouces 아래에 넣으면 되도록 설정 되어있다.
이러한 방식은 실제 URL을 파악할 수 없기 때문에 보안의 장점이 있다.

<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
	<resources location="/resources/" mapping="/resources/*"/>
  <resources location="/resources/imgs/" mapping="/images/*"/>
<img alt="" src="resources/imgs/logo.png"/>
<img alt="" src="images/logo.png"/>

@RequestMapping에서 메서드 속성을 지정해줄 수 있다.

@RequestMapping(value="/ex12", method= {RequestMethod.GET,RequestMethod.POST})
public String ex11() {
  return "ex01";
}
@RequestMapping(value="/ex12", method= {RequestMethod.POST})
public String ex12() {
  return "ex02";
}

@RequestParam 어노테이션을 이용하면 파라미터를 간단하게 받을 수 있다. parseInt 과정도 필요없이 타입에 맞게 가져온다.
변수명을 같게하면 속성정의를 생략할 수 있다. @RequestParam 자체도 생략 가능하다. 순서 상관 X

@RequestMapping(value="/ex12", method= {RequestMethod.POST})
public String ex12(
      @RequestParam("dname") String name,
      @RequestParam int deptno,
      String loc) {
  ...
  return "ex02";
}

값을 전달하기 위해서는 HttpServletRequest 객체 혹은 Model 객체를 받아와서 사용해야한다.

@RequestMapping(value="/ex12", method= {RequestMethod.POST})
	public String ex12(String dname, int deptno, /*HttpServletRequeset req*,/ Model model ) {
//		req.setAttribute("dname", dname);
//		req.setAttribute("deptno", deptno);
		model.addAttribute("dname", dname);
		model.addAttribute("deptno", deptno);
		return "ex02";
	}

@PathVariable 어노테이션을 이용하면 url을 통해 값을 받아낼 수 있다 ex)/ex13/abcd/1234 로 호출하게 되면 abcd와 1234를 가져온다.
@PathVariable은 생략이 불가능하다. 속성까진 생략 가능하다.

@RequestMapping("/ex13/{msg}/{num}")
public String ex13(
    @PathVariable("msg") String msg,
    @PathVariable int num) {
  ...
  return "ex03";
}

@ModelAttribute를 이용해서 객체를 전달 할 수 있다.
변수명이 동일하더라도 속성 생략은 불가능하다.
AOP에서 bean생성하고 값 넣어주던 작업을 어노테이션하나로 할 수 있게 된 것.

@RequestMapping("ex14")
public String ex14(@ModelAttribute("bean") DeptVo bean) {
  bean.setDeptno(2222);
  bean.setDname("test3");
  bean.setLoc("test");
  return "ex04";
}

docker를 이용해서 mysql 사용하기

docker pull mysql
docker run -p 3306:3306 --name mysql8 -e MYSQL_ROOT_PASSWORD=mysql -e MYSQL_DATABASE=lecture -e MYSQL_USER=scott -e MYSQL_PASSWORD=tiger -d mysql
접속확인 및 dummy data 추가 필요

pom.xml에 dependency 추가 (spring jdbc, mysql) root-context.xml에 DB 연결 설정 추가

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
  <property name="url" value="jdbc:mysql://192.168.99.000:3306/lecture"/>
  <property name="username" value="scott"/>
  <property name="password" value="tiger"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
  <property name="dataSource" ref="dataSource"/>
</bean>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

<bean id="deptDao" class="com.bit.sts04.model.DeptDaoImpl">
  <property name="jdbcTemplate" ref="jdbcTemplate"/>
  <property name="transactionManager" ref="transactionManager"></property>
</bean>

CRUD 구현

DeptDao interface

package com.bit.sts04.model;

import java.util.List;

public interface DeptDao {
	List<DeptVo> findAll();
	DeptVo findOne(int key);
	void insertOne(DeptVo bean);
	int updateOne(DeptVo bean);
	int deleteOne(int key);
}

DeptDaoImpl

public class DeptDaoImpl extends JdbcDaoSupport implements DeptDao{
	PlatformTransactionManager transactionManager;
	public void setTransactionManager(PlatformTransactionManager transactionManager) {
		this.transactionManager = transactionManager;
	}
	
	@Override
	public List<DeptVo> findAll() {
		String sql = "select * from dept";
		return getJdbcTemplate().query(sql, new RowMapper<DeptVo>() {
			@Override
			public DeptVo mapRow(ResultSet rs, int rowNum) throws SQLException {
				return new DeptVo(rs.getInt("deptno"), rs.getString("dname"), rs.getString("loc"));
			}
		});
	}
	@Override
	public DeptVo findOne(int key) {
		String sql = "select * from dept where deptno=?";
		return getJdbcTemplate().queryForObject(sql, new RowMapper<DeptVo>() {
			@Override
			public DeptVo mapRow(ResultSet rs, int rowNum) throws SQLException {
				return new DeptVo(rs.getInt("deptno"), rs.getString("dname"), rs.getString("loc"));
			}
		}, key);
	}
	@Override
	public void insertOne(final DeptVo bean) {
		final String sql = "UPDATE dept_seq SET num=num+1;";
		final String sql2 = "INSERT INTO dept VALUE((SELECT * from dept_seq), CONCAT(?,(SELECT * from dept_seq)), ?);";
		TransactionDefinition definition = new DefaultTransactionAttribute();
		TransactionStatus status = transactionManager.getTransaction(definition);
		try {
		PreparedStatementCreator psc = new PreparedStatementCreator() {
			@Override
			public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
				System.out.println(con.hashCode()); // 같은 객체인지 확인
				return con.prepareStatement(sql);
			}
		};
		getJdbcTemplate().update(psc);
		psc = new PreparedStatementCreator() {
			@Override
			public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
				System.out.println(con.hashCode()); // 같은 객체인지 확인
				PreparedStatement pstmt = con.prepareCall(sql2);
				pstmt.setString(1, bean.getDname());
				pstmt.setString(2, bean.getLoc());
				return pstmt;
			}
		};
		getJdbcTemplate().update(psc);
		
		transactionManager.commit(status);
		} catch (Exception e) {
			transactionManager.rollback(status);
		}
	}
	@Override
	public int updateOne(DeptVo bean) {
		String sql = "update dept set dname=?, loc=? where deptno=?";
		return getJdbcTemplate().update(sql, bean.getDname(), bean.getLoc(), bean.getDeptno());
	}
	@Override
	public int deleteOne(int key) {
		String sql = "delete from dept where deptno=?";
		return getJdbcTemplate().update(sql, key);
	}
}

Controller

@Controller
public class RootController {
  @Autowired
  DeptDao deptDao;

  @RequestMapping("/")
  public String index() {
    return "index";
  }
  @RequestMapping("/dept/list")
  public String list(Model model) {
    model.addAttribute("list", deptDao.findAll());
    return "dept/list";
  }
  @RequestMapping(value="/dept/add", method = RequestMethod.GET)
  public String add() {
    return "dept/add";
  }
  @RequestMapping(value="/dept/add", method = RequestMethod.POST)
  public String add(String dname, String loc) {
    deptDao.insertOne(new DeptVo(0, dname, loc));
    return "redirect:list";
  }
  @RequestMapping("/dept/detail")
  public String detail(int deptno, Model model) {
    model.addAttribute("bean",deptDao.findOne(deptno));
    return "dept/detail";
  }
  @RequestMapping(value = "/dept/update", method = RequestMethod.POST)
  public String edit(@ModelAttribute DeptVo bean) {
    deptDao.updateOne(bean);
    return "redirect:list";
  }
  @RequestMapping(value = "/dept/delete", method = RequestMethod.POST)
  public String delete(int deptno) {
    deptDao.deleteOne(deptno);
    return "redirect:list";
  };
}

spring ibatis

의존성 추가

<!-- https://mvnrepository.com/artifact/org.apache.ibatis/ibatis-sqlmap -->
<dependency>
    <groupId>org.apache.ibatis</groupId>
    <artifactId>ibatis-sqlmap</artifactId>
    <version>2.3.4.726</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-ibatis -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-ibatis</artifactId>
    <version>2.0.8</version>
</dependency>

SqlMapConfig.xml, Query.xml

------------------ SqlMapConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMapConfig PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-config-2.dtd">

<sqlMapConfig>
	<sqlMap resource="Query.xml"/>
</sqlMapConfig>

------------------- Query.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap
			PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
			"http://ibatis.apache.org/dtd/sql-map-2.dtd">

<sqlMap>
	<select id="selectAll" resultClass="com.bit.sts04.model.DeptVo">
	select * from dept order by deptno desc
	</select>
	<insert id="insertOne" parameterClass="com.bit.sts04.model.DeptVo">
	insert into dept values (#deptno#,#dname#,#loc#)
	</insert>
	<select id="selectOne" parameterClass="int" resultClass="com.bit.sts04.model.DeptVo">
	select * from dept where deptno=#val#
	</select>
	
	<update id="updateOne" parameterClass="com.bit.sts04.model.DeptVo">
	update dept set dname=#dname#, loc=#loc# where deptno=#deptno#
	</update>
	
	<delete id="deleteOne" parameterClass="int">
	delete from dept where deptno=#val#
	</delete>
</sqlMap>

DeptDaoIbatisImpl

package com.bit.sts04.model;
import ...
public class DeptDaoIbatisImpl extends SqlMapClientDaoSupport implements DeptDao {
	@Override
	public List<DeptVo> findAll() {
		return getSqlMapClientTemplate().queryForList("selectAll");
	}
	@Override
	public DeptVo findOne(int key) {
		return (DeptVo) getSqlMapClientTemplate().queryForObject("selectOne",key);
	}
	@Override
	public void insertOne(DeptVo bean) {
	}
	@Override
	public int updateOne(DeptVo bean) {
		return getSqlMapClientTemplate().update("updateOne",bean);
	}
	@Override
	public int deleteOne(int key) {
		return getSqlMapClientTemplate().delete("deleteOne",key);
	}
}

root-context.xml

<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
  <property name="dataSource" ref="dataSource"/>
  <property name="configLocation" value="classpath:/SqlMapConfig.xml"/>
</bean>
<bean id="sqlMapClientTemplate" class="org.springframework.orm.ibatis.SqlMapClientTemplate">
  <property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
<bean id="deptDao" class="com.bit.sts04.model.DeptDaoIbatisImpl">
  <property name="sqlMapClientTemplate" ref="sqlMapClientTemplate"/>
</bean>