개선전 코드 (코드 리뷰 및 개선점을 요청받은 코드)
더보기
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import kwan.utility.customException.CustomReflectionException;
public class Utility {
static Connection con;
static PreparedStatement ps;
static ResultSet rs;
private static Converter converter = new Converter();
public static <E> E readData(String query, E dto) {
try {
Connection con = DBUtil.getConnection();
PreparedStatement ps = con.prepareStatement(query);
ResultSet rs = ps.executeQuery();
return converter.getDataByDTO(rs, dto);
} catch (SQLException | CustomReflectionException e) {
e.printStackTrace();
System.out.println("실패했습니다!");
} finally {
DBUtil.close(rs, ps, con);
}
return dto;
}
public static <E> List<E> readListData(String query, E dto) {
try {
Connection con = DBUtil.getConnection();
PreparedStatement ps = con.prepareStatement(query);
ResultSet rs = ps.executeQuery();
List<E> returnValue = converter.getDataByList(rs, dto);
return returnValue;
} catch (SQLException | CustomReflectionException e) {
e.printStackTrace();
System.out.println("실패했습니다!");
} finally {
DBUtil.close(rs, ps, con);
}
return new ArrayList<>();
}
/**
* 수정 시에는 항상 완전한 DTO 를 갈아끼우는 형태
* @param query
* @param form
* @param <E>
*/
public static <E> void writeData(String query, E form) {
try {
Connection con = DBUtil.getConnection();
PreparedStatement ps = con.prepareStatement(query);
converter.setData(ps, form);
} catch (SQLException | IllegalAccessException e) {
e.printStackTrace();
System.out.println("실패했습니다!");
return;
}
System.out.println("성공했습니다!");
}
}
더보기
import static kwan.cons.IdentityInfo.DB_ID;
import static kwan.cons.IdentityInfo.DB_PASSWORD;
import static kwan.cons.IdentityInfo.DB_URL;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DBUtil {
public static Connection getConnection() {
Connection con = null;
try {
con = DriverManager.getConnection(DB_URL, DB_ID, DB_PASSWORD);
}catch (SQLException e) {
e.printStackTrace();
}
return con;
}
public static void close(AutoCloseable... closeables){
for(AutoCloseable closeable : closeables){
if(closeable != null){
try {
closeable.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
}
더보기
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import kwan.utility.customException.CustomReflectionException;
public class Converter {
/**
* E form : DTO 객체 전달 (ex. new MovieDTO() )
*/
public <E> E getDataByDTO(ResultSet resultSet, E form) throws SQLException {
Class<?> clazz = form.getClass();
Field[] fields = clazz.getDeclaredFields();
while (resultSet.next()) {
try {
mapToDTO(resultSet, form, fields);
} catch (IllegalAccessException e) {
e.printStackTrace();
throw new CustomReflectionException();
}
}
return form;
}
public <E> List<E> getDataByList(ResultSet resultSet, E form)
throws SQLException {
Class<?> clazz = form.getClass();
Field[] fields = clazz.getDeclaredFields();
List<E> forms = new ArrayList<>();
while (resultSet.next()) {
try {
mapToDTO(resultSet, form, fields); // 객체 한 건 맵핑
forms.add(form);
form = (E) clazz.getDeclaredConstructor().newInstance();
} catch (InvocationTargetException | IllegalAccessException | InstantiationException |
NoSuchMethodException e) {
e.printStackTrace();
throw new CustomReflectionException();
}
}
return forms;
}
private <E> void mapToDTO(ResultSet resultSet, E form, Field[] fields)
throws SQLException, IllegalAccessException {
for (int i = 0; i < fields.length; i++) {
Object value = resultSet.getObject(fields[i].getName()); // value 가 String 값인 경우에는 파싱을 따로 해줘야해 -> 파싱 전용 메서드를 만들어
fields[i].setAccessible(true);
fields[i].set(form, value); // Object 값으로 value 한개씩 뽑아
}
}
public <E> void setData(PreparedStatement ps, E form) throws IllegalAccessException, SQLException {
Class<?> clazz = form.getClass();
Field[] fields = clazz.getDeclaredFields();
for (int i = 1; i <= fields.length; i++) {
fields[i - 1].setAccessible(true);
ps.setObject(i, fields[i - 1].get(form));
}
ps.executeUpdate();
}
}
개선후 코드 (개선점을 알려주고 코드를 제공함)
더보기
import java.sql.PreparedStatement;
import java.sql.SQLException;
@FunctionalInterface
public interface StatementFunc<T> {
void makePreparedStatement(PreparedStatement ps, T obj) throws SQLException;
}
import java.sql.ResultSet;
@FunctionalInterface
public interface RowMapper<T> {
T run(ResultSet rs);
}
더보기
try with resources를 통해 Connection PreparedStatement ResultSet이 Autoclosable임을 활용했고
Converter에서 모든 값을 변환 후 나오게 하는 것이 아닌
함수형 인터페이스를 통해 미리 함수를 등록해 놓고 값 하나마다 메서드를 적용시켜서 원하는 값을 얻어 올 수 있도록 변환했음
이전 코드에서는 Result set 덩어리를 가지고 다니면서 한번에 변환을 시도해서 필요한 기능이 생기면 새로 메서드를 계속 추가해야하는 구조였음
import java.lang.reflect.Array;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
// https://stackoverflow.com/questions/34939499/how-to-properly-return-generic-array-in-java-generic-method 참고 자료
// https://hudi.blog/jdbc-library-mission/
// 조회에 Optional이 들어가면 더 좋을듯?
public class JdbcTemplate {
public <T> T readData(String sql, RowMapper<T> mapper) {
try (ResultSet rs = executeQuery(sql)){
if (rs.next()) {
return mapper.run(rs);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
return null;
}
public <T> T[] findAll(String sql, Class<T> clazz, RowMapper<T> mapper) {
List<T> list = new ArrayList<>();
try (ResultSet rs = executeQuery(sql)){
if (rs.next()) {
list.add(mapper.run(rs));
}
@SuppressWarnings("unchecked")
T[] array = (T[]) Array.newInstance(clazz, list.size());
return list.toArray(array);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public <T> List<T> findAllByList(String sql, RowMapper<T> mapper) {
List<T> list = new ArrayList<>();
try (ResultSet rs = executeQuery(sql)){
while (rs.next()) {
list.add(mapper.run(rs));
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
return list;
}
public <T> void update(String sql, StatementFunc<T> st, T t) {
try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)){
st.makePreparedStatement(ps, t);
ps.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
private ResultSet executeQuery(String sql) {
try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)){
return ps.executeQuery();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
더보기
이전 코드와 달리 기능을 구현하는 코드를 분리해 클래스를 작성하고 해당 클래스에서 메서드를 받아오는 방식으로 역할을 분리하고자 했음
또한 Converter에 있는 메서드에 Field를 직접 주입하기 위해 Class 파라미터를 Utility를 받아올 수 있게 수정함
함수형 인터페이스를 통해 파라미터에 직접 람다 형식으로 메서드를 적용해서 기능이 분리된 jdbctemplate 클래스에서 해당 return 타입을 받아서 사용할 수 있도록 함
package utility;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import utility.customException.CustomReflectionException;
public class Utility {
private static final JdbcTemplate jdbcTemplate = new JdbcTemplate();
public static <E> E readData(String query, Class<E> clazz) {
return jdbcTemplate.readData(query, rs -> mapResultSetToObject(rs, clazz));
}
public static <E> E[] findAll(String query, Class<E> clazz) {
return jdbcTemplate.findAll(query, clazz, rs -> mapResultSetToObject(rs, clazz));
}
public static <T> List<T> readListData(String query, Class<T> clazz) {
return jdbcTemplate.findAllByList(query, rs -> mapResultSetToObject(rs, clazz));
}
private static <E> E mapResultSetToObject(ResultSet rs, Class<E> clazz) {
try {
E e = clazz.getDeclaredConstructor().newInstance();
Converter.mapToDTO(rs, e, clazz.getDeclaredFields());
return e;
} catch (SQLException | ReflectiveOperationException ex) {
try {
throw new SQLException(ex);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
public static <E> void writeData(String query, E form) {
jdbcTemplate.update(query, Converter::setData, form);
}
}
더보기
기존의 getDataByDto / getDataByList 메서드가 하는 역할이 결국 field를 만들어 주입 시켜주는 것이기 때문에
이를 Utility에서 Converter를 사용할 때 직접 파라미터로 넣어주면 된다고 생각했음
위의 메서드의 과정을 묶어서 Utility에 전가함
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import utility.customException.CustomReflectionException;
public class Converter {
public static <E> void mapToDTO(ResultSet resultSet, E form, Field[] fields)
throws SQLException, IllegalAccessException {
for (Field field : fields) {
field.setAccessible(true);
Object value = resultSet.getObject(field.getName());
if (field.getType().isEnum() && value instanceof String enumValue) {
// Enum 타입 필드에 문자열 값을 Enum으로 변환
@SuppressWarnings("unchecked")
Class<? extends Enum> enumClass = (Class<? extends Enum>) field.getType();
value = Enum.valueOf(enumClass, enumValue);
}
field.set(form, value);
}
}
public static <E> void setData(PreparedStatement ps, E form) throws SQLException {
Class<?> clazz = form.getClass();
Field[] fields = clazz.getDeclaredFields();
for (int i = 1; i <= fields.length; i++) {
try {
fields[i - 1].setAccessible(true);
ps.setObject(i, fields[i - 1].get(form));
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
'프로그래밍 > JAVA' 카테고리의 다른 글
[Java] 데이터 입출력 (0) | 2024.08.02 |
---|---|
[Java] 정규 표현식 (0) | 2024.08.02 |
[Java] 자바 21이후~ (0) | 2024.08.02 |
[Java] 데이터베이스 연동(MySQL) (0) | 2024.07.31 |
[Java] 중첩 인터페이스 (0) | 2024.07.29 |