🔗이제 글과 유저를 연결해보자!
다음 SQL을 수행하여서 외래키를 추가해 user 테이블과 post 테이블을 연결해줍니다.
alter table post drop name;
alter table post add user_id int;
alter table post add foreign key(user_id) references user(id);
name 필드를 지우고 다음과 같이 수정합니다. user는 작성한 사람의 정보가 담길 필드입니다.
@Data
public class PostVO {
private int id;
private String title;
private String content;
private Timestamp created_date = new Timestamp(new Date().getTime());
private UserVO user;
}
원래 name 필드를 출력하는 부분을 다음과 같이 수정합니다.
<tbody>
<c:forEach items="${postList}" var="post">
<tr>
<td>${post.id}</td>
<td><a href="/post/${post.id}">${post.title}</a></td>
<td>${post.user.name}</td>
<td><fmt:formatDate value="${post.created_date}" pattern="yyyy-MM-dd"/></td>
</tr>
</c:forEach>
</tbody>
마찬가지로 name 필드를 출력하는 부분을 다음과 같이 수정합니다. 또한 모델 post가 없을 때, 글을 등록하는 부분에서 name을 입력하는 부분을 지웁니다. 그리고 수정할 때 input전체의 readonly 속성을 제거하는 것에서 name이 title인 태그의 readonly속성을 지우는 것으로 수정하고, ajax로 name의 값을 받아 전송하는 부분을 지웁니다.
<div class="form-group">
<input type="text" class="form-control form-control-user" name="name" placeholder="name" value="${post.user.name}" readonly>
</div>
<c:otherwise>
<form action="/post" method="post" class="user">
<div class="form-group">
<input type="text" class="form-control form-control-user" name="title" placeholder="Title">
</div>
<div class="form-group">
<textarea class="form-control form-control-user" name="content"></textarea>
</div>
<button type="submit" class="btn btn-primary btn-user btn-block">
Register Post
</button>
</form>
</c:otherwise>
<c:if test="${isModify}">
<script>
$("[name='title']").removeAttr("readonly")
$("textarea").removeAttr("readonly")
$("#modifySubmit").click(function (){
var postVO =new Object()
postVO.id = $("[name='id']").val()
postVO.title = $("[name='title']").val()
postVO.content= $("[name='content']").val()
$.ajax({
type:"PUT",
url:"/post",
contentType:"application/json; charset=utf-8",
data:JSON.stringify(postVO),
success:function (data) {
if(data)
location.href="/post/"+postVO.id
}
})
})
</script>
</c:if>
PostMapper.xml을 다음과 같이 수정합니다. SELECT로 post를 받아오는 부분을 JOIN을 사용해서 user의 정보도 같이 받아오도록 수정합니다. 또한 INSERT할 때 user_id를 user의 id로 저장할 수 있도록 합니다. 마지막으로 UPDATE에서 name을 저장하는 부분을 지웁니다.
<mapper namespace="ac.kr.smu.mapper.PostMapper">
<resultMap id="post" type="PostVO">
<id property="id" column="id" />
<id property="title" column="title" />
<id property="content" column="content" />
<id property="created_date" column="created_date" />
<association property="user" javaType="UserVO" >
<result property="id" column="user_id" />
<result property="email" column="email" />
<result property="name" column="name" />
</association>
</resultMap>
<insert id="save">
INSERT INTO post(title,content,created_date,user_id)
VALUES(#{title},#{content},#{created_date},#{user.id})
</insert>
<select id="findAll" resultType="PostVO" resultMap="post">
SELECT * from post as p JOIN user AS u on p.user_id = u.id
</select>
<select id="findById" resultType="PostVO" resultMap="post">
SELECT * FROM post as p JOIN user AS u ON p.user_id = u.id WHERE p.id=#{id};
</select>
<select id="update" resultType="PostVO">
UPDATE post SET title=#{title}, content=#{content} WHERE id=#{id};
SELECT * FROM post as p JOIN user AS u ON p.user_id = u.id WHERE p.id=#{id};
</select>
<delete id="delete">
DELETE FROM post WHERE id=#{id}
</delete>
</mapper>
resultMap태그는 PostVO로 객체가 만들어질때 매핑되는 column과 property를 작성하는 태그입니다. 그 안에 association태그는 UserVO로 묶어서 user 필드로 매핑하는 것을 설정하는 부분입니다.
email로 유저의 정보를 받아올 수 있도록 수정합니다.
public interface UserService {
public void save(UserVO userVO);
public boolean checkEmailDuplication(String email);
public boolean checkPassword(String email, String password);
public UserVO findByEmail(String email);
}
@RequiredArgsConstructor
@Service
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
@Override
public void save(UserVO userVO) {
userMapper.save(userVO);
}
@Override
public boolean checkEmailDuplication(String email) {
return userMapper.checkEmailDuplication(email)==0;
}
@Override
public boolean checkPassword(String email, String password) {
return password.equals(userMapper.findByEmail(email).getPassword());
}
@Override
public UserVO findByEmail(String email) {
return userMapper.findByEmail(email);
}
}
이제 우리는 글을 생성할 때 postVO에 유저의 정보를 저장해 주어야합니다. 따라서 Controller에 파라미터를 가공하거나 추가해주는 ArgumentResolver를 추가해줄 것입니다.
public class UserArgumentResolver implements HandlerMethodArgumentResolver {
private UserService userService;
/*
어떤 파라미터가 들어올 때 ArgumentResolver가 수행할지
*/
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterType().equals(UserVO.class);
}
/*
세션에서 email을 가져와 UserService를 이용하여 User반환
*/
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
/*
HttpServletRequest에서 세션 받아오기
RequestContextHolder가 현재 요청의 내용을 가지고 있는 클래스인데,
여기서 currentRequestAttributes() 메소드를 호출하여 현재 요청의 속성을 받아오고,
이것을 ServletRequestAttribuest로 형변환하여 getRequest() 메소드를 호출하면,
HttpServletRequest를 받아올 수 있다.
*/
HttpSession session = ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest().getSession();
/*
* 우선순위 때문에 의존성 주입이 되지 않아서 직접 설정에서 빈을 꺼내서 초기화
* */
if(userService==null){
WebApplicationContext context =
WebApplicationContextUtils.getWebApplicationContext(session.getServletContext());
userService = context.getBean(UserService.class);
}
return userService.findByEmail((String)session.getAttribute("userSession"));
}
}
ServletConfig 클래스에서 아래 메소드를 추가하여 UserArgumentResolver를 추가해줍니다.
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
WebMvcConfigurer.super.addArgumentResolvers(resolvers);
resolvers.add(new UserArgumentResolver());
}
글을 생성할 때 유저의 정보를 저장할 수 있도록 userVO를 넣어서 저장시킵니다.userVO는 위에서 설명했다싶이 UserArgumentResolver가 세션에 저장되어 있는 email로 찾아서 넣어줍니다.
@PostMapping
public String postPost(PostVO postVO, UserVO userVO) {
postVO.setUser(userVO);
postService.save(postVO);
return "board";
}
userService를 필드에 추가하고 의존성 주입을 받을 수 있도록 합니다.
편의를 위해 기존의 글을 모두 지우고 나서 합니다.
delete from post
이를 응용해 여러가지를 할 수 있습니다. 예를 들자면 댓글과 대댓글을 묶어서 한번에 반환할 수 있습니다. 하지만 이를 남용하면 JOIN이 많아져 연산시간이 길어집니다.