1. DAO (Data Access Object)
- DAO는 DB와 직접 통신을 하면서 데이터를 저장하고, 가져오는 객체이다.
- 사용처 : `MyBatis` 혹은 `JPA`와 같은 ORM에서 실제로 DB 접근을 담당하는 객체
- 특징: 보통 인터페이스 + Mapper (XML or 어노테이션 방식)으로 구성됨
예제 (MyBatis)
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface UserDAO {
// 사용자 삽입
@Insert("INSERT INTO users (username, email, password) VALUES (#{username}, #{email}, #{password})")
int insertUser(@Param("username") String username,
@Param("email") String email,
@Param("password") String password);
// 사용자 ID로 조회
@Select("SELECT id, username, email FROM users WHERE id = #{id}")
UserEntity getUserById(@Param("id") int id);
}
이 DAO는 MyBatis를 사용하여 users 테이블에 데이터를 삽입하고, ID를 기반으로 사용자를 조회하는 기능을 수행한다.
아래는 UserDAO의 예제 코드이다.
두 개의 메서드를 포함하여, 하나는 사용자를 삽입(insertUser), 다른 하나는 ID로 사용자를 조회(getUserById)하는 기능이다.
import org.apache.ibatis.annotations.Mapper;
@Mapper
public class UserDAO {
// 사용자 삽입
public int insertUser(UserInsertDto userInsertDto) {
return insert("insertUser", userInsertDto);
}
// 사용자 업데이트
public int updateUser(UserUpdateDto userUpdateDto) {
return insert("updateUser", userUpdateDto);
}
}
2. DTO (Data Transfer Object)
클라이언트, 사용자로부터 데이터를 주고받는 과정에서 사용하는 객체 (Controller ↔ Service ↔ Client)이다. DTO가 제일 많은 역할을 수행한다. 그리고 DTO는 데이터를 변경 가능하다 (가변) 그래서 @Setter 어노테이션 사용이 가능하다.
사용처: API 응답/요청을 위한 데이터 포맷을 만들 때 사용
특징
- 일반적으로 @JsonProperty 등을 사용해 API 응답 필드를 지정
- 데이터 변환 용도로 사용 (Entity ↔ DTO 변환)
- Swagger에서는 주로 DTO가 API 문서에서 노출됨
- UserDto는 필요한 데이터만 포함하여 API 문서화 시 불필요한 필드가 포함되지 않도록 한다. (`swagger`로 `springdoc`을 이용하는 경우 swagger에서 update.json 에 필요 없는 컬럼들이 보이는 원인 관련)
회원가입 DTO
// UserInsertDTO 클래스 추가
public class UserInsertDTO {
private String password;
private String userName;
private String userEmail;
private String userTel;
private int userAge;
public UserInsertDTO(String password, String userName, String userEmail,
String userTel, int userAge) {
this.password = password;
this.userName = userName;
this.userEmail = userEmail;
this.userTel = userTel;
this.userAge = userAge;
}
// Getter 및 Setter 추가
}
회원정보 수정 DTO 추가
// UserUpdateDTO 클래스 추가
public class UserUpdateDTO {
private String userName;
private String userEmail;
private String userTel;
private int userAge;
public UserUpdateDTO(String userName, String userEmail, String userTel, int userAge) {
this.userName = userName;
this.userEmail = userEmail;
this.userTel = userTel;
this.userAge = userAge;
}
// Getter 및 Setter 추가
}
이렇게 회원가입/회원정보 수정의 DTO를 다르게 구성한다면 불필요한 값이 들어가지 않게 된다.
각각의 용도에 맞게 DTO를 구분하면 Swagger에서 불필요한 필드가 보이지 않는다.
3. Entity
Entity는 @Entity로 표시되고, 실제 데이터베이스 테이블과 1:1 매핑되어 데이터를 저장&조회할 때 이용되는 객체이다.
ORM (JPA, MyBatis)에서 DB 테이블과 매핑해서 영속성 관리를 한다.
@Table, @Column 등의 어노테이션으로 DB와 매핑한다.
필드가 많아질 수 있으며, API 응답에 그대로 노출되면 불필요한 데이터가 포함될 수도 있다. 비밀번호나 개인정보같은 민감한 정보가 포함될 수가 있다. Entity는 JAP를 사용하는 경우에 주로 사용된다.
Entity에서는 주로 @Setter 어노테이션 말고 [updateUser]메서드로 데이터를 업데이트 한다.
import jakarta.persistence.*;
import lombok.*;
@Entity
@Table(name = "user")
@Getter
// Protected User() {} JPA 기본 생성자 자동 생성
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String password;
private String userName;
private String userEmail;
private String userTel;
private int userAge;
// 생성자
public User(String password, String userName, String userEmail, String userTel, int userAge) {
this.password = password;
this.userName = userName;
this.userEmail = userEmail;
this.userTel = userTel;
this.userAge = userAge;
}
// Update 메서드 (Setter 대신 사용)
public void updateUser(String userName, String userEmail, String userTel, int userAge) {
this.userName = userName;
this.userEmail = userEmail;
this.userTel = userTel;
this.userAge = userAge;
}
}
4. VO (Value Object)
VO는 값(객체) 자체로 의미를 가지며, 담기는 값이 변경되면 안될 때 사용된다. 그래서 특정 도메인에서 데이터를 불변의 객체로 사용할 때 사용된다. @Setter 어노테이션을 사용하지 않으며, 생성자를 통해서 값을 초기화한다. 한 번 생성자를 통해 값이 초기화되면 값이 변경되지 않는 데이터의 무결성이 보장된다.
- Email VO는 변경되지 않는 값 객체로, UserEntity에서 private Email email;로 사용 가능
- Setter 없이 생성자에서만 값 설정 가능 (@AllArgsConstructor, @Getter 사용)
- VO는 주로 쿼리 결과와 1:1 매핑이 이루어진다.
- 그래서 VO 는 사용자에게 조회된 쿼리 결과 값을 전달할 때 이용된다.
- equals()와 hashCode()를 반드시 오버라이드하여 동일한 값을 가지면 같은 객체로 판단할 수 있도록 함.
*Entity: 테이블과 1:1 매핑
*VO: 쿼리 결과와 1:1 매핑 (쿼리 결과가 VO에 담긴다)
5. Mybatis XML Mapper에서 DTO와 VO사용
`DTO`랑 `MyBatis XML Mapper` (UserMapper.xml)
예시1: Insert
public class UserInsertDTO {
private String userId;
private String password;
private String userName;
private String userEmail;
private String userTel;
private int userAge;
// 생성자
// Getter & Setter
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<!-- 사용자 등록 -->
<insert id="insertUser" parameterType="com.example.dto.UserInsertDTO">
INSERT INTO users (user_id, password, user_name, user_email, user_tel, user_age)
VALUES (#{userId}, #{password}, #{userName}, #{userEmail}, #{userTel}, #{userAge})
</insert>
</mapper>
예시 2: Update
public class UserUpdateDTO {
private String userId;
private String userName;
private String userEmail;
private String userPhone;
private int userAge;
// 생성자
// Getter & Setter
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<!-- 사용자 정보 업데이트 -->
<update id="updateUser" parameterType="com.example.dto.UserUpdateDTO">
UPDATE users
SET user_name = #{userName},
user_email = #{userEmail},
user_phone = #{userPhone},
user_age = #{userAge}
WHERE user_id = #{userId}
</update>
</mapper>
예시 3: select
public class UserViewVO {
private String userID;
private String username;
private String email;
private String phone;
private int age;
// 생성자
// Getter 만, no setter for immutability
<mapper namespace="com.example.UserViewMapper">
<!-- userID로 특정 사용자 조회 -->
<select id="selectUserById" resultType="com.example.UserViewVO" parameterType="String">
SELECT user_id, username, email, phone, age
FROM user_view
WHERE user_id = #{userID}
</select>
<!-- 모든 사용자 조회 -->
<select id="selectAllUsers" resultType="com.example.UserViewVO">
SELECT user_id, username, email, phone, age
FROM user_view
</select>
</mapper>
6. equals()와 hashCode()
VO에서 equals()와 hashCode()를 오버라이드해야 하는 이유는 동일한 값을 가지는 객체를 같은 객체로 판단하기 위해서이다.
- `equals()`: 두 객체의 값이 같으면 true를 반환하도록 오버라이드
- `hashCode()`: 동일한 값을 가진 객체라면 같은 해시코드를 반환하도록 오버라이드(HashSet, HashMap 등 컬렉션에서 중복을 방지하려면 이것도 오버라이딩 해야 한다.)
기본적으로 Object 클래스의 equals()와 hashCode()는 객체의 주소(참조값)를 기준으로 비교를 한다.
하지만 우리가 원하는 건 객체의 속성 값이 같으면 같은 객체로 판단하는 것이므로 오버라이드가 필요하다.
만약에 오버라이드하지 않으면 어떤 일이 일어나는지 아래 코드를 보자.
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Main {
public static void main(String[] args) {
User user1 = new User("Alice", 25);
User user2 = new User("Alice", 25);
System.out.println(user1.equals(user2)); // false (주소가 다르므로)
}
}
예를 들어, 같은 사용자 정보를 가진 두 개의 객체를 만들었을 때,
equals()와 hashCode()를 오버라이드하지 않으면 다른 객체로 인식된다.
위 코드에서 user1과 user2는 같은 name과 age 값을 가지고 있지만, equals()를 오버라이드하지 않으면 다르게 판단한다.
이제 equals()와 hashCode()를 오버라이드 해 보자.
import java.util.Objects;
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true; // 동일한 참조값이면 true
if (obj == null || getClass() != obj.getClass()) return false;
User user = (User) obj;
return age == user.age && Objects.equals(name, user.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
이렇게 하면 같은 값이면 동일한 객체로 판단한다.
public class Main {
public static void main(String[] args) {
User user1 = new User("Alice", 25);
User user2 = new User("Alice", 25);
System.out.println(user1.equals(user2)); // true (값이 같아서!)
}
}
왜 hashCode()도 오버라이드해야 할까? equals()만 오버라이드하면 HashSet, HashMap 같은 컬렉션에서 제대로 동작하지 않는다. hashCode()가 다르면 같은 객체라도 해시 기반 자료구조에서 다르게 저장될 수 있기 때문이다. 예를 들어, HashSet에서 동일한 값을 가진 객체가 중복되지 않게 하려면 hashCode()도 같아야 한다.
public class Main {
public static void main(String[] args) {
Set<User> users = new HashSet<>();
users.add(new User("Alice", 25));
users.add(new User("Alice", 25));
System.out.println(users.size()); // 1 (중복 저장 방지됨!)
}
}
'Spring&JSP' 카테고리의 다른 글
[Spring] Swagger에 불필요한 컬럼이 보이는 원인 및 해결 방법 (2) | 2025.04.02 |
---|---|
[JSP] 게시판 만들기 11 _ 게시글 수정, 삭제 기능 (64) | 2024.12.13 |
[JSP] 게시판 만들기 11 _ 게시판 보기 기능 (96) | 2024.12.09 |
[JSP] 게시판 만들기 10 _ 게시판 글목록 기능 (60) | 2024.12.09 |
[JSP] 게시판 만들기 9 _ 게시판 글쓰기 기능 (write.jsp, BbsDAO.java, writeAction.jsp) (63) | 2024.12.09 |