CRUD — Spring MVC

Naraekang
9 min readSep 13, 2020

--

CRUD is four necessary functions of the storage application

  • create
  • read
  • update
  • delete

Today lets take a look at how to create a simple blog website using spring MVC.

  1. create Legacy Project
  2. Change Spring Version in pom.xml
  3. Add Spring test, Spring JDBC, Spring tx
  4. Change Junit Version
  5. Change Servlet Version
  6. Add HiakriCp, MyBatis, Mybatis-spring, Log4jdbc
  7. Add JDBC Driver into the project
  8. Lombok setting etc

Add Dependency from Maven Repository to pom.xml

Pros and cons of Hikari.

  • Fast with a small number of the user( up to 16000)
  • Can be slow with a big number of user
<!-- https://mvnrepository.com/artifact/com.zaxxer/HikariCP --><dependency><groupId>com.zaxxer</groupId><artifactId>HikariCP</artifactId><version>3.4.5</version></dependency>

Log4jdbc

  • To print log.
  • Useful for developers.

Here is the screen video of

  • create a project
  • add dependencies from maven repository

add dependencies from maven repository

spring tx: spring transaction 5.2.8

DB connect pool

-hikaricp

mybatis 3.5.5

mybatis spring 2.0.5

Lombok 1.18.12

  • Bean
  • Annotation — automatically generate getter and setter.
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.12</version><scope>provided</scope></dependency>

Log4Jdbc Log4j2 JDBC 4 1

<dependency><groupId>org.bgee.log4jdbc-log4j2</groupId><artifactId>log4jdbc-log4j2-jdbc4.1</artifactId><version>1.16</version></dependency>
  • change server path
  • set Language Filter(Korean)

→ Copy and paste the appropriate filter on the XML file

Oracle

1. create an account

2. Create a table and insert a row

create sequence seq_board;

create sequence seq_board;create table board(
bno number primary key,
title varchar2(100) not null,
content varchar2(2000)not null,
writer varchar2(50) not null,
regdate date default sysdate,
updatedate date default sysdate);
insert into board(bno,title, content, writer)values (seq_board.nextval,
'테스트 제목' ,'테스트내용' ,'user01');
select * from baord;

select * from the board; here is a video of the process.

To connect to the database

connect pool

<!-- Root Context: defines shared resources visible to all other web components --><!-- resource적는거임 --><!-- Root Context: defines shared resources visible to all other web components --><bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig"><!-- <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/><property name="jdbcUrl" value="jdbc:oracle:thin:@localhost:1521:XE"/> --><property name="driverClassName"value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"></property><property name="jdbcUrl"value="jdbc:log4jdbc:oracle:thin:@localhost:1521:XE"></property><property name="username" value="spring" /><property name="password" value="1234" /></bean>

SQL session factory

<bean id="sqlSessionFactory"class="org.mybatis.spring.SqlSessionFactoryBean"><!-- datasource로  --><property name="dataSource" ref="dataSource" /></bean>
create the SQL session factory

create a package to create a mapper.

Match the path with BoardMapper.java

match path

→ Service → Controller → Return Result

controller.java

package org.knr.boardex001.controller;import org.knr.boardex001.domain.BoardVO;
import org.knr.boardex001.service.BoardService;
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.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import lombok.AllArgsConstructor;
import lombok.extern.java.Log;
import oracle.net.ano.Service;
@Controller
@Log
@RequestMapping("/board")
@AllArgsConstructor
public class BoardController {

private BoardService service;
@GetMapping("/list")
public void list(Model model) {
log.info("list..........!");
model.addAttribute("list",service.getList());
}

@GetMapping("/register")
public void register() {
}

@PostMapping("/register")
public String registerPro(BoardVO board,RedirectAttributes rttr) {
log.info("registerPro");
service.register(board);
rttr.addFlashAttribute("result", board.getBno());
return "redirect:list";
}

@GetMapping("/get")
public void get(Long bno,Model model) {
log.info("get");
model.addAttribute("board", service.get(bno));
}
@PostMapping("/update")
public String update(BoardVO board) {
log.info("update");
if(service.modify(board)) {
return "redirect:list";
}else {
return "redirect:get";
}

}
@GetMapping("/delete")
public String delete(Long bno) {
log.info("delete");
if(service.remove(bno)) {
return "redirect:list";
}else {
return "redirect:get";
}
}



}

Add objdbc6.jar

  1. project →build path →configure build path →Add external Jar
  2. Build path

project → properties→ deployment assembly→ add

→java build path entries

SET Data Context

  • Write Resource
  • Setting regards database

Domain Package

  • same with oracle database content

“@Data”

package org.knr.boardex001.domain;import java.util.Date;
import lombok.Data;
@Data
public class BoardVO {
private Long bno;
private String title;
private String content;
private Date regdate;
private Date updatedate;
}

Mapper

  • make as an interface
package org.knr.boardex001.mapper;import java.util.List;import org.apache.ibatis.annotations.Select;
import org.knr.boardex001.domain.BoardVO;
//원래 결과arraylist에 넣던거를 이렇게 간단하게 할수있음
public interface BoardMapper {
@Select("select * from board where bno>0")
public List<BoardVO> getList();

let know the Location of Mybatis<the path of BoardMapper in my example>

in root-context.xml

<mybatis-spring:scan base-package="org.knr.boardex001.mapper"/>
root-context.xml

Test database connection with test code

if it doesn’t work, try out the maven update

package org.knr.boardex001;import java.sql.Connection;import javax.sql.DataSource;import org.junit.Test;
import org.junit.runner.RunWith;
import org.knr.boardex001.domain.BoardVO;
import org.knr.boardex001.mapper.BoardMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
//junit가지고 테스트 할게요
@RunWith(SpringJUnit4ClassRunner.class)
//안에 db설정한 파일 설정한거
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
//로그 찍는거
//함수를 쓰려면 객체를 만들어야 되는데
// 객체를 가져와야 하는데 객체를 못 만들기 때문에
@Log4j
public class BoardMapperTest {
@Setter(onMethod_ = @Autowired)
private BoardMapper mapper;
@Setter(onMethod_ = @Autowired)
private DataSource dataSource;
@Test
public void testConnection() {
try (Connection conn = dataSource.getConnection()) {log.info(conn);} catch (Exception e) {
e.printStackTrace();
}
}
// mapper에 getlist의 결과를 얻어와서 각각의 것을 board에 넣어서 log.info를 출력함
@Test
public void testGList() {
mapper.getList().forEach(board -> log.info(board));
}
@Test
public void testInsert() {
BoardVO board = new BoardVO();
board.setTitle("새글 제목");
board.setContent("새글 내용");
board.setWriter("newUser");
mapper.insertSelectKey(board);
log.info(board);
}@Test
public void testRead() {
// L을 붙이는 이유는 Long이라서
BoardVO board = mapper.read(1L);
log.info(board);
}
@Test
public void testDelete() {
int n= mapper.delete(2l);
if(n==1) {
log.info("삭제성공");
}
}

@Test
public void testUpdate() {

BoardVO board = new BoardVO();
board.setBno(1L);
board.setTitle("수정제목");
board.setContent("수정내용");
board.setWriter("user01");
int n =mapper.update(board);
if(n==1) {
log.info("수정성공");
}
}
}
run as JUnit test

Add Mybatis

get data source from the bottom of the root-context file

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
xsi:schemaLocation="http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- Root Context: defines shared resources visible to all other web components -->
<!-- resource적는거임 -->
<bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
<!-- 데이터베이스,url, pw뭔지 -->
<property name="driverClassName"
value="oracle.jdbc.driver.OracleDriver" />
<property name="jdbcUrl"
value="jdbc:oracle:thin:@localhost:1521:XE"></property>
<property name="username" value="spring" />
<property name="password" value="1234" />
</bean>
<!-- 실제로 resource얻을때 datasource로 얻음 이 안에 데이터 설정해 놓은 hikariConfig를 넣는다. -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<constructor-arg ref="hikariConfig" />
</bean>
<!-- 하나 접속이 세션 그걸 생성하는 factorybean -->
<!-- mybatis는 sql로 이루어져 있고 , 접속하는bean을 만드는 factory -->
<!-- 원래는 field로 datasource를 들고 있음 -->
<!-- jsp로 할때는 없어도 되는데 지금은 my batis를 사용하기 때문에 필요함 -->
<bean id="sqlSessionFactory"
class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- datasource로 -->
<property name="dataSource" ref="dataSource" />
</bean>
<!-- mybatis가 어디 있는지 -->
<mybatis-spring:scan base-package="org.knr.boardex001.mapper"/>
<!-- 스캔하는 default package -->
<context:component-scan base-package="org.knr.boardex001"/>

</beans>

Connectpool

  • Save the used one and bring it from the pool.
  • Add JDBC Driver into the project.

Allow Eclipse to use Lombok.

This part is testing directly by Direct Injection.

@Setter(onMethod_=@Autowired)
private DataSource dataSource;

Inject this Bean

<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<constructor-arg ref="hikariConfig" />
</bean>

Servlet Context

  • Include beans managing web setting
  • find controller location
  • Logical view -> physical view

Root context

Settings are other than the web.

Make mapper with MyBatis

<?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">
우리가 만들 mybatis의 namespace위치
<mapper namespace="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</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="org.knr.boardex001.mapper.BoardMapper"><select id="getList"
resultType="org.knr.boardex001.domain.BoardVo">
<!-- where절을 넣지 않으면 index사용 안함 -->
select * from board
</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="org.knr.boardex001.mapper.BoardMapper"><select id="getList"
resultType="org.knr.boardex001.domain.BoardVo">
<!-- where절을 넣지 않으면 index사용 안함 -->
select * from board
</select>
<insert id="insert">
insert into board(bno, title, content, writer)
values
(seq_board.nextval, #{title},#{content},#{writer})
</insert>
<insert id="insertSelectKey">
<selectKey keyProperty="bno" order="BEFORE"
resultType="long">
select seq_board.nextval from dual
</selectKey>
insert into board(bno, title, content, writer)
values (#{bno},#{title},#{content},#{writer})
</insert>
</mapper>

Insert test

  • just have to create function because mapper exists

Business

  • transaction

For example in the bank

Transaction of A and B are grouped as

  • Money going out from A‘s account
  • Money going into B’s account

if one of them fails the transaction itself fails.

We do the same here.

We group the service according to the use of the website.

  1. Create a Service package

2. create a service interface

package org.knr.boardex001.service;import java.util.List;import org.knr.boardex001.domain.BoardVO;
public interface BoardService {
//게시물 등록, 얻기 , 수정 , 삭제, 리스트 얻기
//인터페이스니까 추상 메소드로
public void register(BoardVO board);
public BoardVO get(Long bno);
public boolean modify(BoardVO board);
public boolean remove(Long bno);
public List<BoardVO> getList();}

3. Implement interface and create a class

  • import mapper
  • add log and service
package org.knr.boardex001.service;import java.util.List;import org.knr.boardex001.domain.BoardVO;
import org.knr.boardex001.mapper.BoardMapper;
import org.springframework.stereotype.Service;
import lombok.AllArgsConstructor;
import lombok.extern.java.Log;
//로그 찍기 위해서
@Log
//자동으로dependency injection할거라서 component라는 걸 알려줘야 스캔을 해서 만들 수 있음
@Service
//서비스 객체 생성할때 field자동으로 다 만들어서 주입한다.
@AllArgsConstructor
public class BoardServicempl implements BoardService {
// mapper가 있어야함
private BoardMapper boardMapper;@Override
public void register(BoardVO board) {
// TODO Auto-generated method stub
boardMapper.insert(board);
}
@Override
public BoardVO get(Long bno) {
// TODO Auto-generated method stub
return boardMapper.read(bno);
}
@Override
public boolean modify(BoardVO board) {
// TODO Auto-generated method stub
int n = boardMapper.update(board);
if (n == 1) {
return true;
} else {
return false;
}
}@Override
public boolean remove(Long bno) {
// TODO Auto-generated method stub
int n = boardMapper.delete(bno);
if (n == 1) {
return true;
} else {
return false;
}
}
@Override
public List<BoardVO> getList() {
// TODO Auto-generated method stub
return boardMapper.getList();
}
}

allargsconstructor

create and inject field automatically when creating a service object

  • autowire all mapper inside.

Create a controller

package org.knr.boardex001.controller;import org.knr.boardex001.domain.BoardVO;
import org.knr.boardex001.service.BoardService;
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.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import lombok.AllArgsConstructor;
import lombok.extern.java.Log;
import oracle.net.ano.Service;
@Controller
@Log
@RequestMapping("/board")
@AllArgsConstructor
public class BoardController {

private BoardService service;
@GetMapping("/list")
public void list(Model model) {
log.info("list..........!");
model.addAttribute("list",service.getList());
}

@GetMapping("/register")
public void register() {
}

@PostMapping("/register")
public String registerPro(BoardVO board,RedirectAttributes rttr) {
log.info("registerPro");
service.register(board);
rttr.addFlashAttribute("result", board.getBno());
return "redirect:list";
}

@GetMapping("/get")
public void get(Long bno,Model model) {
log.info("get");
model.addAttribute("board", service.get(bno));
}
@PostMapping("/update")
public String update(BoardVO board) {
log.info("update");
if(service.modify(board)) {
return "redirect:list";
}else {
return "redirect:get";
}

}
@GetMapping("/delete")
public String delete(Long bno) {
log.info("delete");
if(service.remove(bno)) {
return "redirect:list";
}else {
return "redirect:get";
}
}



}

2. list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<table>
<tr>
<th>bno</th>
<th>title</th>
<th>writer</th>
<th>regdate</th>
<th>updatedate</th>
</tr>
<c:forEach items="${list }" var="board">
<tr>
<td>${board.bno }</td>
<td>${board.title }</td>
<td>${board.writer }</td>
<td><fmt:formatDate value="${board.regdate }" /> </td>
<td><fmt:formatDate value="${board.updatedate }" /></td>
</tr>
</c:forEach>
</table>
</body>
</html>

3.get.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>
</head>
<body>
<form action="update" method="post">
<table>
<tr>
<th>번호</th>
<td><input type="text" name="bno" value="${board.bno}" readonly></td>
</tr>
<tr>
<th>제목</th>
<td><input type="text" name="title" value="${board.title}"></td>
</tr>
<tr>
<th>내용</th>
<td><textarea rows="5" cols="50" name="content">${board.content}</textarea></td>
</tr>
<tr>
<th>작성자</th>
<td><input type="text" name="writer" value="${board.writer}"></td>
</tr>
<tr>
<th>작성일</th>
<td>${board.regdate}"</td>
</tr>
<tr>
<th>수정일</th>
<td>${board.updatedate}</td>
</tr>
<tr>
<td colspan="2" align="center"><input type="submit" value="수정">
<input type="button" value="삭제"
onclick="location.href='delete?bno=${board.bno}'"> <input
type="reset" value="취소"></td>
</tr>
</table>
</form>
</body>
</html>

register.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>
</head>
<body>
<form action="register" method="post">
<table>
<tr>
<th>제목</th>
<td><input type="text" name="title"></td>
</tr>
<tr>
<th>내용</th>
<td><textarea rows="5" cols="50" name="content"></textarea></td>
</tr>
<tr>
<th>작성자</th>
<td><input type="text" name="writer"></td>
</tr>
<tr>
<td colspan="2" align="center"><input type="submit" value="등록">
<input type="reset" value="취소"></td>
</tr>
</table>
</form>
</body>
</html>

--

--

No responses yet