반응형

프로젝트 루트에서 gradlew 빌드

$ ./gradlew build

경로 이동

$ cd bulid/libs

ls로 파일을 확인해보면 스냅샷을 확인할수 있다

자바 실행

$ java -jar hello-spring-0.0.1-SNAPSHOT.jar

 

 

참고자료

스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술

반응형
반응형

UUID.randomUUID()로 UUID를 생성하는 방법도 있지만 아주아주아주 희박한 확률로 중복이 발생할수 있다고 하여

시간 기반으로 UUID를 생성하는것을 찾아보았다

 

// 기존 UUID
String fileName = UUID.randomUUID().toString().replace("-", "");

// 시간기반 UUID
String fileName = Generators.timeBasedGenerator().generate().toString().replace("-", "");

 

시간기반이라고 해도 혹시나 모를 중복을 체크하기 위해 10만개를 생성하여 체크했다

로그로 찍어서 sublime text 중복선택(Ctrl+d)으로 확인시 이상 없는것을 확인했다

 

참고자료

https://www.baeldung.com/java-generating-time-based-uuids

https://ciscoking.tistory.com/52

 

[IT꿀팁] Sublime Text(서브라임텍스트) 프로그램 소개 및 간편한 단축키 정리

Sublime Text 파이썬 API용으로 작성된 사유 크로스 플랫폼 소스 코드 편집기이다. 수많은 프로그래밍 언어와 마크업 언어를 지원하며 플러그인을 사용하여 사용자에 의해 기능을 확장할 수 있다.

ciscoking.tistory.com

 

반응형
반응형

✶ 본 포스팅은 인프런 김영한 강사님의 스프링입문으로 공부한 내용을 기반으로 작성하였습니다.

 

Spring Initializr 설정 (start.spring.io)
  • Project Metadate
    Group : 일반적으로 기업 도메인 명을 기입한다.
    Artifact : 빌드 후 나오는 결과물이다. 즉, 프로젝트 명.
  • Dependencies
    가져올 라이브러리를 설정할수 있다.
    - Spring Web : Web Project를 만들기 위한 라이브러리
    - Thymeleaf : 템플릿엔진 라이브러리

설정 후 하단의 GENERATE를 클릭하면 zip파일로 다운 받을수 있다.

 

InteliJ IDEA 실행

InteliJ IDEA Community Edition을 사용했습니다.

  • open
    버튼을 클릭하여 압축을 푼 zip파일 내의 gradle파일을 선택 후 open 클릭한다. (경고창이 뜨면 open as project 선택)

  • 추가로 IntelliJ에서 Build와 run을 실행할 경우 Gradle을 통해서 실행 되도록 default 값이 Gradle로 설정 되어 있는데,
    이 경우, 실행이 느려지는 경우가 있기 때문에 설정을 바꾸어 인텔리제이에서 JAVA를 바로 실행하도록 변경하였다.
    Preferences - 'Gradle' 검색 - IntelliJ IDEA 선택

반응형

'Spring/SpringBoot > Study' 카테고리의 다른 글

[SpringBoot] Welcome Page 생성, Thymeleaf 적용  (0) 2023.03.27
반응형

✶ 본 포스팅은 인프런 김영한 강사님의 스프링입문으로 공부한 내용을 기반으로 작성하였습니다.

 

Welcome Page 만들기

 

resources - static 폴더에 index.html 파일을 생성한다.

스프링부트에서 자동으로 index.html을 찾아서 메인페이지로 보여준다.

간단한 html문을 작성 후 localhost:8080에서 확인 가능하다.

 

Thymeleaf

 

자바 라이브러리 중 하나로 HTML, XML, Javascript, CSS, 텍스트를 생성할 수 있는 템플릿 엔진이다.

View계층에 적합하며, JSP로 만든 기능을 완전히 대체할 수 있다.

 

Thymeleaf 공식 사이트

https://www.thymeleaf.org/
스프링 공식 튜토리얼

https://spring.io/guides/gs/serving-web-content/
스프링부트 메뉴얼

https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/reference/html/spring-boot-features.html#boot-features-spring-mvc-template-engines

 

Thymeleaf를 이용한 view

 

resources - templates 폴더에 hello.html 파일을 생성했다.

  • Thymeleaf 문법을 사용하기 위해 선언
<html xmlns:th="http://www.thymeleaf.org">
  • 문법
표현 설명 예제
@{ ... } URL 링크 th:href="@{/css/bootstrap.min.css}"
th:href="@{/{itemId}/edit(itemId=${item.id})}"
| ... | 리터럴 값 대체
*리터럴 값이란? 변하지 않는 값으로
  예를들어 int a = 1 을 선언했을 때 1이 바로 리터럴 값이다
th:text="|Hi ${user.name}!|"
(= th:text="'Hi '+${user.name}+'!'"
${ ... } 변수 th:text=${user.name}
th:each 반복 출력 <tr th:each="item: ${items}">
  <td th:text="${item.price}">100</td>
</tr>
*{ ... } 선택 변수 <tr th:object="${items}">
  <td th:text="*{price}">100</td>
</tr>
#{ ... } 메시지. properties 같은 외부 자원에서 코드에 해당하는 문자열 get. th:text="#{member.register}"

 

 

 

 참고 사이트

https://github.com/ihoneymon/spring-boot-orm-learn/blob/master/THYMELEAF_TEMPLATE_ENGINE.md

https://yeonyeon.tistory.com/153

https://maenco.tistory.com/entry/Thymeleaf-Literal-%EB%A6%AC%ED%84%B0%EB%9F%B4

https://jongminlee0.github.io/2020/03/12/thymeleaf/

반응형

'Spring/SpringBoot > Study' 카테고리의 다른 글

[SpringBoot] Project 생성 및 초기 세팅  (0) 2023.03.27
반응형

스프링은 스프링 컨테이너에 스프링 빈을 등록할 때 기본적으로 싱글톤으로 등록한다.(유일하게 하나만 등록하여 공유)

때문에 같은 스프링빈은 모두 같은 인스턴스이다. (필요한 경우 설정을 통해 다른 패턴으로 사용할 수 있다)

 

자동 의존관계 설정  - @Component

@Controller, @Service, @Repository는 @Component를 포함하고 있기 떄문에 스프링 빈으로 자동 등록되는것

자동등록이 될수 있는 범위는 @SpringBootApplication(@ComponentScan) 클래스를 가진 동일 또는 하위 패키지

 

수동 등록 - @Bean

MemberRepository는 인터페이스로 new 생생 불가능함

때문에 실 구현 class인 MemoryMemberRepository() 로 생성

 

@Configuration
public class SpringConfig {

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }
    
    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}

 

@Controller
public class MemberController {

    private final MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }
}

여기서 Service는 생성자주입을 이용하였다

 

 

회사에서도 Component를 통한 자동등록을 막고 Config를 이용해서 수동 등록을 이용하는데

왜 그런지 궁금했었는데 김영한 강사님의 인프런 강의를 보고 해소가 되었다.

 

예를 들어, DB에 연결 시 기존 DB에서 다른 DB로 연결하게 되는 경우 자동으로 등록했을 때에는 Service, Repository 등 여러 클래스의 코드를 수정해야한다.

하지만 수동으로 등록했을 때는 Config파일의 Repository부분을 일부만 수정(예를들어 Configuration의 return class명을 바꾼다거나)만 하면 다른 DB로 간단하게 연결이 되어 간편하게 변경이 가능하기 때문이다.

반응형
반응형

유효성 검사는 당연히 JS 영역에서 하는거라고 생각했다.

이번에 유효성 검사를 해야하는 부분이 있었는데, 찾아보니 Controller, DTO 하기 나름이라더라..

특히 JS에서만 유효성 검사를 하는 것은 안전하지 않기 때문에 백단에서 해주는게 좋다고 하는것 같았다.

 

내가 이번에 맡은 부분 중 추가/수정 모달창에 입력한 값에 대한 유효성 검사를 해야했다.

추가/수정 모두 한개의 DTO를 사용하고 있는데,

추가할 경우는 필수값이지만 수정할 경우는 display:none으로 값을 받지 않는 컬럼값이 있어서 이부분에서 좀 오~~~래 해맸다.

 

결론으로는 @Validated와 DTO 유효성 설정시 groups를 나누는 것이 핵심!

 

 

pom.xml

(Spring Boot 2.3 버전 이후인 경우)

우선 @Validated 어노테이션을 사용하기 위해서 dependency를 추가해야한다.

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-validation</artifactId>
   <version>3.0.2</version>
</dependency>

 

DTO interface

생성, 수정할때의 유효성 검사가 다르기 때문에 interface 변수를 별도로 선언한다.

package com.beige0905.test.dto;

public class UserValidGroups {
    public interface createValid {};
    public interface modifyValid {};
}

 

DTO

유효성 검사 변수는 아래와 같이 설정했으며, address는 유효성 검사 제외이다.

생성시 : userType, userName, userCode

수정시 : userType, userName

변수에 선언한 유효성검사 어노테이션의 groups에 위에서 선언해둔 interface 클래스를 넣는다.

package com.beige0905.test.dto;

import lombok.*;

import javax.validation.constraints.*;

@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User {

   @NotNull(message = "학생/교사 중에서 선택하세요"
         ,groups = {UserValidGroups.createValid.class, UserValidGroups.modifyValid.class})
   String userType;

   @Pattern(regexp = "^([ㄱ-ㅎ가-힣A-Za-z])(\\s?[ㄱ-ㅎ가-힣A-Za-z])*$", message = "한글, 영문만 입력 가능합니다."
         ,groups = {UserValidGroups.createValid.class, UserValidGroups.modifyValid.class})
   @NotEmpty(message = "이름을 입력해 주세요."
         ,groups = {UserValidGroups.createValid.class, UserValidGroups.modifyValid.class})
   String userName;

   @Pattern(regexp = "^([0-9])(\\s?[0-9])*$", message = "숫자만 입력 가능합니다."
         ,groups = {UserValidGroups.createValid.class})
   @NotEmpty(message = "인증코드를 입력해주세요."
         ,groups = {UserValidGroups.createValid.class})
   String userCode;
   
   String address;
}

 

Controller

컨트롤러에 가져올 매개변수 앞에 @Validated(interface명) 어노테이션을 달아준다. 

유효성검사에 에러를 가져오기 위해 BindingResult 매개변수를 추가해주면 getFieldErrors() 메소드로 에러를 가져올수 있다.

@PostMapping(path = "/add-valid-check-catalog", consumes = {MediaType.APPLICATION_JSON_VALUE})
public List<FieldError> postAddValidCheckUser(@Validated(UserValidGroups.createValid.class) @RequestBody User userInfo, BindingResult bindingResult) {
   List<FieldError> allErrors = new ArrayList<>();
   if(bindingResult.hasErrors()){
      allErrors = bindingResult.getFieldErrors();
   }
   return allErrors;
}
@PostMapping(path = "/modify-valid-check-catalog", consumes = {MediaType.APPLICATION_JSON_VALUE})
public List<FieldError> postModifyValidCheckUser(@Validated(UserValidGroups.modifyValid.class) @RequestBody User userInfo, BindingResult bindingResult) {
   List<FieldError> allErrors = new ArrayList<>();
   if(bindingResult.hasErrors()){
      allErrors = bindingResult.getFieldErrors();
   }
   return allErrors;
}

 

 

참고 자료

https://wildeveloperetrain.tistory.com/25

 

Spring Boot Validation 적용하는 법, @Valid 작동 안되는 이유

* @Valid 어노테이션이 작동이 안 한다면? spring boot 2.3 version 이상부터는 spring-boot-starter-web 의존성 내부에 있던 validation이 사라졌습니다. 때문에 사용하시는 spring boot version이 2.3 이상이라면 validatio

wildeveloperetrain.tistory.com

https://me-analyzingdata.tistory.com/entry/Spring-Controller-request-%EC%9C%A0%ED%9A%A8%EC%84%B1-%EA%B2%80%EC%82%AC?category=1034382 

 

[Spring] Controller request 유효성 검사

Request 유효성 검사 서버는 client로 부터 값을 받아서 사용할 때 client로 받은 정보가 서버에서 원하는 형식 혹은 값의 조건에 맞는지 검사를 해야한다. 이를 유효성 검사라고 한다. 이러한 유효성

me-analyzingdata.tistory.com

 

반응형
반응형

데이터베이스 1개 연결

SpringBoot에서 제공하는 형식에 따라 spring 하위에 yaml 파일을 설정하기 때문에 별도 Configuration 설정을 불필요하다.

spring
  datasource:
    driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
    url: jdbc:sqlserver://localhost:8080;databaseName={dbName};encrypt=true;trustServerCertificate=true
    username : {ID}
    password: {PW}
  jpa:
    mapping-resources: {native query path}

 

데이터베이스 2개 연결

nativeQuery xml 파일은 spring에 설정해두고 multi라는 property 값을 설정하여 datasource를 나누었다.

위에서 1개 연결 설정시에는 SpringBoot에서 제공하는 형식에 맞추기 때문에 「url(또는 URL) : url 주소」로 설정하지만,

2개의 DB에 연결을 할때는 「jdbc-url : url 주소」 로 설정해주어야 한다고 한다.

이유는 그것이 HikariCP의 Database URL 설정이니까! (끄덕)

그냥 url로 했다가 url 값을 못찾는 오류가 발생했었으니 조심하자.

spring:
  jpa:
    mapping-resources: /query/nativeQuery.xml
multi:
  datasource:
    main:
      driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
      jdbc-url: jdbc:sqlserver://localhost:8080;databaseName={dbName1};encrypt=true;trustServerCertificate=true
      username: {ID}
      password: {PW}
      base-package: {repository path ex)com.beige0905.test.repository}
      jpa:
        database-platform: org.hibernate.dialect.SQLServer2012Dialect
        show-sql: true
        format-sql: true
        use-sql-comments: true
        hibernate.ddl-auto: update
        generate-ddl: false
    second:
      driver-class-name:
      jdbc-url: jdbc:sqlserver://localhost:8080;databaseName={dbName2};encrypt=true;trustServerCertificate=true
      username: {ID}
      password: {PW}
      base-package: {repository path ex)com.beige0905.test.repository}
      jpa:
        database-platform: org.hibernate.dialect.SQLServer2012Dialect
        show-sql: true
        format-sql: true
        use-sql-comments: true
        hibernate.ddl-auto: update
        generate-ddl: false

 

MainDBConfiguration

package com.beige0905.test.config;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.util.Properties;

@Configuration
@EnableJpaRepositories(basePackages = {"${multi.datasource.main.base-package}"},
        entityManagerFactoryRef = "EntityManagerFactory",
        transactionManagerRef = "TransactionManager")
public class MPMainDatabaseConfig {

    @Value("${spring.jpa.mapping-resources}")
    String namedQuery;
    @Value("${multi.datasource.main.base-package}")
    String basePackage;
    @Value("${multi.datasource.main.jpa.database-platform}")
    String dbPlatform;

    @Primary
    @Bean(name = "RepositoryDataSource")
    @ConfigurationProperties(prefix = "multi.datasource.main")
    public DataSource getDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Primary
    @Bean(name = "EntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean masterEntityManager(@Qualifier("RepositoryDataSource") DataSource dataSource) {

        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource);
        em.setPackagesToScan(basePackage);
        em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
        em.setMappingResources(namedQuery);

        Properties jpaProperties = new Properties();
        jpaProperties.put("hibernate.dialect", dbPlatform);
        em.setJpaProperties(jpaProperties);

        return em;
    }

    @Primary
    @Bean(name = "TransactionManager")
    public PlatformTransactionManager transactionManager(@Qualifier("EntityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) {
        JpaTransactionManager transactionManager = new JpaTransactionManager(entityManagerFactory.getObject());
        transactionManager.setNestedTransactionAllowed(true);
        return transactionManager;
    }
}

 

second Configuration

main Configuration과 동일하지만  Bean name을 다르게 설정해야하고 @Primary annotation은 제외한다

package com.beige0905.test.config;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.util.Properties;

@Configuration
@EnableJpaRepositories(basePackages = {"${multi.datasource.second.base-package}"},
        entityManagerFactoryRef = "SubEntityManagerFactory",
        transactionManagerRef = "SubTransactionManager")
public class MPEntryLogConfig {
    @Value("${multi.datasource.second.base-package}")
    String basePackage;
    @Value("${multi.datasource.second.jpa.database-platform}")
    String dbPlatform;

    @Bean(name = "SubRepositoryDataSource")
    @ConfigurationProperties(prefix="multi.datasource.second")
    public DataSource getDataSource() { return DataSourceBuilder.create().build(); }

    @Bean(name = "SubEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean masterEntityManager(@Qualifier("SubRepositoryDataSource") DataSource dataSource) {

        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource);
        em.setPackagesToScan(basePackage);
        em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());

        Properties jpaProperties = new Properties();
        jpaProperties.put("hibernate.dialect", dbPlatform);
        em.setJpaProperties(jpaProperties);

        return em;
    }

    @Bean(name = "SubTransactionManager")
    public PlatformTransactionManager transactionManager(@Qualifier("SubEntityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) {
        JpaTransactionManager transactionManager = new JpaTransactionManager(entityManagerFactory.getObject());
        transactionManager.setNestedTransactionAllowed(true);
        return transactionManager;
    }
}

 

MainVo

package com.beige0905.test.repository.vo;

import lombok.*;

import javax.persistence.*;
import java.sql.Timestamp;

@Getter
@Setter
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "product")
public class MainVo {

    public enum STATUS {
        AVAILABLE,
        UNAVAILABLE
    }
    
    @Id
    @Column(name = "product_no")
    private int productNo;
    
    @Column(name = "product_name")
    private String productName;
    
    @UpdateTimestamp
    @Column(name = "update_date")
    private Timestamp updateDate = Timestamp.valueOf(LocalDateTime.now());
    
    @Column(name ="status")
    @Enumerated(EnumType.STRING)
    private STATUS status;
}

 

MainRepository

package com.beige0905.test.repository;

import com.beige0905.test.repository.vo.MainVo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

public interface MainRepository extends JpaRepository<MainVo, String> {

    @Transactional
    @Modifying
    @Query("update MainVo v set v.status = ?1 where v.no = ?2")
    int updateStatus(MainVo.STATUS processing, int no);
}

 

SecondVo

package com.beige0905.test.second.repository.vo;

import lombok.*;

import javax.persistence.*;
import java.sql.Timestamp;

@Getter
@Setter
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "user_info")
public class SecondVo {
    @Id
    @Column(name = "user_no")
    private int userNo;
    @Column(name = "user_name")
    private String userName;
    @Column(name = "join_date")
    private Timestamp joinDate;

}

 

SecondRepository

package com.beige0905.test.secondDb.repository;

import com.beige0905.test.secondDb.repository.vo.SecondVo;
import org.springframework.data.jpa.repository.JpaRepository;

public interface SecondRepository extends JpaRepository<SecondVo, String> {
}

 

vo, repository 생성시 Main과 Second 별도 패키지를 사용해야한다.

 

 

참고자료

https://velog.io/@lehdqlsl/SpringBoot-JPA-Multiple-Databases-%EC%84%A4%EC%A0%95

 

SpringBoot JPA Multiple Databases 설정

다중 DB는 Spring boot 처럼 Auto Configuration되지 않음설정파일(application.yml 또는 application.properties) 값을 읽어와서 연동 할 DB 수 만큼 Datasource를 수동 설정해야함설정파일 대신 코드로 직접 설

velog.io

https://wiki.webnori.com/pages/viewpage.action?pageId=9798284 

 

다중DB 환경설정 - WebFrameWork - WebNORI - IT개발문화 연구소

 

wiki.webnori.com

https://jojoldu.tistory.com/296

 

Spring Boot & HikariCP Datasource 연동하기

안녕하세요? 이번 시간엔 Spring Boot & Hikari Datasource 연동하기 예제를 진행해보려고 합니다. 모든 코드는 Github에 있기 때문에 함께 보시면 더 이해하기 쉬우실 것 같습니다. (공부한 내용을 정리하

jojoldu.tistory.com

 

반응형

+ Recent posts