@Query(nativeQuery = true, value =
"SELECT LAST_NAME AS lastName ,FIRST_NAME AS firstName, COUNT(PROJECT_ID) AS count "
+ "FROM EMPLOYEE e LEFT JOIN PROJECT_EMPLOYEE pe ON e.EMPLOYEE_ID = pe.EMPLOYEE_ID "
+ "GROUP BY e.EMPLOYEE_ID "
+ "ORDER BY count desc ")
public List<EmployeeProject> employeeProjects();
Client에서 바로 Controller에 접근 하는 것보다 데이터만 전달하는 DTO 클래스를 이용하는 것이 보안과 안정성에서 좋음
DTO클래스는 계층간 데이터 교환이 이루어 질 수 있도록 해주는 객체로 특별한 로직을 가지지 않는 순수한 데이터 객체여야만 하며 데이터를 입력하는 기능을 필요치 않음
public interface EmployeeProject {
// Spring이 자동으로 생성하게 Interface 사용
// set은 필요없이 DB에서 쿼리 결과를 가져오기만 하면 됨
public String getFirstName();
public String getLastName();
public String getCount();
}
home.html, HomeController에도 수정
<tbody>
<tr th:each="employee : ${empProList}">
<td th:text="${employee.lastName}"></td>
<td th:text="${employee.firstName}"></td>
<td th:text="${employee.count}"></td>
</tr>
</tbody>
@GetMapping("/")
public String displayHome1(Model model) {
List<Project> projectList = projectService.findAll();
List<EmployeeProject> empProList = employeeService.employeeProjects();
model.addAttribute("projectList", projectList);
model.addAttribute("empProList", empProList);
return "main/home";
}
POM.XML에서 오른쪽 클릭 -> Spring -> Add Starters -> MySQL Driver 선택
application.properties에 아래의 내용을 입력
# MySQL DB 설정
spring.datasource.url=jdbc:mysql://localhost:3306/pma?useSSL=false&serverTimezone=Asia/Seoul
spring.datasource.username=MySQL 아이디
spring.datasource.password=MySQL 비밀번호
# 초기 테스트용(자동으로 테이블 생성 및 수정, 사제) 1회만 사용
spring.jpa.hibernate.ddl-auto=update
서버에서 처리과정을 크게 3가지로 분리 ( Controller, Service, Repository)
Client의 요청을 받음
요청에 대한 처리는 Service에게 전담
Client에게 응답
사용자의 요구사항 처리
DB정보가 필요할 때는 Repository에게 전달
DB관리 (연결, 해제, 자원관리)
DB CRUD 작업처리
구분되지 않고 하나의 클래스에서 사용하면 Controller마다 같은 메소드를 구현해야 하므로 중복코드가 발생하고 재사용성이 떨어진다
확장성과 재사용성이 좋아지고 중복코드를 제거 할 수 있다는 점에서 분리하여 사용해야함
package com.myapp.pma.services;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.myapp.pma.dao.EmployeeRepository;
import com.myapp.pma.dto.EmployeeProject;
import com.myapp.pma.entities.Employee;
@Service
public class EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
public List<Employee> findAll() {
return employeeRepository.findAll();
}
public void save(Employee employee) {
employeeRepository.save(employee);
}
public List<EmployeeProject> employeeProjects() {
return employeeRepository.employeeProjects();
}
}
기존의 EmployeeRepository에 바로 접근하여 사용하던 곳에 EmployeeService 클래스로 대체
package com.myapp.pma.controllers;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
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 com.myapp.pma.entities.Employee;
import com.myapp.pma.services.EmployeeService;
@Controller
@RequestMapping("/employees")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@GetMapping("/new")
public String displayPrjectForm(Model model) {
Employee e = new Employee();
model.addAttribute("employee", e);
return "employees/new-employee";
}
@PostMapping("/save")
public String createProject(Employee employee) {
employeeService.save(employee); // DB에 employee객체를 테이블에 저장
return "redirect:/employees/new"; //post-redirect-get 패턴
}
@GetMapping("/")
public String displayEmployees(Model model) {
List<Employee> employeeList = employeeService.findAll();
model.addAttribute("employeeList", employeeList);
return"employees/list-employees";
}
}
package com.myapp.pma.services;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.myapp.pma.dao.ProjectRepository;
import com.myapp.pma.entities.Project;
@Service
public class ProjectService {
@Autowired
private ProjectRepository projectRepository;
public List<Project> findAll() {
return projectRepository.findAll();
}
public void save(Project project) {
projectRepository.save(project);
}
}
기존의 ProjectRepository에 바로 접근하여 사용하던 곳에 ProjectService 클래스로 대체
package com.myapp.pma.controllers;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
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.bind.annotation.RequestParam;
import com.myapp.pma.entities.Employee;
import com.myapp.pma.entities.Project;
import com.myapp.pma.services.EmployeeService;
import com.myapp.pma.services.ProjectService;
@Controller
@RequestMapping("/projects")
public class ProjectController {
// Spring에서 repository객체를 처음에 자동생성해서 가지고 있다가
// Autowired는 관련 객체를 필요할 때 자동으로 연결해준다.
@Autowired
private ProjectService projectService;
@Autowired
private EmployeeService employeeService;
@GetMapping("/new")
public String displayPrjectForm(Model model) {
Project p = new Project();
model.addAttribute("project", p);
List<Employee> empList = employeeService.findAll();
model.addAttribute("empList", empList);
return "projects/new-project";
}
// 프로젝트 생성할 때 직원 선택을하면 "employees"라는 이름의 List<Employee> 타입의 데이터를 변수로 받음
@PostMapping("/save")
public String createProject(Project project, @RequestParam("employees") List<Long> ids) {
projectService.save(project); // DB에 project객체를 테이블에 저장
//
// // Project 생성 html에서 올라온 직원 id 값들을 입력하여 직원리스트를 가져와 각각의 직원에ㅔ게 프로젝트를 입력
// Iterable<Employee> selectEmployees = employeeRepository.findAllById(ids);
// for(Employee emp : selectEmployees) {
// emp.setProject(project); // 각각의 직원 객체에 프로젝트 입력하고
// employeeRepository.save(emp); // DB에 다시 저장
// }
return "redirect:/projects/new"; //post-redirect-get 패턴
}
@GetMapping("/")
public String displayProjects(Model model) {
List<Project> projectList = projectService.findAll();
model.addAttribute(projectList);
return"projects/list-projects";
}
}
POM.XML에서 오른쪽 클릭 -> Spring -> Add Starters -> Spring Security 선택
설정 후 페이지 접근 시 로그인 페이지로 이동함
기본 아이디 : user
비밀번호 : Spring 실행 시 Console 창에 출력됨
사용자의 의지와 무관하게 행동하는 것을 의미하며 이를 방지하기 위해서 get방식 보다는 Post방식을 이용하고 csrf방지 기능이 있는 라이브러리 등을 사용한다.
(thymeleaf도 csrf방지 기능이 있음)
package com.myapp.pma.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@EnableWebSecurity
@Configuration
// Security 설정을 위해서 WebSecurityConfigurerAdapter 상속 받음
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override // 인증
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 메모리에 아이디, 비밀번호, 역할(권한)설정
auth.inMemoryAuthentication()
.withUser("admin").password(getPasswordEncoder().encode("1234")).roles("ADMIN")
.and()
.withUser("asd").password(getPasswordEncoder().encode("asd")).roles("USER");
}
@Bean // 패스워드 암호화 객체
public PasswordEncoder getPasswordEncoder() {
return NoOpPasswordEncoder.getInstance(); // 테스트용 비밀번호 암호화(실제론 안됨)
}
@Override // 허가
protected void configure(HttpSecurity http) throws Exception {
http.authorizeHttpRequests()
.antMatchers("/projects/new").hasRole("ADMIN") // 새 프로젝트는 관리자만
.antMatchers("/projects/save").hasRole("ADMIN")
.antMatchers("/employees/new").hasRole("ADMIN") // 새 직원은 관리자만
.antMatchers("/employees/save").hasRole("ADMIN")
.antMatchers("/employees/").authenticated() // 인증된 유저
.antMatchers("/projects/").authenticated()
.antMatchers("/").permitAll() // 누구든 접근 가능
.and()
.formLogin(); // 로그인창 사용
// Security에서는 기본적으로 csrf 방지가 적용중
// http.csrf().disable(); // 사용자 의도치 않게 행동하는 것을 방지, save 후 redirect 하는 과정에서 csrf 룰에 위배되어 에러 출력
}
}