Ver2.0 ๐ Spring Data Envers๋ก ์ํฐํฐ ๋ณ๊ฒฝ ์ด๋ ฅ์ ์ฌ๋ฏธ์๊ฒ ๊ด๋ฆฌํด๋ณด์! ๐ฐ๏ธ

๐ Spring Data Envers๋ก ์ํฐํฐ ๋ณ๊ฒฝ ์ด๋ ฅ์ ์ฌ๋ฏธ์๊ฒ ๊ด๋ฆฌํด๋ณด์! ๐ฐ๏ธ
์๋ ! ์ค๋์ Spring Data Envers๋ฅผ ์ฌ์ฉํด์ ์ํฐํฐ์ ๋ณ๊ฒฝ ์ด๋ ฅ์ ์ด๋ป๊ฒ ๊ด๋ฆฌํ๋์ง ํจ๊ป ์์๋ณผ ๊ฑฐ์ผ. ๐ ๋ฐ์ดํฐ์ ๋ณํ๋ฅผ ์ถ์ ํ๋ ๊ฑด ๋ง์น ํ์๋จธ์ ์ ํ๊ณ ๊ณผ๊ฑฐ๋ก ๋์๊ฐ๋ ๊ฒ์ฒ๋ผ ์ฌ๋ฐ๋ ์ผ์ด์ง! ํนํ ์ค๋ฌด์์๋ "๋๊ฐ, ์ธ์ , ๋ฌด์์" ๋ณ๊ฒฝํ๋์ง ์ถ์ ํ๋ ๊ธฐ๋ฅ์ด ์ ๋ง ์ค์ํ๊ฑฐ๋ .
์ด ๊ธ์ ํตํด Java ๊ธฐ๋ฐ Spring ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ฐ์ดํฐ ๋ณ๊ฒฝ ์ด๋ ฅ์ ์ฝ๊ณ ํจ๊ณผ์ ์ผ๋ก ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ธ ์ ์์ ๊ฑฐ์ผ. ๋ง์น ์ฌ๋ฅ๋ท์์ ๋ค์ํ ์ฌ๋ฅ์ ๊ฑฐ๋ํ๋ฏ์ด, ์ฐ๋ฆฌ๋ Spring Data Envers๋ผ๋ ์ฌ๋ฅ์ ์ต๋ํด์ ํ๋ก์ ํธ์ ์ ์ฉํด๋ณด์! ๐
๐ ๋ชฉ์ฐจ
- Spring Data Envers๋ ๋ฌด์์ธ๊ฐ?
- ์ ์ํฐํฐ ๋ณ๊ฒฝ ์ด๋ ฅ ๊ด๋ฆฌ๊ฐ ํ์ํ ๊น?
- Spring Data Envers ์ค์ ํ๊ธฐ
- ๊ธฐ๋ณธ์ ์ธ ์ฌ์ฉ ๋ฐฉ๋ฒ
- ๊ณ ๊ธ ๊ธฐ๋ฅ ํ์ฉํ๊ธฐ
- ์ค์ ์์ : ์ฌ์ฉ์ ์ ๋ณด ๋ณ๊ฒฝ ์ด๋ ฅ ๊ด๋ฆฌ
- ์ฑ๋ฅ ์ต์ ํ ํ
- ์์ฃผ ๋ฐ์ํ๋ ๋ฌธ์ ์ ํด๊ฒฐ ๋ฐฉ๋ฒ
- ๋ง๋ฌด๋ฆฌ ๋ฐ ์ถ๊ฐ ์๋ฃ
๐ 1. Spring Data Envers๋ ๋ฌด์์ธ๊ฐ?
Spring Data Envers๋ Hibernate Envers๋ฅผ Spring Data JPA์ ํตํฉํ ํ๋ก์ ํธ์ผ. ๊ฐ๋จํ ๋งํ๋ฉด, ์ํฐํฐ์ ๋ชจ๋ ๋ณ๊ฒฝ์ฌํญ์ ์๋์ผ๋ก ๊ธฐ๋กํด์ฃผ๋ ๋ง๋ฒ ๊ฐ์ ๋๊ตฌ์ง! ๐งโโ๏ธ
Hibernate Envers๋ JPA ์ํฐํฐ์ ๊ฐ์ฌ(audit) ๋ฐ ๋ฒ์ ๊ด๋ฆฌ๋ฅผ ์ํ Hibernate ๋ชจ๋์ด์ผ. ์ด๋ฅผ Spring Data์ ๊ฒฐํฉํ๋ฉด ๋ ์ฝ๊ฒ ์ฌ์ฉํ ์ ์๊ฒ ๋๋ ๊ฑฐ์ง. ๋ง์น ์๋ฆฌ ์ค๋ ฅ์ด ์์ด๋ ์ ์๋ ์ธ์ง๋ง ์์ผ๋ฉด ๋ง์๋ ์์์ ๋ฐ์ธ ์ ์๋ ๊ฒ์ฒ๋ผ, ๋ณต์กํ ์ด๋ ฅ ๊ด๋ฆฌ ์ฝ๋๋ฅผ ์ง์ ์์ฑํ์ง ์์๋ Envers๊ฐ ๋ค ํด๊ฒฐํด์ค!
Spring Data Envers์ ํต์ฌ ๊ธฐ๋ฅ์ ๋ค์๊ณผ ๊ฐ์:
- ์ํฐํฐ์ ๋ชจ๋ ๋ณ๊ฒฝ์ฌํญ์ ์๋์ผ๋ก ๊ธฐ๋ก ๐
- ํน์ ์์ ์ ์ํฐํฐ ์ํ๋ฅผ ์ฝ๊ฒ ์กฐํ ๊ฐ๋ฅ โฑ๏ธ
- ๋๊ฐ, ์ธ์ ๋ฐ์ดํฐ๋ฅผ ๋ณ๊ฒฝํ๋์ง ์ถ์ ๐ต๏ธโโ๏ธ
- Spring Data JPA ๋ฆฌํฌ์งํ ๋ฆฌ์ ์๋ฒฝํ๊ฒ ํตํฉ ๐
- ์ต์ํ์ ์ค์ ์ผ๋ก ๊ฐ๋ ฅํ ๊ธฐ๋ฅ ์ ๊ณต โ๏ธ
๐ค 2. ์ ์ํฐํฐ ๋ณ๊ฒฝ ์ด๋ ฅ ๊ด๋ฆฌ๊ฐ ํ์ํ ๊น?
๋ฐ์ดํฐ ๋ณ๊ฒฝ ์ด๋ ฅ์ ๊ด๋ฆฌํ๋ ๊ฑด ์ ์ค์ํ ๊น? ์ผ์์ํ์ ๋น์ ํด๋ณด์๋ฉด, ๋ทํ๋ฆญ์ค์์ ์์ฒญ ๊ธฐ๋ก์ ๋ณด๊ดํ๋ ๊ฒ๊ณผ ๋น์ทํด. ๋ด๊ฐ ์ด๋ค ์ํ๋ฅผ ๋ดค๋์ง, ์ธ์ ๋ดค๋์ง ๊ธฐ๋ก์ด ์์ผ๋ฉด ๋์ค์ ๋ค์ ์ฐพ์๋ณด๊ธฐ ํธํ์์! ๐ฌ
๋น์ฆ๋์ค ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ์ด๋ฐ ์ด๋ ฅ ๊ด๋ฆฌ๊ฐ ๋์ฑ ์ค์ํด. ์ฌ๋ฅ๋ท๊ณผ ๊ฐ์ ํ๋ซํผ์์๋ ์ฌ์ฉ์ ์ ๋ณด๋ ๊ฑฐ๋ ๋ด์ญ์ ๋ณ๊ฒฝ ์ด๋ ฅ์ ์ถ์ ํ๋ ๊ฑด ํ์์ ์ธ ๊ธฐ๋ฅ์ด์ง.
๐ ์ํฐํฐ ๋ณ๊ฒฝ ์ด๋ ฅ ๊ด๋ฆฌ์ ์ฃผ์ ์ด์
1. ๊ท์ ์ค์ ๋ฐ ๊ฐ์ฌ: ๊ธ์ต, ์๋ฃ, ๋ฒ๋ฅ ๋ฑ ๋ง์ ์ฐ์ ์์๋ ๋ฐ์ดํฐ ๋ณ๊ฒฝ ์ด๋ ฅ์ ๋ณด๊ดํ๋ ๊ฒ์ด ๋ฒ์ ์๊ตฌ์ฌํญ์ด์ผ. ๐๏ธ
2. ๋ฌธ์ ํด๊ฒฐ: ๋ฐ์ดํฐ์ ๋ฌธ์ ๊ฐ ์๊ฒผ์ ๋, ์ธ์ ์ด๋ค ๋ณ๊ฒฝ์ด ์์๋์ง ํ์ธํ๋ฉด ์์ธ์ ์ฐพ๊ธฐ ์ฌ์์ ธ. ๐
3. ์ค์ ๋ณต๊ตฌ: ๋๊ตฐ๊ฐ ์ค์๋ก ์ค์ํ ๋ฐ์ดํฐ๋ฅผ ๋ณ๊ฒฝํ๋ค๋ฉด? ์ด์ ์ํ๋ก ์ฝ๊ฒ ๋๋๋ฆด ์ ์์ด! โช
4. ์ฌ์ฉ์ ํ๋ ๋ถ์: ์ฌ์ฉ์๋ค์ด ์ด๋ค ๋ฐ์ดํฐ๋ฅผ ์์ฃผ ๋ณ๊ฒฝํ๋์ง ๋ถ์ํ๋ฉด UX ๊ฐ์ ์ ๋์์ด ๋ผ. ๐
5. ๋ณ๊ฒฝ ๋ด์ญ ์๊ฐํ: ์๊ฐ์ ๋ฐ๋ฅธ ๋ฐ์ดํฐ ๋ณํ๋ฅผ ์๊ฐํํ๋ฉด ํธ๋ ๋๋ฅผ ํ์ ํ๊ธฐ ์ฌ์์ ธ. ๐
์๋ฅผ ๋ค์ด, ์จ๋ผ์ธ ์ผํ๋ชฐ์์ ์ํ ๊ฐ๊ฒฉ์ด ๊ฐ์๊ธฐ ๋ณ๊ฒฝ๋์๋ค๊ณ ์๊ฐํด๋ด. ๊ณ ๊ฐ์ด "์ด์ ๋ ์ด ๊ฐ๊ฒฉ์ด ์๋์๋๋ฐ์?"๋ผ๊ณ ๋ฌธ์ํ์ ๋, ๋ณ๊ฒฝ ์ด๋ ฅ์ด ์๋ค๋ฉด ํ์ธํ๊ธฐ ์ด๋ ต๊ฒ ์ง? ํ์ง๋ง Envers๋ฅผ ์ฌ์ฉํ๋ฉด "๋ค, ์ด์ ์คํ 3์์ ํ๋ก๋ชจ์ ์ด ์ ์ฉ๋์ด ๊ฐ๊ฒฉ์ด ๋ณ๊ฒฝ๋์์ต๋๋ค"๋ผ๊ณ ์ ํํ ๋ต๋ณํ ์ ์์ด! ๐
โ๏ธ 3. Spring Data Envers ์ค์ ํ๊ธฐ
์, ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก Spring Data Envers๋ฅผ ์ค์ ํด๋ณด์! ์๊ฐ๋ณด๋ค ์ ๋ง ๊ฐ๋จํด. ๋ง์น ๋ ๊ณ ๋ธ๋ก ์กฐ๋ฆฝํ๋ฏ์ด ๋ช ๊ฐ์ง ๋ถํ๋ง ์ถ๊ฐํ๋ฉด ๋ผ. ๐งฉ
3.1 ์์กด์ฑ ์ถ๊ฐํ๊ธฐ
๋จผ์ Maven์ด๋ Gradle ํ๋ก์ ํธ์ ํ์ํ ์์กด์ฑ์ ์ถ๊ฐํด์ผ ํด:
Maven (pom.xml)
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-envers</artifactId>
<version>2.7.0</version>
</dependency>
Gradle (build.gradle)
implementation 'org.springframework.data:spring-data-envers:2.7.0'
๋ฒ์ ์ Spring Boot ๋ฒ์ ์ ๋ง๊ฒ ์ ํํ๋ ๊ฒ์ด ์ข์. Spring Boot๋ฅผ ์ฌ์ฉํ๋ค๋ฉด ๋ฒ์ ๊ด๋ฆฌ๋ฅผ ์๋์ผ๋ก ํด์ฃผ๋ ๋ฒ์ ์ ์๋ตํด๋ ๋ผ!
3.2 Spring Boot ์ค์
Spring Boot๋ฅผ ์ฌ์ฉํ๋ค๋ฉด ์ค์ ์ด ๋ ๊ฐ๋จํด์ ธ. @EnableJpaRepositories ์ด๋
ธํ
์ด์
์ repositoryFactoryBeanClass ์์ฑ๋ง ์ถ๊ฐํ๋ฉด ๋ผ:
import org.springframework.data.envers.repository.support.EnversRevisionRepositoryFactoryBean;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@EnableJpaRepositories(repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class)
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
์ด๋ ๊ฒ ํ๋ฉด Spring Data Envers๊ฐ Spring Data JPA ๋ฆฌํฌ์งํ ๋ฆฌ์ ํตํฉ๋์ด ์๋ํด! ๐ฏ
3.3 ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ ์ด๋ธ
Envers๋ ์๋์ผ๋ก ๊ฐ์ฌ ํ
์ด๋ธ์ ์์ฑํด. ๊ธฐ๋ณธ์ ์ผ๋ก ๊ฐ ์ํฐํฐ ํ
์ด๋ธ์ ๋ํด [ํ
์ด๋ธ๋ช
]_AUD๋ผ๋ ์ด๋ฆ์ ๊ฐ์ฌ ํ
์ด๋ธ๊ณผ REVINFO๋ผ๋ ๋ฆฌ๋น์ ์ ๋ณด ํ
์ด๋ธ์ ์์ฑํด.
์ ๊ทธ๋ฆผ์์ ๋ณผ ์ ์๋ฏ์ด, USER_AUD ํ
์ด๋ธ์ ์๋ณธ USER ํ
์ด๋ธ์ ๋ชจ๋ ์ปฌ๋ผ์ ํฌํจํ๊ณ , ์ถ๊ฐ๋ก REV(๋ฆฌ๋น์ ๋ฒํธ)์ REVTYPE(๋ณ๊ฒฝ ์ ํ: 0=์ถ๊ฐ, 1=์์ , 2=์ญ์ )์ ๊ฐ์ง๊ณ ์์ด.
๐ 4. ๊ธฐ๋ณธ์ ์ธ ์ฌ์ฉ ๋ฐฉ๋ฒ
์ด์ Spring Data Envers๋ฅผ ์ค์ ๋ก ์ฌ์ฉํด๋ณด์! ๊ธฐ๋ณธ์ ์ธ ์ฌ์ฉ๋ฒ์ ์ ๋ง ๊ฐ๋จํด. ๋จ์ง ๋ช ๊ฐ์ ์ด๋ ธํ ์ด์ ๋ง ์ถ๊ฐํ๋ฉด ๋ผ! ๐
4.1 ์ํฐํฐ์ @Audited ์ถ๊ฐํ๊ธฐ
๋ณ๊ฒฝ ์ด๋ ฅ์ ์ถ์ ํ๊ณ ์ถ์ ์ํฐํฐ์ @Audited ์ด๋
ธํ
์ด์
์ ์ถ๊ฐํด:
import org.hibernate.envers.Audited;
import javax.persistence.*;
@Entity
@Audited
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
@Column(name = "created_at")
private LocalDateTime createdAt;
// ์์ฑ์, ๊ฒํฐ, ์ธํฐ ๋ฑ
}
์ด๋ ๊ฒ ํ๋ฉด User ์ํฐํฐ์ ๋ชจ๋ ๋ณ๊ฒฝ์ฌํญ์ด ์๋์ผ๋ก ๊ธฐ๋ก๋ผ. ๋ง์ฝ ํน์ ํ๋๋ง ์ถ์ ํ๊ณ ์ถ๋ค๋ฉด ๊ทธ ํ๋์๋ง @Audited๋ฅผ ๋ถ์ด๋ฉด ๋ผ! ๐ฏ
4.2 RevisionRepository ์ธํฐํ์ด์ค ์ฌ์ฉํ๊ธฐ
์ด์ ๋ฆฌํฌ์งํ ๋ฆฌ๋ฅผ ๋ง๋ค์ด๋ณด์. Spring Data JPA์ ์ผ๋ฐ์ ์ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ ๋น์ทํ์ง๋ง, RevisionRepository ์ธํฐํ์ด์ค๋ฅผ ์์๋ฐ์์ผ ํด:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.history.RevisionRepository;
public interface UserRepository extends JpaRepository<User, Long>, RevisionRepository<User, Long, Integer> {
// ์ถ๊ฐ ์ฟผ๋ฆฌ ๋ฉ์๋๊ฐ ํ์ํ๋ค๋ฉด ์ฌ๊ธฐ์ ์ ์
}
RevisionRepository์ ์ ๋ค๋ฆญ ํ์
ํ๋ผ๋ฏธํฐ๋ ๋ค์๊ณผ ๊ฐ์:
- ์ํฐํฐ ํ์ (User)
- ์ํฐํฐ์ ID ํ์ (Long)
- ๋ฆฌ๋น์ ๋ฒํธ ํ์ (๊ธฐ๋ณธ์ ์ผ๋ก Integer)
4.3 ๊ธฐ๋ณธ์ ์ธ ์ด๋ ฅ ์กฐํ
์ด์ RevisionRepository๊ฐ ์ ๊ณตํ๋ ๋ฉ์๋๋ฅผ ์ฌ์ฉํด์ ์ด๋ ฅ์ ์กฐํํ ์ ์์ด:
// ์๋น์ค ํด๋์ค ๋ด๋ถ
@Autowired
private UserRepository userRepository;
// ํน์ ์ํฐํฐ์ ๋ชจ๋ ๋ฆฌ๋น์ ์กฐํ
Revisions<Integer, User> revisions = userRepository.findRevisions(userId);
// ํน์ ์์ ์ ์ํฐํฐ ์ํ ์กฐํ
Optional<Revision<Integer, User>> revision = userRepository.findRevision(userId, revisionNumber);
// ๊ฐ์ฅ ์ต๊ทผ ๋ฆฌ๋น์ ์กฐํ
Optional<Revision<Integer, User>> latestRevision = userRepository.findLastChangeRevision(userId);
Revision ๊ฐ์ฒด์๋ ๋ค์๊ณผ ๊ฐ์ ์ ๋ณด๊ฐ ํฌํจ๋ผ:
- ๋ฆฌ๋น์ ๋ฒํธ (getRevisionNumber())
- ๋ฆฌ๋น์ ๋ ์ง/์๊ฐ (getRevisionDate())
- ํด๋น ์์ ์ ์ํฐํฐ (getEntity())
์ด๋ ๊ฒ ๊ฐ๋จํ๊ฒ ์ํฐํฐ์ ๋ณ๊ฒฝ ์ด๋ ฅ์ ์กฐํํ ์ ์์ด! ๋ง์น ํ์๋จธ์ ์ ํ๊ณ ๊ณผ๊ฑฐ๋ก ๋์๊ฐ๋ ๊ฒ์ฒ๋ผ ๋ง์ด์ผ. โฐ
๐ฅ 5. ๊ณ ๊ธ ๊ธฐ๋ฅ ํ์ฉํ๊ธฐ
๊ธฐ๋ณธ ๊ธฐ๋ฅ์ ์ต์ํด์ก๋ค๋ฉด, ์ด์ Spring Data Envers์ ๋ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ๋ค์ ์ดํด๋ณด์! ์ด๋ฐ ๊ณ ๊ธ ๊ธฐ๋ฅ๋ค์ ํ์ฉํ๋ฉด ์ฌ๋ฅ๋ท๊ณผ ๊ฐ์ ๋ณต์กํ ํ๋ซํผ์์๋ ํจ๊ณผ์ ์ผ๋ก ๋ณ๊ฒฝ ์ด๋ ฅ์ ๊ด๋ฆฌํ ์ ์์ด. ๐ช
5.1 ์ฌ์ฉ์ ์ ๋ณด ์ถ์ ํ๊ธฐ
๋ณ๊ฒฝ ์ด๋ ฅ์ "๋๊ฐ" ๋ณ๊ฒฝํ๋์ง ๊ธฐ๋กํ๊ณ ์ถ๋ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ ๊น? Envers์ RevisionListener๋ฅผ ์ฌ์ฉํ๋ฉด ๋ผ!
import org.hibernate.envers.RevisionListener;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
public class UserRevisionListener implements RevisionListener {
@Override
public void newRevision(Object revisionEntity) {
UserRevisionEntity revision = (UserRevisionEntity) revisionEntity;
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
revision.setUsername(auth.getName());
} else {
revision.setUsername("system");
}
}
}
๊ทธ๋ฆฌ๊ณ ์ปค์คํ ๋ฆฌ๋น์ ์ํฐํฐ๋ฅผ ๋ง๋ค์ด:
import org.hibernate.envers.DefaultRevisionEntity;
import org.hibernate.envers.RevisionEntity;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table(name = "REVINFO")
@RevisionEntity(UserRevisionListener.class)
public class UserRevisionEntity extends DefaultRevisionEntity {
private String username;
// ๊ฒํฐ, ์ธํฐ
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
์ด์ ๊ฐ ๋ณ๊ฒฝ ์ด๋ ฅ์ ์ฌ์ฉ์ ์ด๋ฆ์ด ํจ๊ป ๊ธฐ๋ก๋ ๊ฑฐ์ผ! ๐ค
5.2 ํน์ ํ๋๋ง ๊ฐ์ฌํ๊ธฐ
๋ชจ๋ ํ๋๊ฐ ์๋ ํน์ ํ๋๋ง ์ถ์ ํ๊ณ ์ถ์ ๋๋ ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ด ์์ด:
// ๋ฐฉ๋ฒ 1: ํน์ ํ๋์๋ง @Audited ์ ์ฉ
@Entity
public class Product {
@Id
@GeneratedValue
private Long id;
@Audited
private String name;
@Audited
private BigDecimal price;
// ์ด ํ๋๋ ๊ฐ์ฌ๋์ง ์์
private Integer stockQuantity;
}
// ๋ฐฉ๋ฒ 2: @NotAudited ์ฌ์ฉ
@Entity
@Audited
public class Customer {
@Id
@GeneratedValue
private Long id;
private String name;
private String email;
@NotAudited
private String internalNotes; // ์ด ํ๋๋ ๊ฐ์ฌ์์ ์ ์ธ
}
5.3 ์กฐ๊ฑด๋ถ ๊ฐ์ฌ
ํน์ ์กฐ๊ฑด์์๋ง ๊ฐ์ฌ๋ฅผ ์ํํ๊ณ ์ถ๋ค๋ฉด @ConditionalAudited๋ฅผ ์ฌ์ฉํ ์ ์์ด. ํ์ง๋ง ์ด ๊ธฐ๋ฅ์ ์ง์ ๊ตฌํํด์ผ ํด:
๐ง ์กฐ๊ฑด๋ถ ๊ฐ์ฌ ๊ตฌํ ์์
1. ๋จผ์ ์ปค์คํ ์ด๋ ธํ ์ด์ ์ ๋ง๋ค์ด:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
public @interface ConditionalAudited {
String condition();
}
2. ๊ทธ๋ฆฌ๊ณ ์ด๋ฅผ ์ฒ๋ฆฌํ ๋ฆฌ์ค๋๋ฅผ ๊ตฌํํด:
@Component
public class ConditionalAuditEventListener {
@PrePersist
@PreUpdate
@PreRemove
public void checkAuditCondition(Object entity) {
// ์ํฐํฐ์ @ConditionalAudited ์ด๋
ธํ
์ด์
์ ํ์ธํ๊ณ
// ์กฐ๊ฑด์ ๋ฐ๋ผ ๊ฐ์ฌ ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํ๋ ๋ก์ง ๊ตฌํ
}
}
5.4 ๋ณ๊ฒฝ ์ด๋ ฅ ์ฟผ๋ฆฌ ์ต์ ํ
๋๋์ ๋ณ๊ฒฝ ์ด๋ ฅ์ ํจ์จ์ ์ผ๋ก ์ฟผ๋ฆฌํ๊ธฐ ์ํ ๋ช ๊ฐ์ง ํ์ ์๊ฐํ ๊ฒ:
๐ ํ์ด์ง ์ฒ๋ฆฌ
Page<Revision<Integer, User>> userRevisions = userRepository.findRevisions(
userId, PageRequest.of(0, 10, Sort.by("revisionNumber").descending()));
๐ ํน์ ๊ธฐ๊ฐ ์กฐํ
// ์ปค์คํ
์ฟผ๋ฆฌ ๋ฉ์๋ ๊ตฌํ ํ์
List<Revision<Integer, User>> revisions = userRepository.findRevisionsInPeriod(
userId, startDate, endDate);
์ด๋ฐ ๊ณ ๊ธ ๊ธฐ๋ฅ๋ค์ ํ์ฉํ๋ฉด ๋ ์ธ๋ฐํ๊ณ ํจ์จ์ ์ธ ๋ณ๊ฒฝ ์ด๋ ฅ ๊ด๋ฆฌ๊ฐ ๊ฐ๋ฅํด์ ธ! ๐
๐ผ 6. ์ค์ ์์ : ์ฌ์ฉ์ ์ ๋ณด ๋ณ๊ฒฝ ์ด๋ ฅ ๊ด๋ฆฌ
์ด์ ์ค์ ๋น์ฆ๋์ค ์๋๋ฆฌ์ค๋ฅผ ํตํด Spring Data Envers๋ฅผ ์ ์ฉํด๋ณด์! ์ฌ์ฉ์ ์ ๋ณด์ ๋ณ๊ฒฝ ์ด๋ ฅ์ ๊ด๋ฆฌํ๋ ์์ ๋ฅผ ๋ง๋ค์ด๋ณผ๊ฒ. ์ด๋ฐ ๊ธฐ๋ฅ์ ์ฌ๋ฅ๋ท๊ณผ ๊ฐ์ ํ๋ซํผ์์ ์ฌ์ฉ์ ๊ณ์ ๊ด๋ฆฌ์ ๋งค์ฐ ์ ์ฉํ ๊ฑฐ์ผ. ๐ฅ
6.1 ์ํฐํฐ ์ค๊ณ
import org.hibernate.envers.Audited;
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "users")
@Audited
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String email;
private String fullName;
private String phoneNumber;
@Column(name = "last_login_at")
private LocalDateTime lastLoginAt;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// ์์ฑ์, ๊ฒํฐ, ์ธํฐ ๋ฑ
}
6.2 ๋ฆฌํฌ์งํ ๋ฆฌ ๊ตฌํ
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.history.RevisionRepository;
public interface UserRepository extends
JpaRepository<User, Long>,
RevisionRepository<User, Long, Integer> {
// ์ถ๊ฐ ์ฟผ๋ฆฌ ๋ฉ์๋
}
6.3 ์๋น์ค ๋ ์ด์ด ๊ตฌํ
import org.springframework.data.history.Revision;
import org.springframework.data.history.Revisions;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Transactional
public User createUser(User user) {
user.setCreatedAt(LocalDateTime.now());
user.setUpdatedAt(LocalDateTime.now());
return userRepository.save(user);
}
@Transactional
public User updateUser(Long userId, User userDetails) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("User not found"));
user.setUsername(userDetails.getUsername());
user.setEmail(userDetails.getEmail());
user.setFullName(userDetails.getFullName());
user.setPhoneNumber(userDetails.getPhoneNumber());
user.setUpdatedAt(LocalDateTime.now());
return userRepository.save(user);
}
public List<User> getUserHistory(Long userId) {
Revisions<Integer, User> revisions = userRepository.findRevisions(userId);
return revisions.getContent().stream()
.map(Revision::getEntity)
.collect(Collectors.toList());
}
public Optional<User> getUserAtRevision(Long userId, Integer revisionNumber) {
return userRepository.findRevision(userId, revisionNumber)
.map(Revision::getEntity);
}
public Optional<User> getPreviousUserState(Long userId) {
Revisions<Integer, User> revisions = userRepository.findRevisions(userId);
List<Revision<Integer, User>> revisionList = revisions.getContent();
if (revisionList.size() <= 1) {
return Optional.empty(); // ์ด์ ์ํ๊ฐ ์์
}
// ์ต์ ์ํ ๋ฐ๋ก ์ด์ ์ ์ํ ๋ฐํ
return Optional.of(revisionList.get(revisionList.size() - 2).getEntity());
}
}
6.4 ์ปจํธ๋กค๋ฌ ๊ตฌํ
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
return ResponseEntity.ok(userService.createUser(user));
}
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {
return ResponseEntity.ok(userService.updateUser(id, user));
}
@GetMapping("/{id}/history")
public ResponseEntity<List<User>> getUserHistory(@PathVariable Long id) {
return ResponseEntity.ok(userService.getUserHistory(id));
}
@GetMapping("/{id}/revision/{revisionNumber}")
public ResponseEntity<User> getUserAtRevision(
@PathVariable Long id,
@PathVariable Integer revisionNumber) {
return userService.getUserAtRevision(id, revisionNumber)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@GetMapping("/{id}/previous")
public ResponseEntity<User> getPreviousUserState(@PathVariable Long id) {
return userService.getPreviousUserState(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
}
์ด ์์ ์์๋ ์ฌ์ฉ์ ์ ๋ณด์ ์์ฑ, ์์ , ๊ทธ๋ฆฌ๊ณ ๋ณ๊ฒฝ ์ด๋ ฅ ์กฐํ ๊ธฐ๋ฅ์ ๊ตฌํํ์ด. ํนํ getUserHistory, getUserAtRevision, getPreviousUserState ๋ฉ์๋๋ฅผ ํตํด ๋ค์ํ ๋ฐฉ์์ผ๋ก ์ด๋ ฅ์ ์กฐํํ ์ ์์ง! ๐ต๏ธโโ๏ธ
โก 7. ์ฑ๋ฅ ์ต์ ํ ํ
Spring Data Envers๋ ๊ฐ๋ ฅํ์ง๋ง, ์๋ชป ์ฌ์ฉํ๋ฉด ์ฑ๋ฅ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ด. ์ฌ๊ธฐ ๋ช ๊ฐ์ง ์ฑ๋ฅ ์ต์ ํ ํ์ ์๊ฐํ ๊ฒ! ๐
7.1 ํ์ํ ํ๋๋ง ๊ฐ์ฌํ๊ธฐ
๋ชจ๋ ํ๋๋ฅผ ๊ฐ์ฌํ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ณต๊ฐ์ ๋ง์ด ์ฐจ์งํ๊ณ ์ฟผ๋ฆฌ ์ฑ๋ฅ๋ ์ ํ๋ ์ ์์ด. ๊ผญ ํ์ํ ํ๋๋ง @Audited๋ก ํ์ํ๋ ๊ฒ์ด ์ข์!
@Entity
public class Product {
@Id
@GeneratedValue
private Long id;
@Audited // ์ค์ํ ํ๋๋ง ๊ฐ์ฌ
private String name;
@Audited // ๊ฐ๊ฒฉ ๋ณ๊ฒฝ์ ์ค์ํ๋ฏ๋ก ๊ฐ์ฌ
private BigDecimal price;
// ์์ฃผ ๋ณ๊ฒฝ๋๋ ์ฌ๊ณ ์๋์ ๊ฐ์ฌํ์ง ์์
private Integer stockQuantity;
}
7.2 ์ธ๋ฑ์ค ํ์ฉํ๊ธฐ
๊ฐ์ฌ ํ ์ด๋ธ์ ์ ์ ํ ์ธ๋ฑ์ค๋ฅผ ์ถ๊ฐํ๋ฉด ์ฟผ๋ฆฌ ์ฑ๋ฅ์ ํฌ๊ฒ ํฅ์์ํฌ ์ ์์ด:
@Entity
@Table(name = "REVINFO", indexes = {
@Index(name = "idx_revinfo_revtstmp", columnList = "REVTSTMP")
})
@RevisionEntity(UserRevisionListener.class)
public class UserRevisionEntity extends DefaultRevisionEntity {
// ...
}
7.3 ๊ฐ์ฌ ๋ฐ์ดํฐ ์ ๋ฆฌํ๊ธฐ
์ค๋๋ ๊ฐ์ฌ ๋ฐ์ดํฐ๋ ์ฃผ๊ธฐ์ ์ผ๋ก ์ ๋ฆฌํ๋ ๊ฒ์ด ์ข์. ์๋ฅผ ๋ค์ด, 1๋ ์ด์ ๋ ๊ฐ์ฌ ๋ฐ์ดํฐ๋ฅผ ์์นด์ด๋ธํ๊ฑฐ๋ ์ญ์ ํ๋ ๋ฐฐ์น ์์ ์ ๊ตฌํํ ์ ์์ด:
@Scheduled(cron = "0 0 1 * * ?") // ๋งค์ผ ์๋ฒฝ 1์์ ์คํ
public void cleanupOldAuditData() {
LocalDateTime oneYearAgo = LocalDateTime.now().minusYears(1);
long timestamp = oneYearAgo.toEpochSecond(ZoneOffset.UTC) * 1000;
// ๋ค์ดํฐ๋ธ ์ฟผ๋ฆฌ๋ก ์ค๋๋ ๊ฐ์ฌ ๋ฐ์ดํฐ ์ญ์
entityManager.createNativeQuery(
"DELETE FROM REVINFO WHERE REVTSTMP < :timestamp")
.setParameter("timestamp", timestamp)
.executeUpdate();
}
7.4 ์ง์ฐ ๋ก๋ฉ ํ์ฉํ๊ธฐ
๊ฐ์ฌ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ ๋ ์ง์ฐ ๋ก๋ฉ(Lazy Loading)์ ํ์ฉํ๋ฉด ์ฑ๋ฅ์ ํฅ์์ํฌ ์ ์์ด:
@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private List<UserAddress> addresses;
์ด๋ ๊ฒ ํ๋ฉด ์ฃผ์ ์ ๋ณด๊ฐ ํ์ํ ๋๋ง ๋ก๋ฉ๋์ด ๋ถํ์ํ ๋ฐ์ดํฐ ์กฐํ๋ฅผ ์ค์ผ ์ ์์ด!
7.5 ๋ฐฐ์น ์ฒ๋ฆฌ ํ์ฉํ๊ธฐ
๋๋์ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ ๋๋ ๋ฐฐ์น ์ฒ๋ฆฌ๋ฅผ ํ์ฉํ๋ ๊ฒ์ด ์ข์:
@Transactional
public void processLargeDataSet() {
int batchSize = 100;
List<User> allUsers = userRepository.findAll();
for (int i = 0; i < allUsers.size(); i += batchSize) {
List<User> batch = allUsers.subList(
i, Math.min(i + batchSize, allUsers.size()));
// ๋ฐฐ์น ์ฒ๋ฆฌ ๋ก์ง
processBatch(batch);
// ์์์ฑ ์ปจํ
์คํธ ์ด๊ธฐํ๋ก ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ๊ด๋ฆฌ
entityManager.clear();
}
}
์ด๋ฐ ์ต์ ํ ๊ธฐ๋ฒ๋ค์ ์ ์ฉํ๋ฉด Spring Data Envers๋ฅผ ๋๊ท๋ชจ ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ํจ์จ์ ์ผ๋ก ์ฌ์ฉํ ์ ์์ด! ์ฑ๋ฅ๊ณผ ๊ธฐ๋ฅ ์ฌ์ด์ ๊ท ํ์ ์ ๋ง์ถ๋ ๊ฒ์ด ์ค์ํด. ๐ฏ
โ 8. ์์ฃผ ๋ฐ์ํ๋ ๋ฌธ์ ์ ํด๊ฒฐ ๋ฐฉ๋ฒ
Spring Data Envers๋ฅผ ์ฌ์ฉํ๋ค ๋ณด๋ฉด ๋ช ๊ฐ์ง ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ด. ์ฌ๊ธฐ์๋ ์์ฃผ ๋ฐ์ํ๋ ๋ฌธ์ ์ ๊ทธ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์์๋ณผ๊ฒ! ๐ ๏ธ
8.1 ๋ฌธ์ : ๊ฐ์ฌ ํ ์ด๋ธ์ด ์์ฑ๋์ง ์์
์ฆ์: @Audited ์ด๋ ธํ ์ด์ ์ ์ถ๊ฐํ๋๋ฐ ๊ฐ์ฌ ํ ์ด๋ธ์ด ์์ฑ๋์ง ์์.
ํด๊ฒฐ ๋ฐฉ๋ฒ:
- Hibernate ์ค์ ์์
hibernate.hbm2ddl.auto๊ฐ์ดcreate,create-drop, ๋๋update๋ก ์ค์ ๋์ด ์๋์ง ํ์ธํด. - Spring Boot ์ค์ ์์
spring.jpa.hibernate.ddl-auto๊ฐ์ ํ์ธํด. - @EnableJpaRepositories ์ด๋
ธํ
์ด์
์
๊ฐ ์ค์ ๋์ด ์๋์ง ํ์ธํด.repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class
8.2 ๋ฌธ์ : ๋ฆฌ๋น์ ๋ฒํธ๊ฐ ์ฆ๊ฐํ์ง ์์
์ฆ์: ์ํฐํฐ๋ฅผ ์ฌ๋ฌ ๋ฒ ์์ ํด๋ ๋ฆฌ๋น์ ๋ฒํธ๊ฐ ์ฆ๊ฐํ์ง ์์.
ํด๊ฒฐ ๋ฐฉ๋ฒ:
- ํธ๋์ญ์ ์ด ์ ๋๋ก ์ค์ ๋์ด ์๋์ง ํ์ธํด. @Transactional ์ด๋ ธํ ์ด์ ์ด ํ์ํด.
- ๋ณ๊ฒฝ ๊ฐ์ง๊ฐ ์๋ํ๋์ง ํ์ธํด. ์ํฐํฐ์ ํ๋ ๊ฐ์ด ์ค์ ๋ก ๋ณ๊ฒฝ๋์ด์ผ ํด.
- ์์์ฑ ์ปจํ ์คํธ๊ฐ ์ ๋๋ก ํ๋ฌ์๋๋์ง ํ์ธํด.
@Transactional
public void updateUser(User user) {
// ๋ณ๊ฒฝ ์ฌํญ์ด ์๋์ง ํ์ธ
User existingUser = userRepository.findById(user.getId()).orElseThrow();
if (!existingUser.getName().equals(user.getName())) {
existingUser.setName(user.getName());
userRepository.save(existingUser);
}
}
8.3 ๋ฌธ์ : ์ฐ๊ด ์ํฐํฐ ๊ฐ์ฌ ๋ฌธ์
์ฆ์: ์ฐ๊ด ์ํฐํฐ์ ๋ณ๊ฒฝ ์ฌํญ์ด ๊ฐ์ฌ๋์ง ์์.
ํด๊ฒฐ ๋ฐฉ๋ฒ:
- ์ฐ๊ด ์ํฐํฐ์๋ @Audited ์ด๋ ธํ ์ด์ ์ ์ถ๊ฐํด.
- @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)๋ฅผ ์ฌ์ฉํด ์ฐ๊ด ์ํฐํฐ๋ ๊ฐ์ฌํ์ง ์์ผ๋ฉด์ ๊ด๊ณ๋ง ๊ฐ์ฌํ ์ ์์ด.
@Entity
@Audited
public class Order {
@Id
@GeneratedValue
private Long id;
// Customer ์ํฐํฐ๋ ๊ฐ์ฌ๋จ
@ManyToOne
@Audited
private Customer customer;
// Product ์ํฐํฐ๋ ๊ฐ์ฌ๋์ง ์์ง๋ง, ๊ด๊ณ๋ ๊ฐ์ฌ๋จ
@ManyToMany
@Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
private List<Product> products;
}
8.4 ๋ฌธ์ : ์ฑ๋ฅ ์ ํ
์ฆ์: Envers๋ฅผ ์ ์ฉํ ํ ์ ํ๋ฆฌ์ผ์ด์ ์ฑ๋ฅ์ด ์ ํ๋จ.
ํด๊ฒฐ ๋ฐฉ๋ฒ:
- ํ์ํ ํ๋๋ง ๊ฐ์ฌํ๋๋ก ์ค์ ํด.
- ๊ฐ์ฌ ํ ์ด๋ธ์ ์ ์ ํ ์ธ๋ฑ์ค๋ฅผ ์ถ๊ฐํด.
- ๋๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ์ ๋ฐฐ์น ์ฒ๋ฆฌ๋ฅผ ํ์ฉํด.
- ์ค๋๋ ๊ฐ์ฌ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ธฐ์ ์ผ๋ก ์ ๋ฆฌํด.
8.5 ๋ฌธ์ : ์ปค์คํ ๋ฆฌ๋น์ ์ํฐํฐ ๋ฌธ์
์ฆ์: ์ปค์คํ ๋ฆฌ๋น์ ์ํฐํฐ๊ฐ ์ ๋๋ก ์๋ํ์ง ์์.
ํด๊ฒฐ ๋ฐฉ๋ฒ:
- ์ปค์คํ ๋ฆฌ๋น์ ์ํฐํฐ๊ฐ DefaultRevisionEntity๋ฅผ ์์๋ฐ๋์ง ํ์ธํด.
- @RevisionEntity ์ด๋ ธํ ์ด์ ์ด ์ ๋๋ก ์ค์ ๋์ด ์๋์ง ํ์ธํด.
- ๋ฆฌ๋น์ ๋ฆฌ์ค๋๊ฐ ์ ๋๋ก ๊ตฌํ๋์ด ์๋์ง ํ์ธํด.
@Entity
@Table(name = "REVINFO")
@RevisionEntity(UserRevisionListener.class)
public class UserRevisionEntity extends DefaultRevisionEntity {
private String username;
// ๋ฐ๋์ ๊ฒํฐ์ ์ธํฐ๋ฅผ ๊ตฌํํด์ผ ํจ
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
์ด๋ฐ ๋ฌธ์ ๋ค์ ๋ฏธ๋ฆฌ ์๊ณ ๋๋นํ๋ฉด Spring Data Envers๋ฅผ ๋ ํจ๊ณผ์ ์ผ๋ก ํ์ฉํ ์ ์์ด! ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋๋ผ๋ ๋นํฉํ์ง ๋ง๊ณ ์ฐจ๊ทผ์ฐจ๊ทผ ํด๊ฒฐํด๋ณด์. ๐
๐ 9. ๋ง๋ฌด๋ฆฌ ๋ฐ ์ถ๊ฐ ์๋ฃ
์ง๊ธ๊น์ง Spring Data Envers๋ฅผ ์ฌ์ฉํด ์ํฐํฐ ๋ณ๊ฒฝ ์ด๋ ฅ์ ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์์๋ดค์ด! ์ด์ ๋๋ ๋ฐ์ดํฐ์ ๊ณผ๊ฑฐ์ ํ์ฌ๋ฅผ ์์ ๋กญ๊ฒ ์ค๊ฐ๋ ํ์๋จธ์ ์ ์ฃผ์ธ์ด ๋ ๊ฑฐ์ผ. ๐
Spring Data Envers๋ ๋จ์ํ ๊ธฐ์ ์ ์ธ ๋๊ตฌ๋ฅผ ๋์ด์, ๋น์ฆ๋์ค์ ์ค์ง์ ์ธ ๊ฐ์น๋ฅผ ์ ๊ณตํด. ์ฌ๋ฅ๋ท๊ณผ ๊ฐ์ ํ๋ซํผ์์๋ ์ฌ์ฉ์ ์ ๋ณด๋ ๊ฑฐ๋ ๋ด์ญ์ ๋ณ๊ฒฝ ์ด๋ ฅ์ ์ถ์ ํ๋ฉด ๋ณด์, ๊ฐ์ฌ, ์ฌ์ฉ์ ๊ฒฝํ ๋ฑ ์ฌ๋ฌ ์ธก๋ฉด์์ ํฐ ๋์์ด ๋ ๊ฑฐ์ผ!
๐ ํต์ฌ ์์ฝ
- ์ค์ ์ด ๊ฐ๋จํด: ๋ช ๊ฐ์ ์ด๋ ธํ ์ด์ ๊ณผ ์ธํฐํ์ด์ค๋ง์ผ๋ก ๊ฐ๋ ฅํ ์ด๋ ฅ ๊ด๋ฆฌ ๊ธฐ๋ฅ์ ๊ตฌํํ ์ ์์ด.
- ์๋ํ๋ ์ด๋ ฅ ๊ด๋ฆฌ: ์ํฐํฐ๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค ์๋์ผ๋ก ์ด๋ ฅ์ด ๊ธฐ๋ก๋ผ.
- ์ ์ฐํ ์ฟผ๋ฆฌ ๊ธฐ๋ฅ: ๋ค์ํ ๋ฐฉ์์ผ๋ก ์ด๋ ฅ์ ์กฐํํ๊ณ ๋ถ์ํ ์ ์์ด.
- ์ปค์คํฐ๋ง์ด์ง ๊ฐ๋ฅ: ํ์์ ๋ฐ๋ผ ๊ฐ์ฌ ๋ฐฉ์์ ์ธ๋ฐํ๊ฒ ์กฐ์ ํ ์ ์์ด.
- ์ฑ๋ฅ ์ต์ ํ: ์ ์ ํ ์ค์ ๊ณผ ์ธ๋ฑ์ฑ์ผ๋ก ๋๊ท๋ชจ ์์คํ ์์๋ ํจ์จ์ ์ผ๋ก ์ฌ์ฉ ๊ฐ๋ฅํด.
๐ ์ถ๊ฐ ํ์ต ์๋ฃ
Spring Data Envers๋ ๋ฐ์ดํฐ ๋ณ๊ฒฝ ์ด๋ ฅ ๊ด๋ฆฌ๋ผ๋ ๋ณต์กํ ๋ฌธ์ ๋ฅผ ๊ฐ๋จํ๊ฒ ํด๊ฒฐํด์ฃผ๋ ๊ฐ๋ ฅํ ๋๊ตฌ์ผ. ์ด ๊ธ์ด ๋ค ํ๋ก์ ํธ์ Envers๋ฅผ ๋์ ํ๋ ๋ฐ ๋์์ด ๋์์ผ๋ฉด ์ข๊ฒ ์ด! ๐
๋ค์์๋ ๋ ์ฌํ๋ ์ฃผ์ ๋ ์ค์ ํ๋ก์ ํธ์์์ ์ ์ฉ ์ฌ๋ก๋ฅผ ๋ค๋ค๋ณผ ์๋ ์์ ๊ฒ ๊ฐ์. ํน์ ๋ ์๊ณ ์ถ์ ๋ด์ฉ์ด ์๋ค๋ฉด ์ธ์ ๋ ์ง ์ฌ๋ฅ๋ท์์ ๊ด๋ จ ์ง์์ ์ฐพ์๋ณด๊ฑฐ๋ ์ง๋ฌธํด๋ด! ํจ๊ป ์ฑ์ฅํ๋ ๊ฐ๋ฐ์ ์ปค๋ฎค๋ํฐ๊ฐ ๋์์ผ๋ฉด ์ข๊ฒ ์ด. ๐จโ๐ป๐ฉโ๐ป
๊ทธ๋ผ ๋ค์์ ๋ ๋ง๋์! Happy coding! ๐
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.