spring initializrでプロジェクトを作成する
Maven
Group: com.dailycodework
Artifact: sbr-demo
Name : sbr-demo
Description: Demo project for Spring Boot
Package name: com.dailycodework.sbr-demo
jar
Java 17
lombok
Spring Data JPA
Spring Web
MySQL Driver
モデル、サービス、リポジトリ、例外、コントローラ、および設定ファイルの作成
①src/main/java/dailycodework/sbrdemoにmodel/Student.javaを作成する
package com.dailycodework.sbrdemo.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
@NuturalId(mutable = true)
private String email;
private String department;
}
②src/main/java/dailycodework/sbrdemoにservice/IStudentService.javaを作成する
package com.dailycodework.sbrdemo.service;
import com.dailycodework.sbrdemo.model.Student;
import java.util.List;
public interface IStudentService {
Student addStudent(Student student);
List<Student> getStudents();
Student updateStudent(Student student, Long id);
Student getStudentById(Long id);
void deleteStudent(Long id);
}
③src/main/java/dailycodework/sbrdemoにservice/StudentService.javaを作成する
package com.dailycodework.sbrdemo.service;
import org.springframework.stereotype.Service;
import com.dailycodework.sbrdemo.exception.StudentNotFoundException;
import com.dailycodework.sbrdemo.model.Student;
import com.dailycodework.sbrdemo.repository.StudentRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.jpa.repository.JpaRepository;
public interface StudentService extends JpaRepository<Student, Long>
@Service
@RequiredArgsConstructor
public class StudentService implements IStudentService {
private final StudentRepository studentRepository;
@Override
public List<Student> getStudents() {
return studentRepository.findAll();
}
@Override
public Student addStudent(Student student) {
if (studentAlreadyExists(student.getEmail())){
throw new StudentAlreadyExistsException(student.getEmail()+"already exsits!")
}
return studentRepository.save(student);
}
@Override
public Student updateStudent(Student student, Long id) {
return studentRepository.findById(id).map(st -> {
st.setFirstName(student.getFirstName());
st.setLastName(student.getLastName());
st.setEmail(student.getEmail());
st.setDepartment(student.getDepartment());
return studentRepository.save(st);
}).orElseThrow(() -> new StudentNotFoundException("Sorry, this student could not be found"));
}
@Override
public Student getStudentById(Long id) {
return studentRepository.findById(id)
.orElseThrow(() -> new StudentNotFoundException("Sorry,no student found with the Id:" + id));
}
@Override
public void deleteStudent(Long id) {
if (!studentRepository.existsById(id)) {
throw new StudentNotFoundException("Sorry, student not found");
}
studentRepository.deleteById(id);
}
private boolean studentAlreadyExists(String email) {
return studentRepository.findByEmail(email).isPresent();
}
}
④src/main/java/dailycodework/sbrdemoにrepository/StudentRrepository.javaを作成する
package com.dailycodework.sbrdemo.repository;
import org.springframework.data.jpa.repository.JpaRepository;
public interface StudentRepository extends JpaRepository<Student, Long> {
Optional<Student> findByEmail(String email);
}
⑤src/main/java/dailycodework/sbrdemoにexception/StudentAlreadyExistsException.javaを作成する
package com.dailycodework.sbrdemo.exception;
public class StudentAlreadyExistsException extends RuntimeException {
public StudentAlreadyExistsException(String message) {
super(message);
}
}
⑥src/main/java/dailycodework/sbrdemoにexception/StudentNotFoundException.javaを作成する
package com.dailycodework.sbrdemo.exception;
public class StudentNotFoundException extends RuntimeException {
public StudentNotFoundException(String message) {
super(message);
}
}
⑦src/main/java/dailycodework/sbrdemoにcontroller/StudentController.javaを作成する
package com.dailycodework.sbrdemo.controller;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/students")
@RequiredArgsConstructor
public class StudentController {
private final IStudentService studentService;
@GetMapping
public ResponseEntity<List<Student>> getStudents() {
return new ResponseEntity<>(studentService.getStudents(), HttpStatus.FOUND);
}
@PostMapping
public Student addStudent(@RequestBody Student student) {
return studentService.addStudent(student);
}
@PutMapping("/update/{id}")
public Student updateStudent(@RequestBody Student student, @PathVariable Long id) {
return studentService.updateStudent(student, id);
}
@DeleteMapping("/delete/{id}")
public void deleteStudent(@PathVariable Long id) {
studentService.deleteStudent(id);
}
@GetMapping("/student/{id}")
public void getStudentById(@PathVariable Long id) {
return studentService.getStudentById(id);
}
}
⑧resources/application.ymlを編集する
server:
port: 9192
spring:
datasource:
username: root
password: admin
url: jdbc:mysql://localhost:3306/sbr_db
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
show-sql: true
hibernate:
ddl-auto: create
format_sql :true
⑨src/main/java/dailycodework/sbrdemoにexception/ CustomExceptionHandler.javaを作成する
package com.dailycodework.sbrdemo.exception;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.*;
@RestControllerAdvice
public class CustomExceptionHandler {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, String> handleException(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
BindingResult bindingResult = ex.getBindingResult();
bindingResult.getFieldErrors()
.forEach(fieldError -> errors.put(fieldError.getField(), fieldError.getDefaultMessage()));
return errors;
}
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(StudentNotFoundException.class)
public Map<String, String> userNotFound(StudentNotFoundException ex) {
Map<String, String> errors = new HashMap<>();
errors.put("error", ex.getMessage());
return errors;
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(StudentAlreadyExistsException.class)
public Map<String, String> userAlreadyExists(StudentAlreadyExistsException ex) {
Map<String, String> errors = new HashMap<>();
errors.put("error", ex.getMessage());
return errors;
}
}
Postman
①New Collectionにて名前を入力 ⇨ 『...』をクリック ⇨ 『Add Folder』⇨ studentフォルダを作成
② 『+』タブをクリック ⇨ GETに http://localhost:9192/studentsを入力 ⇨ 『Send』
③ 『Save』の右『Save As』⇨ 『名前を入力したのを選択』⇨ 『studentフォルダ』⇨
『Request name』を入力 ⇨『Save』
④ 『Post』⇨ 『Body』 ⇨ 『raw』 ⇨ textをJsonに選択 ⇨
{
"firstName":"Simpson",
"lastName":"Alfred",
"email":"simpson@email.com",
"deparment":"Software Engineering"
}
⇨ 『Send』
⑤ 『Save』の右『Save As』⇨ 『名前を入力したのを選択』⇨ 『studentフォルダ』⇨
『Request name』を入力 ⇨『Save』
⑥ 『Get』⇨ 『Body』 ⇨ 『raw』 ⇨ textをJsonに選択 ⇨
{
"firstName":"Juliet",
"lastName":"Markson",
"email":"juliet@email.com",
"deparment":"Accounting"
}
⇨ 『Send』
Frontendを作成する
①プロジェクトを作成する
npx i create-react-app <プロジェクト名>
npx i axios
npx i boostap --sava
②src/index.jsを編集する
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
③src/App.jsを編集する
import AddStudent from './components/student/AddStudent.js';
import EditStudent from './components/student/EditStudent.js';
import StudentProfile from './components/student/StudentProfile.js';
import StudentsView from './components/student/StudentsView.js';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
function App() {
return (
<main className='container mt-5'>
<Router>
<NavBar />
<Routes>
<Route exact path='/' element={<Home />}></Route>
<Route exact path='/view-students' element={<StudentsView />}></Route>
<Route exact path='/add-students' element={<AddStudent />}></Route>
<Route
exact
path='/edit-students/:id'
element={<EditStudent />}
></Route>
<Route
exact
path='/student-prfile/:id'
element={<StudentProfile />}
></Route>
</Routes>
</Router>
</main>
);
}
export default App;
④src/Home.jsを作成する
import React from 'react';
const Home = () => {
return <div>Home</div>;
};
export default Home;
⑤src/componets/student/StudentsView.jsを作成する
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { FaTrashAlt, FaEdit, FaEye } from 'react-icons/fa';
import { Link } from 'react-router-dom';
import Search from '../common/Search';
const StudentsView = () => {
const [students, setStudents] = useState([]);
const [search, setSearch] = useState('');
useEffect(() => {
loadStudents();
}, []);
const loadStudents = async () => {
const result = await axios.get('http://localhost:9192/students', {
validateStatus: () => {
return true;
},
});
if (result.status === 302) {
setStudents(result.data);
}
};
const handleDelete = async (id) => {
await axios.delete(`http://localhost:9192/students/delete/${id}`);
loadStudents();
};
return (
<section>
<Search search={search} setSearch={setSearch} />
<table className='table table-bordered table-hover shadow'>
<thead>
<tr className='text-center'>
<th>ID</th>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Depatment</th>
<th colSpan='3'>Actions</th>
</tr>
</thead>
<tbody className='text-center'>
{students
.fill((st) => st.firstName.toLowerCase().includes(search))
.map((student, index) => (
<tr key={student.id}>
<th scope='row' key={index}>
{index + 1}
</th>
<td>{student.firstName}</td>
<td>{student.lastName}</td>
<td>{student.email}</td>
<td>{student.deparment}</td>
<td className='mx-2'>
<Link
to={`/student-profile/${student.id}`}
className='btn btn-info'
>
<FaEye />
</Link>
</td>
<td className='mx-2'>
<Link
to={`/edit-student/${student.id}`}
className='btn btn-warning'
>
<FaEdit />
</Link>
</td>
<td className='mx-2'>
<button
className='btn btn-danger'
onChange={() => handleDelete(student.id)}
>
<FaTrashAlt />
</button>
</td>
</tr>
))}
</tbody>
</table>
</section>
);
};
export default StudentsView;
⑦src/componets/common/Navbar.jsを作成する
import React from 'react';
import { Link } from 'react-router-dom';
const NavBar = () => {
return (
<nav className='navbar navbar-expand-lg navbar-dark bg-dark mb-5'>
<div className='container-fluid'>
<Link className='navbar-brand' to={'/'}>
SBR demo
</Link>
<button
className='navbar-toggler'
type='button'
data-bs-toggle='collapse'
data-bs-target='#navbarNav'
aria-controls='navbarNav'
aria-expanded='false'
aria-label='Toggle navigation'
>
<span className='navbar-toggler-icon'></span>
</button>
<div className='collapse navbar-collapse' id='navbarNav'>
<ul className='navbar-nav'>
<li className='nav-item'>
<Link
className='nav-link active'
aria-current='page'
to={'view-students'}
>
View All Student
</Link>
</li>
<li className='nav-item'>
<Link className='nav-link' to={'/add-student'}>
Add new Student
</Link>
</li>
</ul>
</div>
</div>
</nav>
);
};
export default NavBar;
⑨src/componets/student/AddStudent.jsを作成する
import { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import axios from 'axios';
const AddStudent = () => {
let navigate = useNavigate();
const [student, setStudent] = useState({
firstName: '',
lastName: '',
email: '',
department: '',
});
const { firstName, lastName, email, department } = student;
const handleInputChange = (e) => {
setStudent({ ...student, [e.target.name]: e.target.value });
};
const savaStudent = async (e) => {
e.preventDefault();
await axios.post('http://localhost:9192/students', student);
navigate('/view-students');
};
return (
<div className='col-sm-8 py-2 px-5'>
<from onChange={(e) => savaStudent(e)}>
<div className='input-group mb-5'>
<label className='input-group-text' htmlFor='FirstName'>
First Name
</label>
<input
className='form-controll col-sm-6'
type='text'
name='firstName'
id='firstName'
required
value={firstName}
onChange={(e) => handleInputChange(e)}
/>
</div>
<div className='input-group mb-5'>
<label className='input-group-text' htmlFor='lastName'>
Last Name
</label>
<input
className='form-controll col-sm-6'
type='text'
name='lastName'
id='lastName'
required
value={lastName}
onChange={(e) => handleInputChange(e)}
/>
</div>
<div className='input-group mb-5'>
<label className='input-group-text' htmlFor='email'>
Email
</label>
<input
className='form-controll col-sm-6'
type='email'
name='email'
id='email'
required
value={email}
onChange={(e) => handleInputChange(e)}
/>
</div>
<div className='input-group mb-5'>
<label className='input-group-text' htmlFor='department'>
Department
</label>
<input
className='form-controll col-sm-6'
type='text'
name='department'
id='department'
required
value={department}
onChange={(e) => handleInputChange(e)}
/>
</div>
<div className='col-sm-2'>
<button type='submit' className='btn btn-outline-warning btn-lg'>
Save
</button>
</div>
<div className='col-sm-2'>
<Link
to={'/view-students'}
type='submit'
className='btn btn-outline-warning btn-lg'
>
Cancel
</Link>
</div>
</from>
</div>
);
};
export default AddStudent;
⑩src/componets/student/EditStudent.jsを作成する
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { Link, useNavigate, useParams } from 'react-router-dom';
const EditStudent = () => {
let navigate = useNavigate();
const { id } = useParams();
const [student, setStudent] = useState({
firstName: '',
lastName: '',
email: '',
department: '',
});
const { firstName, lastName, email, department } = student;
useEffect(() => {
loadStudent();
}, []);
const loadStudent = async () => {
const result = await axios.get(
`http://localhost:9192/students/student/${id}`
);
setStudent(result.data);
};
const handleInputChange = (e) => {
setStudent({ ...student, [e.target.name]: e.target.value });
};
const updateStudent = async (e) => {
e.preventDefault();
await axios.put(`http://localhost:9192/students/update/${id}`, student);
navigate('/view-students');
};
return (
<div className='col-sm-8 py-2 px-5'>
<h2 className='mt-5'>Edit Student</h2>
<from onChange={(e) => updateStudent(e)}>
<div className='input-group mb-5'>
<label className='input-group-text' htmlFor='FirstName'>
First Name
</label>
<input
className='form-controll col-sm-6'
type='text'
name='firstName'
id='firstName'
required
value={firstName}
onChange={(e) => handleInputChange(e)}
/>
</div>
<div className='input-group mb-5'>
<label className='input-group-text' htmlFor='lastName'>
Last Name
</label>
<input
className='form-controll col-sm-6'
type='text'
name='lastName'
id='lastName'
required
value={lastName}
onChange={(e) => handleInputChange(e)}
/>
</div>
<div className='input-group mb-5'>
<label className='input-group-text' htmlFor='email'>
Email
</label>
<input
className='form-controll col-sm-6'
type='email'
name='email'
id='email'
required
value={email}
onChange={(e) => handleInputChange(e)}
/>
</div>
<div className='input-group mb-5'>
<label className='input-group-text' htmlFor='department'>
Department
</label>
<input
className='form-controll col-sm-6'
type='text'
name='department'
id='department'
required
value={department}
onChange={(e) => handleInputChange(e)}
/>
</div>
<div className='col-sm-2'>
<button type='submit' className='btn btn-outline-warning btn-lg'>
Save
</button>
</div>
<div className='col-sm-2'>
<Link
to={'/view-students'}
type='submit'
className='btn btn-outline-warning btn-lg'
>
Cancel
</Link>
</div>
</from>
</div>
);
};
export default EditStudent;
⑪src/componets/student/StudentProfile.jsを作成する
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import axios from 'axios';
const StudentProfile = () => {
const { id } = useParams();
const [student, setStudent] = useState({
firstName: '',
lastName: '',
email: '',
department: '',
});
useEffect(() => {
loadStudent();
}, []);
const loadStudent = async () => {
const result = await axios.get(
`http://localhost:9192/students/student/${id}`
);
setStudent(result.data);
};
return (
<section className='shadow' style={{ backgroudColor: 'whitesmoke' }}>
<div className='container py-5'>
<div className='row'>
<div className='col-lg-3'>
<div className='card mb-4'>
<div className='card-body text-center'>
<img
src='https://mdbcdn.b-cdn.net/img/Photos/new-template/boostrap-chat/avatar'
alt='avatar'
className='rounded-circle img-fluid'
style={{ width: 150 }}
/>
<h5 className='my-3'>
{`${student.firstName} ${student.lastName}`}
</h5>
<div className='d-flex justify-content-center mb-2'>
<button type='button' className='btn btn-outline-primary'>
Call
</button>
<button
type='button'
className='btn btn-outline-warning ms-1'
>
Message
</button>
</div>
</div>
</div>
</div>
<div className='col-lg-9'>
<div className='card mb-4'>
<div className='card-body'>
<hr />
<div className='row'>
<div className='col-sm-3'>
<h5 className='mb-0'>First Name</h5>
</div>
<div className='col-sm-9'>
<p className='text-muted mb-0'>{student.firstName}</p>
</div>
</div>
<hr />
<div className='row'>
<div className='col-sm-3'>
<h5 className='mb-0'>Last Name</h5>
</div>
<div className='col-sm-9'>
<p className='text-muted mb-0'>{student.lastName}</p>
</div>
</div>
</div>
<hr />
<div className='row'>
<div className='col-sm-3'>
<h5 className='mb-0'>Email</h5>
</div>
<div className='col-sm-9'>
<p className='text-muted mb-0'>{student.email}</p>
</div>
</div>
<hr />
<div className='row'>
<div className='col-sm-9'>
<h5 className='mb-0'>Department</h5>
</div>
<div className='col-sm-9'>
<p className='text-muted mb-0'>{student.department}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
};
export default StudentProfile;
⑫src/componets/common/Search.jsを作成する
import React from 'react';
const Search = ({ search, setSearch }) => {
return (
<div className='col-sm-6 mb-4'>
<form onSubmit={(e) => e.preventDefault()}>
<input
className='form-controll'
type='search'
role='searchbox'
placeholder='Search students...'
value={search}
onChange={(e) => setSearch(e.target.value)}
></input>
</form>
</div>
);
};
export default Search;
参考サイト
Master Java Full Stack Development with Spring Boot and ReactJS