Spring의 시작

대영·2023년 11월 11일
2

Spring

목록 보기
1/16

🙏내용에 대한 피드백은 언제나 환영입니다!!🙏

📌Spring의 시작

Spring Framework 분야에서 유명한 '토비의 스프링'책을 읽었다.
학업공부와 함께 병행하며 공부를 해 시간이 오래 걸렸지만, Spring의 기본 개념에 대해서 깨우치기에 아주 좋은 책이였다.
하지만, 아직 완벽하다고 생각하지 않는다. 이 책은 대학생활 중에서만이 아니라 취업을 한 후 백엔드 개발자가 되어서도 읽을 것이다.

📌기본

먼저, CRUD구조를 만들어보려고 한다. 자바에서 데이터베이스에 접근 하는 방법은 기본적으로 JDBC 인터페이스이다. 현재 Spring은 JDBCTemplate, JPA 등 많이 발전하고, 발전할 것이다.
하지만, 수학 문제를 풀려면 공식을 알아야 하는 것처럼, 기본적인 동작을 먼저 이해하는 것이 중요하다고 개인적으로 생각한다. 그래서 순수JDBC를 이용하여 만들었다.

(mysql을 사용해보고자 하였지만, 처음 접할 때는 간단히 사용할 수 있는 것을 추천하였고, 그래서 h2데이터베이스를 사용해 보았다. 발전하자!)

👉나아가기 앞서 JDBC란! (JDBC Interface)

JDBC(Java DataBase Connectivity)는 SQL문을 실행할 수 있는 함수 호출 인터페이스이다.

❓이것이 왜 나왔냐!

데이터베이스의 변환 (ex. oracle에서 MogoDB로의 변환)을 할 때 어플리케이션(프로그램)에서 데이터베이스에 접근하는 방법을 바꿔야한다. 이것을 해결하기 위해 JDBC 인터페이스를 기초로 데이터베이스에 접근하고 쿼리를 날려 데이터를 가져오는 일들을 한다.
그리고 각 데이터베이스 제조사들은 jdbc 인터페이스를 기반으로한 JDBC Driver를 제공한다. 따라서, 어떤 데이터베이스를 이용하는 것에 따라서 JDBC Driver만 변경하고, 몇개의 설정만을 바꿔서 어플리케이션의 변화없이 데이터베이스를 사용 가능하다.

<출처 - 나무소리 유튜브>

❓장점, 단점

jdbc의 장점과 단점에 대해서 보겠다.

❕장점
  1. 개발자가 SQL 쿼리를 통해서 데이터베이스를 직접 연결할 수 있다.
  2. 데이터베이스 종속을 줄일 수 있다.(JDBC Interface가 그 이유)
❕단점
  1. 데이터베이스 연결, 결과 처리, 쿼리 등 직접 다루기에 코드가 복잡해진다.
  2. 반복적인 코드 작업이 필요하다.

❓작동방식

  1. 우선 데이터베이스와 연결을 한다. 연결은 Connetion으로한다.
  2. 쿼리를 날린다. PreparedStatment를 통해서 한다!

3-1. update를 하는 경우(데이터베이스를 변경하는 쿼리를 날린 경우. 주로 INSERT, UPDATE, DELETE를 말함.) 변경할 값을 설정 후 executeUpdate()를 통해서 데이터를 변경.

3-2. 데이터베이스의 값을 가지고 오는 경우(주로 SELECT를 말함.) executeQuery()를 통해서 ResultSet에 값을 저장한다.
(ResultSet은 주어진 쿼리에 의해 생성된 데이터를 포함한다.)

📌CRUD구조

👉다이어그램

어떤 것과 어떻게 연결되는지 먼저 다이어그램을 만들어봤다.
(처음 만들어 굉장히 심플하고, 심플하지만.... 미래의 나와 비교해보고 싶다.)

class들이 어떻게 연관되어 작동하는지를 보여주었다.
class의 설명들은 아래에서 하겠다.

👉User class

User 클래스에서는 사용자(user)의 정보를 담고 있다.
이 정보들을 이용하여 사용자를 저장한다.

package com.practice.domain;

public class User {
    private String id;
    private String name;
    private String password;

    public User() {}
 

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    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;
    }
}

👉UserDao interface

동작방법들이 담겨있는 인터페이스이다.
현재 여기서는 Repository밖에 없지만, 나중에 Service를 추가한다면, 이 인터페이스를 구현받아 편리하게 Repository와 연결할 수 있다.

add : 사용자 추가.
get : 사용자의 id를 이용하여 정보 가져오기.
delete : 사용자의 정보 삭제.
deleteAll : 모든 사용자 삭제.
update : 사용자 정보 업데이트.
getAll() : 모든 사용자 가져오기.
getCount() : 사용자가 몇 명인지 확인.

package com.practice.Repository;

import com.practice.domain.User;

import java.sql.SQLException;
import java.util.List;

public interface UserDao {
    void add(User user) throws SQLException, ClassNotFoundException;
    User get(String id) throws SQLException, ClassNotFoundException;
    void delete(User user) throws SQLException, ClassNotFoundException;
    void deleteAll() throws SQLException, ClassNotFoundException;
    void update(User user) throws SQLException, ClassNotFoundException;
    List<User> getAll() throws SQLException, ClassNotFoundException;
    int getCount() throws SQLException, ClassNotFoundException;
}

👉UserDaoRepository class

CRUD구조를 기반으로하여 jdbc구조로 만들어진 class이다.
위의 UserDao interface를 기반으로 만들었다.

package com.practice.Repository;

import com.practice.Dao.ConnectionMaker;
import com.practice.domain.User;
import org.springframework.dao.EmptyResultDataAccessException;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class UserDaoRepository implements UserDao {

    private ConnectionMaker connectionMaker;
    public UserDaoRepository(ConnectionMaker connectionMaker) {
        this.connectionMaker = connectionMaker;
    }

    @Override
    public void add(User user) throws SQLException, ClassNotFoundException {
        Connection c = connectionMaker.makeConnection();   // DB와의 연결.
        PreparedStatement ps = c.prepareStatement("insert into users(id, name, password) values(?,?,?)");   // 아이디를 users(db table)에 추가할 때.
        //PreparedStatement는 미리 컴파일된 SQL 문을 나타내며, SQL 쿼리의 매개변수를 설정할 수 있음.

        ps.setString(1, user.getId());
        ps.setString(2, user.getName());
        ps.setString(3, user.getPassword());

        int rowsAffected = ps.executeUpdate();   //  데이터베이스를 변경하는 쿼리를 실행할 때 사용
		
        if (rowsAffected > 0) { 
            System.out.println("추가된 행 수: " + rowsAffected);
        } else {
            System.out.println("추가되지 않았습니다..");
        }

        ps.close();
        c.close();
    }

    @Override
    public User get(String id) throws SQLException, ClassNotFoundException {
        Connection c = connectionMaker.makeConnection();
        PreparedStatement ps = c.prepareStatement("select * from users where id = ?");  // 아이디를 users(db table)에서 확인할 때.

        ps.setString(1, id);

        ResultSet rs = ps.executeQuery();  //  executeQuery :  Select 문에서만 실행.  반환값 ResultSet
        //ResultSet : object that contains the data produced by the given query

        User user = null;

        if (rs.next()) {  //ResultSet의 next() 메서드를 호출하여 결과 집합의 첫 번째 레코드로 이동합니다.
            // 이 메서드는 레코드가 있을 경우 true를 반환하고, 더 이상 레코드가 없으면 false를 반환합니다.
            user = new User();
            user.setId(rs.getString("id"));
            user.setName(rs.getString("name"));
            user.setPassword(rs.getString("password"));
        } // id를 조건으로 한 쿼리의 결과가 있으면 User 오브젝트를 만들고 값을 넣어준다.

        if(user == null) throw new EmptyResultDataAccessException(1);
        //결과가 없으면 User는 null 상태 그대로일 것이다. 이를 확인해서 예외를 던져준다.

        rs.close();
        ps.close();
        c.close();

        return user;
    }

    @Override
    public void delete(User user) throws SQLException, ClassNotFoundException {
        Connection c = connectionMaker.makeConnection();
        PreparedStatement ps = c.prepareStatement("delete from users where id = ?");

        ps.setString(1, user.getId());

        int rowsAffected = ps.executeUpdate(); // DELETE 쿼리 실행

        if (rowsAffected > 0) {
            System.out.println("삭제된 행 수: " + rowsAffected);
        } else {
            System.out.println("해당 ID를 가진 사용자를 찾을 수 없습니다.");
        }

        ps.close();
        c.close();
    }

    @Override
    public void deleteAll() throws SQLException, ClassNotFoundException {
        Connection c = connectionMaker.makeConnection();
        PreparedStatement ps = c.prepareStatement("delete from users");

        ps.executeUpdate(); // DELETE 쿼리 실행

        ps.close();
        c.close();
    }

    @Override
    public void update(User user) throws SQLException, ClassNotFoundException {
        Connection c = connectionMaker.makeConnection();
        PreparedStatement ps = c.prepareStatement("update users set name = ?, password = ? where id = ?");

        ps.setString(1, user.getName());       // User 객체의 이름 필드
        ps.setString(2, user.getPassword());   // User 객체의 비밀번호 필드
        ps.setString(3, user.getId());         // User 객체의 ID 필드

        int rowsAffected = ps.executeUpdate();  // UPDATE 쿼리 실행
		
        if (rowsAffected > 0) { 
            System.out.println("업데이트된 행 수: " + rowsAffected);
        } else {
            System.out.println("업데이트 되지 않았습니다..");
        }

        ps.close();
        c.close();

    }

    @Override
    public List<User> getAll() throws SQLException, ClassNotFoundException {
        Connection c = connectionMaker.makeConnection();
        PreparedStatement ps = c.prepareStatement("select * from users order by id");

        List<User> userList = new ArrayList<>();

        ResultSet rs = ps.executeQuery();

        while (rs.next()) {
            User user = new User();
            user.setId(rs.getString("id"));
            user.setName(rs.getString("name"));
            user.setPassword(rs.getString("password"));
            // 다른 필드에 대한 설정 추가

            userList.add(user);
        }

        rs.close();
        ps.close();
        c.close();

        return userList;
    }

    @Override
    public int getCount() throws SQLException, ClassNotFoundException {
        Connection c = connectionMaker.makeConnection();
        PreparedStatement ps = c.prepareStatement("SELECT COUNT(*) FROM users");
        ResultSet rs = ps.executeQuery();
        int count = 0;

        if (rs.next()) {
            count = rs.getInt(1);
        }

        rs.close();
        ps.close();
        c.close();

        return count;
    }
}

👉ConnectionMaker interface

데이터베이스에 연결하기 위한 interface이다.
회사라고 가정하자. 회사들의 데이터베이스는 다르다.
만약, 이 코드를 이용하여 사용을 한다면, 각 회사에 맞는 데이터베이스를 연결하여 사용해야한다. 그래서 interface를 통해 각 회사드르이 정보를 추가하여 연결이 쉽게 만들었다.

package com.practice.Dao;

import java.sql.Connection;
import java.sql.SQLException;

public interface ConnectionMaker {
    public Connection makeConnection() throws ClassNotFoundException, SQLException;
}

👉NConnetionMaker class

위에서 말한 회사N라고 하자. 그 회사에 맞는 정보를 넣었다.

package com.practice.Dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class NConnectionMaker implements ConnectionMaker {
    @Override
    public Connection makeConnection() throws ClassNotFoundException, SQLException {
        Class.forName("org.h2.Driver");
        Connection c = DriverManager.getConnection("jdbc:h2:tcp://localhost/~/practice1","sa","");
        return c;
    }

}

👉DaoFactory class

이 공간은 스프링 컨테이너(IoC 컨테이너) 공간이다.
즉, 빈(bean)을 생성하고 관리하며, DI(의존성 주입), 이 코드에서는 안보이지만, AOP 지원, 트랜잭션 관리 등의 역할을 한다. (앞으로 많이 볼 것이다!!)

package com.practice.demo;

import com.practice.Dao.ConnectionMaker;
import com.practice.Dao.NConnectionMaker;
import com.practice.Repository.UserDaoRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DaoFactory {
    @Bean
    public UserDaoRepository userDao() {
        return new UserDaoRepository(connectionMaker());
    }

    public ConnectionMaker connectionMaker() {
        return new NConnectionMaker();
    }

}

@Configuration을 통해서 스프링 컨테이너임을 지정한다.
@Bean을 통해서 빈으로 등록한다.
connetionMaker()를 통해서 데이터베이스를 연결한 것이다.

💡느낀점

스프링이 발전되어 반복적이고 효율적이지 않은 순수JDBC는 많이 사용되지 않는다.
데이터베이스와의 연결도 application.properties에 담아서 연결하고, crud 구조도 JdbcTemplate 또는, JPA를 사용해서 더 편리하게 코드를 짤 수 있을 것이다.
그래도, 동작 원리를 알아두면 스프링의 이해에 더욱 도움이 될 것이다.

profile
Better than yesterday.

0개의 댓글