네로개발일기

개발자 네로의 개발 일기, 자바를 좋아합니다 !

'2022/04'에 해당되는 글 17건


반응형

Lists in Thymelead Example

예제를 위해 Book.java 을 생성한다.

@Getter
@Setter
public class Book {

    private Long id;
    private String title;
    private String author;
}

 

Displaying List Elements

BookController.java

@GetMapping("/all")
public String showAll(Model model) {
    model.addAttribute("books", bookService.findAll());
    return "books/allBooks";
}

books/allBooks.html

<table>
  <thead>
    <tr>
      <td>Title</td>
      <td>Author</td>
    </tr>
  </thead>
    <tr th:if="${books.empty}">
      <td colspan="2">No Books Avaliable</td>
    </tr>
    <tr th:each="book : ${books}">
      <td th:text="${book.title}">Title</td>
      <td th:text="${book.author}">Author</td>
    </tr>
  <tbody>
  </tbody>
</table>

th:each 속성을 사용해서 list의 iterate 문을 사용할 수 있다.

Binding a List Using Selection Expression

@AllArgsConstructor
@Getter
@Setter
public class BooksCreationDto {
    
    private List<Book> books;
    
    public void addBook(Book book) {
        this.books.add(book);
    }
}

controller에서 List 객체를 전송하기 위해선 List 객체를 그대로 사용할 수 없다. 위와 같이 wrapper 객체를 만들어주어야 한다.

 

@GetMapping("/create")
public String showCreateForm(Model model) {
    BooksCreationDto booksForm = new BooksCreationDto();

    for (int i = 1; i <= 3; i++) {
        booksForm.addBook(new Book());
    }

    model.addAttribute("form", booksForm);
    return "books/createBooksForm";
}

Model 속성에 3개의 빈 Book 객체를 생성하여 추가하였다.

 

<form action="#" th:action="@{/books/save}" th:object="${form}"method="post">
    <fieldset>
        <input type="submit" id="submitButton" th:value="Save">
        <input type="reset" id="resetButton" name="reset" th:value="Reset"/>
        <table>
            <thead>
                <tr>
                    <th> Title</th>
                    <th> Author</th>
                </tr>
            </thead>
            <tbody>
                <tr th:each="book, itemStat : *{books}">
                    <td><input th:field="*{books[__${itemStat.index}__].title}" /></td>
                    <td><input th:field="*{books[__${itemStat.index}__].author}" /></td>
                </tr>
            </tbody>
        </table>
    </fieldset>
</form>

Thymeleaf를 실행하면 3개의 빈 Book 객체가 있을 것이다.

th:object="${form}"

form에서 submit을 할 때, form의 데이터가 th:object에 설정해준 객체로 받아진다.

<tr th:each="book, itemStat : *{books}">

각각 필드들을 매핑을 해주는 역할을 한다. 설정해 준 값으로, th:object에 설정해 준 객체의 내부와 매칭해준다.

@PostMapping("/save")
public String saveBooks(@ModelAttribute BooksCreationDto form, Model model) {
    bookService.saveAll(form.getBooks());

    model.addAttribute("books", bookService.findAll());
    return "redirect:/books/all";
}

@ModelAttribute 어노테이션을 활용하여 객체를 가져올 수 있다.

Book list를 저장하고 list 화면으로 redirect 해주면 다음과 같은 화면을 얻을 수 있다.

Binding a List Using Variable Expression

@GetMapping("/edit")
public String showEditForm(Model model) {
    List<Book> books = new ArrayList<>();
    bookService.findAll().iterator().forEachRemaining(books::add);

    model.addAttribute("form", new BooksCreationDto(books));
    return "books/editBooksForm";
}
<tr th:each="book, itemStat : ${form.books}">
    <td>
        <input hidden th:name="|books[${itemStat.index}].id|" th:value="${book.getId()}"/>
    </td>
    <td>
        <input th:name="|books[${itemStat.index}].title|" th:value="${book.getTitle()}"/>
    </td>
    <td>
        <input th:name="|books[${itemStat.index}].author|" th:value="${book.getAuthor()}"/>
    </td>
</tr>

적절하게 data를 submit하기 위해선 name과 value값을 지정해주어야 한다. edit 해주기 위해선 books.id도 전달해주어야 하는데, hidden 속성을 통해 보이지 않게 전달하고 있다.

 

출처

https://www.baeldung.com/thymeleaf-list

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

Express Example

적당한 위치에 애플리케이션 디렉터리를 생성하고 npm init을 실행한다. 일단 기본 설정으로 package.json을 생성한다.

$ mkdir express-mysql-example
$ cd express-mysql-example
$ npm init --yes

mysql 모듈을 설치한다.

$ npm install mysql

package.json을 아래와 같이 수정한다.

{
  "name": "express-mysql-example",
  "version": "0.0.1",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node index"
  },
  "dependencies": {
    "mysql": "^2.18.1"
  }
}

Node.js와 MySQL 연동

index.js를 아래와 같이 변경한다. 

createConnection 메서드의 인자로 전달되는 객체에 자신의 데이터베이스 정보(유저명과 패스워드 등)를 입력해야 한다. 

const mysql = require('mysql');
const connection = mysql.createConnection({
    host	: 'localhost',
    user	: 'jy.jeon',
    password: 'password',
    database: 'my_db'
});

connection.connect();

connection.query('SELECT * FROM Users', (error, rows, fields) => {
    if (error) throw error;
    console.log('User info is: ', rows);
});

connection.end();

콘솔에 아래와 같은 결과가 출력되면 성공이다.

User info is:  [ RowDataPacket { id: 'nero', password: '1234' } ]

만약 ‘ER_NOT_SUPPORTED_AUTH_MODE’ 에러가 발생하면 아래 sql을 실행하고 다시 접속해보자.

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';
FLUSH PRIVILEGES;

 

npm start로 실행한다.

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

DTO(Data Transfer Object)

계층(Layer)간 데이터를 교환하기 위한 객체

DTO의 특징

1. DTO는 데이터 접근 메서드 외 기능을 가지고 있지 않는다. (getter, setter 메서드 외에 비즈니스 로직을 가지지 않는다.)

- 정렬, 직렬화 등 데이터 표현을 위한 기능은 가질 수 있다.

2. 값을 유연하게 변경할 수 있다. (가변성, mutable)

3. 데이터 캡슐화를 통해 유연한 대응이 가능하다.

- 데이터 요청 수 감소 효과

 

VO (Value Object)

값을 가지는 객체

VO의 특징

1. 변하지 않는 값을 가지는 객체(불변성, immutable)

- 값이 변하지 않음을 보장하며 코드의 안정성과 생산성을 높임

2. 값이 같다면 동일한 객체

- 각 객체를 비교하는데 사용되는 ID가 없음

- 같은 객체인지 판단하기 위해 각 속성들의 값을 비교함

- equals() 메서드와 hashCode() 메서드를 오버라이드해서 객체 비교를 구현함

 

공통점 및 차이점

공통점

- 레이어 간 데이터를 전달할 때 사용 가능

- VO는 불변을 보장하기 때문에 데이터 전달 용도로 사용 가능

차이점

DTO VO
값이 변할 수 있음 (가변 객체) 값이 변하지 않음 (불변 객체)
레이어와 레이어 사이에서 사용 가능 모든 레이어에서 사용 가능
내부의 속성(필드)값이 같아도 다른 객체로 식별 내부의 속성(필드)값들이 같다면 같은 객체로 식별
데이터 접근 이외의 기능을 가지지 않음 특정 비즈니스 로직을 가질 수 없음

 

 참고 

https://parkadd.tistory.com/53

 

DTO와 VO 둘 의 사실과 오해 (그리고 Entity)

보통 DTO == VO 즉, DTO와 VO는 혼용해도 된다, DTO와 VO는 같은것이다. 라는 오해가 많이 있습니다. (그런 오해를 가진 분들을 위한 글입니다!! 이미 알고 계신분은 글을 읽고 검수 해주시면 더욱 감사

parkadd.tistory.com

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

The Scheduled Annotation in Spring

@Scheduler 를 사용해서 일정한 시간 간격으로, 혹은 특정 일정에 코드가 실행되도록 해보자.

 

Spring Scheduler

Dependency

spring-boot-starter에 기본적으로 의존 org.springframework.scheduling

Enable Scheduling 

- Project Application Class에 @EnableScheduling 추가

@EnableScheduling // 추가
@SpringBootApplication
public class ScheduledrApplication {

    public static void main(String[] args) {
        SpringApplication.run(ScheduledrApplication.class, args);
    }
}

- scheduler를 사용할 클래스에 @Component, 메서드에 @Scheduled 추가

 

[@Scheduled 규칙]

  • 메서드는 void 타입으로
  • 메서드는 매개변수 사용 불가

Example

fixedDelay

- 해당 메서드가 끝나는 시간 기준, milliseconds 간격으로 실행

- 하나의 인스턴스만 항상 실행되도록 해야 할 상황에서 유용

@Scheduled(fixedDelay = 1000)
// @Scheduled(fixedDelayString = "${fixedDelay.in.milliseconds}") // 문자열 milliseconds 사용 시
public void scheduledFixedDelayTask() throws InterruptedException {
    log.info("Fixed delay task - {}", System.currentTimeMillis() / 1000);
    Thread.sleep(5000);
}

 

fixedRate

- 해당 메서드가 시작하는 시간 기준 milliseconds 간격으로 실행

- 병렬로 Scheduler를 사용할 경우, 클래스에 @EnableAsync, 메서드에 @Async 추가

- 모든 실행이 독립적인 경우에 유용

@Async
@Scheduled(fixedRate = 1000)
// @Scheduled(fixedRateString = "${fixedDelay.in.milliseconds}") // 문자열 milliseconds 사용 시
public void scheduledFixedRateTask() throws InterruptedException {
    log.info("Fixed rate task - {}", System.currentTimeMillis() / 1000);
    Thread.sleep(5000);
}

fixedDelay + fixedRate

- initialDelay 값 이후 처음 실행되고, fixedDelay 값에 따라 계속 실행

@Scheduled(fixedDelay = 1000, initialDelay = 5000) 
public void scheduleFixedRateWithInitialDelayTask() { 
    long now = System.currentTimeMillis() / 1000; 
    log.info("Fixed rate task with one second initial delay - {}", now); 
}

Cron

- 작업 예약으로 실행

@Scheduled(cron = "0 15 10 15 * ?") // 매월 15일 오전 10시 15분에 실행
// @Scheduled(cron = "0 15 10 15 11 ?") // 11월 15일 오전 10시 15분에 실행
// @Scheduled(cron = "${cron.expression}")
// @Scheduled(cron = "0 15 10 15 * ?", zone = "Europe/Paris") // timezone 설정
public void scheduleTaskUsingCronExpression() {
    long now = System.currentTimeMillis() / 1000;
    log.info("scheduled tasks using cron jobs - {}", now);
}

 

Setting Information

fixedDelay

- 이전 작업이 종료된 후 설정 시간(milliseconds) 이후에 다시 시작

- 이전 작업이 완료될 때까지 대기

fixedDelayString 

- fixedDelay와 동일, 설정 시간(milliseconds)을 문자로 입력하는 경우에 사용

fixedRate

- 고정 시간 간격으로 시작

- 이전 작업이 완료될 때까지 다음 작업이 진행되지 않음

- 병렬 동작을 사용할 경우 @Async 추가

fixedRateString

- fixedRate와 동일, 설정시간을 문자로 입력하는 경우에 사용

initialDelay

- 설정된 initialDelay 시간 후부터 fixedDelay 시간 간격으로 실행

initialDelayString

- initialDelay와 동일, 설정시간을 문자로 입력하는 경우에 사용

cron

- cron 표현식을 사용한 작업 예약

- 첫번째부터 초(0-59), 분(0-59), 시간(0-23), 일(1-31), 월(1-12), 요일(0-7)

zone

- 미설정시 Local 시간대 사용

 

 

 

참고

https://www.baeldung.com/spring-scheduled-tasks

https://data-make.tistory.com/699

 

[Spring Boot] Scheduler 사용해보기(일정 주기로 실행하는 스프링 스케쥴러)

The Scheduled Annotation in Spring @Scheduler 를 사용해서 일정한 시간 간격으로, 혹은 특정 일정에 코드가 실행되도록 해보자. Spring Scheduler Dependency Spring Boot starter 에 기본적으로 의존 org.spri..

data-make.tistory.com

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

스프링 빈 생명주기 콜백

데이터베이스 커넥션 풀이나, 네트워크 소켓처럼 애플리케이션 시작 시점에 필요한 연결을 미리 해두고, 애플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면, 객체의 초기화와 종료 작업이 필요하다.

빈 생명주기 콜백은 스프링 빈이 생성된 후 의존관계 주입이 완료되거나 죽기 직전에 스프링 빈 안에 있는 메서드를 호출해주는 기능이다.

 

스프링 빈의 이벤트 사이클

스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 소멸 전 콜백 -> 스프링 종료

- 초기화 콜백: 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출

- 소멸전 콜백: 빈이 소멸되기 직전에 호출

 

스프링 빈은 크게 3가지 방법으로 빈 생명주기 콜백을 지원

- 인터페이스 (InitializingBean, DisposableBean)

- 설정정보 초기화 메서드, 종료 메서드 지정

- @PostConstruct, @PreDestroy

 

@PostConstruct, @PreDestroy

@PostConstruct
public void init() {
    System.out.println("초기화 메서드 호출");
}

@PreDestroy
public void destroy() {
    System.out.println("종료 메서드 호출");
}

- 패키지는 javax.annotation.PostConstruct이다. 스프링에 종속적인 기술이 아니라 JSR-250 표준이기 때문에 스프링이 아닌 다른 컨테이너에서도 동작한다.

- 컴포넌트 스캔과 잘 어울린다.

- 외부 라이브러리에는 적용하지 못한다. 외부 라이브러리를 초기화, 종료를 해야 할 경우, @Bean(initMethod="", destroyMethod="")를 사용하자.

 

@PostConstruct

- 객체의 초기화 단계

- 객체가 생성된 후, 별도의 초기화 작업을 위해 실행하는 메서드에 선언한다.

- @PostConstruct 어노테이션을 설정해놓은 Init 메서드는 WAS가 띄워질 때 실행된다.

 

@PreDestroy

- 객체 소멸 단계 직전

- 스프링 컨테이너에서 객체(빈)를 제거하기 전에 해야할 작업이 있다면 실행할 메서드에 선언한다.

- close() (AbstractApplicationContext) context.close() 하기 직전에 실행된다.

 

InitializingBean, DisposableBean 인터페이스

각각 afterPropertiesSet(), destroy()를 지원한다.

- afterPropertiesSet(): 의존관계 주입이 끝난 후 호출

- destroy(): 빈이 죽기 직전에 호출

 

* 단점

- 이 인터페이스는 스프링 전용 인터페이스이기 때문에 스프링에 의존한다.

- 초기화, 소멸 메서드의 이름을 변경할 수 없다.

- 외부 라이브러리에 적용할 수 없다.

 

빈 초기화, 소멸 메서드 지정

설정 정보에 @Bean(initMethod = "init", destroyMethod="close") 처럼 초기화, 소멸 메서드를 지정할 수 있다.

 

메서드 이름을 자유롭게 쓸 수 있고, 스프링 빈이 스프링 코드에 의존하지 않는다.

설정 정보를 사용하기 때문에 외부 라이브러리에도 초기화, 종료 메서드를 사용할 수 있다.

 

추론

@Bean의 destroyMethod 속성에는 특별한 기능인 추론이 있다.

라이브러리는 대부분 close, shutdown이라는 이름의 종료 메서드를 사용한다.

@Bean의 destroyMethod는 기본값이 inferred 추론으로 되어있다.

이 추론 기능은 close, shutdown이란 이름의 메서드를 자동으로 호출해준다. 이름 그대로 종료 메서드를 추론해서 호출해준다.

따라서 직접 스프링 빈으로 등록하면 종료 메서드는 따로 적어주지 않아도 잘 동작한다. 추론 기능을 사용하기 싫으면 destroyMethod=""처럼 공백을 지정하면 된다.

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !