package com.lsj.blog;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.lsj.blog.repository.UserRepository;
import com.lsj.dto.ResponseDto;
@RestController
public class DummyController {
@Autowired // DI
private UserRepository userRepository;
@PostMapping("/api/login")
public ResponseDto<Integer> login(@RequestBody BlogUser user, HttpSession session){
BlogUser principal = loginService(user); //principal (접근주체)
if(principal != null) session.setAttribute("principal", principal);
System.out.println("principal : " + session.getAttribute("principal"));
return new ResponseDto<Integer>(HttpStatus.OK.value(), 1);
}
@Transactional(readOnly = true) // Select 할 때 트랜잭션 시작, 서비스 종료시 트랜잭션 종료 (데이터 일관성)
public BlogUser loginService(BlogUser user) {
return userRepository.findByUsernameAndPassword(user.getUsername(), user.getPassword());
}
}
package com.lsj.blog.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import com.lsj.blog.BlogUser;
// extends JapRepository가 있는 경우 @Repository 생략가능
// JpaRepostiory는 다양한 기능을 가지고 있다. findAll (모든 레코드 select) 페이징해서 보내줄 수 있는 기능도 존재
public interface UserRepository extends JpaRepository<BlogUser, Integer>{ // BlogUser의 Repository이고 PK는 Integer이다.
// JPA Naming 쿼리
// SELECT * FROM bloguser WHERE username = ? AND password = ?;
// SELECT * FROM ${리턴타입} WHERE username = ${첫번째 파라미터} AND password = ${두번째 파라미터};
BlogUser findByUsernameAndPassword(String username, String password);
// nativeQuery 방식 위와 동일하다
@Query(value="SELECT * FROM bloguser WHERE username =1 ? AND password = 2?", nativeQuery = true)
BlogUser login(String username, String password);
}
package com.lsj.blog;
import java.util.Random;
import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.lsj.blog.model.RoleType;
import com.lsj.blog.repository.UserRepository;
import com.lsj.dto.ResponseDto;
@RestController
public class DummyController {
@Autowired // DI
private UserRepository userRepository;
@PostMapping("/api/user")
@Transactional
public ResponseDto<String> save(@RequestBody BlogUser user){
BlogUser usre2 = new BlogUser();
Random random = new Random();
usre2.setUsername(String.valueOf(random.nextInt(10)));
usre2.setPassword("12345");
usre2.setEmail("abc@naver.com");
userRepository.save(usre2);
new ResponseDto<String>(HttpStatus.OK.value(), "데이터 insert에 성공했습니다.");
// HttpStatus.OK.value() → 200이라는 코드 반환 (정상 성공)
}
}
package com.lsj.blog.handler;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import com.lsj.dto.ResponseDto;
@ControllerAdvice // 어떤 Exception이 오든 이 페이지로 오게 된다.
@RestController
public class GlobalExceptionHandler {
@ExceptionHandler(value=Exception.class) // 모든 부모 Exception
public ResponseDto<String> handleException(Exception e) {
return new ResponseDto<String>(HttpStatus.INTERNAL_SERVER_ERROR.value(),e.getMessage());
// HttpStatus.INTERNAL_SERVER_ERROR.value() → 500 코드 반환 (서버 에러)
//return "<h1>" + e.getMessage() + "</h1>";
}
}
package com.lsj.dto;
import org.springframework.http.HttpStatus;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ResponseDto<T> {
int status;
// HttpStatus status;
T data;
}
test.java
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class test extends HttpServlet {
public void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
System.out.println("hello Servlet");
}
}
1. cmd test.java 있는 경로로 이동
2. class 파일 만들기 → javac -cp C:\tomcat9\lib\servlet-api.jar test.java
3. 호출하기 위한 설정파일 수정 (web.xml) [WEB-INF안에 있는 파일은 직접 요청이 불가능하다.]
WEB-INF 호출하기 위한 설정 파일 경로 : C:\tomcat9\webapps\ROOT\WEB-INF\web.xml
실행시킬 class파일 : C:\tomcat9\webapps\ROOT\WEB-INF\classes\test.class
web.xml 내용
<servlet>
<servlet-name>testing</servlet-name>
<servlet-class>test</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>testing</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
→
hello라는 url 요청이 오면 testing이라는 서블릿의 이름을 가진 애를 실행시키라는 의미
testing이라는 이름을 가지는 서블릿의 클래스파일은 test이다
이렇게 매핑정보를 xml으로 할수도 있지만(2.x) annotation(3.x)으로도 사용 가능하다.
annotation 방식을 이용하고 싶을 경우 metadata-complete를 false로 바꿔야한다. 경로 : C:\tomcat9\webapps\ROOT\WEB-INF\web.xml
1. cmd 관리자 권한으로 실행
2. cd ${jdk/bin경로}
3. 하기 명령어 입력
keytool -genkey -alias tomcat -keyalg RSA -keystore tomcat.keystore
설명 : keytool -genkey -alias tomcat -keyalg RSA -keystore ${keystore 파일명}
4. 하기 내용 입력하기
키 저장소 비밀번호 입력:
새 비밀번호 다시 입력:
이름과 성을 입력하십시오.
[Unknown]:
조직 단위 이름을 입력하십시오.
[Unknown]:
조직 이름을 입력하십시오.
[Unknown]:
구/군/시 이름을 입력하십시오?
[Unknown]:
시/도 이름을 입력하십시오.
[Unknown]: seoul
이 조직의 두 자리 국가 코드를 입력하십시오.
[Unknown]: KR
N=maruara, OU=Unknown, O=Unknown, L=Unknown, ST=seoul, C=KR이(가) 맞습니까?
[아니오]: y
tomcat>에 대한 키 비밀번호를 입력하십시오.
(키 저장소 비밀번호와 동일한 경우 Enter 키를 누른다):
📝2. tomcat의 server.xml에서 https 설정하기
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS"
keystorePass="gkgkgk" keystoreFile="d:/openssl/tomcat.keystore"/>
port → https 통신 포트
keystorePass → 위에서 설정한 패스워드
keystoreFile → ssl 인증서 경로
package com.company.macro;
import java.util.List;
import com.company.macro.VO.CustomerVO;
public interface MybatisImpl {
public String selectCount();
public List<CustomerVO> selectAll();
public void insertAll();
// @Select("SELECT * FROM customer")
// public String selectAll();
}
MybatisImpl.java
query문의 이름과 반환 타입을 선언합니다.
public class CustomerVO {
private String customer_no;
private String customer_nm;
// DB에 있는 컬럼명과 VO와 동일하게 가야한다.
public String getCustomer_no() {
return customer_no;
}
public void setCustomer_no(String customer_no) {
this.customer_no = customer_no;
}
public String getCustomer_nm() {
return customer_nm;
}
public void setCustomer_nm(String customer_nm) {
this.customer_nm = customer_nm;
}
}
CustomerVO.java
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- 상기 내용은 반드시 필요한 내용 -->
<configuration>
<properties>
<property name="mybatis 안에서 사용될 column의 이름" value="디비 컬럼명" />
<property name="tblCustomer" value="customer" />
<property name="customer_no" value="customer_no" />
<property name="customer_nm" value="customer_nm" />
<property name="tblMember" value="tbl_member"/>
<property name="colUserid" value="userid"/>
<property name="colPassword" value="password"/>
<property name="colEmail" value="email"/>
</properties>
<typeAliases>
<typeAlias alias="customerVO" type="com.company.macro.VO.CustomerVO" />
</typeAliases>
<!-- VO가 들어있는 패키지명 -->
</configuration>
webapps/resources/mybatis-config.xml
property는 mapper.xml에서 쓰이는
typeAliases는 mapper.xml의 select해서 나온 데이터를 VO에 바로 담을수 있게 사용하겠다는 의미입니다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 상기 내용은 반드시 필요한 내용 -->
<!-- interface 명 -->
<mapper namespace="com.company.macro.mybatisImpl">
<select id="selectCount" resultType="string">
SELECT count(*) FROM customer
</select>
<select id="selectAll" resultType="customerVO">
SELECT * FROM ${tblCustomer}
</select>
</mapper>
webapps/resources/mappers/mapper.xml
resultType에는 typeAliases에 적은 VO를 사용할 수 있습니다.
${property name명} 으로 name에 해당하는 value를 가져올 수 있습니다.
Service → 비즈니스 로직 (DaoImpl을 통해 여러가지 쿼리문(Dao를 1개 이상 실행)을 가져오고 Java를 이용해 데이터를 만들어 반환 가능)
Dao → DB Query 실행문이 들어감 (1개의 Query문)
mapper.xml → Query문에 대한 내용
Interface사용 이유 → DI 하기 위함과 규격화를 위해 사용 → DI를 제대로 활용할 줄 몰라서 아직 와닿지 않음
* 참고 : 보통은 어떻게 처리하는지 모르겠지만 Service에 로직이 길어졌는데 해당 부분에 공통 로직이 있어서 (로그인 세션 체크 등...) 공통 API로 뺄 때 그 안에 Mapper를 이용해야하는 경우(DB Connection이 들어가는 로직)일 경우 해당 Util(공통 API)도 또한 Mapper를 Injection해서(Autowired) 사용할텐데 해당 Mapper를 이용하기 위해서는 @Component 또는 @Service 등 해당 클래스를 IoC컨테이너에 담아야한다.
LoginUtil util = new Util
LoginVo vo = util.getSessionInfo()
----------- (X)
@Autowired
LoginUtil loginUtil;
....
LoginVo vo = loginUtil.getSessionInfo();
----------- (O)
<!-- spring version과 spring-jdbc version은 동일해야한다 -->
<!-- mybatis, mybatis-spring, spring-version 서로 호환되는 버전으로해야한다. -->
<!-- 만약 호환 안 되는 버전 사용시 sqlSession 객체 만들 때 에러 나오는데 버전이 맞지 않습니다라 이런 에러도 안 나와서 에러 체킹이 힘듬 -->
<!-- root-context.xml에서 설정후 실행 시 sqlSession 객체 만들 때 정상 호환 버전들이면 정상 작동하거나 잘못 된 부분에 대한 정확한 에러가 나옴 -->
<properties>
<java-version>11</java-version>
<org.springframework-version>5.0.5.RELEASE</org.springframework-version>
<org.aspectj-version>1.6.10</org.aspectj-version>
<org.slf4j-version>1.6.6</org.slf4j-version>
</properties>
....
<!-- jdbc -->
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>2.0.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!-- Mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!-- Mybatis + Spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- Root Context: defines shared resources visible to all other web components -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName"
value="org.mariadb.jdbc.Driver" />
<property name="url"
value="jdbc:mariadb://192.168.0.40:7001/ocr_search" />
<property name="username" value="testid" />
<property name="password" value="testpassword" />
</bean>
<bean id="sqlSessionFactory"
class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation"
value="classpath:mybatis-config.xml" />
<property name="mapperLocations"
value="classpath:mappers/*Mapper.xml" />
</bean>
<!-- classpath란 src/main/java 또는 src/main/resources를 의미한다. -->
<!-- classpath는 build path - source에 등록된 경로들이다. -->
<!-- classpath:mybatis-config.xml → Alias등 설정 하는 설정 파일 -->
<!-- classpath:mappers/*Mapper.xml → Query를 적는 파일이다 -->
<!-- src/main/java/mappers 폴더에 관리하겠다는 의미이고 파일명이 Mapper.xml로 끝나는 파일들을 등록하겠다는 의미이다. -->
<bean id="sqlSession"
class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="sqlSessionFactory" />
</bean>
<!-- mapper 어노테이션을 인식 시키기위해 필요 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.company.ocrsearch" />
</bean>
</beans>
root-context.xml
SqlSession : DB 연결하기 위한 객체 및 Query 수행 역할 (SqlSessionFactory가 주입 됨)
SqlSessionFactory : mapper.xml에 대한 설정 및 mapper.xml(쿼리문) 읽을 객체
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
public class EngineDto {
private String uuid = "";
private String id = "";
private String password = "";
private String salt = "";
private String fail_cnt ="";
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
public String getFail_cnt() {
return fail_cnt;
}
public void setFail_cnt(String fail_cnt) {
this.fail_cnt = fail_cnt;
}
public String toStringShortPrefix() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}
→ 실제 비즈니스 로직 구현한 것으로 DB 쿼리문을 사용할 경우 1개 이상의 Dao로 구성되어 있다.
받아온 쿼리문을 가지고 여러 동작을 하거나 여러개의 Dao를 호출해서 결과를 조합해서 Dto에 담아서 내려주는 형태
package com.company.ocrsearch.engine.dao;
import org.apache.ibatis.annotations.Mapper;
import com.company.ocrsearch.engine.dto.EngineDto;
@Mapper
public interface EngineDao {
public EngineDto selectUser(String uuid) throws Exception;
}
EngineDao.java
→ root-context.xml에서 설정한 org.mybatis.spring.mapper.MapperScannerConfigurer에 포함된 패키지일 경우 @mapper 어노테이션을 찾아서 *mapper.xml과 연결해준다. interface이기 때문에 규격화및 DI 역할을 한다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.company.ocrsearch.engine.dao.EngineDao">
<select id="selectUser" parameterType="String"
resultType="com.company.ocrsearch.engine.dto.EngineDto">
SELECT
ID -- 아이디
, FAIL_CNT -- 로그인 실패 횟수
FROM admin_user
WHERE
uuid = #{UUID}
</select>
</mapper>
EngineInfoMapper.xml
→ 쿼리문이 여기에 작성되며 연결할 interface명을 mapper namespace에 적는다.
xml을 EngineDao inteface를 implements한 class라고 생각하면 된다.
id → Dao Interface에서 구현해야할 Funciton Name을 의미한다.
parameterType → 동적 쿼리를 위해 넘겨주는 파라미터를 의미한다. #{UUID}에 들어가는 파라미터는 String이 된다.
[ String 뿐만 아니라 HashMap 등이 있기 때문에 상황에 맞게 쓰면 된다 java.util.HashMap 등...)
String의 경우 HashMap의 경우 키 값으로 들어가기 때문에
resultType → 결과가 담길 곳을 의미한다. 일반적으로 여러개의 결과이고 데이터의 이동은 Dto에 담아서 하기 때문에 Dto를 적어줬다.
너무 당연한 이야기지만 내려받을 필드명과 Dto 필드는 같아야한다. (Alias 걸어서 Dto 수정 없이 Dto에 맞출 수도 있음)
[ String 뿐만 아니라 HashMap 등이 있기 때문에 상황에 맞게 쓰면 된다 java.util.HashMap 등...)
* 상속받는 놈(mapper.xml)이 ParameterType을 받기로 했기 때문에 Dao (interface)에도 그렇게 설정해줘야한다.
→ 매개변수가 1개의 String 이여야 함 (String의 경우 이름(#{UUID})을 정할 수 없기 때문에 들어온 그대로 들어가게 된다.)
여러개의 매개변수를 받으려면 HashMap이나 Dto가 적당하다.
타입은 신경 안 쓰는 것 같다. → DB 필드가 문자 타입이여도 parameterType=int로 보내줘도 인식함