티스토리 뷰
리액트와 스프링프레임워크 연동 / 게시판 만들기
1. 리액트와 스프링프레임워크 연동
(1) 프로젝트 생성
-이름 : reactServer
-gradle project / Jar / 11 / wsy
-dependecies
Spring Boot DevTools | Spring Configuration Processor | Spring Web | Lombok |
thymeleaf | MyBatis Framework | MySQL Driver |
(2) 디비 연동 끄기
-디비 연동 안 되어 있기 때문에.. 꺼줘야 지금 에러 안 뜸.
(2-1) Gadle에서 mabatis랑 mysql 주석 처리
(2-2) 프로젝트 우클릭 -> 그래들 -> refresh Gradle
>> 포트 번호 변경_application.properties
server.port=9090
>> 프로젝트의 디폴트 패키지에서 새로운 패키지 생성 : controller
package com.wsy.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class TestController {
@RequestMapping("/")
public String index() throws Exception {
return "index";
}
}
>> template 우클릭 -> New -> Other -> web : HTML 파일 : index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>React와 연동할 서버 테스트</h1>
</body>
</html>
>> 프로젝트 실행했을 때, 제대로 뜨는지 확인 : 호출 ↓
http://localhost:9090/ |
(3) ajax 통신을 위한 처리
(3-1) index.html
-jQuery 코드 가져오기 및 ajax 통신을 위한 코드 추가 : js 코드, 버튼 추가
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<!-- jqeury google cdn -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
$("#btn-click").on("click", function(){
alert("클릭");
});
//ajax 통신
$("#btn-ajax").on("click", function(){
var datas = {data :$("#input-text").val()};
$.ajax({
url: "/ajax/test",
type: "post",
data: datas,
success: function(res){
alert("통신 성공 \n"+res);
},
error: function(err){
alert("에러가 발생했습니다 \n"+err);
}
});
});
});
</script>
</head>
<body>
<h1>React와 연동할 서버 테스트</h1>
<label for="input-text">전송할 데이터 : </label>
<input type="text" id="input-text"><br><br>
<button type="button" id="btn-click">클릭</button>
<!-- ajax 통신 버튼 -->
<button type="button" id="btn-ajax">서버와 ajax 통신</button>
</body>
</html>
(3-2) TestController
package com.wsy.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class TestController {
@RequestMapping("/")
public String index() throws Exception {
return "index";
}
//ajax 통신을 위한 컨트롤러
@ResponseBody
@RequestMapping(value="/ajax/test", method=RequestMethod.POST)
public Object ajaxTest(@RequestParam ("data") String data) {
System.out.println("서버로 전송된 데이터 : "+data);
return data;
}
}
→ 버튼 클릭했을 때, 버튼 정상적 작동, 데이터값 제대로 가져오고 있음
(4) 리액트 프로젝트로 axios 통신
(4-1) 리액트 프로젝트 생성
-VSCODE 터미널에서
>>yarn create react-app react-bbs |
>>cd react-bbs |
>>yarn start |
>> 웹 페이지 제대로 뜨는지 확인
(4-2) axios 설치
-해당 프로젝트에서
>>yarn add axios |
>>0.27.2 버전 설치
>>yarn start |
>> 제대로 동작하는지 확인만
(4-3) 비주얼스튜디오
-STS에서 해도 되는데 비주얼스튜디오가 훨씬 편할 것임.
-폴더 열기 : react-bbs
(+) 깃허브 연동
깃허브 사이트 -> 레파지토리 생성 : react-bbs -> 주소 복사
▶ 소스트리 오픈 -> [ + ] -> Add : react-bbs 추가 -> 올리고 설명 "axios 모듈 추가" 후 커밋 ->
-> 원격 추가 : 이름 깃허브 토큰 /
-> 푸시(push) : 마스터 체크
(+) prettierrc 파일 추가
react-bbs 폴더에 prettierrc 파일 추가
(4-4) src / axiosTest.js 생성
import React from 'react';
class AxiosTest extends React.Component {
render(){
return(
<div>
React 화면 테스트
</div>
);
}
}
export default AxiosTest;
(4-5) App.js
-안 쓰는 거 삭제 / AxiosTest 출력 및 import
// import logo from './logo.svg';
import './App.css';
import AxiosTest from './axiosTest';
function App() {
return (
<div className="App">
<AxiosTest/>
</div>
);
}
export default App;
>>axiosTest.js 추가 및 수정
import React from 'react';
import axios from 'axios';
class AxiosTest extends React.Component {
getData = () => {
axios
.get('http://localhost:9090/ajax/test')//STS에서 만들어둔 주소.
.then((res) =>{
console.log('통신성공');
console.log(res);
})
.catch((err) => {
console.log('통신실패');
console.log(err);
});
};
render(){
return(
<div>
<h1>React 화면 테스트</h1>
<button type='button' onClick={this.getData}>
클릭 시 서버와 통신
</button>
</div>
);
}
}
export default AxiosTest;
└서버 돌고 있음. 리액트랑, STS.
근데 웹브라우저에서 [f12]눌러서 확인해보면. 통신 실패로 뜨는데 지금 상태에서는 에러 뜨는 게 맞음.
※ CORS(Cross-Origin Resource Sharing)
http 헤더를 사용하여 한 출처에서 실행중인 웹 애플리케이션이 다른 출처의 자원에 접근할 경우, 오류 발생
→ 리액트 프로젝트의 package.json 파일에 "proxy" : "서버 주소" 추가
→ 스프링서버에서 처리
└> @CorsOrigin 어노테이션 사용 : Controller 파일에 설정
└> WebMvcConfigurer에서 설정 : 이 파일을 새로 생성해서 설정해야 함.
(4-6) CORS 오류 처리 방법 - proxy
-STS에서 통신 방식 변경 - get
>>TestController
//ajax 통신을 위한 컨트롤러
@ResponseBody
@RequestMapping(value="/ajax/test", method=RequestMethod.GET) //get으로 수정
public Object ajaxTest(@RequestParam ("data") String data) {
System.out.println("서버로 전송된 데이터 : "+data);
return data;
}
>>index.js
//ajax 통신
$("#btn-ajax").on("click", function(){
var datas = {data :$("#input-text").val()};
$.ajax({
url: "/ajax/test",
type: "get", //get으로 변경
data: datas,
success: function(res){
alert("통신 성공 \n"+res);
},
error: function(err){
alert("에러가 발생했습니다 \n"+err);
}
});
});
-VSCODE에서 App.js
데이터 실어 보내려고 임시로 쿼리스트링 적음.
import React from 'react';
import axios from 'axios';
class AxiosTest extends React.Component {
getData = () => {
axios
.get('http://localhost:9090/ajax/test?data=test')//더미 데이터 보내려고 쿼리스트링추가
.then((res) =>{
console.log('통신성공');
console.log(res);
})
.catch((err) => {
console.log('통신실패');
console.log(err);
});
};
render(){
return(
<div>
<h1>React 화면 테스트</h1>
<button type='button' onClick={this.getData}>
클릭 시 서버와 통신
</button>
</div>
);
}
}
export default AxiosTest;
-프로젝트 실행 후 웹브라우저의 버튼을 클릭했을 때, 에러 발생 : http의 보안때문에 발생한 에러
리소스 요청 시 , 서버의 주소와 요청하는 곳의 주소가 같아야 하는데 현 상태가 다르기 때문에 에러.
Access to XMLHttpRequest at 'http://localhost:9090/ajax/test?getDate=data' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. |
※ CORS(Cross-Origin Resource Sharing)
http 헤더를 사용하여 한 출처에서 실행중인 웹 애플리케이션이 다른 출처의 자원에 접근할 경우, 오류 발생
(현재) 스프링부트 로컬호스트 9090 / 리액트 로컬호스트 3000라서 에러
>>일단 영화 앱에서 데이터 가져오는 거 되는 지 확인. 웹사이트 주소라서 에러 안 뜰 것임.
-axiosTest.js → 코드 수정 후 웹브라우저 실행했을 때, 버튼 클릭 시 영화 데이터 가져옴.
import React from 'react';
import axios from 'axios';
class AxiosTest extends React.Component {
getData = () => {
axios
// .get('http://localhost:9090/ajax/test?data=test')//STS에서 만들어둔 주소.
//영화사이트에서 데이터 가져오기
.get('http://yts-proxy.now.sh/list_movies.json') //통신성공
.then((res) =>{
console.log('통신성공');
console.log(res);
})
.catch((err) => {
console.log('통신실패');
console.log(err);
});
};
render(){
return(
<div>
<h1>React 화면 테스트</h1>
<button type='button' onClick={this.getData}>
클릭 시 서버와 통신
</button>
</div>
);
}
}
export default AxiosTest;
-package.json : proxy 추가
먼저, 리액트 실행 중인 거 종료. [ ctrl ] + [ v ] 여러 번 눌러서. → proxy 코드 추가
{
"name": "react-bbs",
"version": "0.1.0",
"private": true,
"proxy":"http://localhost:9090", //★ 이거 추가
"dependencies": {
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^13.0.0",
"@testing-library/user-event": "^13.2.1",
"axios": "^0.27.2",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.0"
},
...
-axiosTest.js
import React from 'react';
import axios from 'axios';
class AxiosTest extends React.Component {
getData = () => {
axios
// .get('http://localhost:9090/ajax/test?data=test')//STS에서 만들어둔 주소.
// .get('http://yts-proxy.now.sh/list_movies.json') //통신성공
//package.json에서 proxy 추가 후
.get('/ajax/test?data=test') //데이터 보내기
.then((res) =>{
console.log('통신성공');
console.log(res);
})
.catch((err) => {
console.log('통신실패');
console.log(err);
});
};
render(){
return(
<div>
<h1>React 화면 테스트</h1>
<button type='button' onClick={this.getData}>
클릭 시 서버와 통신
</button>
</div>
);
}
}
export default AxiosTest;
>> 웹브라우저에서 데이터 제대로 가져오는지 확인
(4-6) CORS 오류 처리 방법 - Srping
-기존에 설정해두었던 VSCODE의 package.json에서 프록시 주소 변경 또는 삭제
//"proxy":"http://localhost:9090",
"proxy":"http://localhost:8085",
-AxiosTest.js
import React from 'react';
import axios from 'axios';
class AxiosTest extends React.Component {
getData = () => {
axios
.get('http://localhost:9090/ajax/test?data=test')//STS에서 만들어둔 주소.
.then((res) =>{
console.log('통신성공');
console.log(res);
})
.catch((err) => {
console.log('통신실패');
console.log(err);
});
};
render(){
return(
<div>
<h1>React 화면 테스트</h1>
<button type='button' onClick={this.getData}>
클릭 시 서버와 통신
</button>
</div>
);
}
}
export default AxiosTest;
>> 실행해서 버튼 클릭했을 때, 에러 뜨는 상황인 것 확인
-방법1) STS의 TestController : 어노테이션 추가
package com.wsy.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
//@CrossOrigin(origins = "http://localhost:3000/")
@Controller
public class TestController {
@RequestMapping("/")
public String index() throws Exception {
return "index";
}
//ajax 통신을 위한 컨트롤러
@CrossOrigin(origins = "http://localhost:3000/")
@ResponseBody
@RequestMapping(value="/ajax/test", method=RequestMethod.GET)
public Object ajaxTest(@RequestParam ("data") String data) {
System.out.println("서버로 전송된 데이터 : "+data);
return data;
}
}
-방법 2) WebMvc _ Error 상태
spring Framework의 webMvcConfigurer 인터페이스 상속받아 오버라이딩 하는 방식
WebMvcConfiguere를 상속받는 클래스에 @Configuration 어노테이션 사용
addMapping("적용할 주소") : 매개변수로 스프링 서버에서 적용될 주소 입력, 컨트롤러에서 사용자 입력을 받기 위한 주소
allowedOrigins("접속하는 외부서버주소") : 현재 스프링 서버로 접속할 외부 주소 입력
>>패키지 생성 : configuration / 클래스 생성 WebMvcConfigurer
package com.wsy.demo.configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer{
@Override
public void addCorsMapping(CorsRegistry registry) { //Error : @Override 지우라고 함...
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000/");
}
}
└오버라이드 어노테이션 지우라는 에러떠서 지우고 하면 안 됨... 통신 에러..
>> VSCODE에서 Proxy 설정하는 걸로 돌림.
(5) 디비연결
(5-1) 디비 연결 : sql
-mysql에서 스키마 생성 : react_board_db
-테이블 생성 : board
(5-2) 디비 연결 : STS
-application.properties에서 디비 연결
server.port=9090
#DB connect setting : oracle or mysql
spring.datasource.hikari.driver-class-name=com.mysql.cj.jdbc.Driver
#DB 접속 주소, 사용옵션 / 3306은 mysql 주소
spring.datasource.hikari.jdbc-url=jdbc:mysql://localhost:3306/react_board_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
#
spring.datasource.hikari.username=wsy
spring.datasource.hikari.password=1234
spring.datasource.hikari.connection-test-query=SELECT 1
#mybatis 사용 시 카멜케이스 적용
mybatis.configuration.map-underscore-to-camel-case=true
#rest api 사용 시, GET / POST / PUT / DELETE 옵션 사용하도록 설정
spring.mvc.hiddenmethod.filter.enabled=true
(+) 디비 설정할 거기 때문에 그래들에서 주석 처리 해둔 것 주석 해제 → 프로젝트 우클릭 : Gradle Refresh
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.2'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'mysql:mysql-connector-java'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
>>configuration 패키지에 클래스 생성 : DatabaseConfiguration
package com.wsy.demo.configuration;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
@Configuration
@PropertySource("classpath:/application.properties")
public class DatabaseConfiguration {
@Autowired
private ApplicationContext appContext;
//database setting
@Bean
@ConfigurationProperties(prefix="spring.datasource.hikari")
public HikariConfig hikariConfig() {
return new HikariConfig();
}
@Bean
public DataSource dataSource() throws Exception{
DataSource dataSource = new HikariDataSource(hikariConfig());
System.out.println(dataSource.toString());
return dataSource;
}
//sql session factory
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource ds) throws Exception{
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
ssfb.setDataSource(ds);
ssfb.setMapperLocations(appContext.getResources("classpath:/sql/**/sql-*.xml"));
ssfb.setConfiguration(mybatisConfig());
return ssfb.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory ssf) {
return new SqlSessionTemplate(ssf);
}
//mybatis
@Bean
@ConfigurationProperties(prefix="mybatis.configuration")
public org.apache.ibatis.session.Configuration mybatisConfig(){
return new org.apache.ibatis.session.Configuration();
}
}
>>src/main/resource -> sql 폴더 생성
xml 파일 생성 : sql-board.xml > 열 때, 제네릭으로 열기
(파일이름이 sql의 이름과 같아야 함)
<?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="com.wsy.demo.BoardMapper"></mapper>
(*폴더 위치 중요)
>> 로컬호스트 호출했을 때, 웹브라우저 정상적으로 연결되는지 확인
http://localhost:9090/ |
>> 기본 디비 설정 완료 <<
컨트롤러에서 요청을 하면 -> Service에서 받아서 mapper랑 연결해야 함. ->
DTO 파일도 만들어야 함.
(5-3) DTO 생성
-패키지 dto 생성 - 클랫 생성 : BoardDto
package com.wsy.demo.dto;
import lombok.Data;
@Data
public class BoardDto {
private int boardNo;
private String title;
private String content;
private String createId;
private String createDate;
private String updateId;
private String updateDate;
private int hitCnt;
}
(+) 실제로 데이터 가져오는지 아닌지 체크하는 부분
-TestController.java
package com.wsy.demo.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.wsy.demo.dto.BoardDto;
//@CrossOrigin(origins = "http://localhost:3000/")
@Controller
public class TestController {
//★
@Autowired
private BoardService boardService;
@RequestMapping("/")
public String index() throws Exception {
return "index";
}
//ajax 통신을 위한 컨트롤러
// @CrossOrigin(origins = "http://localhost:3000/")
@ResponseBody
@RequestMapping(value="/ajax/test", method=RequestMethod.GET)
public Object ajaxTest(@RequestParam ("data") String data) {
System.out.println("서버로 전송된 데이터 : "+data);
return data;
}
//★
@ResponseBody
@RequestMapping(value="/ajax/boardList", method=RequestMethod.GET)
public Object ajaxBoardList() throws Exception{
List<BoardDto> dataList = boardService.selectBoardList();
return dataList;
}
}
└@Autowired 마우스 커서 갖다대면 create 뜨는데 인터페이스 선택. 이후 자동 완성으로 Service, mapper 생성
-sql-board.xml
<?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="com.wsy.demo.mapper.BoardMapper">
<select id="selectBoardList" resultType="com.wsy.demo.dto.BoardDto">
<![CDATA[
SELECT
board_no,
title,
create_id,
DATE_FORMAT(create_date, '%Y.%m.%d %H:%i:%s') AS create_date,
hit_cnt
FROM
board
WHERE
delete_yn = 'N'
ORDER BY board_no DESC
]]>
</select>
</mapper>
(5-4) Service 생성
package com.wsy.demo.service;
import java.util.List;
import com.wsy.demo.dto.BoardDto;
public interface BoardService {
List<BoardDto> selectBoardList() throws Exception;
}
package com.wsy.demo.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.wsy.demo.dto.BoardDto;
import com.wsy.demo.mapper.BoardMapper;
@Service
public class BoardServiceImpl implements BoardService {
@Autowired
private BoardMapper boardMapper;
@Override
public List<BoardDto> selectBoardList() throws Exception {
return boardMapper.selectBoardList();
}
}
(5-6) mapper 생성
package com.wsy.demo.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.wsy.demo.dto.BoardDto;
@Mapper
public interface BoardMapper {
List<BoardDto> selectBoardList() throws Exception;
}
>> index.html tnwjd
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<!-- jqeury google cdn -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
...
$("#btn-board-list").on("click", function(){
$.ajax({
url: "/ajax/boardList",
type: "get",
success: function(res){
alert("통신 성공 \n"+res);
console.log(res);
},
error: function(err){
alert("에러가 발생했습니다 \n"+err);
}
});
});
});
</script>
</head>
<body>
<h1>React와 연동할 서버 테스트</h1>
...
<button type="button" id="btn-board-list">board BTN</button>
</body>
</html>
>> 실행 : 웹브라우저
(5-7) VSCODE와 연동되는지 확인
import React from 'react';
import axios from 'axios';
class AxiosTest extends React.Component {
getData = () => {
axios
.get('/ajax/boardList') //spring 연동
.then((res) =>{
console.log('통신성공');
console.log(res);
})
.catch((err) => {
console.log('통신실패');
console.log(err);
});
};
render(){
return(
<div>
<h1>React 화면 테스트</h1>
<button type='button' onClick={this.getData}>
클릭 시 서버와 통신
</button>
</div>
);
}
}
export default AxiosTest;
>> 버튼 눌렀을 때, 데이터 가져오는지 확인(스프링부트 서버 시작, 리액트 서버 돌아가는 상태여야 함)
3. 실습 :
문제 1) BoardService, BoardServiceImpl, BoardMapper, sql-board.xml에
글 읽기, 글 쓰기, 글 수정, 글 삭제 기능 구현하기
(1) mysql 쿼리문 작성
SELECT * FROM react_board_db.board;
-- 전체 리스트
SELECT
board_no,
title,
create_id,
DATE_FORMAT(create_date, '%Y.%m.%d %H:%i:%s') AS create_date,
hit_cnt
FROM
board
WHERE
delete_yn = 'N'
ORDER BY board_no DESC;
-- 수정한 내용 조회
SELECT
board_no,
title,
create_id,
create_date,
update_id,
update_date,
hit_cnt,
delete_yn
FROM
board
WHERE
delete_yn = 'N'
ORDER BY board_no DESC;
-- 상세글 확인
SELECT board_no, title, content, create_id, create_date, hit_cnt
FROM board
WHERE delete_yn = 'N' AND board_no = 1;
-- 상세글 확인 시 조회수(hit_cnt) 변경
UPDATE board
SET hit_cnt = hit_cnt + 1
WHERE board_no = 1;
-- 글 쓰기
INSERT INTO board(title, content, create_id, create_date)
VALUES ('test3', 'content_test3', 'wsy', NOW());
-- 글 수정 : delete_yn 필요없음
UPDATE board
SET title = 'Edit_test', content = 'Edit_content', update_id = 'yun', update_date = now()
WHERE board_no = 1;
-- 글 삭제 : Update로 삭제 처리
-- SET 부분에 update_id = 'admin' 추가해도 되고 아니고
UPDATE board
SET delete_yn = 'Y', update_date = NOW()
WHERE board_no = 1;
(1-1) spring : xml 코드
(2) 인터페이스 BoardService
package com.wsy.demo.service;
import java.util.List;
import com.wsy.demo.dto.BoardDto;
public interface BoardService {
// 전체글
List<BoardDto> selectBoardList() throws Exception;
// 상세글 확인
public BoardDto selectBoardDetail(int boardNo) throws Exception;
// 글 쓰기
public void insertBoard(BoardDto board) throws Exception;
// 글 수정
public void updateBoard(BoardDto board) throws Exception;
public void deleteBoard(int boardNo) throws Exception;
// public int hitCnt(int boardNo);
}
(2-1) boardServiceImple
package com.wsy.demo.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.wsy.demo.dto.BoardDto;
import com.wsy.demo.mapper.BoardMapper;
@Service
public class BoardServiceImpl implements BoardService {
@Autowired
private BoardMapper boardMapper;
@Override
public List<BoardDto> selectBoardList() throws Exception {
return boardMapper.selectBoardList();
}
@Override
public BoardDto selectBoardDetail(int boardNo) throws Exception {
boardMapper.updateHitCount(boardNo);
return boardMapper.selectBoardDetail(boardNo);
}
@Override
public void insertBoard(BoardDto board) throws Exception {
boardMapper.insertBoard(board);
}
@Override
public void updateBoard(BoardDto board) throws Exception {
boardMapper.updateBoard(board);
}
@Override
public void deleteBoard(int boardNo) throws Exception {
boardMapper.deleteBoard(boardNo);
}
}
(2-3) boardMapper
package com.wsy.demo.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import com.wsy.demo.dto.BoardDto;
@Mapper
public interface BoardMapper {
List<BoardDto> selectBoardList() throws Exception;
// public void insert(BoardDto dto);
// public BoardDto read(int boardNo);
// public boolean update(BoardDto dto);
// public boolean delete(int boardNo);
// public int hitCnt(int boardNo);
void updateHitCount(@Param("boardNo")int boardNo) throws Exception;
BoardDto selectBoardDetail(@Param("boardNo") int boardNo) throws Exception;
void insertBoard(BoardDto board) throws Exception;
void updateBoard(BoardDto board) throws Exception;
void deleteBoard(@Param("boardNo") int boardNo) throws Exception;
}
(2-4) xml 쿼리문
<?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="com.wsy.demo.mapper.BoardMapper">
<!--기본 타입(Int, ..)아닌 것은 mysql이 데이터타입을 모르기 때문에 정확히 명시해줘야 함.-->
<select id="selectBoardList" resultType="com.wsy.demo.dto.BoardDto">
<![CDATA[
SELECT
board_no,
title,
create_id,
DATE_FORMAT(create_date, '%Y.%m.%d %H:%i:%s') AS create_date,
hit_cnt
FROM
board
WHERE
delete_yn = 'N'
ORDER BY board_no DESC
]]>
</select>
<select id="read" resultType="com.wsy.demo.dto.BoardDto">
<![CDATA[
SELECT board_no, title, content, create_id, create_date, hit_cnt
FROM board
WHERE delete_yn = 'N' AND board_no = ${boardNo}
]]>
</select>
<select id="selectBoardDetail" parameterType="int" resultType="com.wsy.demo.dto.BoardDto">
<![CDATA[
SELECT board_no, title, content, create_id, create_date, hit_cnt
FROM board
WHERE delete_yn = 'N' AND board_no = ${boardNo}
]]>
</select>
<!-- 조회수-->
<update id="updateHitCount" parameterType="int">
<![CDATA[
UPDATE board
SET hit_cnt = hit_cnt + 1
WHERE board_no =${boardNo}
]]>
</update>
<insert id="insertBoard" parameterType="com.wsy.demo.dto.BoardDto">
<![CDATA[
INSERT INTO board(title, content, create_id, create_date)
VALUES (#{title}, #{content}, #{createId}, NOW())
]]>
</insert>
<update id="updateBoard" parameterType="com.wsy.demo.dto.BoardDto">
<![CDATA[
UPDATE board
SET title = #{title},
content = #{title},
update_id = #{updateId},
update_date = now()
WHERE board_no = #{boardNo}
]]>
</update>
<update id="deleteBoard" parameterType="int">
<![CDATA[
UPDATE board
SET delete_yn = 'Y', update_date = NOW()
WHERE board_no = #{boardNo}
]]>
</update>
</mapper>
>> 데이터 넘어오는지 확인하기 위해서
(3) Controller
package com.wsy.demo.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.wsy.demo.dto.BoardDto;
import com.wsy.demo.service.BoardService;
//@CrossOrigin(origins = "http://localhost:3000/")
@Controller
public class TestController {
@Autowired
private BoardService boardService;
@RequestMapping("/")
public String index() throws Exception {
return "index";
}
// ajax 통신을 위한 컨트롤러
// @CrossOrigin(origins = "http://localhost:3000/")
@ResponseBody
@RequestMapping(value="/ajax/test", method=RequestMethod.GET)
public Object ajaxTest(@RequestParam ("data") String data) {
System.out.println("서버로 전송된 데이터 : "+data);
return data;
}
@ResponseBody
@RequestMapping(value="/ajax/boardList", method=RequestMethod.GET)
public Object ajaxBoardList() throws Exception{
List<BoardDto> dataList = boardService.selectBoardList();
return dataList;
}
//연결
//상세글 확인
@ResponseBody
@RequestMapping(value="/ajax/boardDetail/{boardNo}", method=RequestMethod.GET)
public Object ajaxBoardDetail(@PathVariable("boardNo") int boardNo) throws Exception {
BoardDto data = boardService.selectBoardDetail(boardNo);
return data;
}
//글 쓰기
@ResponseBody
@RequestMapping(value="/ajax/boardWrite", method=RequestMethod.POST)
public Object ajaxBoardInsert(BoardDto board) throws Exception {
boardService.insertBoard(board);
return "success";
//원래는 if문을 써서 해당값이 들어갔는지 확인하는 작업이 필요한데..간단하게 하는 거라 생략
}
//글 수정
@ResponseBody
@RequestMapping(value="/ajax/boardUpdate/{boardNo}", method = RequestMethod.PUT)
public Object ajaxBoardUpdate(BoardDto board) throws Exception {
boardService.updateBoard(board);
return "success";
}
//글 삭제
@ResponseBody
@RequestMapping(value="/ajax/boardDelete/{boardNo}", method = RequestMethod.DELETE)
public Object ajaxBoardDelete(@PathVariable("boardNo") int boardNo) throws Exception {
boardService.deleteBoard(boardNo);
return "success";
}
}
(4) index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<!-- jqeury google cdn -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
$("#btn-click").on("click", function(){
alert("클릭");
});
//ajax 통신
$("#btn-ajax").on("click", function(){
var datas = {data :$("#input-text").val()};
$.ajax({
url: "/ajax/test",
type: "get",
data: datas,
success: function(res){
alert("통신 성공 \n"+res);
},
error: function(err){
alert("에러가 발생했습니다 \n"+err);
}
});
});
$("#btn-board-list").on("click", function(){
$.ajax({
url: "/ajax/boardList",
type: "get",
success: function(res){
alert("통신 성공 \n"+res);
console.log(res);
},
error: function(err){
alert("에러가 발생했습니다 \n"+err);
}
});
});
//
$("#btn-detail").on("click", function(){
$.ajax({
url: "/ajax/boardDetail/1",
type: "GET",
success: function(res){
alert("통신 성공_detail"+res);
},
error: function(err){
alert("통신 오류_detail"+err);
}
});
});
//
$("#btn-write").on("click", function(){
var datas = {title:$("#input-title").val(), content:$("#input-content").val(), createId:$("#input-user-id").val()};
$.ajax({
url: "/ajax/boardWrite",
type: "POST",
data: datas,
success: function(res){
alert("통신 성공_wri");
},
error: function(err){
alert("통신 오류_wri");
}
});
});//
$("#btn-update").on("click", function(){
var boardNo = $("#input-num").val()
var datas = {title: $("#input-title").val(), content:$("#input-content").val(), update_id:$("#input-user-id").val()};
$.ajax({
url: "/ajax/boardUpdate/"+boardNo,
type: "PUT",
data: datas,
success: function(res){
alert("통신 성공_up");
},
error: function(err){
alert("통신 오류_up");
}
})
})//
$("#btn-delete").on("click", function(){
var boardNo = $("#input-num").val()
$.ajax({
url: "/ajax/boardDelete/"+boardNo,
type: "DELETE",
success: function(res){
alert("통신 성공_del");
},
error: function(err){
alert("통신 오류_del");
}
})
})
});
</script>
</head>
<body>
<h1>React와 연동할 서버 테스트</h1>
<label for="input-text">전송할 데이터 : </label>
<input type="text" id="input-text"><br><br>
<button type="button" id="btn-click">클릭</button>
<!-- ajax 통신 버튼 -->
<button type="button" id="btn-ajax">서버와 ajax 통신</button>
<button type="button" id="btn-board-list">board BTN</button>
<br><hr><br>
<h1>Board</h1>
<br>
<label for="input-num">Board No : </label>
<input type="text" id="input-num"><br>
<label for="input-title">Board title : </label>
<input type="text" id="input-title"><br>
<label for="input-content">Content : </label>
<input type="text" id="input-content"><br>
<label for="input-user-id">User ID : </label>
<input type="text" id="input-user-id"><br>
<button type="button" id="btn-detail">Detail</button>
<button type="button" id="btn-write">Write</button>
<button type="button" id="btn-update">Update</button>
<button type="button" id="btn-delete">Delete</button>
<hr>
</body>
</html>
(5) ReactBoardController.java
기존의 testController의 내용 모두 주석 처리하고 복붙. responsebody만 빼고
package com.wsy.demo.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.wsy.demo.dto.BoardDto;
import com.wsy.demo.service.BoardService;
@RestController
public class ReactBoardController {
@Autowired
private BoardService boardService;
@RequestMapping("/")
public String index() throws Exception {
return "index";
}
// ajax 통신을 위한 컨트롤러
// @CrossOrigin(origins = "http://localhost:3000/")
@RequestMapping(value="/ajax/test", method=RequestMethod.GET)
public Object ajaxTest(@RequestParam ("data") String data) {
System.out.println("서버로 전송된 데이터 : "+data);
return data;
}
@RequestMapping(value="/ajax/boardList", method=RequestMethod.GET)
public Object ajaxBoardList() throws Exception{
List<BoardDto> dataList = boardService.selectBoardList();
return dataList;
}
//연결
//상세글 확인
@RequestMapping(value="/ajax/boardDetail/{boardNo}", method=RequestMethod.GET)
public Object ajaxBoardDetail(@PathVariable("boardNo") int boardNo) throws Exception {
System.out.println(boardNo);
// return "success";
BoardDto data = boardService.selectBoardDetail(boardNo);
return data;
}
//글 쓰기
@RequestMapping(value="/ajax/boardWrite", method=RequestMethod.POST)
public Object ajaxBoardInsert(BoardDto board) throws Exception {
System.out.println(board);
// return "success";
// boardService.insertBoard(board);
return "success";
//원래는 if문을 써서 해당값이 들어갔는지 확인하는 작업이 필요한데..간단하게 하는 거라 생략
}
//글 수정
@RequestMapping(value="/ajax/boardUpdate/{boardNo}", method = RequestMethod.PUT)
public Object ajaxBoardUpdate(BoardDto board) throws Exception {
boardService.updateBoard(board);
System.out.println(board);
return "success";
}
//글 삭제
@RequestMapping(value="/ajax/boardDelete/{boardNo}", method = RequestMethod.DELETE)
public Object ajaxBoardDelete(@PathVariable("boardNo") int boardNo) throws Exception {
boardService.deleteBoard(boardNo);
return "success";
}
}
-CMD : 리액트 라우터 돔 설치
yarn add react-router-dom@5.2.1 |
-App.js
exact={true} 넣어줘야 화면이 중첩되지 않음.
// import logo from './logo.svg';
import './App.css';
// import AxiosTest from './axiosTest';
import React from 'react';
import { HashRouter, Route } from 'react-router-dom';
import Home from './routes/Home';
import Detail from './routes/Detail';
import Write from './routes/Write';
import Navigation from './components/Navigation';
function App() {
return (
<HashRouter>
<Navigation />
<Route path="/" exact={true} component={Home} />
<Route path="/detail" exact={true} component={Detail} />
<Route path="/write" exact={true} component={Write} />
</HashRouter>
// <div className="App">
// <AxiosTest/>
// </div>
);
}
export default App;
-src : 폴더 생성 : components, routers
-Navigation.js
import React from 'react';
import { Link } from 'react-router-dom';
function Navigation() {
return (
<div className="nav">
<Link to="/">Home</Link>
<Link to="/write">Write</Link>
</div>
);
}
export default Navigation;
-routers 폴더 안에 Detail.js, Home.js, Write.js 생성
import React from 'react';
class Home extends React.Component {
render() {
return (
<div>
<h1>Home</h1>
</div>
);
}
}
export default Home;
//
import React from 'react';
class Detail extends React.Component {
render() {
return (
<div>
<h1>Detail</h1>
</div>
);
}
}
export default Detail;
//
import React from 'react';
class Write extends React.Component {
render() {
return (
<div>
<h1>write</h1>
</div>
);
}
}
export default Write;
-index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>,
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
>> 화면 연결까지 완료
(2) 부트 스트랩
(2-1) 설치
>>yarn add react-bootstrap bootstrap |
(2-2) public / index.html - <head> 태그 안에 입력
<!--부트스트랩 사용을 위한 Link -->
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
crossorigin="anonymous"
/>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
crossorigin="anonymous"
></script>
(3) 화면 그리기
(3-1) Home.js
import React from 'react';
class Home extends React.Component {
render() {
return (
//html 부분은 아래에 따로 첨부
);
}
}
export default Home;
<div>
{/* header */}
<header>
<div class="p-5 mb-4 bg-light rounded-3 text-center">
<div class="container-fluid py-5">
<h1 class="display-5 fw-bold">React와 SrpingBoot를 활용한 게시판</h1>
<p>목록 보기</p>
</div>
</div>
</header>
{/* main */}
<main class="container">
<section>
<article class="row">
<div class="col">
<table class="table table-hover table-striped text-center">
<colgroup>
<col width="10%" />
<col width="40%" />
<col width="15%" />
<col width="25%" />
<col width="10%" />
</colgroup>
<thead>
<tr>
<th>글번호</th>
<th>제목</th>
<th>글쓴이</th>
<th>등록시간</th>
<th>조회수</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>테스트 제목</td>
<td>테스트 내용</td>
<td>2022.04.27 12:50:00</td>
<td>0</td>
</tr>
<tr>
<td>2</td>
<td>테스트 제목</td>
<td>테스트 내용</td>
<td>2022.04.27 12:50:00</td>
<td>0</td>
</tr>
</tbody>
<tfoot></tfoot>
</table>
</div>
<div class="d-flex justify-content-end">
<button class="btn btn-primary">글쓰기</button>
</div>
</article>
</section>
</main>
{/* footer */}
<footer class="container-fluid py-5 mt-5 border-top">
<p class="lead text-mute text-center">made by bitc</p>
</footer>
</div>
>>웹 브라우저
(3-2) Detail.js
import React from 'react';
class Detail extends React.Component {
render() {
return (
<div>
{/* header */}
<header>
<div class="p-5 mb-4 bg-light rounded-3 text-center">
<div class="container-fluid py-5">
<h1 class="display-5 fw-bold">React와 SrpingBoot를 활용한 게시판</h1>
<p>목록 보기</p>
</div>
</div>
</header>
{/* main */}
<main class="container">
<section>
<article class="">
<div class="row">
<div class="col-sm-12">
<div class="my-3">
<input type="text" class="form-control" id="title" />
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-sm-4">
<input type="text" class="form-control" id="create-id" />
</div>
<div class="col-sm-4">
<input type="text" class="form-control" id="hit-cnt" />
</div>
<div class="col-sm-4">
<input type="text" class="form-control" id="create-date" />
</div>
</div>
<div class="row mb-3">
<div class="col-sm-12">
<textarea rows="5" class="form-control" id="content"></textarea>
</div>
</div>
<div class="my-3 d-flex justify-content-between">
<div>
<button class="btn btn-secondary">목록</button>
</div>
<div>
<button class="btn btn-primary mx-3">등록</button>
<button class="btn btn-danger">삭제</button>
</div>
</div>
</article>
</section>
</main>
{/* footer */}
<footer class="container-fluid py-5 mt-5 border-top">
<p class="lead text-mute text-center">made by bitc</p>
</footer>
</div>
);
}
}
export default Detail;
(3-3) Write.js
import React from 'react';
class Write extends React.Component {
render() {
return (
<div>
{/* header */}
<header>
<div class="p-5 mb-4 bg-light rounded-3 text-center">
<div class="container-fluid py-5">
<h1 class="display-5 fw-bold">React와 SrpingBoot를 활용한 게시판</h1>
<p>목록 보기</p>
</div>
</div>
</header>
{/* main */}
<main class="container">
<section>
<article>
<div class="row">
<div class="col-sm-6 mx-auto">
<div class="my-3">
<label for="title">Title : </label>
<input
type="text"
class="form-control"
id="title"
name="title"
placeholder="Enter title"
/>
</div>
<div class="my-3">
<label for="user-id">ID : </label>
<input
type="text"
class="form-control"
id="user-id"
name="user-id"
placeholder="Enter user ID"
/>
</div>
<div class="my-3">
<label for="content">Content : </label>
<textarea
rows="10"
class="form-control"
id="content"
name="content"
placeholder="Enter content"
></textarea>
</div>
<div class="my-3 d-flex justify-content-between">
<div>
<button class="btn btn-secondary">목록</button>
</div>
<div>
<button class="btn btn-primary mx-3">등록</button>
<button class="btn btn-warning">취소</button>
</div>
</div>
</div>
</div>
</article>
</section>
</main>
{/* footer */}
<footer class="container-fluid py-5 mt-5 border-top">
<p class="lead text-mute text-center">made by bitc</p>
</footer>
</div>
);
}
}
export default Write;
(+) App.js
// import logo from './logo.svg';
import './App.css';
// import AxiosTest from './axiosTest';
import React from 'react';
import { HashRouter, Route } from 'react-router-dom';
import Home from './routes/Home';
import Detail from './routes/Detail';
import Write from './routes/Write';
import Navigation from './components/Navigation';
function App() {
return (
<HashRouter>
<Navigation />
<Route path="/" exact={true} component={Home} />
<Route path="/detail/:boardno" exact={true} component={Detail} /> //boardno 추가
<Route path="/write" exact={true} component={Write} />
</HashRouter>
// <div className="App">
// <AxiosTest/>
// </div>
);
}
export default App;
>> 화면 구상 완료<<
(4) 데이터 가져오기
-글 번호 가져와서 axios 사용해 전송하는 방식
(4-1) Write.js
import React from 'react';
import axios from 'axios';
class Write extends React.Component {
state = {
title: '',
content: '',
userId: '',
};
//Change - input 태그의 onChange랑 한쌍
titleChange = (e) => {
this.setState({ title: e.target.value });
};
userIdChange = (e) => {
this.setState({ userId: e.target.value });
};
contentChange = (e) => {
this.setState({ content: e.target.value });
};
//데이터 보내기
sendData = () => {
const datas = {
title: this.state.title,
content: this.state.content,
createId: this.state.userId,
};
console.log(datas);
axios
.post('/ajax/boardWrite', null, { params: datas })
.then((res) => {
console.log('통신성공');
console.log(res.data);
})
.catch((err) => {
console.log('통신실패');
console.log(err);
});
};
render() {
return (
<div>
{/* header */}
...
{/* main */}
<main class="container">
...
<div class="my-3 d-flex justify-content-between">
<div>
<button class="btn btn-secondary">목록</button>
</div>
<div>
<button class="btn btn-primary mx-3" onClick={this.sendData}>
등록
</button>
<button class="btn btn-warning">취소</button>
</div>
</div>
</div>
</div>
</article>
</section>
</main>
{/* footer */}
...
);
}
}
export default Write;
>> sts에서 ReactBoardController.java 글쓰기 부분 삭제
//글 쓰기
@RequestMapping(value="/ajax/boardWrite", method=RequestMethod.POST)
public Object ajaxBoardInsert(BoardDto board) throws Exception {
System.out.println(board);
// return "success";
boardService.insertBoard(board);
return "success";
//원래는 if문을 써서 해당값이 들어갔는지 확인하는 작업이 필요한데..간단하게 하는 거라 생략
}
>> 웹브라우저에서 데이터 입력하고 글쓰기버튼 눌렀을 때, 디비에 데이터 들어가는지 확인
(4-2) Detail.js
import React from 'react';
import axios from 'axios';
import { Link } from 'react-router-dom';
class Detail extends React.Component {
state = {
boardNo: 1,
title: '',
content: '',
userId: '',
createDate: '',
hitCnt: 0,
};
componentDidMount() {
const boardNo = this.props.match.params.boardno; //글 번호 가져와서 axios 사용해 전송하는 방식 사용할 예정
const url = `/ajax/boardDetail/${boardNo}`;
axios
.get(url)
.then((res) => {
console.log('통신성공');
console.log(res);
this.setState({
boardNo: res.data.boardNo,
title: res.data.title,
content: res.data.content,
userId: res.data.createId,
createDate: res.data.createDate,
hitCnt: res.data.hitCnt,
});
})
.catch((err) => {
console.log('통신실패');
console.log(err);
});
}
render() {
const data = this.props.match.params.boardno;
console.log(data);
return (
<div>
{/* header */}
<header>
<div class="p-5 mb-4 bg-light rounded-3 text-center">
<div class="container-fluid py-5">
<h1 class="display-5 fw-bold">React와 SrpingBoot를 활용한 게시판</h1>
<p>목록 보기</p>
</div>
</div>
</header>
{/* main */}
<main class="container">
<section>
<article class="">
<div class="row">
<div class="col-sm-12">
<div class="my-3">
<input type="text" class="form-control" id="title" value={this.state.title} />
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-sm-4">
<input
type="text"
class="form-control"
id="create-id"
value={this.state.userId}
/>
</div>
<div class="col-sm-4">
<input type="text" class="form-control" id="hit-cnt" value={this.state.hitCnt} />
</div>
<div class="col-sm-4">
<input
type="text"
class="form-control"
id="create-date"
value={this.state.createDate}
/>
</div>
</div>
<div class="row mb-3">
<div class="col-sm-12">
<textarea
rows="5"
class="form-control"
id="content"
value={this.state.content}
></textarea>
</div>
</div>
<div class="my-3 d-flex justify-content-between">
<div>
<button class="btn btn-secondary">
<Link to="/">목록</Link>
</button>
</div>
<div>
<button class="btn btn-primary mx-3">등록</button>
<button class="btn btn-danger">삭제</button>
</div>
</div>
</article>
</section>
</main>
{/* footer */}
...
</div>
);
}
}
export default Detail;
>>웹 브라우저 : db에서 delete_yn이 N인 글의 번호를 입력해야 함.
http://localhost:3000/#/detail/5 |
(4-3) Home.js
import React from 'react';
class Home extends React.Component {
render() {
return (
<div>
{/* header */}
<header>
<div class="p-5 mb-4 bg-light rounded-3 text-center">
<div class="container-fluid py-5">
<h1 class="display-5 fw-bold">React와 SrpingBoot를 활용한 게시판</h1>
<p>목록 보기</p>
</div>
</div>
</header>
{/* main */}
<main class="container">
<section>
<article class="row">
<div class="col">
<table class="table table-hover table-striped text-center">
<colgroup>
<col width="10%" />
<col width="40%" />
<col width="15%" />
<col width="25%" />
<col width="10%" />
</colgroup>
<thead>
<tr>
<th>글번호</th>
<th>제목</th>
<th>글쓴이</th>
<th>등록시간</th>
<th>조회수</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>테스트 제목</td>
<td>테스트 내용</td>
<td>2022.04.27 12:50:00</td>
<td>0</td>
</tr>
<tr>
<td>2</td>
<td>테스트 제목</td>
<td>테스트 내용</td>
<td>2022.04.27 12:50:00</td>
<td>0</td>
</tr>
</tbody>
<tfoot></tfoot>
</table>
</div>
<div class="d-flex justify-content-end">
<button class="btn btn-primary">글쓰기</button>
</div>
</article>
</section>
</main>
{/* footer */}
<footer class="container-fluid py-5 mt-5 border-top">
<p class="lead text-mute text-center">made by bitc</p>
</footer>
</div>
);
}
}
export default Home;
'수업 > └리액트(React)' 카테고리의 다른 글
[08-1] 실습 : 부트스트랩_state (0) | 2022.04.27 |
---|---|
[08]부트스트랩(Bootstrap) (0) | 2022.04.27 |
[07]라우트 (0) | 2022.04.22 |
[06]비동기 통신 모듈 : Axios & 영화 정보 불러오기 (0) | 2022.04.22 |
[00]컴포넌트 실습 - 시간에 따라 출력문 변경 (0) | 2022.04.21 |
- Total
- Today
- Yesterday
- 입력양식
- typeof
- html atrribute
- 미디어 태그
- input type 종류
- initialized
- 기본선택자
- empty-cell
- A%B
- improt
- Java
- ScriptTag
- text formatting
- html a tag
- html pre
- scanner
- selcetor
- 스크립태그
- JavaScript
- border-spacing
- caption-side
- 외부구성요소
- html
- CascadingStyleSheet
- html input type
- html layout
- BAEKJOON
- html base tag
- 변수
- css
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |