22.06.08
전에 했던 score와 board를 DB연동해서 적용시키기.
1.Connection 객체 생성
2.PrepareStatment 객체 생성
3.SQL 문 실행
3.ResultSet 객체 생성 결과처리
전통적인 JDBC 프로그래밍은 위에 순서들이 너무 반복되는 작업이 계속되는 단점이있다.
그래서 이제는 Spring-JDBC를 사용할 것이다.
1.pom.xml에 버전 업데이트,
jdbc, hikariCP(커넥션풀), 커넥터 드라이버 설정 추가.
Maven Repository사이트이용.
<!-- spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- HikariCP: 커넥션 풀 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>3.3.1</version>
</dependency>
<!-- ojdbc6 DB 커넥터 드라이버-->
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.4</version>
</dependency>
2.DB 설정 파일(root-context.xml)에 DataSource와 HikariCP 빈으로
등록하기 위한 namespace와 설정 추가.
<!-- 히카리 커넥션 풀 빈 등록 -->
<!-- hikariConfig이름으로 컨테이너에 객체 생성
세터 주입으로 name에 value를 저장-->
<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="username" value="spring" />
<property name="password" value="spring" />
</bean>
<!-- 히카리 데이터소스 빈 등록 -->
<!-- Class속성에 정의된 클래스를 dataSource 이름으로 컨테이너에 객체생성
생성자 주입으로 위에 선언한 hikariConfig를 주입, ref는 참조속성-->
<bean id="ds" class="com.zaxxer.hikari.HikariDataSource">
<constructor-arg ref="hikariConfig" />
</bean>
<!-- Spring JDBC를 사용하기 위한 핵심 객체 JdbcTemplate 클래스 빈 등록 -->
<!-- Class속성에 정의된 클래스를 jdbcTemplate이름으로 컨테이너에 객체생성
dataSource 메서드에 앞서 생성한 dataSource를 주입, ref는 참조속성 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="ds" />
</bean>
CREATE TABLE scores(
stu_id NUMBER PRIMARY KEY,
stu_name VARCHAR2(30) NOT NULL,
kor NUMBER DEFAULT 0,
eng NUMBER DEFAULT 0,
math NUMBER DEFAULT 0,
total NUMBER DEFAULT 0,
average NUMBER(5, 2)
);
CREATE SEQUENCE id_seq
START WITH 1
INCREMENT BY 1
MAXVALUE 1000
NOCYCLE
NOCACHE;
<script>
package com.spring.db.model;
public class ScoreVO {
private int stuId;
private String stuName;
private int kor;
private int eng;
private int math;
private int total;
private double average;
//총점,평균 구하는 메서드
public void calcData() {
this.total = this.kor + this.eng + this.math;
this.average = Math.round((this.total / 3.0) * 100) / 100.0;
}
public void setStuId(int stuId) {
this.stuId = stuId;
}
public int getStuId() {
return stuId;
}
...
@Override
public String toString() {
return "ScoreVO [stuId=" + stuId + ", stuName=" + stuName + ", kor=" + kor + ", eng=" + eng + ", math=" + math
+ ", total=" + total + ", average=" + average + "]";
}
}
</script>
<script>
package com.spring.db.service;
public interface IScoreService {
//점수 등록
void insertScore(ScoreVO score);
//점수 전체 조회
List<ScoreVO> selectAllScores();
//점수 삭제
void deleteScore(int num);
//점수 개별 조회
ScoreVO selectOne(int num);
}
</script>
<script>
package com.spring.db.repository;
public interface IScoreDAO {
//점수 등록
void insertScore(ScoreVO score);
//점수 전체 조회
List<ScoreVO> selectAllScores();
//점수 삭제
void deleteScore(int num);
//점수 개별 조회
ScoreVO selectOne(int num);
}
</script>
<script>
package com.spring.db.service;
//자동 빈 등록
@Service
public class ScoreService implements IScoreService {
//JdbcTemplate을 이용한 SQL 처리
@Autowired
private IScoreDAO dao;
@Override
public void insertScore(ScoreVO score) {
score.calcData();//총점,평균 계산
System.out.println("service: " + score);
dao.insertScore(score);
}
@Override
public List<ScoreVO> selectAllScores() {
return dao.selectAllScores();
}
@Override
public void deleteScore(int num) {
dao.deleteScore(num);
}
@Override
public ScoreVO selectOne(int num) {
return dao.selectOne(num);
}
}
</script>
<script>
package com.spring.db.commons;
public class ScoreMapper implements RowMapper<ScoreVO> {
@Override
public ScoreVO mapRow(ResultSet rs, int rowNum) throws SQLException {
ScoreVO vo = new ScoreVO();
vo.setStuId(rs.getInt("stu_id"));
vo.setStuName(rs.getString("stu_name"));
vo.setKor(rs.getInt("kor"));
vo.setEng(rs.getInt("eng"));
vo.setMath(rs.getInt("math"));
vo.setTotal(rs.getInt("total"));
vo.setAverage(rs.getDouble("average"));
return vo;
}
}
</script>
그런데 한곳에서만 사용할 클래스를 굳이 파일로 만들어야 할까?
이런 상황에서는 내부 클래스를 사용하거나 람다식을 이용할 수 있다.
두 클래스가 굉장히 긴밀한 관계가 있을 때 주로 선언.
내부 클래스는 public 붙일수 없음.
해당 클래스 안에서만 사용할 클래스를 굳이 새 파일로 선언하지 않고도 만들 수 있다.
ScoreMapper클래스 안만들고 익명객체 이용해서 선언하는 방법.
인터페이스에 구현할 메서드가 하나일때 람다식으로 사용가능.
클래스 파일 안만들고 즉석에서 클래스 선언할 수 있음.
다른 메서드에서는 다시 선언해야한다.
<script>
package com.spring.db.repository;
@Repository //자동 빈 등록
public class ScoreDAO implements IScoreDAO {
//내부클래스 선언
class scoreMapper implements RowMapper<ScoreVO> {
@Override
public ScoreVO mapRow(ResultSet rs, int rowNum) throws SQLException {
System.out.println("mapRow 메서드 발동!");
System.out.println("rowNum:" + rowNum);
ScoreVO vo = new ScoreVO();
vo.setStuId(rs.getInt("stu_id"));
vo.setStuName(rs.getString("stu_name"));
vo.setKor(rs.getInt("kor"));
vo.setEng(rs.getInt("eng"));
vo.setMath(rs.getInt("math"));
vo.setTotal(rs.getInt("total"));
vo.setAverage(rs.getDouble("average"));
return vo;
}
}
//# Spring-jdbc 방식의 처리 : JdbcTemplate 활용
@Autowired
private JdbcTemplate template;
@Override
public void insertScore(ScoreVO score) {
String sql = "INSERT INTO scores VALUES(id_seq.NEXTVAL,?,?,?,?,?,?)";
template.update(sql, score.getStuName(), score.getKor(), score.getEng(),
score.getMath(), score.getTotal(), score.getAverage());
//sql전달하고 물음표 채우기
}
@Override
public List<ScoreVO> selectAllScores() {
String sql = "SELECT * FROM scores ORDER BY stu_id ASC";
return template.query(sql, new ScoreMapper());
//람다식 이용(다른 메서드에서 사용 불가)
return template.query(sql, (rs, roNum) -> {
ScoreVO vo = new ScoreVO();
vo.setStuId(rs.getInt("stu_id"));
vo.setStuName(rs.getString("stu_name"));
vo.setKor(rs.getInt("kor"));
vo.setEng(rs.getInt("eng"));
vo.setMath(rs.getInt("math"));
vo.setTotal(rs.getInt("total"));
vo.setAverage(rs.getDouble("average"));
return vo;
});//객체 여러개를 리턴할때 template.query()
}
@Override
public void deleteScore(int num) {
String sql = "DELETE FROM scores WHERE stu_id = ?";
template.update(sql, num);
}
@Override
public ScoreVO selectOne(int num) {
String sql = "SELECT * FROM scores WHERE stu_id = ?";
try {
ScoreVO vo = template.queryForObject(sql, new ScoreMapper(),num);
return vo;
} catch (Exception e) {
return null;
}
}
//객체를 하나만 리턴할때는 template.queryForObject()
// template.queryForObject() 데이터가 없으면 null이아니라 예외를 던지기 때문에 try{} catch 사용
}
</script>
<script>
package com.spring.db.controller;
@Controller
@RequestMapping("/score")
public class ScoreController {
//컨트롤러와 서비스 계층 사이의 의존성 자동 주입을 위해 변수를 선언.
@Autowired
private IScoreService service;
//점수 등록 화면을 열어주는 처리를 하는 메서드
@GetMapping("/register")
public String register() {
System.out.println("/score/register: GET");
return "score/write-form";
}
//점수 등록 요청을 처리할 메서드
@PostMapping("/register")
public String register(ScoreVO vo) {
System.out.println("/score/register: POST");
System.out.println("param: " + vo);
service.insertScore(vo);//service에 받아온 객체 넣기
return "score/write-result";
}
//점수 전체 조회를 처리하는 요청 메서드
@GetMapping("/list")
public void list(Model model) {
System.out.println("/score/list: GET");
model.addAttribute("sList", service.selectAllScores());
}
//점수 삭제 요청 처리 메서드
@GetMapping("/delete")
public String delete(@RequestParam("stuNum") int stuNum, RedirectAttributes ra) {
ra.addFlashAttribute("msg","delSuccess");//경고창
return "redirect:/score/list";
}
//점수 개별 조회 화면 처리 메서드
@GetMapping("/search")
public void search() {
System.out.println("/score/search: GET");
}
//점수 개별 조회 요청 처리 메서드
@GetMapping("/selectOne")
public String selectOne(@RequestParam("stuNum") int stuNum,
Model model, RedirectAttributes ra) {
ScoreVO vo = service.selectOne(stuNum);
System.out.println(vo);
if(vo == null) {
ra.addFlashAttribute("msg","검색 결과가 없습니다.");
return "redirect:/score/search";
}
model.addAttribute("stu", vo);
return "score/search-result";
}
}
</script>
jsp같은 경우는 학생번호를 원래 list의 index를 이용했었는데
이제는 DB연동을 하기때문에 시퀀스가 자동으로 올려주어 학생번호만 수정하였다.
나머지는 전에 예제와 같음.