가장 먼저, 블로그 글 CRUD 로직을 구현해 보자.
모델 생성
클래스 생성
블로그 글에는 제목과 글 내용, 작성자가 필요하다. 추가로, 블로그 포스팅을 구분하기 위한 인덱스 값까지 네개의 필드로 `Posting` 객체를 만들어 주자.
@Data
public class Posting {
int idx;
String title;
int user_idx;
String content;
public Posting(String title, int user_idx, String content) {
this.title = title;
this.user_idx = user_idx;
this.content = content;
}
}
테이블 생성
DB에도 게시글을 저장하기 위한 테이블을 생성해 둔다.
create table posting(
idx int auto_increment primary key,
title char(20) not null,
user_idx int not null,
content char(255) not null,
foreign key (user_idx) references users(idx)
);
DAO 생성
그리고, `Posting`의 DB관련 로직을 구현할 `PostingDAO`를 생성해 준다.
public class PostingDAO {
private final String url = DAOConfig.URL;
private final String userName = DAOConfig.USERNAME;
private final String password = DAOConfig.PASSWORD;
private static PostingDAO postingDAO = null;
public static PostingDAO getPostingDAO(){
if(postingDAO == null)
postingDAO = new PostingDAO();
return postingDAO;
}
//...
}
DB에 접근하기 위한 url, userName, password는 한 곳에서 관리를 하는 것이 좋을 것 같아 `DAOConfig` 클래스를 생성 후 내부에 final static String`으로 값을 저장해 두었다.
그리고, PostingDAO` 객체를 싱글톤으로 관리하기 위한 코드를 함께 작성해 주었다.
로직 구현
저장 기능
이제, `PostingDAO`에서 `Posting`을 저장하기 위한 로직을 구현해 본다.
public void save(Posting posting, User user){
try(Connection conn = DriverManager
.getConnection(url, userName, password)) {
PreparedStatement pstm = conn.prepareStatement(
"insert into posting (title, user_idx, content) VALUES (?,?,?)");
pstm.setString(1, posting.getTitle());
pstm.setString(2, Integer.toString(user.getIdx()));
pstm.setString(3, posting.getContent());
pstm.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
`save()` 함수의 동작을 확인할 테스트 코드를 작성하자.
class PostingDAOTest {
User user = new User("new user", "user password");
Posting posting = new Posting("title1", user.getIdx(), "content1");
@Test
@Transactional
public void save() throws Exception{
//given
userDAO.save(user);
//when
postingDAO.save(posting, user);
//then
}
}
그런데 오류가 발생한다...
java.sql.SQLIntegrityConstraintViolationException:
Cannot add or update a child row:
a foreign key constraint fails
(`selfmadeblog`.`posting`,
CONSTRAINT `posting_ibfk_1` FOREIGN KEY (`user_idx`)
REFERENCES `users` (`idx`))
at co m.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:118)
at co m.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
//...//
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
외래키 참조에 문제가 생겼다는데, 쿼리가 어떻게 날아갔는지를 확인해 봐야 할 것 같다.
생성된 쿼리를 출력해 확인해 보자.
다음이 `PreparedStatement` 쿼리문이다.
insert into posting (title, user_idx, content) VALUES ('title1','0','content1')
`User`의 인덱스로 0이 사용되고 있다.
`UserDAO`의 저장 로직을 다시 확인해 보자.
//save() 함수의 일부
PreparedStatement pstmt = connection.prepareStatement(
"insert into users (id, password) VALUES (?, ?)");
pstmt.setString(1, user.getId());
pstmt.setString(2, user.getPassword());
pstmt.executeUpdate();
connection.close();
//User 클래스
@Data
public class User {
int idx;
String id;
String password;
public User(String id, String password) {
this.id = id;
this.password = password;
}
}
`User`의 `idx` 필드는`auto_increment`의 사용으로 생성시에 초기화 해 줄 수 없기 때문에 저장 시점에 변경을 해 주어야 한다. 그러나, `User`의 저장 로직은 저장 이후 별다른 동작을 하고 있지 않기 때문에 문제가 생긴 것이다.
`User`의 저장 이후 `idx` 값을 업데이트 해 주자.
//변경 이후의 save() 코드 일부
PreparedStatement pstmt = connection.prepareStatement(
"insert into users (id, password) VALUES (?, ?)",
Statement.RETURN_GENERATED_KEYS);
pstmt.setString(1, user.getId());
pstmt.setString(2, user.getPassword());
pstmt.executeUpdate();
ResultSet rs = pstmt.getGeneratedKeys();
if(rs.next()){
user.setIdx(rs.getInt(1));
}
connection.close();
`pstmt.getGeneratedKeys()` 메서드를 사용해 자동으로 생성된 키의 값을 받아 올 수 있으며, 이를 위해 `PreparedStatement`를 생성할 떄 `Statement.RETURN_GENERATED_KEYS`을 함께 사용해 주어야 한다.
테스트 성공이다!
중복 로직 추출
앞으로도, SQL을 실행하고 생성된 키를 받아오는 로직은 많이 사용될 것 같으니 해당 문맥 자체를 추출해서 재사용 하는 편이 좋아 보인다.
우선 DAOContext의 메서드를 정의할 `DAOInterface`를 만들자.
public interface DAOInterface {
public int executeSQLAndReturn(String sql, String... args);
}
우선 간단하게, SQL을 실행하고, `auto_increment` 등으로 자동으로 생생되는 값을 리턴해 주는 메서드 하나만 정의해 두자.
해당 메서드에는 `String` 타입의 SQL과 해당 SQL에 동적으로 들어갈 변수들만 전달을 해 줄것이다. 이 떄 전달될 변수의 개수는 정해지지 않았으므로 `String...` 으로 정의해 두었다.
다음으로는 실제로 문맥을 작성할 `DAOContext` 클래스를 작성하자.
public class DAOContext implements DAOInterface{
private static DAOContext daoContext = null;
private static final String url = DAOConfig.URL;
private static final String userName = DAOConfig.USERNAME;
private static final String password = DAOConfig.PASSWORD;
//싱글톤을 유지하기 위한 private 생성자, getDAOContext(),
@Override
public int executeSQLAndReturn(String sql, String... args) {
int generatedKey = 0;
try (Connection conn = getConnect()) {
PreparedStatement ps = conn.prepareStatement(
sql, Statement.RETURN_GENERATED_KEYS);
for(int i = 0; i<args.length; i++)
ps.setString(i+1, args[i]);
ps.executeUpdate();
ResultSet rs = ps.getGeneratedKeys();
if (rs.next())
generatedKey = rs.getInt(1);
} catch (SQLException e) {
e.printStackTrace();
}
return generatedKey;
}
public Connection getConnect() throws SQLException {
return DriverManager.getConnection(
url, userName, password);
}
}
위와 같이 전체 문맥을 추출해 `executeSQLAndReturn()` 메서드를 만들고, 생성된 값을 리턴해 주었다.
이제 새로 만든 메서드를 적용해 보자.
public void save(Posting posting, User user){
String sql = "insert into posting (title, user_idx, content) VALUES (?,?,?)";
int generatedKey = daoContext.executeSQLAndReturn(sql,
posting.getTitle(),
Integer.toString(user.getIdx()),
posting.getContent());
posting.setIdx(generatedKey);
}
성공이다!!!
물론 지금은 각각의 DAO 클래스들이 DAOContext라는 구현 클래스에 의존하고 있다는 점이 아쉽지만, 이번 프로젝트에서는 의존성 주입보다는 전체 기능 구현에 좀 더 집중 하려고 하기에 넘어 가도록 하자.
'프로젝트 > selfmade Blog - V1 (deprecated)' 카테고리의 다른 글
5. 블로그 글 수정, 삭제 기능 (0) | 2023.08.13 |
---|---|
4. 스프링을 사용한 의존성 주입 (0) | 2023.08.12 |
3. 블로그 글 작성 기능 - 조회 기능 및 중복 Context 추출 (0) | 2023.08.11 |
2. JDBC를 이용한 DB 설정 (0) | 2023.08.11 |
1. JSP를 사용한 View (0) | 2023.08.11 |
댓글