🍼 들어가기 전에...
프레임워크와 라이브러리의 차이
Framework란?
- '뼈대나 근간'을 이루는 코드들의 묶음(프레임)으로 프로그램의 기본 흐름이나 구조를 정하고, 이 구조에 자신의 코드 추가하는 방식으로 개발할 수 있도록 하는 프로그래밍의 기본 틀을 의미한다.
개발에 필요한 구조 제공+필요한 부분을 조립라이브러리
- 라이브러리란 자주 사용되는 로직을 재사용하기 편리하도록 잘 정리한 일련의 코드들의 집합(기능을 하는 부품)
스프링 프레임워크는 자바 플랫폼을 위한 오픈소스 애플리케이션 프레임워크로서 간단히 스프링 Spring 이라고도 불린다.
Spring과 Spring boot의 차이
특징 스프링 프레임워크(Spring Framework) 스프링 부트(Spring Boot) 목적 및 용도 포괄적인 엔터프라이즈급 애플리케이션 개발 프레임워크 마이크로서비스와 빠른 애플리케이션 개발을 위한 도구 설정 방식 XML 또는 애너테이션 기반 설정 자동 설정과 기본 설정 제공, 간편한 설정 (application.yml) 내장 서버 제공 내장 서버 미포함, 외부 서블릿 컨테이너에 배포 Tomcat, Jetty 등 내장 서버 포함, 독립 실행 가능 의존성 관리 라이브러리와 버전을 수동으로 설정 스프링 부트 스타터로 간편하게 의존성 추가 및 버전 관리 배포 방식 주로 WAR 파일 형태로 배포하여 서버에 배포 JAR 또는 WAR 파일로 배포, java -jar로 실행 가능
https://start.spring.io/ 에서 환경설정을 하면 좀 더 간편하게 프로젝트를 생성할 수 있다.
Project
1. 사용할 빌드 툴을 Maven이나 Gradle 중 선택할 수 있다.
2. 프로젝트에 필요한 의존성을 관리하고 빌드 라이프 사이클을 관리해주는 툴이다.
Maven과 Gradle
이를 위해서는 *.html등 (FE)이 필요한데, 이클립스에서는 "자바 웹 개발 플러그인"을 설치해야 한다.
- FE영역에서 자바에서는 Thymeleaf를 사용하여 동적 웹 페이지를 쉽고 간편하게 구현할 수 있다.
<html xmlns:th="http://www.thymeleaf.org">를 파일 상단에 추가하여 "thymeleaf"를 사용한다를 선언하여 사용한다.- thymeleaf를 사용하면 BE의 데이터를
<p th:text="'hello ' + ${name}">Hello! empty!!</p>간단하게 표현할 수 있다.
만약, thymeleaf 의 표현식에 한글이 있다면application.properties에서 한글 인코딩 세팅을 할 수 있고, thymeleaf는 서버재실행 시 원활한 캐시비우기를 위헤 다음과 같이 설정 할 수 있다.# 한글 세팅 server.servlet.encoding.charset=UTF-8 server.servlet.encoding.enabled=true server.servlet.encoding.force=true # 렌더링시 캐시 비우기 spring.thymeleaf.cache=false
@Controller 어노테이션으로 컨트롤러 역할을 부여한다.@PostMapping("url")이나 @GetMapping("url") get방식이나 post방식으로 해당 url을 매핑을 한다.viewResolver
- 컨트롤러에서 리턴값으로 문자를 반환하면, "뷰 리졸버"가 화면을 찾아서 처리한다.
- 스프링 부트 템플릿 엔진은 기본적으로 viewName으로 매핑된다.
- 매핑 경로는
resouces/templates/ + {viewName} + .html이다.- viewResolver는 실행할 뷰를 찾는 역할이고, 컨트롤러가 리턴한 뷰 이름에 해당하는 뷰 객체를 매핑하는 역할이다.
- forward방식, redirect방식의 페이지 이동에 대한 리턴 값을 표현할 수 있다.
- forward방식 :
return "forward:/hello"기본값- redirect방식 :
return "redirect:/hello"
Model이란 무엇인가
- Controller에서 데이터를 Model에 담는다.
- view 는 Model에 담겨있는 데이터만 쏙쏙 골라서 화면에 바인딩해준다.
- Model 객체는 컨트롤러에서 데이터를 생성해
이를 view에 전달하는 역할을 한다.- HashMap의 형태를 갖고 있고 key와 value 값을 저장한다.
- Servlet의 request.setAttribute()와 비슷한 역할을 한다.
🧱 ModelAndView- model에서 view영역이 좀 더 확장된 형태로, Model과 View를 동시에 설정이 가능하며
컨트롤러는 ModelAndView객체만 리턴하지만, Model과 View가 모두 리턴 가능하다.addObject("key", value);로 key, value값을 가져온다.
Model vs ModelAndView
Model은 데이터만 저장하고,
ModelAndView은 데이터와 이동하고자 하는 View Page를 같이 저장한다.
🧱 @RequestMapping@RequestMapping어노테이션은 활용이 매우 유연하고, 실용적인 컨트롤러를 만들 수 있다. (@PostMapping이든,@GetMapping이든)- 해당 url이 호출되면 이 메서드가 호출된다.
(메서드의 이름은 임의로 짓고 보통, 어노테이션을 기반으로 동작한다.)@RequestParam: param 값을 받아온다.- required : 파라미터값의 필수 여부를 지정한다. (true일 경우, 필수
default값, false 일 경우, 필수 아님)- defaultValue : 파라미터 값이 없을 경우에 기본으로 들어갈 값이다.
@GetMapping("url") public String methodName(@RequestParam(value = "name", required = false, defaultValue = "default Spring") String name, Model model) { model.addAttribute("name", name); return "template"; }
Embed Tomcat라이브러리가 내장되어 있기 때문에 Boot Dashboard창에서 간편하게 서버 실행이 가능하다.application.properties파일에서 server.port=로 톰캣 서버의 포트번호를 수정할 수 있다.🎉 Embed Tomcat
- 스프링 부트는 내장형 톰캣을 가지고 있기 때문에 별도의 톰캣을 설정할 필요가 없어졌으며,
- 따라서, 독립적으로 실행가능한 jar로 손쉽게 배포가 가능해졌다.
DispatcherServlet -> FrameworkServlet -> HttpServletBean -> HttpServlet
Service
- 서비스 레이어 단에서 세분화된 비지니스 로직을 처리하는 객체로, 비지니스 로직이 들어가는 부분이다.
- Controller가 Request를 받으면,
MemberService mService = new MemberService();
적절한 Service에 전달하고,
전달받은 Service는 비지니스 로직회원가입,전체회원조회을 처리한다.public class MemberService { MemberRepository memberRepository = new MemoryMemberRepository(); // 회원가입 public int join(Member member) { memberRepository.save(member); return member.getId(); } // 전체 회원 조회 public List<Member> findMembers(){ return memberRepository.findAll(); } }
// MemberRepository 인터페이스로부터 상속받은
public class MemoryMemberRepository implements MemberRepository{
// static영역에 메모리 사용 (서버 기동시, 캐시 삭제)
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;
}
@Override
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
}
JDBC 사용한 DAO
build.gradle에 등록해야 한다.implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation group: 'com.oracle.database.jdbc', name: 'ojdbc6', version: '11.2.0.4'
application.properties에서 oracle db계정 등록하고,# dbms
spring.datasource.url=jdbc:oracle:thin:@localhost:1521:xe
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource.username=spring
spring.datasource.password=spring
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 pstmt = null;
ResultSet rs = null;
List<Member> members = new ArrayList<>();
try {
conn = dataSource.getConnection();
pstmt = conn.prepareStatement(sql);
rs = pstmt.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;
}
JPA 사용한 DAO
DAO를 작성하기 전에, build.gradle에 implementation 'org.springframework.boot:spring-boot-starter-data-jpa'라이브러리를 등록한다.
application.properties에서 jpa 설정 등록을 한다.
#jpa 설정
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto = none
dto에서 db데이터 관리를 위한 jpa 설정이 필요하다.// jpa가 관리하는 dto클래스라고 지정
@Entity
public class Member {
// 아이디 지정 (PK값, seq)
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "mySequence")
@SequenceGenerator(name = "mySequence", sequenceName = "member_seq", allocationSize = 1)
private int id;
private String name;
~중략~
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();
}
service객체에서는 @Transactional어노테이션 선언이 필요하다.
JPA
JPA는 기존의 반복 코드는 물론이고, 기본적인 SQL도 JPA가 직접 만들어서 실행해 준다.
JPA를 사용하면, SQL과 데이터 중심의 설계에서
객체 중심의 설계로 패러다임을 전환할 수 있다.
JPA를 사용하면, 개발 생산성을 크게 높일 수 있다.
Controller에서 Service를 호출하기 위해MemberService mService = new MemberService();방식으로 객체를 호출하는 것이 직관적으로 이해하기 쉬울 수 있으나, Spring 프레임워크에서 기본적으로 가지고 있는 "스프링 컨테이너"라는 특성때문에 자바 객체 (스프링 빈)을 등록하는 방식으로 제어의 흐름을 관리한다.
@ComponentScan로 선언되어 있다.. 가져올 스프링 빈을 선언하여 잠궈주고, (해당부분에서 변경할 객체를 변경할 필요가 없다.)
private final MemberService memberService;
Controller가 생성될때 생성자를 호출해준다. (가져올 service 객체까지 생성해서 자동으로 호출해준다.)
@Autowired를 선언해주면, MemberController를 생성하면서 스프링이 memberService와 연결을 해준다.@Autowired
필요한 의존 객체의 "타입"에 해당하는 빈을 찾아 주입한다. 그리고 Autowired는 기본값이 true이기 때문에 의존성 주입을 할 대상을 찾지 못한다면 애플리케이션 구동에 실패한다.
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
@Autowired가 있으면, 스프링이 연관된 객체를 스프링 컨테이너에서 찾아서 넣어준다. 이러한 객체 의존관계를 외부에서 넣어주는 것을 DI(Dependency Injection), 의존성 주입이라 한다.DI(Dependency Injection), 의존성 주입
의존성 주입이란, 객체간의 의존성이 존재할 경우 개발자가 직접 객체를 생성하거나 제어하는 것이 아니라, 제어반전에 의하여 특정 객체에 필요한 다른 객체를 프레임워크가 자동으로 연결시켜 주는 것을 말한다.
의존성 주입을 하는 방법에는 3가지 방법이 있다. Field Injection (필드 주입), Setter Injection (수정자 주입), Constructor Injection (생성자 주입)
POJO란, 객체 지향적인 원리에 충실하면서 환경과 기술에 종속되지 않고, 필요에 따라 재활용 될 수 있는 방식으로 설계된 오브젝트를 말한다. 그러한 POJO에 애플리케이션의 핵심 로직과 기능을 담아 설계하고 개발하는 방법을 POJO프로그래밍이라고 한다.
@RequestMapping("/request-param-v1")
public void requestParam1(HttpServletRequest request, HttpServletResponse response) throws IOException {
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
response.getWriter().write("ok");
}
@RequestParam(스프링이 제공하는 어노테이션)을 이용하면 파라미터의 이름으로 바인딩할 수 있다.@ResponseBody를 이용하여 view 조회를 무시하고, HTTP message body에 직접 해당 내용 입력할 수 있다. @ResponseBody
@RequestMapping("/request-param-v2")
public String requestParam2(@RequestParam("username") String memberName, @RequestParam("age") int memberAge) {
return "ok";
}
@ResponseBody
@RequestMapping("/request-param-v3")
public String requestParam3(@RequestParam String username, @RequestParam int age) {
return "ok";
}
만약에 이클립스에서 서버 기동시, 파라미터를 받아오지 못하는 이클립스 오류가 난다면, 설정 > Java > Compiler 에서 해당부분을 적용시켜보면 된다.
String, int 등의 "단순한 타입"이라면, @RequestParam도 생략가능하다.@ResponseBody
@RequestMapping("/request-param-v4")
public String requestParam4(String username, int age) {
return "ok";
}
required속성 : true일 경우, 반드시 파라미터값이 들어와야 한다. "기본값"@ResponseBody
@RequestMapping("/request-param-required")
public String requestParamRequired(@RequestParam(required = true) String username, @RequestParam(required = false) Integer age) {
return "ok";
}
defaultValue속성 : 입력값이 없을때 값을 지정할 수 있다.@ResponseBody
@RequestMapping("/request-param-default")
public String requestParamDefault(@RequestParam(defaultValue = "guest") String username, @RequestParam(defaultValue = "-1") int age) {
return "ok";
}
Map의 형태로 key값을 파싱할 수있다. (Key=value)형태@ResponseBody
@RequestMapping("/request-param-map")
public String requestParamMap(@RequestParam Map<String, Object> paramMap) {
return "ok";
}
자바(Java) 프로그래밍 언어에서 사용되는 라이브러리로, 반복적인 코드를 줄이고, 개발자가 더 간결한 코드 작성을 할 수 있도록 도와준다.
롬복 라이브러리는 어노테이션 기반의 코드를 자동 생성해 주는 기능을 제공하여, getter나 setter, toString(), equals(), hashCode() 등의 메서드를 직접 작성할 필요 없이 코드에 추가할 수 있게 한다.
이를 통해 자바 코드의 가독성과 유지보수성을 크게 높일 수 있다.
lombok 라이브러리 설치하기
- 라이브러리에서 lombok을 찾아 "Run as" 클릭 후, sts에 설치한다.
request 요청 파라미터를 받아 필요한 객체를 생성해 그 객체에 값을 넣어주어야 한다.
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(
@RequestParam String username, @RequestParam int age) {
HelloData helloData = new HelloData();
helloData.setUsername(username);
helloData.setAge(age);
return "ok";
}
@ModelAttribute를 사용하면 요청 파라미터를 객체에 담아주는 것을 완전히 자동화하여 요청 파라미터의 이름으로 객체의 프로퍼티를 찾아서 setter를 호출해서 파라미터의 값을 바인딩 한다.@ResponseBody
@RequestMapping("/model-attribute-v2")
public String modelAttributeV2(@ModelAttribute HelloData helloData) {
return "ok";
}
@RequestParam이 생략 가능한 것과 마찬가지로 @ModelAttribute 역시, 생략가능하다. String이나, int와 같은 단순한 타입을 요청 받아온 경우는 @RequestParam를 생략한 것이고 객체 타입으로 요청 받아온 경우는 @ModelAttribute를 생략한 것이다.@ResponseBody
@RequestMapping("/model-attribute-v3")
public String modelAttributeV3(HelloData helloData) {
return "ok";
}
@RequestMapping("/response-view-v1")
public ModelAndView responseViewV1() {
ModelAndView mav = new ModelAndView("response/hello").addObject("data","hello!!!");
return mav;
}
@ResponseBody가 없으면, response/hello로 뷰 리졸버가 실행되어서 뷰를 찾아 렌더링한다. (templates/response/hello.html으로 이동)@ResponseBody가 있으면 뷰 리졸버를 실행하지 않고, HTTP 메시지 바디에 직접 response/hello 라는 문자가 입력 된다.