🙏내용에 대한 피드백은 언제나 환영입니다!!🙏
Spring Framework 분야에서 유명한 '토비의 스프링'책을 읽었다.
학업공부와 함께 병행하며 공부를 해 시간이 오래 걸렸지만, Spring의 기본 개념에 대해서 깨우치기에 아주 좋은 책이였다.
하지만, 아직 완벽하다고 생각하지 않는다. 이 책은 대학생활 중에서만이 아니라 취업을 한 후 백엔드 개발자가 되어서도 읽을 것이다.
먼저, CRUD구조를 만들어보려고 한다. 자바에서 데이터베이스에 접근 하는 방법은 기본적으로 JDBC 인터페이스이다. 현재 Spring은 JDBCTemplate, JPA 등 많이 발전하고, 발전할 것이다.
하지만, 수학 문제를 풀려면 공식을 알아야 하는 것처럼, 기본적인 동작을 먼저 이해하는 것이 중요하다고 개인적으로 생각한다. 그래서 순수JDBC를 이용하여 만들었다.
(mysql을 사용해보고자 하였지만, 처음 접할 때는 간단히 사용할 수 있는 것을 추천하였고, 그래서 h2데이터베이스를 사용해 보았다. 발전하자!)
JDBC(Java DataBase Connectivity)는 SQL문을 실행할 수 있는 함수 호출 인터페이스이다.
데이터베이스의 변환 (ex. oracle에서 MogoDB로의 변환)을 할 때 어플리케이션(프로그램)에서 데이터베이스에 접근하는 방법을 바꿔야한다. 이것을 해결하기 위해 JDBC 인터페이스를 기초로 데이터베이스에 접근하고 쿼리를 날려 데이터를 가져오는 일들을 한다.
그리고 각 데이터베이스 제조사들은 jdbc 인터페이스를 기반으로한 JDBC Driver를 제공한다. 따라서, 어떤 데이터베이스를 이용하는 것에 따라서 JDBC Driver만 변경하고, 몇개의 설정만을 바꿔서 어플리케이션의 변화없이 데이터베이스를 사용 가능하다.
<출처 - 나무소리 유튜브>
jdbc의 장점과 단점에 대해서 보겠다.
3-1. update를 하는 경우(데이터베이스를 변경하는 쿼리를 날린 경우. 주로 INSERT, UPDATE, DELETE를 말함.) 변경할 값을 설정 후 executeUpdate()를 통해서 데이터를 변경.
3-2. 데이터베이스의 값을 가지고 오는 경우(주로 SELECT를 말함.) executeQuery()를 통해서 ResultSet에 값을 저장한다.
(ResultSet은 주어진 쿼리에 의해 생성된 데이터를 포함한다.)
어떤 것과 어떻게 연결되는지 먼저 다이어그램을 만들어봤다.
(처음 만들어 굉장히 심플하고, 심플하지만.... 미래의 나와 비교해보고 싶다.)
class들이 어떻게 연관되어 작동하는지를 보여주었다.
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;
}
}
동작방법들이 담겨있는 인터페이스이다.
현재 여기서는 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;
}
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;
}
}
데이터베이스에 연결하기 위한 interface이다.
회사라고 가정하자. 회사들의 데이터베이스는 다르다.
만약, 이 코드를 이용하여 사용을 한다면, 각 회사에 맞는 데이터베이스를 연결하여 사용해야한다. 그래서 interface를 통해 각 회사드르이 정보를 추가하여 연결이 쉽게 만들었다.
package com.practice.Dao;
import java.sql.Connection;
import java.sql.SQLException;
public interface ConnectionMaker {
public Connection makeConnection() throws ClassNotFoundException, SQLException;
}
위에서 말한 회사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;
}
}
이 공간은 스프링 컨테이너(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를 사용해서 더 편리하게 코드를 짤 수 있을 것이다.
그래도, 동작 원리를 알아두면 스프링의 이해에 더욱 도움이 될 것이다.