Spring(2)

9mond·2023년 9월 21일
0
post-thumbnail

  • 컨트롤러 : 서비스를 의존
    MemberService 객체가 생성돼야 작업을 할 수 있으므로 하나의 컨트롤러가 여러 서비스를 의존할 수 있음. 이경우 각각의 객체를 생성해줘야 한다.
  • 서비스 : CRUD 작업 -> 레퍼지토리를 의존한다.
    MemberRepository 객체가 생성돼야 작업을 할 수 있으므로
  • 레퍼지토리
    Oracle을 쓰다가 고객사에서 MySQL의 요구가 들어오면 바꿔줘야함.
    레퍼지토리를 수정할 경우 서비스 영역에서 레포지토리 영역을 의존하기 때문에 서비스의 코드 수정도 일부 이루어져야함.

1. 스프링 컨테이너(Spring Container)란?

  • 스프링 컨테이너는 자바 객체의 생명 주기를 관리하며, 생성된 자바 객체들에게 추가적인 기능을 제공하는 역할을 한다.
  • 여기서 말하는 자바 객체를 스프링에서 빈(Bean)이라고 부른다.
  • 개발자는 객체를 생성하고 소멸할 수 있는데, 스프링 컨테이너가 이 역할을 대신해준다.
  • 즉, 이러한 제어의 흐름을 외부에서 관리하는 것이다.
  • 또한 객체들 간의 의존 관계를 스프링 컨테이너가 런타임 과정에서 알아서 만들어준다.
  • 스프링은 실행 시 객체들을 담고 있는 Container가 있다.

2. MemberController

  • 스프링스럽게 작업하기
  • 생성자에 @Autowired가 있으면 스프링이 연관된 객체를 스프링 컨테이너에서 찾아서 넣어준다.
  • 이렇게 객체 의존관계를 외부에서 넣어주는 것을 DI(Dependency Injection, 의존성 주입)이라고 한다.
  • 개발자가 직접 주입하는 형식이라면, @Autowired에 의해서 스프링이 주입해준다.
  • 단일 생성자에 한해서 @Autowired를 붙이지 않아도 된다.

3. IoC

  • 제어역전(IoC, Inversion of Control) -> 스프링 프레임워크에 제어를 맡긴다.
  • 개발자가 프레임워크의 기능을 호출하는 형태가 아니라,
    프레임워크가 개발자의 코드를 호출하기 때문에 개발자는 전체를 직접 구현하지 않고
    자신의 코드를 부분적으로 "끼워넣기"하는 형태로 구현할 수 있다.
  • 이는 개발자로 하여금 구현하고자 하는 특정 분야의 기능에 집중할 수 있도록 한다.
  • 프레임워크가 객체의 생성, 소멸과 같은 라이프 사이클을 관리하며 스프링으로부터 필요한 객체를 얻어올 수도 있다.
  • 객체의 의존성을 역전시켜서 객체 간에 결합도를 줄이고 유연한 코드를 작성할 수 있게 하여 가독성 및 코드 중복, 유지보수를 편하게 할 수 있게 한다.

4. POJO

  • Plain Old Java Object(단순한 자바 오브젝트)
  • POJO란 객체 지향적인 원리에 충실하면서 환경과 기술에 종속되지 않고 필요에 따라 재활용될 수 있는 방식으로 설계된 오브젝트를 말한다.
  • 이렇게 POJO에 어플리케이션의 핵심 로직과 기능을 담아 설계하고 개발하는 방법을 POJO 프로그래밍이라고 할 수 있다.

5. Component scan

  • Component scan의 대상은 Spring이 scan을 하는 대상이 된다.

6. DI의 3가지 방법

  • Field Injection(필드 주입 방식)
  • Setter Injection(수정자 주입 방식)
  • Constructor Injection(생성자 주입) -> 이 방법 사용

7. JPA

  • JPA는 기존의 반복 코드는 물론이고 기본적인 SQL도 JPA가 직접 만들어서 실행해준다.
  • JPA를 사용하면 SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환할 수 있다.
  • JPA를 사용하면 개발 생산성을 크게 높일 수 있다.

8. SOLID

  • 클린코드로 유명한 로버트 마틴이 좋은 객체지향 설계의 5가지 원칙을 정리

1. SRP : 단일 책임 원칙(Single Responsibility Principle)

  • 한 클래스는 하나의 책임만 가져야 한다.
  • 하나의 책임이라는 것은 모호하다.
  • 클수도 작을수도 있고, 문맥과 상황에 따라 다르다.
  • 중요한 기준은 변경이다.
  • 변경이 있을 때 파급효과가 적으면 단일 책임 원칙을 잘 따르는 것.

2. OCP : 개방-폐쇄 원칙(Open-Closed Principle)

  • 확장에는 열려있고 수정, 변경에는 닫혀있다.

3. LSP : 리스코프 치환 원칙(Liskov Substitution Principle)

  • 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
  • 다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다는 것이다.
  • 다형성을 지원하기 위한 원칙, 인터페이스를 구현한 구현체를 믿고 사용하려면 이 원칙이 필요하다.
  • 단순히 컴파일에 성공하는것을 넘어선다는 것.

4. ISP : 인터페이스 분리 원칙(Interface Segregation Principle)

  • 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
  • 만약 자동차 인터페이스와 사용자 클라이언트 인터페이스를 분리하면, 서로 수정이 되더라도 영향을 주지 않는다.
  • 인터페이스가 명확해지고 대체 가능성이 높아진다.

5. DIP : 의존관계 역전 원칙(Dependency Inversion Principle)

  • 프로그래머는 "추상화에 의존해야지, 구체화에 의존하면 안된다."
  • 쉽게 말해서 구현 클래스에 의존하지 말고 인터페이스에 의존하라는 뜻.
  • 구현체에 의존하면 변경이 아주 어려워진다.

9. DB연결



💻코드

Controller

MemberController.java

package com.codingbox.core2.controller;

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 com.codingbox.core2.dto.Member;
import com.codingbox.core2.dto.MemberForm;
import com.codingbox.core2.service.MemberService;

@Controller
public class MemberController {

	// 'Controller가 Service에 의존한다.'라고 표현
	// MemberService mService = new MemberService();
	
	// Sercive는 여러 Controller에서 가져다 쓸 수 있기 때문에
	// Spring Container에 등록을 해야한다.
	// 스프링스럽게 작업하기 - 의존성 주입(DI), 무조건 이 방법으로 사용해야 한다.
	// 다른 Bean이 할당되지 못하도록 private final로 잠그는 것
	private final MemberService memberService;
	
	// service에 오류가 생기면 서버 기동조차 안되게 파라미터로 넣어줌
	// @Autowired를 통해 스프링이 알아서 제어하도록 함
	@Autowired
	public MemberController(MemberService memberService) {
		this.memberService = memberService;
	}
	
	@GetMapping(value = "/members/new")	// value 는 생략 가능
	public String createForm() {
		return "members/createMemberForm";
	}
	
	@PostMapping("/members/new")
	public String create(MemberForm form) {
		Member member = new Member();
		member.setName(form.getName());
		
		memberService.join(member);
		
		// 회원가입 후 홈 화면으로 돌린다.
		return "redirect:/";	// ("/")이거로 인해 HomeController로 갔다가 home.html로 간다.home.html로 간다.
	}
	
	
	// /members 방식으로 getMapping이 될 것
	// list(Model model), return "members/memberList"
	@GetMapping("/members")	// value 는 생략 가능
	public String list(Model model) {
		List<Member> members = memberService.findMembers();	// members를 키 값으로 담아서 memberList로 보냄
		model.addAttribute("members", members);
		return "members/memberList";
	}
	
}

HomeController.java

package com.codingbox.core2.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {
	
	// 웰컴파일 -> static/index.html
	// localhost:9090
	@GetMapping("/")	// -> 여기가 index.html보다 우선순위가 됨.
	public String home() {
		return "home";
	}
}

dto

Member.java

package com.codingbox.core2.dto;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.SequenceGenerator;

@Entity		// @Entity : '이거는 JPA에서 관리하는 class야' 라고 알려줌
public class Member {
	
	@Id
	@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "mySeq")
	@SequenceGenerator(name = "mySeq", sequenceName = "member_seq", allocationSize = 1)
	private int id;
	private String name;
	
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}

MemberForm.java

package com.codingbox.core2.dto;

public class MemberForm {
	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

Repository

JdbcMemberRepository.java

package com.codingbox.core2.repository;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

import javax.sql.DataSource;

import org.springframework.stereotype.Repository;

import com.codingbox.core2.dto.Member;

//@Repository
public class JdbcMemberRepository implements MemberRepository{

	private final DataSource dataSource;
	public JdbcMemberRepository(DataSource dataSource) {
		this.dataSource = dataSource;
	}
	
	@Override
	public Member save(Member member) {
		String sql = "INSERT INTO MEMBER VALUES(member_seq.nextval, ?)"; // 이런 방식은 이제 잘 안쓴다.
		
		Connection conn = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			conn = dataSource.getConnection();
			String generatedColumns[] = {"ID"};
			pstmt = conn.prepareStatement(sql, generatedColumns);
			pstmt.setString(1, member.getName());
			pstmt.executeUpdate();
			rs = pstmt.getGeneratedKeys();
			
			if( rs.next() ) {
				member.setId(rs.getInt(1));
			}
			
		}catch(Exception e) {
			e.printStackTrace();
		}
		
		
		return member;
	}

	@Override
	public List<Member> findAll() {
		String sql = "SELECT * FROM MEMBER";
		
		Connection conn = null;
		PreparedStatement pstm = null;
		ResultSet rs = null;
		
		List<Member> members = new ArrayList<>();
		try {
			conn = dataSource.getConnection();
			pstm = conn.prepareStatement(sql);
			rs = pstm.executeQuery();
			
			while(rs.next()) {
				Member member = new Member();
				member.setId(rs.getInt("id"));
				member.setName(rs.getString("name"));
				members.add(member);
			}
			
		}catch( Exception e ) {
			e.printStackTrace();
		}
		
		return members;
	}
}

JpaMemberRepository.java

package com.codingbox.core2.repository;

import java.util.List;

import org.springframework.stereotype.Repository;

import com.codingbox.core2.dto.Member;

import jakarta.persistence.EntityManager;

@Repository
public class JpaMemberRepository implements MemberRepository{

	private final EntityManager em;
	public JpaMemberRepository(EntityManager em) {
		this.em = em;
	}
	@Override
	public Member save(Member member) {
		em.persist(member);
		return member;
	}
	@Override
	public List<Member> findAll() {
		return em.createQuery("select m from Member m", Member.class).getResultList();
	}
}

MemberRepository.java

package com.codingbox.core2.repository;

import java.util.List;

import com.codingbox.core2.dto.Member;

public interface MemberRepository {

	// 회원 저장
	Member save(Member member);
	
	// 전체 조회
	List<Member> findAll();
}

MemoryMemberRepository.java

package com.codingbox.core2.repository;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.stereotype.Repository;

import com.codingbox.core2.dto.Member;

//@Repository
public class MemoryMemberRepository implements MemberRepository{

	// 메모리 사용 -> 아직 DB가 정해지지 않아서 임시로 메모리 사용
	private static Map<Integer, Member> store = new HashMap<>();
	private static int sequence = 0;
	
	// 메모리 영역에 간단하게 저장하는 방법
	@Override
	public Member save(Member member) {
		member.setId(++sequence);
		store.put(member.getId(), member);
		return member;
	}

	// store에 있는 values를 통째로 리턴해준다.
	@Override
	public List<Member> findAll() {
		
		return new ArrayList<>(store.values());
	}
	
}

templates

home.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1>Hello Spring</h1>
	<p>회원기능</p>
	<p>
		<a href="/members/new">회원가입</a>
		<a href="/members">회원목록</a>
	</p>
</body>
</html>

templates/members/

createMemberForm.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<form action="/members/new" method="post">
		<label>이름</label>
		<input type="text" id="name" name="name" placeholder="이름을 입력하세요"/>
		<button type="submit">등록</button>
	</form>
</body>
</html>
  • templates/members/memberList.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<table>
		<thead>
			<tr>
				<th>no</th>
				<th>이름</th>
			</tr>
		</thead>
		<tbody>
			<!-- members 키 값으로 넣어주는데 arrylist로 반복으로 넣어줌 -->
			<tr th:each="member : ${members}">
				<td th:text="${member.id}"></td>
				<td th:text="${member.name}"></td>
			</tr>
		</tbody>
	</table>
</body>
</html>

application.properties

#port 세팅
server.port=9090

#한글설정
server.servlet.encoding.charset=UTF-8
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true

#thymleaf cache
spring.thymeleaf.cache=false

#DBMS
spring.datasource.url=jdbc:oracle:thin:@localhost:1521:XE
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource.username=springweb
spring.datasource.password=springweb


#JPA
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
profile
개발자

0개의 댓글

관련 채용 정보