개발환경

 

  1. 운영체제 : Mac OS
  2. 통합개발환경(IDE) : IntelliJ Ultimate
  3. JDK version : JDK 22
  4. Spring Boot version : 3.3.2
  5. Database : MySQL
  6. Build Tool : Maven
  7. Packaging : Jar

 

Dependencies
  • Spring Web
  • Spring Data JPA
  • MySQL Driver
  • H2 Database
  • Thymeleaf
  • Lombok
  • Spring Security
  • Spring Boot DevTools
  • querydsl
  • thymeleaf-layout-dialect
  • validation
  • thymeleaf-extras-springsecurity6

 

패키지 설계

 

스프링 시큐리티 설정


  • config 패키지에 securityconfig 클래스 생성
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    
        http.authorizeHttpRequests(configurer ->
                configurer
                        .requestMatchers("/h2-console/**").permitAll() // H2 콘솔에 대한 접근 허용
                        .anyRequest().authenticated() // 나머지 요청에 대해 인증 필요

        );
        
        // use HTTP Basic authentication
        http.httpBasic(Customizer.withDefaults());

        // disable Cross Site Request Forgery(CSRF)
        http.csrf(csrf -> csrf.disable());

        return http.build();
    
    }
    
    // H2 콘솔 관련 설정
    @Bean
    @ConditionalOnProperty(name = "spring.h2.console.enabled", havingValue = "true")
    public WebSecurityCustomizer configureH2ConsoleEnable() {
        return web -> web.ignoring()
                .requestMatchers(PathRequest.toH2Console());
    }

 

  • @Configuration : Spring의 구성 클래스를 나타내며, Bean 정의를 포함하는 클래스를 의미
  • @EnableWebSecurity : Spring Security를 활성화. Spring Security의 설정을 정의하고 적용
💡  BCryptPasswordEncoder
      비밀번호를 암호화하고 검증하는 데 사용되는 PasswordEncoder 구현체. BCrypt 알고리즘을 사용하여 비밀번호를 암호화한다. BCrypt는 비밀번호 해시를 생성하는 데 안전한 방법으로 널리 사용

 

 

SecurityFilterChain HTTP 요청에 대한 보안 필터 체인을 정의. HttpSecurity 객체를 사용하여 필터 체인을 설정.

HttpSecurity : HTTP 요청에 대한 보안 설정을 구성하는 데 사용됨

authorizeHttpRequests(configurer -> ...) : HTTP 요청에 대한 접근 권한을 설정

requestMatchers(...) : URL 패턴에 따라 요청을 필터링.

permitAll() : 해당 URL 패턴에 대해 인증 없이 접근을 허용.

anyRequest().authenticated() : 위에서 정의된 패턴을 제외한 모든 요청은 인증된 사용자만 접근할 수 있도록 설정.

httpBasic(Customizer.withDefaults()) : HTTP Basic Authentication을 활성화. 이 방법은 간단한 인증 방법으로 사용자 이름과 비밀번호를 HTTP 헤더에 포함시켜 인증을 수행.

csrf.disable() : Cross Site Request Forgery (CSRF) 보호를 비활성화. CSRF는 웹 애플리케이션에서 악의적인 요청을 방지하기 위한 보안 메커니즘. 일반적으로 비활성화하지 않는 것이 좋지만, API 또는 비상 상황에서는 비활성화할 수 있다.

 

https://wikidocs.net/162150

 

3-05 스프링 시큐리티란?

* `[완성 소스]` : [https://github.com/pahkey/sbb3/tree/v3.05](https://github.com/pahkey/sbb3/tree/v3.05…

wikidocs.net

 

📍 내가 노션에 정리한 내용!

https://www.notion.so/5-Spring-Boot-Security-b95a14467d6f4a87937a66b8d217c2d2?pvs=4

https://www.notion.so/8-Spring-MVC-Security-3fc33d56e52e4d809dac4b7fbdc0f025?pvs=4

 

 

.requestMatchers("/**").permitAll() - 일단 스프링 시큐리티 적용안하고 바로 화면 보이도록 설정...

 

 

엔티티, DTO 코드 설정


 

1. 일반 유저 / 관리자 역할 코드

  •  com.doraflower 패키지 아래 'constant' 키지 생성 - Role.java 생성
  •  enum(열거형)

 

https://velog.io/@mooh2jj/Java-Enum%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0

 

[Java] Enum을 사용하는 이유와 활용법

Java enum은 제한된 값 목록을 갖는 타입입니다. enum은 다음과 같은 이점을 갖습니다.enum은 컴파일 타임에 타입 안정성을 보장합니다. 특정 범위의 값만 사용 가능하므로 컴파일 오류나 런타임 예

velog.io

 

public enum Role {
    USER, ADMIN
}

 

 

2. MemberFormDTO 클래스 코드 - 회원가입 화면으로부터 넘어오는 정보를 담는다.

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MemberFormDTO {

    @NotBlank(message = "Name is required")
    private String name;

    @NotBlank(message = "Email is required")
    @Pattern(regexp = "^[\\w-.]+@([\\w-]+\\.)+[\\w-]{2,4}$",
            message = "Please enter a valid email address.")
    private String email;

    @NotBlank(message = "Password is required")
    @Length(min=8, max=16, message = "Enter a password between 8 and 16 characters, including numbers.")
    private String password;

    @NotBlank(message = "Address is required")
    private String address;
}

 

2024.07.25 - [Spring & Spring Boot] - @Lombok 및 Entity 관련 어노테이션

 

  • 회원가입 폼에서 받을 내용 - 이름 | 이메일 주소 (로그인 시 확인) | 비밀번호 | 주소
  • message는 유효성 검사시 에러가 발생하면 출력될 내용!

 

3. Member 클래스 코드 - 회원 정보를 저장하는 엔티티 (DB에서 관리)

@Entity
@Table(name = "member")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Member {

    @Id
    @GeneratedValue (strategy = GenerationType.IDENTITY)
    @Column(name="member_id")
    private Long memberId;

    @Column(nullable = false)
    private String name;

    @Column(unique = true, nullable = false)
    private String email;

    @Column(nullable = false)
    private String password;

    @Column(nullable = false)
    private String address;

    @Enumerated(EnumType.STRING)
    private Role role;

    public static Member createMember(MemberFormDTO memberFormDTO,
                                      PasswordEncoder passwordEncoder) {

        String password = passwordEncoder.encode(memberFormDTO.getPassword());

        Member member = Member.builder()
                .name(memberFormDTO.getName())
                .email(memberFormDTO.getEmail())
                .password(password)
                .address(memberFormDTO.getAddress())
                .role(Role.USER)
                .build();

        return member;
    }

}

 

  • 데이터베이스 테이블 이름은 'member'로 지정
  • 이메일을 통해 회원 구분을 위해 'unique'설정
  • Role은 string으로 저장하도록 설정
  • Member 엔티티 생성하는 메소드 설정 - 파라미터로 dto와 passwordencoder를 받음
  • 패스워드는 db에 암호화 돼서 저장됨
  • role은 'USER'로 설정해줌 -> ADMIN 먼저 만들고 바꿔줘도 됨

 

 

Repository 코드 설정 - DAO 역할


  • repository 패키지 아래 MemberRepository 인터페이스 생성
public interface MemberRepository extends JpaRepository<Member, Long> {

    Member findByEmail(String email);
}
  • 회원가입 시 중복된 회원이 있는지 검사하기 위해 이메일로 회원을 검사할 수 있도록 쿼리 메소드 작성

 

 

Service 코드 설정


  • service 패키지 아래 MemberService 인터페이스, MemberServiceImpl 클래스 생성
특성 서비스 계층이 필요한 경우 서비스 계층이 필요없는 경우
비즈니스 로직의 복잡성 복잡한 비즈니스 로직이 있는 경우 비즈니스 로직이 거의 없거나 단순한 경우
유지보수 비즈니스 로직을 중앙화하여 유지 보수 용이 간단한 애플리케이션에서 유지 보수가 덜 필요
테스트 용이성 독립적인 테스트가 필요한 경우 테스트가 크게 중요하지 않은 경우
트랙잭션 관리 트랜잭션 관리를 필요로 하는 경우 트랜잭션 관리가 필요 없는 경우
계층화된 아키텍처 MVC 패턴 등 계층화된 아키텍처를 선호하는 경우 단일 클래스 또는 구조화된 아키텍처가 불필요

 

public interface MemberService {

    Member saveMember(Member theMember);
}
@Service
@Log4j2
@RequiredArgsConstructor
@Transactional
public class MemberServiceImpl implements MemberService, UserDetailsService {

    private final MemberRepository memberRepository;

    @Override
    public Member saveMember(Member theMember) {

        Member tempMember = memberRepository.findByEmail(theMember.getEmail());
        if (tempMember != null) {
            throw new IllegalStateException("Already Registered Member!");
        }

        return memberRepository.save(theMember);
    }

}

 

특성 단일 클래스 인터페이스 + 구현체
구조 단순하지만 커질 수 있음 명확하게 구조화됨
유지보수 어려울 수 있음 용이함
테스트 어려울 수 있음 용이함 (모킹 가능)
확장성 낮음 높음
의존성 주입 어려울 수 있음 쉬움

 

 

  • @Autowired는 빈에 생성자가 1개이고 생성자의 파라미터 타입이 빈으로 등록 가능하면 사용 안해도 의존성 주입 가능..
  • 이미 가입된 회원은 IllegalStateException 예외 발생

 

회원 가입 기능 테스트


  • 마우스 오른쪽 버튼 - Go To - Test 자동생성 기능 사용
@SpringBootTest
@Transactional
@Log4j2
@RequiredArgsConstructor
@TestPropertySource(locations = "classpath:application-test.properties")
class MemberServiceImplTest {

    @Autowired
    MemberService memberService;

    @Autowired
    PasswordEncoder passwordEncoder;

    @Test
    @DisplayName("회원가입 테스트")
    public void saveMemberTest() {

        MemberFormDTO theMemberDTO = MemberFormDTO.builder()
                .email("test@email.com")
                .name("Dora")
                .address("Daegu")
                .password("1234")
                .build();

        // 비밀번호 인코딩 및 회원 생성
        Member member = Member.createMember(theMemberDTO, passwordEncoder);

        // 회원 저장
        Member savedMember = memberService.saveMember(member);

        // 로그로 저장된 Member 확인
        log.info("Saved Member: {}", savedMember);
        assertNotNull(savedMember.getMemberId()); // ID가 null이 아닌지 확인
        assertEquals("test@email.com", savedMember.getEmail());

    }

    @Test
    @DisplayName("중복 회원 가입 테스트")
    public void saveDuplicateMember() {

        // 객체 생성
        Member member1 = new Member(null, "Dora", "test@email.com", "1234", "Daegu", ADMIN);
        Member member2 = new Member(null, "Dora", "test@email.com", "1234", "Daegu", USER);

        // 객체 저장
        memberService.saveMember(member1);

        // 예외 처리
        IllegalStateException thrown = assertThrows(IllegalStateException.class, () -> {
            memberService.saveMember(member2);
        });

        assertTrue(thrown.getMessage().contains("Already Registered Member!"));
    }
}

 

  • h2 db에 테스트 할 거기 때문에 기존 properties 외 새로 하나 더 만들어서 경로를 지정해줬다.
  • Junit의 Assertions 클래스의 assertEquals 메소드를 이용해서 저장하려고 요청했던 값과 실제 저장된 데이터를 비교함.
  • assertThrows 메소드 이용하면 예외 처리 테스트가 가능.

https://junit.org/junit5/docs/5.0.1/api/org/junit/jupiter/api/Assertions.html

 

Assertions (JUnit 5.0.1 API)

Asserts that all supplied executables do not throw exceptions. If any supplied Executable throws an exception (i.e., a Throwable or any subclass thereof), all remaining executables will still be executed, and all exceptions will be aggregated and reported

junit.org

 

https://velog.io/@roycewon/JUnit-Assert-Methods1

 

JUnit - Assert Methods(1)

JUnit 5 모듈인 Jupiter는 JUnit4에 있는 Assertion method를 포함하여 여러 메소드를 제공한다. Java8에서 추가된 람다와 함께 사용하면 좋다.Assert method는 org.junit.jupiter.api.Assertions 라는 클래

velog.io

 

 

 

Controller 코드 구현


  • controller 패키지 아래 MemberController 생성
@Controller
@RequestMapping("/members")
@RequiredArgsConstructor
public class MemberController {

    private final MemberService memberService;
    private final PasswordEncoder passwordEncoder;

    @GetMapping("/new")
    public String memberForm(Model model) {

        model.addAttribute("memberFormDTO", new MemberFormDTO());
        return "member/memberForm";
    }
    
    @PostMapping("/new")
    public String memberForm(@Valid MemberFormDTO memberFormDTO,
                             BindingResult bindingResult,
                             Model model) {

        // 유효성 오류시 실행할 메서드
        if (bindingResult.hasErrors()) {
            System.out.println("Binding results: " + bindingResult.toString());
            return "member/memberForm";
        }

        // 이메일 중복 시 예외처리
        try {
            Member member = Member.createMember(memberFormDTO, passwordEncoder);
            memberService.saveMember(member);
        } catch (IllegalStateException e){
            model.addAttribute("errorMessage", e.getMessage());
            return "member/memberForm";
        }

        // 성공 시 리다이렉트
        return "redirect:/";
    }
}

 

  • ModelmemberFormDTO라는 이름으로 새로운 MemberFormDTO 객체를 추가. 뷰에서 이 객체를 사용하여 폼을 렌더링할 수 있다.
  • localhost:8080/members/new 접속하면 member 패키지 아래에 있는 memberForm.html 화면이 출력됨.
  • @PostMapping은 form 을 제출했을 때 실행되는 코드~ 페이지 구현하고 작성해도 됨!

 

 

회원가입 페이지 구현


  • templates 패키지 아래 member 패키지 생성 - memberForm.html 생성
  • Thymeleaf 템플릿 사용해서 작성함.
  • fragments 패키지 아래에 header/footer html 만들어서 layout01.html에 fragment로 설정하고 content영역만 따로 페이지 구현해서 연동되도록 만들었습니닷,

2024.05.09 - [Spring & Spring Boot] - [Spring Boot] Thymeleaf

https://github.com/SominY/Dora-Flower-Shop/blob/main/src/main/resources/templates/member/memberForm.html(소스코드)

 

Dora-Flower-Shop/src/main/resources/templates/member/memberForm.html at main · SominY/Dora-Flower-Shop

쇼핑몰 프로젝트 (개인 프로젝트). Contribute to SominY/Dora-Flower-Shop development by creating an account on GitHub.

github.com

 

 

 

 

 

 

 

내용 참고: 책 '스프링 부트 쇼핑몰 프로젝트 with JPA'


JpaRepositoy 지원 메소드

메소드 기능
<S extends T> save(S entity) 엔티티 저장 및 수정
void delete(T entity) 엔티티 삭제
count() 엔티티 총 개수 반환
Iterable<T> findAll() 모든 엔티티 조히

 

📍 연관 페이지

https://www.notion.so/4-Spring-Boot-Rest-CRUD-b666e9cbc99746048343b5ac0b464871?pvs=4

2024.05.25 - [Spring & Spring Boot] - [Spring Boot] Spring Data JPA (1)

 

[Spring Boot] Spring Data JPA (1)

1.  Spring Data JPAJDBC에서 작업하다가 MyBatis를 배울 경우에는 SQL 작성법도 거의 동일하고, 코드의 간략화가 목표여서 러닝커브가 높지 않지만JPA의 경우에는 JDBC, MyBatis와는 사용방법이 상당한

allriver.tistory.com

 


Query method & JPQL snippet

Keyword Sample JPQL snippet
And findByLastnameAndFirstname ... where x.lastname =  ?1 and x.firstname = ?2
Or findByLastnameOrFirstname ... where x.lastname =  ?1 or x.firstname = ?2
Is, Equals findByFirstname
findByFirstnameIs
findByFirstnameEquals
... where x.firstname = ?1
Between findByStartDateBetween ... where x.startDate between ?1 and ?2
LessThan findByAgeLessThan ... where x.age < ?1
LessThanEqual findByAgeLessThanEqual ... where x.age <= ?1
GreaterThan findByAgeGreaterThan ... where x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual ... where x.age >= ?1
After findByStartDateAfter ... where x.startDate > ?1
Before findByStartDateBefore ... where x.startDate < ?1
IsNull, Null
IsNotNull
findByAge(Is)Null ... where x.age is null
NotNull findByAge(Is)NotNull ... where x.age not null
Like findByAgeFirstnameLike ... where x.firstname like ?1
NotLike findByAgeFirstnameNotLike ... where x.firstname not like ?1
StartingWith findByFirstnameStartingWith ... where x.firstname like ?1 (parameter bound with appended %)
EndingWith findByFirstnameEndingWith ... where x.firstname like ?1 (parameter bound with prepended %)
Containing findByFirstnameContaining ... where x.firstname like ?1 (parameter bound wrapped in %)
OrderBy findByAgeOrderByLastnameDesc ... where x.age ?1 order by x.lastname desc
Not findByLastnameNot ... where x.lastname <> ?1
In findByAgeIn(Collection<Age> ages) ... where x.age in ?1
NotIn findByAgeNotIn(Collection<Age> ages) ... where x.age not in ?1
True findByActiveTrue() ... where x.active = true
False findByActiveFalse() ... where x.active = false
IgnoreCase findBYFirstnameIgnoreCase ... where UPPER(x.firstname) = UPPER(?1)

 

 

 


 

javax.validation 어노테이션 예시

어노테이션 설명
@NotEmpty NULL 체크 및 문자열의 경우 길이 0인지 검사
@NotBlank NULL 체크 및 문자열의 경우 길이 0 및 빈 문자열(" ") 검사
@Length(min=, max=) 최소, 최대 길이 검사
@Email 이메일 형식인지 검사
@Max(숫자) 지정한 값보다 작은지 검사
@Min(숫자) 지정한 값보다 큰지 검사
@Null 값이 NULL인지 검사
@NotNull 값이 NULL이 아닌지 검사

 

 

 

 

 

 

* 내용 참고 : 책 '스프링부트 쇼핑몰 프로젝트 with JPA'


1.  application-test.properties 파일 생성

# datasource 설정
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:./data/demo
spring.datasource.username=sa
spring.datasource.password=
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

# H2 데이터베이스 방언 설정
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

 

- username, password 는 기본값으로 설정해줘도 됨

- port 는 기본값이고 path를 지정해주면 localhost:8080/h2-console 로 접속하면됨

- '인메모리 모드' 에서는 url 설정을 jdbc:h2:mem:{db이름} 으로 설정

- 'embedded mode' 에서는 url 설정을 jdbc:h2:{db가 저장될 경로} 로 설정

 

2. Spring Security 적용 중이라면 h2 database 접속시 설정이 안 되도록 코드 입력

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    @ConditionalOnProperty(name = "spring.h2.console.enabled",havingValue = "true")
    public WebSecurityCustomizer configureH2ConsoleEnable() {
        return web -> web.ignoring()
                .requestMatchers(PathRequest.toH2Console());
    }
    
}

 

- @ConditionalOnProperty: 이 애너테이션은 특정 프로퍼티가 설정된 경우에만 해당 빈을 생성. 즉, spring.h2.console.enabled가 true로 설정된 경우에만 configureH2ConsoleEnable() 메서드에서 반환한 빈이 생성됨.

 

- WebSecurityCustomizer: 이 빈은 WebSecurityCustomizer 타입의 빈을 생성하고, 이를 사용하여 특정 URL 패턴에 대해 보안 필터를 무시하도록 설정함. PathRequest.toH2Console()을 사용하여 H2 콘솔의 URL 패턴을 정의하고, 이 패턴에 대해 보안 필터를 무시하게 설정.

 

3. 웹 브라우저 창에서 localhost:8080/h2-console 접속

- properties에서 설정한 driver class, jdbc url, username, password 입력 후 test connection 클릭

- 그 후 connect 클릭하면 쿼리 작성할 수 있는 창이 뜨고, 결과확인 코드를 입력해서 RUN 클릭하면 됨!

 

* 테스트 코드 결과물은 어플리케이션이 종료되면 없어짐!

 

 

내용 참고:

https://dukcode.github.io/spring/h2-console-with-spring-security/

 

Spring Security에서 H2 Console 사용하기

Spring Security에서 H2 Console 사용하기

dukcode.github.io

https://colabear754.tistory.com/193

 

[Spring Boot] H2 DB Embedded Mode 사용하기

목차 들어가기 전에 H2는 굉장히 작고 가벼운 RDBMS로, 그 특성상 제공되는 기능은 제한적이지만 속도가 빠르고 별도의 프로그램 없이 웹브라우저 기반의 DB 콘솔을 사용할 수 있다는 장점도 있다.

colabear754.tistory.com

 


@Lombok 

🚀 Lombok 라이브러리는 Getter/Setter, ToString 과 같은 반복적인 자바 코드를 컴파일할 때 자동으로 생성해주는 라이브러리

 

어노테이션 설명
@Getter/Setter 코드를 컴파일할 때 속성들에 대한 Getter/Setter 메소드 작성
@ToString toString() 메소드 작성
@ToString(exclude={”변수명”} 원하지 않는 속성 제외한 toString()메소드 작성
@NonNull 해당 변수가 null 체크, NullPointerException 예외 발생
@EqualsAndHashCode equals()와 hashCode() 메소드 작성
@Builder 빌더 패턴을 이용한 객체 생성
@NoArgsConstructor 파라미터가 없는 기본 생성자 생성
@AllArgsConstructor 모든 속성에 대한 생성자 생성
@RequiredArgsConstructor 초기화되지 않은 Final, @NonNull 어노테이션이 붙은 필드에 대한 생성자 생성
@Log log 변수 자동 생성
@Value 불변(immutable) 클래스 생성
@Data @ToString, @EqualsAndHashCode, @Getter, @Setter, @RequiredArgsConstructor를 합친 어노테이션

 


Entity Mapping 관련 어노테이션

어노테이션 설명
@Entity 클래스를 엔티티로 선언 (JPA에 엔티티 클래스라는 것을 알려줌)
@Table 엔티티와 매핑할 테이블을 지정
@Id 테이블의 기본키에 사용할 속성을 지정
@GeneratedValue 키 값을 생성하는 전략 명시
@Column 필드와 컬럼 매핑
@Lob BLOB, CLOB 타입 매핑
@CreationTimestamp insert 시 시간 자동 저장
@UpdateTimestamp update 시 시간 자동 저장
@Enumerated enum 타입 매핑
@Transient 해당 필드 데이터베이스 매핑 무시
@Temporal 날짜 타입 매핑
@CreateDate 엔티티가 생성되어 저장될 때 시간 자동 저장
@LastModifiedDate 조회한 엔티티의 값을 변경할 때 시간 자동 저장

 

💡 CLOB과 BLOB 의미
     CLOB 이란 사이즈가 큰 데이터를 외부 파일로 저장하기 위한 데이터 타입. 문자형 대용량 파일을 저장하는데 사용
     BLOB 이란 바이너리 데이터를 DB 외부에 저장하기 위한 타입. 이미지, 사운드, 비디오 같은 멀티미디어 데이터를 다룰 때 사용

 


@Column 속성

속성 설명 기본값
name 필드와 매핑할 컬럼의 이름 설정 객체의 필드 이름
unique(DDL) 유니크 제약 조건 설정  
insertable insert 가능 여부 true
updatable update 가능 여부 true
length String 타입의 문자 길이 제약조건 설정 255
nullable(DDL) null 값의 허용 여부 설정. false 설정 시 DDL 생성 시에 not null 제약 조건 추가  
columnDefinition 데이터베이스 컬럼 정보 직접 기술

ex.
@Column(columnDefinition = "varchar(5) default'10' not null")
 
precision, scale(DDL) BigDecimal 타입에서 사용(BigInteger 가능) precision은 소수점을 포함한 전체 자리수이고, scale은 소수점 자리수.
Double과 float 타입에는 적용되지 않음.
 

 

💡 DDL(Data Definition Language)
     테이블, 스키마, 인덱스, 뷰, 도메인을 정의, 변경, 제거할 때 사용하는 언어.
     가령, 테이블을 생성하거나 삭제하는 CREATE, DROP 등이 이에 해당

 


@GeneratedValue

🚀  Entity 클래스는 반드시 기본키를 가져야 함

생성 전략 설명
GenerationType.AUTO(default) JPA 구현체가 자동으로 생성 전략 결정
GenerationType.IDENTITY 기본키 생성을 DB에 위임
ex. MySQL 의 경우 AUTO_INCREMENT를 사용하여 기본키 생성
GenerationType.SEQUENCE DB 시퀀스 오브젝트를 이용한 기본키 생성
@SequenceGenerator를 사용하여 시퀀스 등록 필요
GenerationType.TABLE 키 생성용 테이블 사용. @TableGenerator 필요

 

 

📍   관련글    

2024.02.24 - [Database/MySQL] - [MySQL] 제약조건 | 식별자, 기본키, 복합키, 유니크, 체크

2024.02.24 - [Database/MySQL] - [MySQL] 제약조건 | 일련번호, 시퀀스, AUTO_INCREMENT

 

 

 

 

 

 

* 내용 참고 : 책 '스프링부트 쇼핑몰 프로젝트 with JPA'


1.  Thymeleaf

스프링과 마찬가지로 스프링 부트도 다양한 뷰 view 관련 기술을 적용할 수 있음. 스프링은 대부분 JSP를 위주로 개발하는 경우가 많지만 스프링 부트는 Thymeleaf라는 템플릿 엔진을 주로 이용.

Thymeleaf는 '템플릿'이기 때문에 JSP 처럼 직접 데이터를 생성하지 않고, 만들어진 결과에 데이터를 맞춰서 보여주는 방식으로 구현.
JSP와 마찬가지로 서버에서 동작하기는 하지만 Thymleaf는 HTML을 기반으로 화면을 구성하기 때문에 HTML에 조금 더 가까운 방식으로 작성


2.   Thymeleaf 기초 문법

Thymeleaf는 JSP를 대신하는 목적으로 작성된 라이브러리이므로, JSP에서 필요한 기능들을 Thymeleaf로 구성


1) 인텔리제이 설정

앞에서 작성한 hello.html을 열기. Thymeleaf를 이용하기 위해서 가장 중요한 설정은 네임스페이스 xmlns에 Thymeleaf를 지정. 네임스페이스를 지정하면 'th:'와 같은 Thymeleaf의 모든 기능을 사용할 수 있음.

작성된 hello.html은 다음과 같이 'th:'로 시작하는 기능을 사용할 수 있지만 Model에 담긴 데이터를 사용할 때는 '해당 변수를 찾을 수 없다'는 방식으로 에러가 날 수 있음.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1 th:text="${msg}"></h1>
</body>
</html>


  📍  만일 에러가 발생하는 경우에는 인텔리제이의 설정을 조금 변경해서 에러 없는 화면을 보는 것이 더 좋기 때문에 Setting 메뉴에서 Thymeleaf를 검색하고 Unresolved references .. 를 체크 해제 해 주도록 함

    ➡️  설정을 변경하고 기존에 열려있는 hello.html 에디터를 종료한 후에 다시 에디터로 보면 변수에 대한 검사를 하지 않는 것을 확인


2)  Thymleaf 출력

Model로 전달된 데이터를 출력하기 위해서 HTML 태그 내에 'th:'로 시작하는 속성을 이용하거나 inlining을 이용
SampleController에 ex1()을 추가해서 '/ex/ex1'이라는 경로를 호출할 때 동작하도록 구성

 @GetMapping("/ex/ex1")
    public void ex1(Model model) {
        List<String> list = Arrays.asList("AAA", "BBB", "CCC", "DDD");

        model.addAttribute("list", list);
    }

 

ex1()의 결과 화면은 templates 내에 ex 디렉토리를 생성하고 ex1.html을 추가
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!-- inlining 사용 -->
<h4>[[${list}]]</h4>
<hr>
<!-- 'th:' 속성 사용 -->
<h4 th:text="${list}"></h4>
</body>
</html>


 

3)  Thymeleaf 주석 처리

Thymeleaf가 작성하는 단계에서는 단순해 보이지만 디버깅할 때는 상황이 다름. 에러가 발생하게 되면 에러의 원인을 찾아내기 힘들다.
에러가 난 부분을 찾기 위해서는 주석 처리를 해야할 때는 '<!--/* ... */-->'를 이용하는 것이 좋음. 주석은 Thymeleaf가 파싱 처리할 때 삭제되어 처리되기 때문에 1) 잘못된 문법 에 대한 체크도 건너 뛸 수 있고, 삭제된 상태에서 처리되므로 2)브라우저에서는 아예 해당 부분은 결과 자체가 없음

 

hello.html 수정

 


 

4)  th:with를 이용한 변수 선언

Thymeleaf를 이용하는 과정에서 임시로 변수를 선언해야 하는 상황에서는 'th:with'를 이용해서 간단히 처리 가능
'th:with'로 만드는 변수를 '변수명 = 값'의 형태로 ', '를 이용해서 여러 개를 선언 할 수도 있음

    <div th:with="num1 = ${10}, num2 = ${20}">
        <h4 th:text="${num1 + num2}"></h4>
    </div>

 


3.  반복문과 제어문 처리

화면 구성에서 가장 많이 사용되는 반복문과 제어문 처리.
SampleController의 ex1()에서는 Model을 이용해 'List<String>'을 담고 ex1.html을 이용해서 출력하도록 구성.

 @GetMapping("/ex/ex1")
    public void ex1(Model model) {
        List<String> list = Arrays.asList("AAA", "BBB", "CCC", "DDD");

        model.addAttribute("list", list);
    }

 

반복문 처리는 크게 2가지 방법을 이용


    -  반복이 필요한 태그에 'th:each'를 적용하는 방법
    -  <th:block>이라는 별도의 태그를 이용하는 방법


    💡  'th:each' 속성을 이용할 때는 기존의 HTML을 그대로 둔 상태에서 반복 처리를 할 수 있다는 장점이 있지만 JSTL 과는 조금 이질적인 형태이고, <th:block>을 이용할 때는 추가로 태그가 들어가는 단점이 있음.

 

ex1.html의 내용을 변경
<ul>
    <li th:each="str: ${list}" th:text="${str}"></li>
</ul>

<ul>
    <th:block th:each="str: ${list}">
        <li>[[${str}]]</li>
    </th:block>
</ul>


 

1)  반복문의 status 변수

Thymeleaf는 th:each를 처리할 때 현재 반복문의 내부 상태에 변수를 추가해서 사용할 수 있음.
일명 status 변수라고 하는데 index / count / size / first / last / odd / even 등을 이용해서 자주 사용하는 값들을 출력할 수 있음

<ul>
    <li th:each="str, status: ${list}">
        [[${status.index}]] -- [[${str}]]
    </li>
</ul>

 

  ✓  status 변수명은 사용자가 지정할 수 있고, index는 0부터 시작하는 번호를 의미. count는 1부터 시작


 

2)  th:if / th:unless / th:switch

Thymeleaf는 제어문의 형태로 th:if / th:unless / th:switch를 이용할 수 있음

th:if / th:unless는 별도의 속성으로 사용할 수 있으므로 if ~ else 와는 다르게 사용
예를 들어 반복문의 홀수 / 짝수를 구분해서 처리하고 싶다면 다음과 작성

<ul>
    <li th:each="str, status: ${list}">
        <span th:if="${status.odd}">ODD -- [[${str}]]</span>
        <span th:unless="${status.odd}">EVEN -- [[${str}]]</span>
    </li>
</ul>


 

📍  ?를 이용하면 앞선 방식보다는 좀 더 편하게 이항 혹은 삼항 처리가 가능
      예를 들어 반복 중에 홀수 번째만 무언가를 보여주고 싶다면 다음과 같이 ? 뒤에 하나만 표현식을 사용할 수 있음

<ul>
    <li th:each="str, status: ${list}">
        <span th:text="${status.odd} ? 'ODD ---' + ${str}"></span>
    </li>
</ul>


 

📍  ?를 삼항연산자 그대로 사용할 수도 있음

<ul>
    <li th:each="str, status: ${list}">
        <span th:text="${status.odd} ? 'ODD ---' + ${str} : 'EVEN ---' + ${str}"></span>
    </li>
</ul>


 

📍 th:switch는 th:case와 같이 사용해서 Switch 문을 처리할 때 사용할 수 있음

<ul>
    <li th:each="str, status: ${list}">
        <th:block th:switch="${status.index % 3}">
            <span th:case="0">0</span>
            <span th:case="1">1</span>
            <span th:case="2">2</span>
        </th:block>
    </li>
</ul>


4.  Thymeleaf 링크 처리

  -  Thymeleaf는 '@'로 링크를 작성하기만 하면 됨

<a th:href="@{/hello}">Go to /hello </a>

 

 

1) 링크의 쿼리 스트링 처리

 

링크를 'key=value'의 형태로 필요한 파라미터를 처리해야 할 때 상당히 편리.

쿼리 스트링은 '()'를 이용해서 파라미터의 이름과 값을 지정.

<a th:href="@{/hello(name="AAA", age=16)}">Go to /hello </a>


 

📍  GET 방식으로 처리되는 링크에서 한글이나 공백 문자는 항상 주의해야 하는데 Thymeleaf를 이용하면 이에 대한 URL 인코딩 처리가 자동으로 이루어짐

<a th:href="@{/hello(name="한글처리", age=16)}">Go to /hello</a>


 

📍  만일 링크를 만드는 값이 배열과 같이 여러 개일 때는 자동으로 같은 이름의 파라미터를 처리

<a th:href="@{/hello(types=${{'AAA', 'BBB', 'CCC'}}, age=16}">Go to /hello</a>


5.  Thymeleaf의 특별한 기능들

1)  인라인 처리

Thymeleaf는 여러 편리한 점이 있지만 상황에 따라 동일한 데이터를 다르게 출력해 주는 인라인 기능은 자바 스크립트를 사용할 때 편리한 기능.

 

SampleController에 다양한 종류의 데이터를 Model에 담아서 전달하는 메서드를 추가

 

  • 추가되는 코드는 내부 클래스인 SampleDTO와 ex2()
  • SampleDTO를 정의할 때는 반드시 getter들을 만들어 줌
class SampleDTO {
        private String p1, p2, p3;

        public String getP1() {
            return p1;
        }

        public String getP2() {
            return p2;
        }

        public String getP3() {
            return p3;
        }

    }

 

@GetMapping("ex/ex2")
    public void ex2(Model model) {

        log.info("ex/ex2..........");

        List<String> strList = IntStream.range(1,10)
                .mapToObj(i -> "Data"+i)
                .collect(Collectors.toList());

        model.addAttribute("list", strList);

        Map<String, String> map = new HashMap<>();
        map.put("A", "AAAA");
        map.put("B", "BBBB");

        model.addAttribute("map", map);

        SampleDTO sampleDTO = new SampleDTO();
        sampleDTO.p1 = "Value -- p1";
        sampleDTO.p2 = "Value -- p2";
        sampleDTO.p3 = "Value -- p3";

        model.addAttribute("dto", sampleDTO);

    }

 

화면 구성을 위해 ex2.html을 추가
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div th:text="${list}"></div>
    <div th:text="${map}"></div>
    <div th:text="${dto}"></div>

    <script th:inline="javascript">

        const list = [[${list}]]

        const map = [[${map}]]

        const dto = [[${dto}]]

        console.log(list)
        console.log(map)
        console.log(dto)

    </script>
</body>
</html>

 

📍 HTML 코드를 이용하거나 자바 스크립트 코드를 이용할 때 같은 객체를 사용. 다만 차이점은 <script th:inline="javascript">가 지정된 점

📍 프로젝트를 실행해서 만들어진 결과를 보면 HTML은 기존처럼 출력되고, <script> 부분은 자바 스크립트에 맞는 문법으로 만들어진 것을 확인

 


2)  Thymeleaf의 레이아웃 기능

Thymeleaf의 <th:block>을 이용하면 레이아웃을 만들고 특정한 페이지에서는 필요한 부분만을 작성하는 방식으로 개발이 가능

 

레이아웃 기능을 위해서 별도의 라이브러리가 필요하므로 build.gradle에 추가
// 레이아웃 기능
implementation group: 'nz.net.ultraq.thymeleaf', name: 'thymeleaf-layout-dialect', version: '3.1.0'

 

templates에 layout 폴더를 생성하고 레이아웃을 위한 layout1.html을 작성
<!DOCTYPE html>
<html lang="en"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Layout page</title>
</head>
<body>

    <div>
        <h3>Sample Layout Header</h3>
    </div>

    <div layout:fragment="content">
        <p>Page content goes here</p>
    </div>

    <div>
        <h3>Sample Layout Footer</h3>
    </div>

    <th:block layout:fragment="script">

    </th:block>

</body>
</html>

 

📍 코드 위쪽에는 http://www.ultraq.net.nz/thymeleaf/layout을 이용해서 Thymeleaf의 Layout을 적용하기 위한 네임스페이스를 지정
📍 코드 중간에는 layout:fragment 속성을 이용해서 해당 영역은 나중에 다른 파일에서 해당 부분만을 개발할 수 있음
     ➡️  layout1.html에는 content와 script 부분을 fragment로 지정

 

SampleController에 레이아웃 예제를 위한 ex3()을 추가
  @GetMapping("/ex/ex3")
    public void ex3(Model model) {
        model.addAttribute("arr", new String[] {"AAA", "BBB", "CCC"});
    }

 

templates의 ex폴더에 ex3.html을 생성. 가장 중요한 부분은 <html>에서 사용된 레이아웃 관련 설정
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layout/layout1.html}">

<div layout:fragment="content">

    <h1>ex3.html</h1>

</div>

 

fragment에 content 부분만 작성한 것을 확인

 

 

layout1.html에는 content와 script 영역을 따로 구성했으므로 이를 이용해서 자바스크립트를 처리하고 싶다면 별도의 영역을 지정하고 fragment를 지정
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layout/layout1.html}">

<div layout:fragment="content">

    <h1>ex3.html</h1>

</div>

<script layout:fragment="script" th:inline="javascript">

    const arr = [[${arr}]]

</script>

 


1.  스프링 부트란 ?

🐰  스프링 부트는 엄밀하게 말하면 '스프링 프레임워크 개발 도구'. 즉, 엔터프라이즈급 에플리케이션을 개발 하기 위해서 필요한 기능을 제공하는 개발 도구
🐰  스프링의 중요한 특징자동 설정 Auto Configuration (라이브러리만으로 설정을 인식하려는 특성) 을 내세울 수 있음
     예를 들어 스프링 부트는 데이터베이스와 관련된 모듈을 추가하면 자동으로 데이터베이스 관련 설정을 찾아서 실행
      ✓  스프링만을 이용하는 경우와 비교해보면 추가한 모듈 설정이 전혀 필요하지 않고, 설정 자체도 단순
🐰  다른 특징으로는 '내장 톰캣'과 단독 실행 가능한 도구라는 점. 스프링 부트는 별도의 서버 설정 없이도 개발이 가능하고, 실행도 가능

 

 

1) 기존 개발과 차이점


✓ 설정과 관련해서는 직접 필요한 라이브러리를 기존 build.gradle 파일에 추가하는 설정이 상당히 단순하기도 하지만 자동으로 처리

✓ 특히 톰캣이 내장된 상태로 프로젝트가 생성되기 때문에 WAS의 추가 설정이 필요하지 않다는 점도 편리
✓ 빈 설정은 XML을 대신해서 자바 설정을 이용하는 것으로 약간의 변경이 있음

스프링 MVC에서는 JSP를 이용할 수 있지만 기본 설정이 아니라서 라이브러리를 추가해야 되고, 스프링 부트는 Thymeleaf라는 템플릿 엔진을 활용하는 경우가 많음
스프링 부트에서도 MyBatis를 이용할 수 있지만, JPA를 이용하면 객체지향으로 구성된 객체들을 데이터베이스에 반영할 수 있는데 이를 자동으로 처리할 수 있으므로 별도의 SQL의 개발 없이도 개발이 가능

 

스프링 부트의 프로젝트 생성 방식


  👾  스프링 부트를 위한 프로젝트의 생성 방법은 크게 2가지
      -  Spring Initializr를 이용한 자동 생성 ( 대부분 많이 사용. 프로젝트의 기본 템플릿 구조를 만들어 주기 때문 )

            ➡️  웹 사이트(https://start.spring.io/) 에서 프로젝트를 생성하거나, 이클립스나 인텔리제이, VS Code 등에서도 Spring Initializr를 지원하기 때문에 호환성 면에서도 유리
      -  Maven이나 Gradle을 이용한 직접 생성

  👾  스프링 부트는 스프링을 쉽게 사용하기 위한 도구이므로 프로젝트를 생성하고 필요한 라이브러리들을 추가하는 형태의 개발도 가능


 

2.  프로젝트의 실행

🐰  스프링 부트의 프로젝트는 이미 서버를 내장한 상태에서 만들어지기 때문에 스프링만을 이용할 때와 달리 별 의 WAS (Web Application Server) 설정이 필요하지 않고 main() 메서드의 실행을 통해서 프로젝트를 실행

 

main()을 실행하면 자동으로 내장된 톰캣이 실행되는 것을 로그를 통해서 확인

  ✓  실행 결과는 에러가 발생
  ✓  스프링 부트가 자동 설정을 통해서 인식한 Spring Data JPA를 실행했을 때 DB와 관련된 설정을 찾을 수 없어서 발생한 에러

  📍  에러가 발생하긴 했지만, 아무런 설정이 없는 상태인데 자동으로 데이터베이스 관련 설정을 이용을 함
        ➡️  이와 같이 라이브러리만으로 설정을 인식하려는 특성을 '자동 설정 auto configuration'이라고 함

  📍  스프링 부트 설정은 프로젝트 생성 시에 만들어진 application.properties 파일을 이용하거나 application.yml (YAML 이라고 함) 파일을 이용할 수 있음
        ➡️  만일 파일 설정을 피하고 싶으면 @Configuration이 있는 클래스 파일을 만들어서 필요한 설정을 추가할 수 있음

  📍  대부분의 스프링을 지원하는 개발 도구 IDE에서는 application.properties 파일에 들어갈수 있는 내용을 쉽게 완성해 주는 기능을 제공

 

application.properties 파일에 데이터베이스 설정을 추가
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/sample
spring.datasource.username="아이디"
spring.datasource.password="비밀번호"

 

  💡  만일8080 port가 다른 프로젝트에서 실행되고 있다면 포트번호를 application.properties에서 server.port를 지정해서 변경할 수 있음

server.port=8082

 

서블릿이나 스프링처럼 프로젝트를 실행하면 브라우저가 자동으로 실행되지 않음. 부트의 경우 서버를 내장하고 있어서 브라우저가 자동 실행이 안되어서, 직접 브라우저를 열어서 주소창에 주소를 입력해야 함


 

3.  편의성을 높이는 몇 가지 설정

DataSource를 이용하는 것만으로도 스프링 부트가 편리하다는 사실을 알수 있지만, 추가적인 설정 몇 가지만 더 한다면 개발 시간을 더욱 단축할 수 있음

자동 리로딩 설정

 

  • 웹 개발 시에 코드를 수정하고 다시 deploy를 하는 과정을 자동으로 설정. Edit Configuration 메뉴를 실행
  • Bulid and run 메뉴에 있는 Modify options를 선택
  • On 'Update' action / On frame deactivation 의 옵션값을 Update classes and resources 로 지정

 

 

Lombok을 테스트 환경에서도 사용하기


스프링 부트는 체크박스를 선택하는 것만으로 Lombok 라이브러리를 추가하지만 테스트 환경에서는 설정이 빠져 있음
build.gradle 파일 내 dependencies 항목에 test 관련 설정을 조정

// lombok을 테스트 환경에서도 사용.
testCompileOnly('org.projectlombok:lombok')
testAnnotationProcessor('org.projectlombok:lombok')



로그 레벨의 설정


스프링 부트는 기본적으로 Log4j2가 추가되어 있기 때문에 라이브러리를 추가하지 않아도 됨.
applocation.properties 파일을 이용해서 간단하게 로그 설정을 추가할 수 있음.

logging.level.org.springframework=info
logging.level.kr.nomadlab=debug

 

 

인텔리제이의 DataSource 설정


인텔리제이 ultimate의 경우 JPA 관련 플러그인이 이미 설치되어 있기 때문에 DataSource를 설정해두면 나중에 엔티티 클래스의 생성이나 기타 클래스의 생성과 설정 시에 도움이 됨

 

 

테스트 환경과 의존성 주입 테스트


스프링에는 'spring-test-xxx' 라이브러리를 추가해야 하고 JUnit 등도 직접 추가 해야만 하지만, 스프링 부트는 프로젝트 생성할 때 이미 테스트 관련 설정이 완료되고 테스트 코드가 하나 생성되어 있음

 

  ✓  테스트 코드의 실행을 점검하기 위해서 DataSourceTest를 작성해서 HikariCP의 테스트와 Lombok을 확인

  ✓  DataSource는 application.properties에 설정된 DataSource 관련 설정을 통해서 생성된 빈 Bean이고, 이에 대한 별도의 설정 없이 스프링에서 바로 사용이 가능

package com.example.springboot;

import lombok.Cleanup;
import lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

@SpringBootTest
@Log4j2
public class DataSourceTest {
    @Autowired
    private DataSource dataSource;

    @Test
    public void connection() throws SQLException {
        @Cleanup Connection connection = dataSource.getConnection();

        log.info(connection);
        Assertions.assertNotNull(connection);
    }
}

 

 

Spring Data JPA을 위한 설정


DataSource 설정까지 모든 테스트가 완료되었다면 Spring Data JPA를 이용할 때 필요한 설정을 추가
application.properties에 다음과 같은 내용을 추가

spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.show-sql=true

 

  ✓  spring.jpa.hibernate.ddl-auto 속성은 프로젝트 실행 시 DDL 문을 처리 할 것인지를 명시
  ✓  DDL : Data Definition Language  ➡️  테이블이나 관계의 구조를 생성하는데 사용하며 CREATE, ALTER, DROP, TRUNCATE 문 등이 있음

  ✓  spring.jpa.properties.hibernate.format_sql 속성은 실제로 실행되는 SQL을 포맷팅해서 알아보기 쉽게 출력
  ✓  spring.jpa.show-sql은 JPA가 실행하는 SQL을 같이 출력

 

속성값 의미
none DDL을 하지 않음
create-drop 실행할 때 DDL을 실행하고 종료시에 만들어진 테이블 등을 모두 삭제
create 실행할 때마다 새롭게 테이블을 생성
update 기존과 다르게 변경돤 부분이 있을 때는 새로 생성
validate 변경된 부분만 알려주고 종료

 

📍  update 속성 값의 경우 테이블이 없을 때는 자동으로 생성하고 변경이 필요할 때는 alter table이 실행. 테이블뿐만 아니라 인덱스나 외래키 등도 자동으로 처리

 


 

4.  스프링 부트에서 웹 개발

🐰  스프링 부트를 이용해서 웹을 개발하는 일은 컨트롤러나 화면을 개발하는 것은 유사하지만, web.xml이나 server-context.xml과 같은 웹 관련 설정 파일들이 없기 때문에 이를 대신하는 클래스를 작성해 준다는 점이 다름
   ✓  xml을 통한 설정은 오류가 발생했을 때 찾기 힘듬. 마크업 언어라서 문법을 체크하는데 한계가 있음. 인텔리제이같은 통합 개발 환경에서도 자동완성을 사용하기 힘듬

 

1) 컨트롤러와 Thymeleaf 만들기

프로젝트에 우선 controller라는 패키지를 생성하고 SampleController 클래스를 생성
SampleController 자체의 개발은 기존의 스프링 MVC를 그대로 이용

@Controller
@Log4j2
public class SampleController {

    @GetMapping("/hello")
    public void hello(Model model) {

        log.info("hello...");
        model.addAttribute("msg", "Hello World");

    }
}

 

  📍 화면은 Thymeleaf를 이용하는데 위치를 주의해서 작성. 프로젝트 생성 시에 만들어져 있는 resources/templates 폴더에 hello.html을 작성
  📍 중요한 부분은 Thymeleaf의 네임스페이스 namespace를 추가

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1 th:text="${msg}"></h1>
</body>
</html>

 

 

 

Thymeleaf는 JSP와 동일하게 서버에서 결과물을 생성해서 보내는 방식이지만
좀더 HTML에 가깝게 작성할 수 있고 다양한 기능을 가지고 있음

 

 

 

 


 

2)  JSON 데이터 만들기

스프링을 사용할 때는 jackon-databind 라는 별도의 라이브러리를 출력한 후에 개발할 수 있지만, 스프링 부트는 'web' 항목을 추가할 때 자동으로 포함되므로 별도의 설정 없이 바로 개발할 수 있음

 

controller 패키지에 SampleJSONController라는 클래스를 작성
import lombok.extern.log4j.Log4j2;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@Log4j2
@RestController
public class SampleJSONController {

    @GetMapping("/helloArr")
    public String[] helloArr() {
        log.info("HelloArr...");

        return new String[] {"AAA", "BBB", "CCC"};
    }

}

 

브라우저에 'helloArr' 경로를 호출하면 배열이 그대로 출력. 중요한 점은 서버에서 해당 데이터는 Content_Type을 'application/json' 방식 으로 전송

 

+ Recent posts