Search

스프링 입문

어노테이션

@GetMapping(”link”) : ~~/link 로 GET 요청을 매핑해준다.
@RequestParam(”param”) : ~~/link?param=xxx 같이 URI로 전달된 파라미터를 사용한다.
value : 단독으로 사용할 땐 생략 가능하며 다른 속성과 같이 사용할 땐 파라미터 이름에 붙여 줘야함
required : 기본값은 true로 해당 파라미터를 필수값으로 지정
@BeforeEach, @AfterEach : 테스트에서 메서드에 붙여 각 테스트 전, 후마다 실행한다.
@ComponentScan : 패키지 단위로 @Component가 붙은 클래스를 스프링 빈으로 자동 등록한다.
@Component : @ComponentScan에 의해 자동으로 스프링 빈으로 등록된다.
@Controller : 컨트롤러임을 명시하며 스프링 빈으로 자동 등록됨
@Service : 서비스임을 명시하며 스프링 빈으로 자동 등록됨
@Respository : 레포지토리임을 명시하며 스프링 빈으로 자동 등록됨
@Configuration : 해당 클래스가 설정 정보임을 명시한다.
@Bean : @Configuration 안에서 해당 클래스를 실행해 생성된 객체를 반환하는 빈을 수동 등록함
@SpringBootTest : JVM만을 사용하는 테스트가 아닌 스프링 부트를 사용하는 테스트라고 명시함
@Transactional : 테스트 시작하기 전 트랜잭션을 걸고 테스트가 끝난 후 롤백한다.

thymeleaf

Controller에서 리턴 값으로 문자를 반환하면 viewResolver가 화면을 찾아서 처리한다.
스프링 부트 템플릿엔진의 기본 viewName 매핑
“resources:templates/” + ViewName + “.html”

콘솔로 빌드하고 실행하기

1. ./gradlew build 2. cd build/libs 3. java -jar 패키지명-0.0.1-SNAPSHOT.jar 4. 실행확인
Java
복사

정적 컨텐츠

/hello-static.html에 대한 요청에 스프링은 Controller에서 먼저 찾고 없을 시 static에서 찾는다.

MVC와 템플릿 엔진

//HelloController.java @Controller public class HelloController { @GetMapping("hello-mvc") public String helloMvc(@RequestParam(value = "name", required = false) String name, Model model) { model.addAttribute("name", name); return "hello-template"; } }
Java
복사
<!-- hello-template.html --> <!DOCTYPE html> <html lang="en"> <html xmlns="http://www.thymeleaf.org"></html> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Hello</title> </head> <body> <p th:text="'hello' + ${name}">Hello empty</p> </body> </html>
HTML
복사
1.
웹 브라우저로부터 온 /hello-mvc 요청을 톰캣이 받은 후 스프링에게 전달한다.
2.
스프링은 hello-mvc가 Controller를 뒤져 매핑된 메서드를 찾는다.
3.
Controller 로직 처리 후 model과 html 이름을 스프링에게 반환
4.
스프링은 viewResolver에게 전달
5.
viewResolver는 html 이름에 해당하는 뷰를 찾고 thymeleaf 템플릿 엔진에 전달
6.
thymeleaf 템플릿 엔진은 html을 변환 후 웹 브라우저에 전달한다. (정적일 땐 변환 X)

API

@ResponseBody 문자 반환
@GetMapping("hello-string") @ResponseBody public String helloString(@RequestParam("name") String name) { return "hello " + name; }
Java
복사
viewResolver를 사용하지 않고 HTTP Body에 문자 내용을 직접 반환한다. (HTML TAG X)
viewResolver 대신 HttpMessageConverter가 동작한다.
기본 문자처리 : StringHttpMessageConverter
기본 객체처리 : MappingJackson2HttpMessageConverter
byte 처리 등 기타 여러 HttpMessageConverter가 기본으로 등록되어 있다.
@ResponseBody 객체 반환
@GetMapping("hello-api") @ResponseBody public Hello helloApi(@RequestParam("name") String name) { Hello hello = new Hello(); hello.setName(name); return hello; }
Java
복사
@ResponseBody로 객체를 반환하면 기본적으로 JSON으로 변환된다.
@ResponseBody가 붙어있으면 HttpMessageConverter가 동작한다.
내용이 문자면 StringConverter, 객체면 JsonConverter 등

일반적인 웹 애플리케이션 계층 구조

컨트롤러 : 웹 MVC의 컨트롤러 역할
서비스 : 핵심 비즈니스 로직 구현
리포지토리 : 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
도메인 : 비즈니스 도메인 객체 (회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장하고 관리됨)

순수 JDBC

DataSource는 데이터베리스 커넥션을 획득할 때 사용하는 객체이다.
스프링 부트는 데이터베이스 커넥션 정보를 바탕으로 DataSource를 생성하고 스프링 빈으로 만들어두기 때문에 DI를 받을 수 있다.
//MemberRepository를 구현한 JdbcMemberRepository 구현 객체 public class JdbcMemberRepository implements MemberRepository { private final DataSource dataSource; @Autowired public JdbcMemberRepository(DataSource dataSource) { this.dataSource = dataSource; } @Override public Member save(Member member) { String sql = "insert into member(name) values(?)"; Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; try { conn = getConnection(); pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); pstmt.setString(1, member.getName()); pstmt.executeUpdate(); rs = pstmt.getGeneratedKeys(); if (rs.next()) { member.setId(rs.getLong(1)); } else { throw new SQLException("id 조회 실패"); } return member; } catch (Exception e) { throw new IllegalStateException(e); } finally { close(conn, pstmt, rs); } } ... }
Java
복사
//Config @Configuration public class SpringConfig { private final DataSource dataSource; @Autowired public SpringConfig(DataSource dataSource) { this.dataSource = dataSource; } ...
Java
복사
sql 에 sql문을 미리 작성해둔다. (변하는 값은 ?로 적어둠)
Connection은 dataSource.getConnection()을 통해 데이터베이스 커넥션을 받아온 것이다.
PreparedStatement.setString()은 sql문을 작성하기 위한 것이다.
PreparedStatement.executeUpdate()는 sql문을 db에 날려주는 것이다.
ResultSet은 sql문 실행 결과를 받아오기 위한 것이다.
데이터베이스 커넥션은 close를 통해 사용 후엔 릴리즈를 꼭 해줘야한다!

DB 통합 테스트

@SpringBootTest @Transactional class MemberServiceIntegrationTest { @Autowired MemberService memberService; @Autowired MemberRepository memberRepository; @Test void join() { Member member = new Member(); member.setName("hello"); Long saveId = memberService.join(member); Member findMember = memberService.findOne(saveId).get(); assertThat(member.getName()).isEqualTo(findMember.getName()); } @Test void duplicatedMember() { Member member1 = new Member(); member1.setName("spring"); Member member2 = new Member(); member2.setName("spring"); memberService.join(member1); IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2)); assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다."); } }
Java
복사
@SpringBootTest를 붙여 스프링 컨테이너까지 올려 테스트한다.
@Transactional을 테스트에 붙이면 테스트 시작 전 트랜잭션을 걸고 테스트 완료 후 롤백을 한다.

JDBC Templates

public class JdbcTemplateMemberRepository implements MemberRepository { private final JdbcTemplate jdbcTemplate; @Autowired public JdbcTemplateMemberRepository(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } private RowMapper<Member> memberRowMapper() { return (rs, rowNum) -> { Member member = new Member(); member.setId(rs.getLong("id")); member.setName(rs.getString("name")); return member; }; } @Override public Member save(Member member) { SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate); jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id"); Map<String, Object> parameters = new HashMap<>(); parameters.put("name", member.getName()); Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters)); member.setId(key.longValue()); return member; } ...
Java
복사
DataSource를 매개변수로 전달해 JdbcTemplate를 DI 받아준다.
RowMapper를 통해 데이터베이스로부터 데이터를 받아온다.
JdbcTemplate.query를 통해 sql문, mapper, args를 보내 sql를 실행하고 결과를 받는다.

JPA

기존의 반복 코드는 물론이고 기본적인 SQL도 JPA가 직접 만들어서 실행해준다.
JPA를 사용하면 SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환할 수 있다.
JPA를 사용하면 개발 생산성을 크게 높일 수 있다.
//gradle.build implementation 'org.springframework.boot:spring-boot-starter-data-jpa' //application.properties spring.jpa.show-sql=true //JPA가 생성하는 SQL을 출력 spring.jpa.hibernate.ddl-auto=none //테이블을 자동으로 생성하는 기능 (none은 끄기, create는 엔티티를 바탕으로 테이블 생성)
Java
복사
//domain.Member @Entity public class Member { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; ...
Java
복사
//MemberService @Transactional public class MemberService {
Java
복사
//JpaMemberRepository public class JpaMemberRepository implements MemberRepository { private final EntityManager entityManager; @Autowired public JpaMemberRepository(EntityManager entityManager) { this.entityManager = entityManager; } @Override public Member save(Member member) { entityManager.persist(member); return member; } @Override public Optional<Member> findById(Long id) { Member member = entityManager.find(Member.class, id); return Optional.ofNullable(member); } @Override public Optional<Member> findByName(String name) { List<Member> result = entityManager.createQuery("select m from Member m where m.name = :name", Member.class).setParameter("name", name).getResultList(); return result.stream().findAny(); } @Override public List<Member> findAll() { List<Member> result = entityManager.createQuery("select m from Member m", Member.class).getResultList(); return result; } }
Java
복사
//SpringConfig @Configuration public class SpringConfig { private EntityManager entityManager; @Autowired public SpringConfig(EntityManager entityManager) { this.entityManager = entityManager; } @Bean public MemberService memberService() { return new MemberService(memberRepository()); } @Bean public MemberRepository memberRepository() { return new JpaMemberRepository(entityManager); } }
Java
복사
domain 객체에 @Entity를 통해 테이블과 매핑할 수 있다.
@Id 를 통해 기본키와 매핑할 수 있으며 Entity 하나당 최소 1개가 있어야 한다.
@GenratedValue 는 기본키 생성 전략을 지정할 수 있다.
@Column(name = “”)을 통해 변수의 이름이 컬럼의 이름과 다르더라도 매핑할 수 있게 한다.
JPA를 통한 모든 데이터 변경은 반드시 트랜잭션 안에서 실행 (Service에 꼭 @Transactional을 붙이자)

스프링 데이터 JPA

스프링 데이터 JPA는 JPA를 사용하는 프레임워크이다.
JPA를 기반으로 기본적인 CRUD나 단순기능들을 기본적으로 제공해준다.
//SpringDataMemberRepository.interface public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository { Optional<Member> findByName(String name); }
Java
복사
//SpringConfig @Configuration public class SpringConfig { private final MemberRepository memberRepository; @Autowired public SpringConfig(MemberRepository memberRepository) { this.memberRepository = memberRepository; } @Bean public MemberService memberService() { return new MemberService(memberRepository); } }
Java
복사

AOP (Aspect Oriented Programming)

공통 관심 사항(cross-cutting concern) , 핵심 관심 사항(core concern) 분리
AOP 적용 후 작동원리
@Aspect @Component public class TimeTraceAop { @Around("execution(* com.example.demo2..*(..)))") public Object execute(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis(); System.out.println("Start : " + joinPoint.toString()); try { return joinPoint.proceed(); } finally { long finish = System.currentTimeMillis(); long timeMs = finish - start; System.out.println("End : " + joinPoint.toString() + " " + timeMs + "ms"); } } }
Java
복사
@Aspect 를 붙여야 AOP를 사용할 수 있다
@Component를 이용해 빈으로 등록해도 되지만 @Bean으로 수동 등록을 하는 것이 좋다. (명시적)
@Around(””)를 통해 표현식을 사용해 적용할 곳을 지정한다.