From fa6dd0361a2274048c155dce0dde77f2b1eeed11 Mon Sep 17 00:00:00 2001 From: codingbaraGo Date: Thu, 15 Jan 2026 11:02:31 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat(database):=20H2=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle b/build.gradle index c7b2ba3f1..4e5ee18de 100644 --- a/build.gradle +++ b/build.gradle @@ -16,6 +16,8 @@ dependencies { implementation 'ch.qos.logback:logback-classic:1.2.3' testImplementation 'org.assertj:assertj-core:3.16.1' + runtimeOnly 'com.h2database:h2:2.2.224' + testImplementation 'org.mockito:mockito-core:4.11.0' testImplementation('org.mockito:mockito-junit-jupiter:4.11.0') { exclude group: 'org.junit.jupiter' From 263a851e3e7277111eee0e29e00038fcbae7d256 Mon Sep 17 00:00:00 2001 From: codingbaraGo Date: Thu, 15 Jan 2026 11:07:03 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat(database):=20ConnectionManager=20?= =?UTF-8?q?=EA=B0=9C=EB=B0=9C=20-=20ConnectionManager=20=EC=9D=B8=ED=84=B0?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EC=A0=95=EC=9D=98=20(=ED=96=A5?= =?UTF-8?q?=ED=9B=84=20=EC=BB=A4=EB=84=A5=EC=85=98=20=ED=92=80=20=ED=99=95?= =?UTF-8?q?=EC=9E=A5=20=EA=B0=80=EB=8A=A5=EC=84=B1=20=EA=B3=A0=EB=A0=A4)?= =?UTF-8?q?=20-=20H2=20db=EC=97=90=20=EB=8C=80=ED=95=9C=20DriverManager?= =?UTF-8?q?=EB=A5=BC=20=ED=86=B5=ED=95=B4=20Connection=EC=9D=84=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=ED=95=98=EB=8A=94=20H2DbManager=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EA=B0=9C=EB=B0=9C=20-=20Database=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=84=A4=EC=A0=95=EC=9D=84=20=EA=B4=80?= =?UTF-8?q?=EC=9E=A5=ED=95=98=EB=8A=94=20DatabaseConfig=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/config/AppConfig.java | 15 ++++++++++ src/main/java/config/DatabaseConfig.java | 12 ++++++++ src/main/java/database/ConnectionManager.java | 7 +++++ src/main/java/database/H2DbManager.java | 28 +++++++++++++++++++ 4 files changed, 62 insertions(+) create mode 100644 src/main/java/config/DatabaseConfig.java create mode 100644 src/main/java/database/ConnectionManager.java create mode 100644 src/main/java/database/H2DbManager.java diff --git a/src/main/java/config/AppConfig.java b/src/main/java/config/AppConfig.java index 64c8413d1..4b6b7b7f9 100644 --- a/src/main/java/config/AppConfig.java +++ b/src/main/java/config/AppConfig.java @@ -1,6 +1,9 @@ package config; +import app.db.UserRepository; import app.handler.*; +import database.ConnectionManager; +import database.H2DbManager; import exception.ExceptionHandlerMapping; import exception.handler.ErrorExceptionHandler; import exception.handler.ServiceExceptionHandler; @@ -94,6 +97,7 @@ public List webHandlerList() { ); } + // ===== Handler ===== public StaticContentHandler staticContentHandler() { return getOrCreate( "staticContentHandler", @@ -298,4 +302,15 @@ public SessionStorage sessionStorage() { return getOrCreate("sessionStorage", SessionStorage::new); } + + /** + * ===== DB ===== + */ + public ConnectionManager connectionManager(){ + return h2DbManager(); + } + + public H2DbManager h2DbManager(){ + return getOrCreate(H2DbManager.class.getSimpleName(), H2DbManager::new); + } } diff --git a/src/main/java/config/DatabaseConfig.java b/src/main/java/config/DatabaseConfig.java new file mode 100644 index 000000000..0ee7d8e9d --- /dev/null +++ b/src/main/java/config/DatabaseConfig.java @@ -0,0 +1,12 @@ +package config; + +import app.model.User; +import database.TestDao; + +import java.util.List; + +public class DatabaseConfig { + public static final String H2_DB_URL = "jdbc:h2:tcp://localhost//Users/apple/h2/testdb"; + public static final String H2_DB_USER = "sa"; + public static final String H2_DB_PASSWORD = ""; +} diff --git a/src/main/java/database/ConnectionManager.java b/src/main/java/database/ConnectionManager.java new file mode 100644 index 000000000..1b1b06278 --- /dev/null +++ b/src/main/java/database/ConnectionManager.java @@ -0,0 +1,7 @@ +package database; + +import java.sql.Connection; + +public interface ConnectionManager { + Connection getConnection(); +} diff --git a/src/main/java/database/H2DbManager.java b/src/main/java/database/H2DbManager.java new file mode 100644 index 000000000..0b54cab18 --- /dev/null +++ b/src/main/java/database/H2DbManager.java @@ -0,0 +1,28 @@ +package database; + +import config.DatabaseConfig; +import exception.ErrorException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +public class H2DbManager implements ConnectionManager{ + private static final String DB_URL = DatabaseConfig.H2_DB_URL; + private static final String DB_USER = DatabaseConfig.H2_DB_USER; + private static final String DB_PASSWORD = DatabaseConfig.H2_DB_PASSWORD; + private static final Logger log = LoggerFactory.getLogger(H2DbManager.class); + + @Override + public Connection getConnection() { + try { + return DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD); + } catch (SQLException e){ + log.info(e.fillInStackTrace().toString()); + log.info(e.getSQLState()); + throw new ErrorException("DB error"); + } + } +} From c7fe85f364639213200e86b065b91f1d60cc2ca2 Mon Sep 17 00:00:00 2001 From: codingbaraGo Date: Thu, 15 Jan 2026 11:12:06 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat(database):=20DDL=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EA=B8=B0=EB=8A=A5=20=EA=B0=9C=EB=B0=9C=20?= =?UTF-8?q?-=20DatabaseConfig=EC=97=90=20DAO=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=EB=A5=BC=20=EB=93=B1=EB=A1=9D=ED=95=98=EB=8A=94=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80=20-=20=EB=93=B1=EB=A1=9D?= =?UTF-8?q?=EB=90=9C=20DAO=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=93=A4=EC=97=90?= =?UTF-8?q?=20=EB=8C=80=ED=95=9C=20DDL=EC=9D=84=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=EC=83=9D=EC=84=B1=ED=95=98=EB=8A=94=20DdlGenerator=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/bootstrap/WebServer.java | 3 + src/main/java/config/AppConfig.java | 7 +- src/main/java/config/DatabaseConfig.java | 20 +++- src/main/java/config/DdlGenerator.java | 111 +++++++++++++++++++++++ 4 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 src/main/java/config/DdlGenerator.java diff --git a/src/main/java/bootstrap/WebServer.java b/src/main/java/bootstrap/WebServer.java index 95f524e1f..51c7811d1 100644 --- a/src/main/java/bootstrap/WebServer.java +++ b/src/main/java/bootstrap/WebServer.java @@ -6,6 +6,7 @@ import java.util.concurrent.Executors; import config.AppConfig; +import config.DatabaseConfig; import config.FilterConfig; import config.SecurityConfig; import org.slf4j.Logger; @@ -18,6 +19,7 @@ public class WebServer { private static final AppConfig LOADER = new AppConfig(); private static final SecurityConfig securityConfig = new SecurityConfig(); private static final FilterConfig filterConfig = new FilterConfig(); + private static final DatabaseConfig databaseConfig = new DatabaseConfig(); private static final ExecutorService executor = Executors.newFixedThreadPool(32); public static void main(String args[]) throws Exception { @@ -53,5 +55,6 @@ public static void main(String args[]) throws Exception { private static void config(){ securityConfig.config(); filterConfig.config(); + databaseConfig.config(); } } diff --git a/src/main/java/config/AppConfig.java b/src/main/java/config/AppConfig.java index 4b6b7b7f9..7944b2cbf 100644 --- a/src/main/java/config/AppConfig.java +++ b/src/main/java/config/AppConfig.java @@ -1,6 +1,5 @@ package config; -import app.db.UserRepository; import app.handler.*; import database.ConnectionManager; import database.H2DbManager; @@ -313,4 +312,10 @@ public ConnectionManager connectionManager(){ public H2DbManager h2DbManager(){ return getOrCreate(H2DbManager.class.getSimpleName(), H2DbManager::new); } + + public DdlGenerator ddlGenerator(){ + return getOrCreate(DdlGenerator.class.getSimpleName(), () -> + new DdlGenerator(connectionManager())); + } } + diff --git a/src/main/java/config/DatabaseConfig.java b/src/main/java/config/DatabaseConfig.java index 0ee7d8e9d..70e72a47c 100644 --- a/src/main/java/config/DatabaseConfig.java +++ b/src/main/java/config/DatabaseConfig.java @@ -1,12 +1,28 @@ package config; import app.model.User; -import database.TestDao; - import java.util.List; public class DatabaseConfig { + private final AppConfig appConfig = new AppConfig(); + + public static final List> ENTITY_CLASSES = List.of( + User.class + ); + + public static final List RESOLVED_WORD = List.of( + "user" + ); + public static final String H2_DB_URL = "jdbc:h2:tcp://localhost//Users/apple/h2/testdb"; public static final String H2_DB_USER = "sa"; public static final String H2_DB_PASSWORD = ""; + + public static final boolean CREATE_TABLES = false; + public static final boolean DROP_IF_EXISTS = false; + + public void config(){ + DdlGenerator ddlGenerator = appConfig.ddlGenerator(); + if(CREATE_TABLES) ddlGenerator.generateTables(); + } } diff --git a/src/main/java/config/DdlGenerator.java b/src/main/java/config/DdlGenerator.java new file mode 100644 index 000000000..3dfb27dbf --- /dev/null +++ b/src/main/java/config/DdlGenerator.java @@ -0,0 +1,111 @@ +package config; + +import database.ConnectionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.time.LocalDateTime; +import java.util.List; + +public class DdlGenerator { + private static final Logger log = LoggerFactory.getLogger(DdlGenerator.class); + private final List resolvedWord = DatabaseConfig.RESOLVED_WORD; + private final ConnectionManager connectionManager; + private final boolean dropIfExists = DatabaseConfig.DROP_IF_EXISTS; + private final List> entityClasses = DatabaseConfig.ENTITY_CLASSES; + + public DdlGenerator(ConnectionManager connectionManager) { + this.connectionManager = connectionManager; + } + + public void generateTables() { + try (Connection conn = connectionManager.getConnection(); + Statement stmt = conn.createStatement()) { + + for (Class entityClass : entityClasses) { + String tableName = toTableName(entityClass); + String ddl = buildDdlForEntity(entityClass, tableName); + log.info("DDL for {}:\n{}", tableName, ddl); + stmt.execute(ddl); + } + + } catch (SQLException e) { + throw new RuntimeException("DDL 생성/실행 중 오류", e); + } + } + + private String buildDdlForEntity(Class entityClass, String tableName) { + StringBuilder ddl = new StringBuilder(); + + if (dropIfExists) { + ddl.append("DROP TABLE IF EXISTS ").append(tableName).append(";\n"); + } + + ddl.append("CREATE TABLE ").append(tableName).append(" (\n"); + + Field[] fields = entityClass.getDeclaredFields(); + + boolean firstColumn = true; + + for (Field field : fields) { + if (Modifier.isStatic(field.getModifiers())) { + continue; + } + + String columnName = field.getName(); + Class fieldType = field.getType(); + + if (!firstColumn) { + ddl.append(",\n"); + } + firstColumn = false; + + if ("id".equals(columnName) && (fieldType == Long.class || fieldType == long.class)) { + ddl.append(" id BIGINT AUTO_INCREMENT PRIMARY KEY"); + } else { + String sqlType = toSqlType(fieldType); + ddl.append(" ").append(columnName).append(" ").append(sqlType); + } + } + + ddl.append("\n);"); + + return ddl.toString(); + } + + private String toSqlType(Class fieldType) { + if (fieldType == Long.class || fieldType == long.class) { + return "BIGINT"; + } else if (fieldType == Integer.class || fieldType == int.class) { + return "INT"; + } else if (fieldType == String.class) { + return "VARCHAR(255)"; + } else if (fieldType == Boolean.class || fieldType == boolean.class) { + return "BOOLEAN"; + } else if (fieldType == LocalDateTime.class || + fieldType == java.util.Date.class || + fieldType == java.sql.Timestamp.class) { + return "TIMESTAMP"; + } else if (fieldType == Double.class || fieldType == double.class) { + return "DOUBLE"; + } else if (fieldType == Float.class || fieldType == float.class) { + return "FLOAT"; + } + return "VARCHAR(255)"; + } + + private String toTableName(Class clazz) { + String name = clazz.getSimpleName().toLowerCase(); + + for(String word : resolvedWord){ + if(word.equals(name)) + return "`" + word + "`"; + } + return name; + } +} From f2971293c7f3fcf30d1d40120ac77e1b77b218c5 Mon Sep 17 00:00:00 2001 From: codingbaraGo Date: Thu, 15 Jan 2026 11:35:28 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat(database):=20CRU=20=EC=BF=BC=EB=A6=AC?= =?UTF-8?q?=20=EC=9E=90=EB=8F=99=EC=83=9D=EC=84=B1=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B0=9C=EB=B0=9C=20-=20DAO=EC=97=90=20=EB=8C=80=ED=95=9C=20CR?= =?UTF-8?q?U=20=EC=BF=BC=EB=A6=AC=EB=A5=BC=20=EC=83=9D=EC=84=B1=ED=95=B4?= =?UTF-8?q?=EC=84=9C=20Connection=EC=97=90=20=EB=B3=B4=EB=82=B4=EB=8A=94?= =?UTF-8?q?=20CrudRepository=20=EA=B0=9C=EB=B0=9C=20-=20Delete=EC=9D=98=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20=ED=96=A5=ED=9B=84=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EC=98=88=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/app/db/Database.java | 2 +- src/main/java/app/handler/LoginWithPost.java | 2 +- .../java/app/handler/RegisterWithGet.java | 4 +- src/main/java/app/model/User.java | 12 +- src/main/java/database/CrudRepository.java | 314 ++++++++++++++++++ 5 files changed, 324 insertions(+), 10 deletions(-) create mode 100644 src/main/java/database/CrudRepository.java diff --git a/src/main/java/app/db/Database.java b/src/main/java/app/db/Database.java index 8a95d06ac..333135d8d 100644 --- a/src/main/java/app/db/Database.java +++ b/src/main/java/app/db/Database.java @@ -14,7 +14,7 @@ public class Database { public static void addUser(User user) { long id = sequentialId.getAndIncrement(); - user.setUserId(id); + user.setId(id); users.put(id, user); } diff --git a/src/main/java/app/handler/LoginWithPost.java b/src/main/java/app/handler/LoginWithPost.java index 6f64f319d..987ad8160 100644 --- a/src/main/java/app/handler/LoginWithPost.java +++ b/src/main/java/app/handler/LoginWithPost.java @@ -38,7 +38,7 @@ public HandlerResponse handle(QueryParameters params) { } SessionEntity session = sessionManager.create( - user.getUserId(), + user.getId(), user.getUserRole(), user.getNickname()); diff --git a/src/main/java/app/handler/RegisterWithGet.java b/src/main/java/app/handler/RegisterWithGet.java index 2d9ab46f7..fa0740b57 100644 --- a/src/main/java/app/handler/RegisterWithGet.java +++ b/src/main/java/app/handler/RegisterWithGet.java @@ -11,7 +11,7 @@ import web.filter.authentication.UserRole; import web.handler.SingleArgHandler; import web.response.HandlerResponse; -import web.response.StaticViewResponse; +import web.response.RedirectResponse; public class RegisterWithGet extends SingleArgHandler { private static final Logger log = LoggerFactory.getLogger(RegisterWithGet.class); @@ -28,6 +28,6 @@ public HandlerResponse handle(QueryParameters params) { String nickname = params.getQueryValue("nickname").orElseThrow(()-> new ServiceException(ErrorCode.MISSING_REGISTER_TOKEN, "nickname required")); Database.addUser(new User(password, nickname, email, UserRole.MEMBER.toString())); log.info("Registered - password:{}, nickname:{}, email:{}", password, nickname, email); - return StaticViewResponse.of("/login"); + return RedirectResponse.to("/login"); } } diff --git a/src/main/java/app/model/User.java b/src/main/java/app/model/User.java index 550178337..936b915e6 100644 --- a/src/main/java/app/model/User.java +++ b/src/main/java/app/model/User.java @@ -1,7 +1,7 @@ package app.model; public class User { - private Long userId; + private Long id; private String password; private String nickname; private String email; @@ -14,12 +14,12 @@ public User(String password, String nickname, String email, String userRole) { this.userRole = userRole; } - public Long getUserId() { - return userId; + public Long getId() { + return id; } - public void setUserId(Long userId){ - this.userId = userId; + public void setId(Long id){ + this.id = id; } public String getPassword() { @@ -40,6 +40,6 @@ public String getUserRole() { @Override public String toString() { - return "User [userId=" + userId + ", password=" + password + ", name=" + nickname + ", email=" + email + "]"; + return "User [userId=" + id + ", password=" + password + ", name=" + nickname + ", email=" + email + "]"; } } diff --git a/src/main/java/database/CrudRepository.java b/src/main/java/database/CrudRepository.java new file mode 100644 index 000000000..55c67cfe4 --- /dev/null +++ b/src/main/java/database/CrudRepository.java @@ -0,0 +1,314 @@ +package database; + +import config.DatabaseConfig; +import exception.ErrorException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.sql.*; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class CrudRepository { + private static final Logger log = LoggerFactory.getLogger(CrudRepository.class); + private final List resolvedWord = DatabaseConfig.RESOLVED_WORD; + private final ConnectionManager connectionManager; + private final Class type; + private final String tableName; + + public CrudRepository(ConnectionManager connectionManager, Class type) { + this.connectionManager = connectionManager; + this.type = type; + this.tableName = toTableName(type); + } + + // ========== CREATE ========== + public T save(T entity) { + try (Connection conn = connectionManager.getConnection()) { + + Field[] allFields = type.getDeclaredFields(); + + StringBuilder sqlBuilder = new StringBuilder(); + StringBuilder placeholder = new StringBuilder(); + + sqlBuilder.append("INSERT INTO ").append(tableName).append(" ("); + + List insertFields = new ArrayList<>(); + + for (Field field : allFields) { + if (Modifier.isStatic(field.getModifiers())) { + continue; + } + if ("id".equals(field.getName())) { + continue; + } + sqlBuilder.append(field.getName()).append(", "); + placeholder.append("?, "); + insertFields.add(field); + } + + if (!insertFields.isEmpty()) { + sqlBuilder.setLength(sqlBuilder.length() - 2); + placeholder.setLength(placeholder.length() - 2); + } + + sqlBuilder.append(") VALUES (").append(placeholder).append(")"); + + String sql = sqlBuilder.toString(); + + try (PreparedStatement pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { + + int index = 1; + for (Field field : insertFields) { + field.setAccessible(true); + Object value = field.get(entity); + setParameter(pstmt, index++, value); + } + + pstmt.executeUpdate(); + + try (ResultSet resultSet = pstmt.getGeneratedKeys()) { + if (resultSet.next()) { + long generatedId = resultSet.getLong(1); + setId(entity, generatedId); + } + } + } + + log.info("{} created: {}", tableName, sql); + return entity; + } catch (SQLException | IllegalAccessException e) { + throw new ErrorException("엔티티 저장 중 오류", e); + } + } + + // ========== READ ========== + public Optional findById(Long id) { + String sql = "SELECT * FROM " + tableName + " WHERE id = ?"; + + try (Connection conn = connectionManager.getConnection(); + PreparedStatement pstmt = conn.prepareStatement(sql)) { + + pstmt.setLong(1, id); + + try (ResultSet resultSet = pstmt.executeQuery()) { + if (resultSet.next()) { + log.info("{} queried: {}", tableName, sql); + return Optional.of(mapRow(resultSet)); + } + return Optional.empty(); + } + + } catch (SQLException e) { + throw new ErrorException("엔티티 조회 중 오류 (id=" + id + ")", e); + } + } + + public List findAll() { + String sql = "SELECT * FROM " + tableName; + + try (Connection conn = connectionManager.getConnection(); + PreparedStatement pstmt = conn.prepareStatement(sql); + ResultSet resultSet = pstmt.executeQuery()) { + + List result = new ArrayList<>(); + while (resultSet.next()) { + result.add(mapRow(resultSet)); + } + log.info("{} queried({} results): {}", tableName, result.size(), sql); + return result; + + } catch (SQLException e) { + throw new ErrorException("엔티티 전체 조회 중 오류", e); + } + } + + public List findByColumn(String columnName, Object value) { + String sql = "SELECT * FROM " + tableName + " WHERE " + columnName + " = ?"; + + try (Connection conn = connectionManager.getConnection(); + PreparedStatement pstmt = conn.prepareStatement(sql)) { + + setParameter(pstmt, 1, value); + + try (ResultSet resultSet = pstmt.executeQuery()) { + List result = new ArrayList<>(); + while (resultSet.next()) { + result.add(mapRow(resultSet)); + } + log.info("{} queried({} results): {}", tableName, result.size(), sql); + return result; + } + + } catch (SQLException e) { + throw new ErrorException("엔티티 조회 중 오류 (column=" + columnName + ", value=" + value + ")", e); + } + } + + // ========== UPDATE ========== + public void update(T entity) { + Long id = getId(entity); + if (id == null) { + throw new ErrorException("id가 null인 엔티티는 업데이트할 수 없습니다."); + } + + Field[] allFields = type.getDeclaredFields(); + + StringBuilder sql = new StringBuilder(); + sql.append("UPDATE ").append(tableName).append(" SET "); + + List updateFields = new ArrayList<>(); + + for (Field field : allFields) { + if (Modifier.isStatic(field.getModifiers())) { + continue; + } + if ("id".equals(field.getName())) { + continue; + } + sql.append(field.getName()).append(" = ?, "); + updateFields.add(field); + } + + if (!updateFields.isEmpty()) { + sql.setLength(sql.length() - 2); + } + + sql.append(" WHERE id = ?"); + + String finalSql = sql.toString(); + + try (Connection conn = connectionManager.getConnection(); + PreparedStatement pstmt = conn.prepareStatement(finalSql)) { + + int index = 1; + + for (Field field : updateFields) { + field.setAccessible(true); + Object value = field.get(entity); + setParameter(pstmt, index++, value); + } + + pstmt.setLong(index, id); + + pstmt.executeUpdate(); + + log.info("{} updated: {}", tableName, sql); + } catch (SQLException | IllegalAccessException e) { + throw new ErrorException("엔티티 업데이트 중 오류", e); + } + } + + // ========== private methods ========== + private void setParameter(PreparedStatement pstmt, int index, Object value) throws SQLException { + if (value == null) { + pstmt.setObject(index, null); + return; + } + + if (value instanceof Long) { + pstmt.setLong(index, (Long) value); + } else if (value instanceof Integer) { + pstmt.setInt(index, (Integer) value); + } else if (value instanceof String) { + pstmt.setString(index, (String) value); + } else if (value instanceof Boolean) { + pstmt.setBoolean(index, (Boolean) value); + } else if (value instanceof LocalDateTime) { + pstmt.setTimestamp(index, Timestamp.valueOf((LocalDateTime) value)); + } else if (value instanceof java.util.Date) { + pstmt.setTimestamp(index, new Timestamp(((java.util.Date) value).getTime())); + } else { + pstmt.setObject(index, value); + } + } + + private void setId(T entity, Long id) { + try { + Field idField = type.getDeclaredField("id"); + idField.setAccessible(true); + idField.set(entity, id); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new ErrorException("id 필드 설정 중 오류", e); + } + } + + private Long getId(T entity) { + try { + Field idField = type.getDeclaredField("id"); + idField.setAccessible(true); + Object value = idField.get(entity); + if (value == null) { + return null; + } + if (value instanceof Long) { + return (Long) value; + } + throw new ErrorException("id 필드 타입이 Long이 아닙니다: " + value.getClass()); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new ErrorException("id 필드 조회 중 오류", e); + } + } + + private T mapRow(ResultSet resultSet) { + try { + T instance = type.getDeclaredConstructor().newInstance(); + + Field[] fields = type.getDeclaredFields(); + for (Field field : fields) { + if (Modifier.isStatic(field.getModifiers())) { + continue; + } + + String columnName = field.getName(); + field.setAccessible(true); + + Class fieldType = field.getType(); + + Object value; + + if (fieldType == Long.class || fieldType == long.class) { + long v = resultSet.getLong(columnName); + value = resultSet.wasNull() ? null : v; + } else if (fieldType == Integer.class || fieldType == int.class) { + int v = resultSet.getInt(columnName); + value = resultSet.wasNull() ? null : v; + } else if (fieldType == String.class) { + value = resultSet.getString(columnName); + } else if (fieldType == Boolean.class || fieldType == boolean.class) { + boolean v = resultSet.getBoolean(columnName); + value = resultSet.wasNull() ? null : v; + } else if (fieldType == LocalDateTime.class) { + Timestamp ts = resultSet.getTimestamp(columnName); + value = (ts != null) ? ts.toLocalDateTime() : null; + } else if (fieldType == java.util.Date.class) { + Timestamp ts = resultSet.getTimestamp(columnName); + value = (ts != null) ? new java.util.Date(ts.getTime()) : null; + } else { + value = resultSet.getObject(columnName); + } + + field.set(instance, value); + } + + return instance; + + } catch (Exception e) { + throw new ErrorException("ResultSet → 엔티티 매핑 중 오류", e); + } + } + + private String toTableName(Class clazz) { + String name = clazz.getSimpleName().toLowerCase(); + + for(String word : resolvedWord){ + if(word.equals(name)) + return "`" + word + "`"; + } + return name; + } +} From 726480682e9edb185e0e5e173eea46e1cb6090b4 Mon Sep 17 00:00:00 2001 From: codingbaraGo Date: Thu, 15 Jan 2026 12:05:25 +0900 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20test=EC=97=90=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/web/dispatch/adapter/SingleArgHandlerAdapterTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/web/dispatch/adapter/SingleArgHandlerAdapterTest.java b/src/test/java/web/dispatch/adapter/SingleArgHandlerAdapterTest.java index 56737345e..e0b0536e2 100644 --- a/src/test/java/web/dispatch/adapter/SingleArgHandlerAdapterTest.java +++ b/src/test/java/web/dispatch/adapter/SingleArgHandlerAdapterTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import web.handler.WebHandler; +import web.response.RedirectResponse; import web.response.StaticViewResponse; import web.response.HandlerResponse; @@ -25,7 +26,7 @@ void test(){ HandlerResponse response = adapter.handle(request, handler); assertThat(response).isNotNull(); - assertThat(response).isInstanceOf(StaticViewResponse.class); + assertThat(response).isInstanceOf(RedirectResponse.class); } } \ No newline at end of file From f1a189583d20501c126a550158ec5704e2cf3223 Mon Sep 17 00:00:00 2001 From: codingbaraGo Date: Thu, 15 Jan 2026 12:18:13 +0900 Subject: [PATCH 6/6] =?UTF-8?q?fix:=20=EC=97=90=EB=9F=AC=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=98=20=EB=A1=9C=EA=B7=B8=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD(info=20->=20error)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/database/H2DbManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/database/H2DbManager.java b/src/main/java/database/H2DbManager.java index 0b54cab18..03f74d4e3 100644 --- a/src/main/java/database/H2DbManager.java +++ b/src/main/java/database/H2DbManager.java @@ -20,8 +20,8 @@ public Connection getConnection() { try { return DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD); } catch (SQLException e){ - log.info(e.fillInStackTrace().toString()); - log.info(e.getSQLState()); + log.error(e.fillInStackTrace().toString()); + log.error(e.getSQLState()); throw new ErrorException("DB error"); } }