Spring/Spring Boot

[SpringBoot] 2. 스프링부트 프로젝트로 oracle DB 연결하기

칸쵸. 2023. 11. 27. 02:50
728x90

 

그동안은 Java와 Oracle을 연결을 jdbc에만 의존해서 해왔습니다.

하지만 그렇게 하면, 아무것도 모르는 저일지라도  코드의 중복이 다소 많지 않나? 하는 생각은 들더라구요.

 

그래서 이번에는 스프링부트를 이용해서, 그리고 MyBatis라는 어떤 새로운 개념을 들고와서

이 둘을 연결해보겠습니달라

 

아, 그리고 저번 글에서 말했다시피

저는 온전한 이해를 바탕으로 글을 작성하고있지 않습니다..

틀린 정보 속출 가능하구요, 그저 이렇게 하면 뭐라도 머리에 들어오지 않을까? 해서 써보는 글입니다.

 

어떤 양질의 정보를 기대하고 들어오셨다면 상당히 죄송합니다

 


 

일단 프로젝트 하나를 만들어줘야겠죠?

Project Explorer > 마우스 우클릭 > New > Other > Spring Starter Project 눌러줍시다

 

 

 

그리고 프로젝트 설정은 저번과 동일하게

  • Type : Maven
  • Java Version : 17
  • Packaging : Jar
  • Language : Java

로 만들어줍시다. 프로젝트 명 및 패키지 구성은 본인 자유~

저는 spring 이랑 db 연결이라는 뜻에서 그냥 Spring-DB-Oracle 이라고 지었습니다.

 

 

블로그 쓰다보니 처음 발견한거긴 한데, 저 위에 Service URL이라는 놈이 있네요.

어, 근데 어쩐지 익숙한 링크입니다.

지금은 이렇게 이클립스에서 직접 프로젝트를 생성하고 있지만, 불과 저번 글에서만해도 저희는 저 사이트를 통해서 프로젝트를 생성해주고, 이클립스에서는 import를 했잖아요?

 

그래서 추론해보건데, 잘은 모르겠지만 이 Service URL이라는 것은

뭔가 이 파일을 조작할 수 있는 어떤 서비스에 대한 주소값을 나타내는 것같습니다.

 

그리고 마지막으로 Dependencies로는

  • Spring Boot DevTools : 클래스 경로 변경을 자동감지해서 자동으로 서버를 restart (LiveReload)
  • Spring Boot Actuator : 애플리케이션의 운영환경에서 정보를 제공, 엔드포인트를 통해 상태 조회 및 조작
  • MyBatis Framework : SQL 쿼리와  Java 객체 간 매핑을 담당하는 프레임워크
  • Oracle Driver : Java에서 Oracle에 접속할 수 있도록 하는 Java API
  • Thymeleaf  : 마크업 언어를 처리하는 java template 엔진
  • Spring Web : (동기식)웹 애플리케이션을 개발하는데 사용되는 모듈

이렇게 총 6가지를 추가해줬습니다. 

 

 

그리고 Finish를 누르고 몇 초 잠깐 기다리면 Project Explorer에 이렇게 새 프로젝트가 생성된 것을 확인할 수 있습니다

 

 

 

이제 진짜로 연결을 해볼까요..

Spring Boot로 Oracle DB를 연결하는 순서는 다음과 같습니다.

(Select문으로 데이터를 조회하려는 상황 가정)

 

  1. 스프링 프로젝트 생성 (완료)
  2. application.properties 설정 > DB연결
  3. model class 생성
  4. mapper interface 생성
  5. mapper.xml 생성
  6. controller class 생성
  7. service class 생성
  8. template에 html 생성

그 외 ) 어노테이션 추가 및 경로 설정 주의

 


 

프로젝트는 생성한 상태이니, application.properties를 설정하러 가봅시다

src/main/resource에 존재하는 파일입니다.

 

 

jdbc를 통해 연결하던 것과 유사한 모양입니다.

이 application.properties에 연결 설정을 해두면, 매번 다른 클래스 및 메서드에 이와 같은 정보를 다시 쓸 필요가 없습니다.

다른건 몰라도 여기서 일단 코드 재사용성을 높일 수 있는건 알겠네요.

 

그리고 저희는 myBatis라는 프레임워크를 통해서 SQL쿼리와 Java 객체를 매핑하기로 했으니, 이를 위한 설정도 적어줍니다.

 

**매핑(Mapping) : 쉽게 말하자면 어떤 값을 다른 값에 대응시키는 과정을 총칭하는 개념 정도로 보면 될듯 합니다

 

그리고 저장을 누르면 경고창 하나가 뜨는데 UTF-8 선택해주심 됩니다

 

 


 

이제 model 클래스를 생성을 해보겠습니다.

저희가 Spring이라는 MVC 웹 프레임워크를 사용하니, 이런 식으로 클래스를 생성해나가는 것같네요.

 

아무튼

src/main/java 폴더 위치에 model 패키지를 따로 생성해서 그곳에 클래스를 만들 예정입니다. (그 뒤로 controller, service 및 mapper도 마찬가지)

 

그리고 제가 oracle db에서 연결하고자 하는 테이블의 정보는 아래와 같기에

 

이 테이블에 존재하는 레코드를 나타낼 수 있는 model을 생성합니다.

 

package com.example.springdb.model;

public class Product {
	
	/*
		PRODUCT_ID	NUMBER(5,0)
		PRODUCT_NAME	VARCHAR2(100 BYTE)
		CATEGORY	VARCHAR2(50 BYTE)
		PRICE	NUMBER(10,2)
		STOCK_QUANTITY	NUMBER(5,0)
	 */
	
	//멤버변수
	private int product_id;
	private String product_name;
	private String category;
	private double price;
	private int stock_quantity;
	
	//Getter--------------------
	public int getProduct_id() {
		return product_id;
	}
	public String getProduct_name() {
		return product_name;
	}
	public String getCategory() {
		return category;
	}
	public double getPrice() {
		return price;
	}
	public int getStock_quantity() {
		return stock_quantity;
	}
	
	//Setter--------------------
	public void setProduct_id(int product_id) {
		this.product_id = product_id;
	}
	public void setProduct_name(String product_name) {
		this.product_name = product_name;
	}
	public void setCategory(String category) {
		this.category = category;
	}
	public void setPrice(double price) {
		this.price = price;
	}
	public void setStock_quantity(int stock_quantity) {
		this.stock_quantity = stock_quantity;
	}

}

 


 

다음으로는 mapper 인터페이스를 생성해주러 갑니다. 

model 클래스 생성과 마찬가지고 패키지 하나를 파주고 거기에 생성해 줬어요.

 

Products 테이블에 있는 데이터 전체를 List 형으로 반환하는 추상메서드 getAllProducts() 하나를 작성합니다.

그리고 이 인터페이스를 매퍼로 사용하겠다는 뜻으로, @Mapper 어노테이션을 추가

 

package com.example.springdb.mapper;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

import com.example.springdb.model.Product;

@Mapper
public interface ProductMapper {
	List<Product> getAllProducts();
}

 

그리고 src/main/resource 폴더에 mapper 폴더를 추가로 생성 후 그 곳에 mapper xml을 작성하겠습니다.

 

 

 

Next > Create File from a templates 선택하고 생성하면

 

 

이런 파일이 나타나는데, 저희는 별도의 설정이 필요합니다.

myBatis를 이용해서 매핑할 것이기 때문이죠

 

<?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 xml 파일의 구조를 보자면, 크게 DTD선언부와 SQL Mapping부로 나뉘어있다고 할 수 있겠네요.

 

<?xml version="1.0" encoding="UTF-8"?>
<!-- mapper DTD 선언 -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- SQL Mapping -->
<mapper namespace="com.example.springdb.mapper.ProductMapper">
	<select id="getAllProducts" resultType="com.example.model.Product"">
		SELECT * FROM products
	</select>
</mapper>

 

얘네가 뭘 나타내는지는 뒤에 보면 알아서 뭔가를 연결하고 있는 자신을 볼 수 있을 겁니다.

하지만 namespace, id, resultType 정도는 잘 기억해둡시다.

 


 

이제 절반 넘었습니당.. 할 일이 태산같았는데 또 막상 하니까 뭔가 되는게 신기하네요.

아무튼 이번에는 컨트롤러를 만들러 가봅시다. 마찬가지로 전용 패키지 하나 파주고 거기에 클래스를 만들어요.

 

코드를 뭔가 더 작성하기에 앞서, 이 친구도 컨트롤러 기능을 하는 클래스임을 나타내는 어노테이션 @Controller 추가 필수입니다.

 

 

그럼 여기까지 작성한게 되는데..

지금까지 저희는 Controller에서 view와 model을 연결하는 모든 기능을 써내려갔는데 이제는 그러지 않기로해요.

 

왜냐? Service라는 친구가 있거든요

긍까 어떤 느낌이냐면.. 어떤 요청이 들어오면 Controller는 알겠어 내가 모델이랑 뷰? 응응 걔네랑 잘 연결해볼게 응... 하고 생색은 실컷 다 내잖아요. 그래놓고서는 뒤에서는 아주 야비하게 귀찮은 일처리는 Service에게 다 떠넘기는 식입니다.

비유가 맞는지는 모르겠지만 암튼 Service는 Controller의 하청업체인 셈이라 칠게요.

 

왜 이런 방식을 쓰는지는 사실 잘 모르겠지만 암튼 그렇게 하라니까 할게요.

 

그럼 컨트롤러는 잠깐 내버려두고, Service를 먼저 만들러가볼까요.

 


 

네.. 마찬가지로 패키지 따로 파주고 클래스 만들어주쉐이

그리고 어노테이션 @Service 필수

 

저희는.. JPA를 사용하지 않고 mybatis를 통해 sql 매핑을 하기로 했으니 레퍼지토리는 사용하지 않습니다.

그럼 코드를 아래와 같이 작성해주면 서비스 클래스는 끝

 

package com.example.springdb.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.example.springdb.mapper.ProductMapper;
import com.example.springdb.model.Product;

@Service
public class ProductService {

	@Autowired
	private ProductMapper productMapper;
	
	public List<Product> getAllProducts(){
		return productMapper.getAllProducts();
	}
}

 

그리고 저 정체 불명의 어노테이션 @Autowired는 대체 뭘까요?

일단은 Spring에서 의존성을 주입시켜주는 역할을 담당하는 녀석이라고 보고 넘어가도 될듯합니당..

 

보통은 인터페이스 선언시 사용 되는데, 작성한 코드를 통해 이해를 하자면

productMapper라는 ProductMapper 인터페이스 변수에, ProductMapper 인터페이스를 재정의한 클래스 객체를 주입시킨다. 라는 의미를 가진다고 볼 수 있겠네요. 

 

즉, 이전에 지겹도록 객체를 생성했던 과정에서 사용했던 new라는 키워드가 필요가 없게 되는 셈~

사실 저도 완전한 이해는 못했지만 암튼 그냥 이런 느낌이구나..만 짚고 넘어가려합니다

 


 

그러고 다시 넘어온 컨트롤러.. 일단 완성된 코드는 다음과 같습니다

package com.example.springdb.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import com.example.springdb.service.ProductService;

@Controller
public class ProductController {

	@Autowired
	private ProductService productService;
	
	@GetMapping("/productLists")
	public String displayProductList(Model model) {
		model.addAttribute("products",productService.getAllProducts());
		return "productLists";
	}
}

 

오케이. 서비스 클래스 불러오는거 알겠고,

그럼 저 @GetMapping은 무엇일까요. 자꾸 괴상한 놈들이 튀어나옵니다;;

 

이 녀석은 DB(Select)나, 서버에서 받은 요청을 읽어 전달하는 어노테이션으로, @GetMapping(경로) 방식으로 사용합니다.

 

위에서는 이제 "/productLists" 경로로 GET 요청이 오면, Model 객체를 받아서 어쩌고 해주겠다는 뜻이 되겠네요.

**자매품 @PostMapping도 존재합니다. (얘는 이제 Insert할 때..)

 

그리고 또 이 친구.

model.addAttribute(String name, Object value);

 

이놈은 이제 Model에 데이터를 담을 때 쓰는 메서드로, value 객체를 name이라는 이름으로 추가한다는 뜻입니다.

 

그럼 아까 경로에 있는 "/productLists"가 뭔가? 하면 네. 드디어 마지막 파일 html 만들러 갑시다.

 


 

src/main/resource에 templates폴더에 new누르고 html 파일 하나 생성해주세요.

그리고 이 파일의 이름이 바로 아까의 경로가 될 것입니다. 그러니 동일하게 productLists.html으로 생성해야겠죠?

 

저희는 또 Thymeleaf를 사용하기에, 파일 상단부에 그에 대한 선언이 필요합니다.

그리고 타임리프를 활용해 products라는 name을 가진 모델 데이터(컨트롤러 참고)를 불러오면 다음과 같은 코드가 완성됩니다.

 

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
	<head>
	<meta charset="UTF-8">
	<title>Product List</title>
	</head>
	<body>
	
		<h1>Porudct List</h1>
		
		<table border="1">
			<thead>
				<tr>
					<th>ID</th>
					<th>Name</th>
					<th>Category</th>
					<th>Price</th>
					<th>Stock Quantity</th>
				</tr>
			</thead>
			<tbody>
				<tr th:each="product : ${products}">
					<td th:text="${product.product_id}"></td>
					<td th:text="${product.product_name}"></td>
					<td th:text="${product.product_category}"></td>
					<td th:text="${product.product_price}"></td>
					<td th:text="${product.product_quantity}"></td>
				</tr>	
			</tbody>
		</table>
	</body>
</html>

 


 

와 이제 진짜진짜 끝인가했더니 마지막으로 남아있는게 있습니다.

바로..

SpringDBOracleApplication.java 파일에 어노테이션 추가하기.

 

생성한 매퍼를 찾을 수 있도록 @MapperScan 을 추가해줍시다.

package com.example.springdb;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.example.springdb.mapper")
public class SpringDbOracleApplication {

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

}

 


 

지금까지 작업한 프로젝트의 구조를 보면 아래와 같습니다.

 

그럼 이제 진짜 실행을 해볼까요...? 두근두근

 

...

 

 

근데 사실 큰 기대는 안했어요 진짜로..

전 항상 이랬거든요. 매번 1트에 성공하는 법이 없어서 옆에 친구들꺼는 잘 돌아가는거 보고 헐레벌떡 오류 고치고 있는거. 그게 제 평소 모습이라서 딱히 놀랍진 않아요 호호.. 아무튼 봅시다..

 

엄.. 보니까 bean을 만드는데 실패했대요 근데 저게 보니까 비단 컨트롤러의 문제가 아니라.. 밑으로 쭉 읽어보니까?

그냥 다 문제네요. 어디서부터 잘못된걸까요...

 

일단 클래스를 다 돌면서 체크해본 결과, 어노테이션은 빼먹지 않고 작성했습니다.

import 또한 멀쩡히 한 것같구요.

 

검색을 해보니.. 어노테이션이 문제가 아니면 xml에서 문제가 있는지 보는것을 권장한다네요. xml이라면 아마도..

경로문제거나 sql 구문 오류이거나 할 것같네요. 봅시다~

 

 

아! 역시나 경로 문제였습니다. 신나서 패키지 명 작성하는데 springdb를 빼먹었네요 ㅎㅋㅋ 죄송

역시 제가 경솔하고 마음이 급한 탓이었습니다. 그래요 컴퓨터는 거짓말을 하지 않는답니다?

 

 

고쳐줬더니, 이젠 오류 없이 잘 돌아가는 모습을 볼 수 있습니다. 그럼 진짜 두근두근

 

 

대체 왜 또 그러는거니... 정말이지...

어쩔 수 없죠. 그래 너가 거짓말을 했겠니 설마.. 다 인간인 내가 잘못한거지

다시 콘솔창을 보러 갈까요

 

 

오우.. 이번에는 sql문제인것같군요. 'product_category'가 없다라...

html 파일에서 쓴 놈인데 분명? 보러 갑시다.

 

ㅎㅋㅋ

 

아 혹시나 했지만 역시나 제 잘못이네요.

model에 적어놓은 값이랑 영 딴판인 값을 적어놓고서는 가져오라고 윽박을 지르고있었군요 제가 ㅎ

미안미안 다시 적어줄게

 

수정완 ㅎㅎ

 

이제 진짜 다시 돌려봅시다

 

와아

 

드.디.어 진짜 아름다운 결과값이 나왔네요 ㅋ ㅋㅋㅋ ㅋㅋㅋㅋ

아무리 생각해도 컴퓨터는 진짜 너무 예민한것같아요. (본인이 잘못해놓고 남탓 ㄹㅈㄷ)

 


 

블로그를 쓰면서 느꼈습니다..

무지성 받아쓰기는 이렇게나 위험하구나!

 

그리고 글을 다 쓰니 이제 구조가 눈에 보일듯..말듯..

사실 아직도 신기루같아요

 

하지만 그거 있잖아요 뭐지 속담.. 첫 술에 배부르랴?

엄밀히 말하자면 두 술째긴 한데, 그거나 그거나죠 뭐.

밥 한 공기가 될 때까지 하다보면 또 뭐든 머리에 들어오겠죠~

 

그 때까지 해볼겠습니다ㅎㅎ 그럼 안뇽