![[Next Step] 7장 DB를 활용해 데이터를 영구적으로 저장하기](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbdj36X%2FbtsAvVow6eD%2FW9kAK3uzDFgmvVMMakedQK%2Fimg.png)

실습 프로젝트 저장소
실습의 경우 처음에 fork 받았는데, 깃 허브 잔디가 심어지지 않아 기술 블로그 참고(링크)하여 저장소 설정을 변경하도록 함
jwp-basic
https://github.com/slipp/jwp-basic/tree/step2-user-with-mvc-framework
GitHub - slipp/jwp-basic: 자바 웹 프로그래밍 기본 실습
자바 웹 프로그래밍 기본 실습. Contribute to slipp/jwp-basic development by creating an account on GitHub.
github.com
자바 진영은 데이터베이스에 대한 접근 로직 처리를 담당하는 객체를 별도로 분리해 구현하는 것을 추천한다. 이 객체를 DAO(Data Access Object)라고 부른다. (p228)
UserDao 클래스 생성
public class UserDao {
public void insert(User user) throws SQLException {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = ConnectionManager.getConnection();
String sql = "INSERT INTO USERS VALUES(?, ?, ?, ?)";
pstmt = con.prepareStatement(sql);
pstmt.setString(1, user.getUserId());
pstmt.setString(2, user.getPassword());
pstmt.setString(3, user.getName());
pstmt.setString(4, user.getEmail());
pstmt.executeUpdate();
} finally {
if(pstmt != null) pstmt.close();
if(con != null) con.close();
}
}
public void update(User user) throws SQLException {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = ConnectionManager.getConnection();
pstmt = con.prepareStatement("UPDATE USERS SET password = ?, name = ?, email = ? WHERE userId = ?");
pstmt.setString(1, user.getPassword());
pstmt.setString(2, user.getName());
pstmt.setString(3, user.getEmail());
pstmt.setString(4, user.getUserId());
pstmt.executeUpdate();
} finally {
if(pstmt != null) pstmt.close();
if(con != null) con.close();
}
}
public List<User> findAll() throws SQLException {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = ConnectionManager.getConnection();
pstmt = con.prepareStatement("SELECT * FROM USERS");
rs = pstmt.executeQuery();
List<User> users = new ArrayList<>();
while(rs.next()) {
User user = new User(rs.getString("userId"),
rs.getString("password"),
rs.getString("name"),
rs.getString("email"));
users.add(user);
}
return users;
} finally {
if(rs != null) rs.close();
if(pstmt != null) pstmt.close();
if(con != null) con.close();
}
}
public User findByUserId(String userId) throws SQLException {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = ConnectionManager.getConnection();
pstmt = con.prepareStatement("SELECT * FROM USERS WHERE userId = ?");
pstmt.setString(1, userId);
rs = pstmt.executeQuery();
User user = null;
while(rs.next()) {
user = new User(rs.getString("userId"), rs.getString("password"),
rs.getString("name"), rs.getString("email"));
}
return user;
} finally {
if(rs != null) rs.close();
if(pstmt != null) pstmt.close();
if(con != null) con.close();
}
}
}
p234
중복 코드를 리팩토링하려면 먼저 변화가 발생하는 부분(개발자가 구현할 수 밖에 없는 부분)과 변화가 없는 부분(공통 라이브러리로 분리할 부분)을 분리해야 한다. UserDao를 통해 분리해보면 다음과 같다
개발자가 구현할 부분 3가지를 제외한 나머지는 공통 라이브러리에 책임을 위임할 수 있다.
p235
UserDao를 리팩토링할 때 해결해야 할 또 다른 한가지는 SQLException에 대한 처리이다. (..)
“expert one-on-one J2EE 설계와 개발” 책을 보면 컴파일 타입 Exception과 런타임 Exception을 사용해야 되는 가이드 라인을 다음과 같이 제시하고 있다.
① API를 사용하는 모든 곳에서 이 예외를 처리해야 하는가? 예외가 반드시 메소드에 대한 반환 값이 되어야 하는가?
이 질문에 대한 답이 “예”일 경우 컴파일 타임 Exception을 사용해 컴파일러의 도움을 받는다
② API를 사용하는 소수 중 이 예외를 처리해야 하는가?
이 질문에 대한 답이 “예”일 경우 런타임 Exception으로 구현한다. API를 사용하는 모든 코드가 Exception을 catch하도록 강제하지 않는 것이 좋다.
③ 무엇인가 큰 문제가 발생했는가? 이 문제를 복구할 방법이 없는가?
이 질문에 대한 답이 "예"라면 런타임 Exception으로 구현한다. API를 사용하는 코드에서 Exception을 catch하더라도 에러에 대한 정보를 통보 받는 것 외에 아무것도 할 수 있는 것이 없다.
④ 아직도 불명확한가? 그렇다면 런타임 Exception으로 구현하라. Exception에 대해 문서화하고 API를 사용하는 곳에서 Exception에 대한 처리를 결정하도록 하라
UserDao 리팩토링
*강의 영상 링크(p244)
DAO 리팩토링 1 insert, update, delete 중복 제거
DAO 리팩토링 2 select 문 중복 제거
DAO 리팩토링 3 JdbcTemplate과 SelectJdbcTemplate 통합
DAO 리팩토링 4 라이브러리 리팩토링 및 목록 기능 추가
DAO 리팩토링 5 SQLException을 DataAccessException으로 래핑
DAO 리팩토링 6 람다 표현식을 사용하도록 리팩토링
1. 메소드 추출(Extract Method)
-메소드가 한 가지 작업만 처리하도록 작은 단위로 분리하다보면 중복 코드가 명확하게 드러나는 경우를 종종 경험한다
-이번 리팩토링의 경우 개발자가 DB 접근 로직 구현할 때 매번 구현해야 하는 부분과 그렇지 않을 부분을 기준으로 분리하면 더 명확한 기준을 가지고 분리할 수 있다
(예로 query 생성, PrepareStatement 파라미터 설정, ResultSet의 Row 데이터 추출 부분을 개발자가 직접 구현해야 한다)
public class UserDao {
public void insert(User user) throws SQLException {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = ConnectionManager.getConnection();
pstmt = con.prepareStatement(createQueryForInsert());
setValuesForInsert(user, pstmt);
pstmt.executeUpdate();
} finally {
if(pstmt != null) pstmt.close();
if(con != null) con.close();
}
}
private void setValuesForInsert(User user, PreparedStatement pstmt) throws SQLException {
pstmt.setString(1, user.getUserId());
pstmt.setString(2, user.getPassword());
pstmt.setString(3, user.getName());
pstmt.setString(4, user.getEmail());
}
private String createQueryForInsert() {
return "INSERT INTO USERS VALUES(?, ?, ?, ?)";
}
public void update(User user) throws SQLException {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = ConnectionManager.getConnection();
pstmt = con.prepareStatement(createQueryForUpdate());
setValuesForUpdate(user, pstmt);
pstmt.executeUpdate();
} finally {
if(pstmt != null) pstmt.close();
if(con != null) con.close();
}
}
private void setValuesForUpdate(User user, PreparedStatement pstmt) throws SQLException {
pstmt.setString(1, user.getPassword());
pstmt.setString(2, user.getName());
pstmt.setString(3, user.getEmail());
pstmt.setString(4, user.getUserId());
}
private String createQueryForUpdate() {
return "UPDATE USERS SET password = ?, name = ?, email = ? WHERE userId = ?";
}
public List<User> findAll() throws SQLException {
[..]
}
public User findByUserId(String userId) throws SQLException {
[..]
}
}
2. InsertJdbcTemplate과 UpdateJdbcTemplate 생성
InsertJdbcTemplate 추상 클래스
public abstract class InsertJdbcTemplate {
public void insert(User user) throws SQLException {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = ConnectionManager.getConnection();
pstmt = con.prepareStatement(createQueryForInsert());
setValuesForInsert(user, pstmt);
pstmt.executeUpdate();
} finally {
if(pstmt != null) pstmt.close();
if(con != null) con.close();
}
}
abstract void setValuesForInsert(User user, PreparedStatement pstmt) throws SQLException;
abstract String createQueryForInsert();
}
UpdateJdbcTemplate 추상 클래스
public abstract class UpdateJdbcTemplate {
public void update(User user) throws SQLException {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = ConnectionManager.getConnection();
pstmt = con.prepareStatement(createQueryForUpdate());
setValuesForUpdate(user, pstmt);
pstmt.executeUpdate();
} finally {
if(pstmt != null) pstmt.close();
if(con != null) con.close();
}
}
abstract void setValuesForUpdate(User user, PreparedStatement pstmt) throws SQLException;
abstract String createQueryForUpdate();
}
UserDao 리팩토링
- InsertJdbcTemplate 메서드에서 UserDao 인자 제거
- 개발자가 구현해야 할 추상 메서드 선언
- 추상 클래스로 변경
- UserDao에서 익명 클래스로 추상 메서드 구현 담당
public class UserDao {
public void insert(User user) throws SQLException {
InsertJdbcTemplate insertJdbcTemplate = new InsertJdbcTemplate() {
@Override
void setValuesForInsert(User user, PreparedStatement pstmt) throws SQLException {
pstmt.setString(1, user.getUserId());
pstmt.setString(2, user.getPassword());
pstmt.setString(3, user.getName());
pstmt.setString(4, user.getEmail());
}
@Override
String createQueryForInsert() {
return "INSERT INTO USERS VALUES(?, ?, ?, ?)";
}
};
insertJdbcTemplate.insert(user);
}
public void update(User user) throws SQLException {
UpdateJdbcTemplate updateJdbcTemplate = new UpdateJdbcTemplate() {
@Override
void setValuesForUpdate(User user, PreparedStatement pstmt) throws SQLException {
pstmt.setString(1, user.getPassword());
pstmt.setString(2, user.getName());
pstmt.setString(3, user.getEmail());
pstmt.setString(4, user.getUserId());
}
@Override
String createQueryForUpdate() {
return "UPDATE USERS SET password = ?, name = ?, email = ? WHERE userId = ?";
}
};
updateJdbcTemplate.update(user);
}
public List<User> findAll() throws SQLException {
[..]
}
public User findByUserId(String userId) throws SQLException {
[..]
}
}
3. SelectJdbcTemplate 클래스 생성
추상 메서드로 PrepareStatement 파라미터 설정, ResultSet의 Row 데이터 추출용 선언
MySelectJdbcTemplate<T> 추상 클래스
public abstract class MySelectJdbcTemplate<T> {
private static final Logger log = LoggerFactory.getLogger(MySelectJdbcTemplate.class);
public List<T> query(String query) throws SQLException {
List<T> result = new ArrayList<>();
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
con = ConnectionManager.getConnection();
ps = con.prepareStatement(query);
rs = ps.executeQuery();
while(rs.next()) {
result.add(mapRow(rs));
}
return result;
} finally {
if(rs != null) rs.close();
if(ps != null) ps.close();
if(con != null) con.close();
}
}
public T queryForObject(String query) throws SQLException {
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
con = ConnectionManager.getConnection();
ps = con.prepareStatement(query);
setValues(ps);
rs = ps.executeQuery();
T result = null;
while(rs.next()) {
result = mapRow(rs);
}
return result;
} finally {
if(rs != null) rs.close();
if(ps != null) ps.close();
if(con != null) con.close();
}
}
abstract protected void setValues(PreparedStatement ps) throws SQLException;
abstract protected T mapRow(ResultSet rs) throws SQLException;
}
UserDao 클래스 리팩토링
public class UserDao {
public void insert(User user) throws SQLException {
[..]
}
public void update(User user) throws SQLException {
[..]
}
public List<User> findAll() throws SQLException {
MySelectJdbcTemplate<User> jdbcTemplate = new MySelectJdbcTemplate() {
@Override
protected void setValues(PreparedStatement ps) throws SQLException {
}
@Override
protected User mapRow(ResultSet rs) throws SQLException {
return new User(rs.getString("userId"),
rs.getString("password"),
rs.getString("name"),
rs.getString("email"));
}
};
return jdbcTemplate.query("SELECT * FROM USERS");
}
public User findByUserId(String userId) throws SQLException {
MySelectJdbcTemplate<User> jdbcTemplate = new MySelectJdbcTemplate() {
@Override
protected void setValues(PreparedStatement ps) throws SQLException {
ps.setString(1, userId);
}
@Override
protected User mapRow(ResultSet rs) throws SQLException {
return new User(rs.getString("userId"), rs.getString("password"), rs.getString("name"), rs.getString("email"));
}
};
return jdbcTemplate.queryForObject("SELECT * FROM USERS WHERE userId = ?");
}
}
4. JdbcTemplate 통합
InsertJdbcTemplate과 UpdateJdbcTemplate의 구현 부분이 동일하다. 둘 중 하나를 사용하도록 리팩토링한다.
- *JdbcTemplate 함수 호출시 인자로 String query 전달
- SQL 쿼리와 같이 변경되는 부분을 추상 메소드가 아닌 메소드의 인자로 전달한다
이때 UserDao 에서 추상메서드 구현시 콜백 방식으로 직접 User, String query 전달하여 쿼리 생성 메소드 삭제 가능
→ 코딩량도 줄고 가독성도 향상됨
→ 이를 template method 디자인 패턴이라 한다 (values, rowMapper 추상 메서드)
추가로 MySelectJdbcTemplate도 추가하도록 한다
MyJdbcTemplate<T> 추상 클래스 생성
public abstract class MyJdbcTemplate<T> {
private static final Logger log = LoggerFactory.getLogger(MyJdbcTemplate.class);
public void update(String query){
try (
Connection con = ConnectionManager.getConnection();
PreparedStatement ps = con.prepareStatement(query);
){
setValues(ps);
ps.executeUpdate();
} catch (SQLException e) {
log.error(e.getMessage());
}
}
public List<T> query(String query) throws SQLException {
List<T> result = new ArrayList<>();
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
con = ConnectionManager.getConnection();
ps = con.prepareStatement(query);
rs = ps.executeQuery();
while(rs.next()) {
result.add(mapRow(rs));
}
return result;
} finally {
if(rs != null) rs.close();
if(ps != null) ps.close();
if(con != null) con.close();
}
}
public T queryForObject(String query) throws SQLException {
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
con = ConnectionManager.getConnection();
ps = con.prepareStatement(query);
setValues(ps);
rs = ps.executeQuery();
T result = null;
while(rs.next()) {
result = mapRow(rs);
}
return result;
} finally {
if(rs != null) rs.close();
if(ps != null) ps.close();
if(con != null) con.close();
}
}
abstract protected void setValues(PreparedStatement ps) throws SQLException;
abstract protected T mapRow(ResultSet rs) throws SQLException;
}
UserDao 리팩토링
public class UserDao {
public void insert(User user) throws SQLException {
MyJdbcTemplate jdbcTemplate = new MyJdbcTemplate() {
@Override
protected void setValues(PreparedStatement ps) throws SQLException {
ps.setString(1, user.getUserId());
ps.setString(2, user.getPassword());
ps.setString(3, user.getName());
ps.setString(4, user.getEmail());
}
@Override
protected Object mapRow(ResultSet rs) throws SQLException {
return null;
}
};
jdbcTemplate.update("INSERT INTO USERS VALUES(?, ?, ?, ?)");
}
public void update(User user) throws SQLException {
MyJdbcTemplate jdbcTemplate = new MyJdbcTemplate() {
@Override
protected void setValues(PreparedStatement pstmt) throws SQLException {
pstmt.setString(1, user.getPassword());
pstmt.setString(2, user.getName());
pstmt.setString(3, user.getEmail());
pstmt.setString(4, user.getUserId());
}
@Override
protected Object mapRow(ResultSet rs) throws SQLException {
return null;
}
};
jdbcTemplate.update("UPDATE USERS SET password = ?, name = ?, email = ? WHERE userId = ?");
}
public List<User> findAll() throws SQLException {
MyJdbcTemplate<User> jdbcTemplate = new MyJdbcTemplate() {
@Override
protected void setValues(PreparedStatement ps) throws SQLException {
}
@Override
protected User mapRow(ResultSet rs) throws SQLException {
return new User(rs.getString("userId"),
rs.getString("password"),
rs.getString("name"),
rs.getString("email"));
}
};
return jdbcTemplate.query("SELECT * FROM USERS");
}
public User findByUserId(String userId) throws SQLException {
MyJdbcTemplate<User> jdbcTemplate = new MyJdbcTemplate() {
@Override
protected void setValues(PreparedStatement ps) throws SQLException {
ps.setString(1, userId);
}
@Override
protected User mapRow(ResultSet rs) throws SQLException {
return new User(rs.getString("userId"), rs.getString("password"), rs.getString("name"), rs.getString("email"));
}
};
return jdbcTemplate.queryForObject("SELECT * FROM USERS WHERE userId = ?");
}
}
이때 추상클래스 MyJdbcTemplate에 추상 메서드 RowMapper가 추가됨으로써 Insert/Update 할 때 사용하지 않는 데도 구현을 해줘야 한다(Templte Method 디자인 패턴의 단점)
→추상 메서드를 인터페이스로 분리하도록 한다
5. PrepareStatementSetter, RowMapper 인터페이스 선언 및 사용
PreparedStatementSetter 인터페이스
@FunctionalInterface
public interface PreparedStatementSetter {
void values(PreparedStatement ps) throws SQLException;
}
RowMapper 인터페이스
@FunctionalInterface
public interface RowMapper<T> {
T mapRow(ResultSet rs) throws SQLException;
}
MyJdbcTemplte<T> 클래스 변경
-try-with-resources 구문을 적용
-자바 7버전부터 해당 문법을 적용해 자원을 반납하는 것이 가능하다. 해당 문법을 통해 finally 절의 복잡도를 낮추도록 리팩토링하였다
-추가로 UserDao의 문제점이었던 SQLException(컴파일 예외) 처리하는 부분을 DataAccessException(런타임 예외)로 변경하여 문제를 해결한다
public class MyJdbcTemplate<T> {
private static final Logger log = LoggerFactory.getLogger(MyJdbcTemplate.class);
public void update(String query, PreparedStatementSetter preparedStatementSetter) throws DataAccessException {
try (
Connection con = ConnectionManager.getConnection();
PreparedStatement ps = con.prepareStatement(query);
){
preparedStatementSetter.values(ps);
ps.executeUpdate();
} catch (SQLException e) {
log.error(e.getMessage());
}
}
public List<T> query(String query, PreparedStatementSetter preparedStatementSetter, RowMapper<T> rowMapper) throws DataAccessException {
List<T> result = new ArrayList<>();
ResultSet rs = null;
try (
Connection con = ConnectionManager.getConnection();
PreparedStatement ps = con.prepareStatement(query);
) {
preparedStatementSetter.values(ps);
rs = ps.executeQuery();
while(rs.next()) {
result.add(rowMapper.mapRow(rs));
}
return result;
} catch (SQLException e) {
throw new DataAccessException(e.getMessage());
} finally {
if(rs != null) {
try {
rs.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
public T queryForObject(String query, PreparedStatementSetter preparedStatementSetter, RowMapper<T> rowMapper) throws DataAccessException {
ResultSet rs = null;
try (
Connection con = ConnectionManager.getConnection();
PreparedStatement ps = con.prepareStatement(query);
){
preparedStatementSetter.values(ps);
rs = ps.executeQuery();
T result = null;
while(rs.next()) {
result = rowMapper.mapRow(rs);
}
return result;
} catch (SQLException e) {
throw new DataAccessException(e.getMessage());
} finally {
if(rs != null) {
try {
rs.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
}
DataAccessException
public class DataAccessException extends RuntimeException {
public DataAccessException() {
super();
}
public DataAccessException(String message) {
super(message);
}
[..]
}
UserDao 클래스 리팩토링
-인터페이스의 메서드가 하나이기 때문에 람다식으로 표현가능하다
-앞서 불필요하게 구현해야 했던 부분이 제거되었다
public class UserDao {
public void insert(User user) throws DataAccessException {
MyJdbcTemplate<User> jdbcTemplate = new MyJdbcTemplate();
jdbcTemplate.update("INSERT INTO USERS VALUES(?, ?, ?, ?)", (ps) -> {
ps.setString(1, user.getUserId());
ps.setString(2, user.getPassword());
ps.setString(3, user.getName());
ps.setString(4, user.getEmail());
});
}
public void update(User user) throws DataAccessException {
MyJdbcTemplate<User> jdbcTemplate = new MyJdbcTemplate();
jdbcTemplate.update("UPDATE USERS SET password = ?, name = ?, email = ? WHERE userId = ?", ps -> {
ps.setString(1, user.getPassword());
ps.setString(2, user.getName());
ps.setString(3, user.getEmail());
ps.setString(4, user.getUserId());
});
}
public List<User> findAll() throws DataAccessException {
MyJdbcTemplate<User> jdbcTemplate = new MyJdbcTemplate();
return jdbcTemplate.query("SELECT * FROM USERS",
(ps) -> {},
(rs) -> new User(rs.getString("userId"),
rs.getString("password"),
rs.getString("name"),
rs.getString("email")));
}
public User findByUserId(String userId) throws DataAccessException {
MyJdbcTemplate<User> jdbcTemplate = new MyJdbcTemplate();
return jdbcTemplate.queryForObject("SELECT * FROM USERS WHERE userId = ?",
(ps) -> {ps.setString(1, userId);},
(rs) -> new User(rs.getString("userId"), rs.getString("password"), rs.getString("name"), rs.getString("email"))
);
}
}
6. 가변 인자 메서드 추가 및 사용
- 가변인자는 인자로 전달할 값의 갯수가 고정되지 않고 동적으로 변경되는 경우 유용하게 사용할 수 있다
- SQL문에 따라 전달할 값의 갯수가 달라지기 때문에 가변인자를 활용할 수 있는 적절한 부분이다
- 인터페이스의 추상메서드가 하나 뿐이므로 람다식을 사용하여 좀 더 깔끔하게 구현할 수 있다
MyJdbcTemplate<T> 클래스
public class MyJdbcTemplate<T> {
[..]
private PreparedStatementSetter createPreparedStatementSetter(Object... parameters) {
return (ps) -> {
for(int i = 1; i <= parameters.length; i++) {
ps.setString(i, String.valueOf(parameters[i - 1]));
}
};
}
public void update(String query, Object... parameters) {
update(query, createPreparedStatementSetter(parameters));
}
public T queryForObject(String query, RowMapper<T> rowMapper, Object... parameters) throws DataAccessException {
return queryForObject(query, rowMapper, createPreparedStatementSetter(parameters));
}
public List<T> query(String query, RowMapper<T> rowMapper, Object... parameters) throws DataAccessException {
return query(query, rowMapper, createPreparedStatementSetter(parameters));
}
}
UserDao 리팩토링 (최종)
public class UserDao {
public void insert(User user) throws DataAccessException {
MyJdbcTemplate<User> jdbcTemplate = new MyJdbcTemplate();
String sql = "INSERT INTO USERS VALUES(?, ?, ?, ?)";
jdbcTemplate.update(sql, user.getUserId(), user.getPassword(), user.getName(), user.getEmail());
}
public void update(User user) throws DataAccessException {
MyJdbcTemplate<User> jdbcTemplate = new MyJdbcTemplate();
String sql = "UPDATE USERS SET password = ?, name = ?, email = ? WHERE userId = ?";
jdbcTemplate.update(sql, user.getPassword(), user.getName(), user.getEmail(), user.getUserId());
}
public List<User> findAll() throws DataAccessException {
MyJdbcTemplate<User> jdbcTemplate = new MyJdbcTemplate();
return jdbcTemplate.query("SELECT * FROM USERS",
(rs) -> new User(rs.getString("userId"), rs.getString("password"), rs.getString("name"), rs.getString("email")));
};
public User findByUserId(String userId) throws DataAccessException {
MyJdbcTemplate<User> jdbcTemplate = new MyJdbcTemplate();
return jdbcTemplate.queryForObject("SELECT * FROM USERS WHERE userId = ?",
(rs) -> new User(rs.getString("userId"), rs.getString("password"), rs.getString("name"), rs.getString("email")),
userId
);
}
}
'독서 > 📚' 카테고리의 다른 글
[Next Step] 9장 두 번째 양파 껍질을 벗기기 위한 중간 점검 (0) | 2023.11.18 |
---|---|
[Next Step] 8장 Ajax를 활용해 새로고침 없이 데이터 갱신하기 (0) | 2023.11.17 |
[Next Step] 6장 서블릿/JSP를 활용해 동적인 웹 애플리케이션 개발하기 (0) | 2023.11.16 |
[Next Step] 5장 웹 서버 리팩토링, 서블릿 컨테이너와 서블릿의 관계 (0) | 2023.11.16 |
[Next Step] 3~4장 HTTP 웹서버 구현을 통해 HTTP 이해하기(No Framework) (0) | 2023.11.15 |

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!