탈퇴한 회원 그냥 repository.delete() 했는데 개빡치게 몇 년간 보관해야된다
음... 탈퇴한 유저 데이터에 접근하는 경우가 뭐지
흠. 삭제 회원 데이터 접근할 일 딱히 없을 것 같고.. DB 분리하는게 성능상 이점이 있다
DB 분리
module-common → [application.yml]
DB를 2개 이상 사용할 경우, 직접 DataSource를 생성해야한다.
yml 파일 설정 중 만난 오류
: jdbcUrl is required with driverClassName.
HikariCP의 Database URL 설정은 url이 아닌 jdbcUrl을 사용하기 때문에 발생하는 오류로, 대부분의 블로그에서 datasource.url을 datasource.jdbc-url로 변경하라고 추천한다.
하지만 자동설정의 경우엔 datasource.jdbc-url이 아닌 datasource.url이 Datasource의 url이 된다.
만약 datasource.jdbc-url로 값을 설정하면 자동설정에선 인식하지 못한다.
SpringBoot에선 HikariCP의 설정이 추가로 있다.
application.properties, application.yml에 spring.datasource.hikari로 값을 세팅하면 된다~!
spring:
application:
name: module-common
datasource:
db1:
hikari:
jdbc-url: jdbc:postgresql://localhost:5432/{DB name}
username: {username}
password: {password}
driver-class-name: org.postgresql.Driver
db2:
hikari:
jdbc-url: jdbc:postgresql://localhost:5432/{DB name}
username: {username}
password: {password}
driver-class-name: org.postgresql.Driver
module-common → [Db1Config]
: DB1Config로 기존의 project DB 설정
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = "com.example.domain.member.repository", // db1의 repository 패키지
entityManagerFactoryRef = "db1EntityManager",
transactionManagerRef = "db1TransactionManager"
)
@EntityScan(basePackages = "com.example.domain.member.entity") // db1의 entity 패키지
public class Db1Config {
@Primary
@Bean(name = "db1DataSource")
@ConfigurationProperties(prefix = "spring.datasource.db1.hikari")
public DataSource db1DataSource() {
return DataSourceBuilder.create().build();
}
@Primary
@Bean(name = "db1EntityManager")
public LocalContainerEntityManagerFactoryBean db1EntityManager(
EntityManagerFactoryBuilder builder,
@Qualifier("db1DataSource") DataSource dataSource) {
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto", "update");
properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
return builder
.dataSource(dataSource)
.packages("com.example.domain.member.entity") // db1의 엔티티 패키지
.persistenceUnit("db1")
.properties(properties)
.build();
}
@Primary
@Bean(name = "db1TransactionManager")
public PlatformTransactionManager db1TransactionManager(
@Qualifier("db1EntityManager") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
module-common → [Db2Config]
: DB2Config로 추가 project2 DB 설정
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = "com.example.domain.deleted.repository", // db2의 repository 패키지
entityManagerFactoryRef = "db2EntityManager",
transactionManagerRef = "db2TransactionManager"
)
@EntityScan(basePackages = "com.example.domain.deleted.entity")
public class Db2Config {
@Bean(name = "db2DataSource")
@ConfigurationProperties(prefix = "spring.datasource.db2.hikari")
public DataSource db2DataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "db2EntityManager")
public LocalContainerEntityManagerFactoryBean db2EntityManager(
EntityManagerFactoryBuilder builder,
@Qualifier("db2DataSource") DataSource dataSource) {
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto", "update");
properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
return builder
.dataSource(dataSource)
.packages("com.example.domain.deleted.entity") // db2의 엔티티 패키지
.persistenceUnit("db2")
.properties(properties)
.build();
}
@Bean(name = "db2TransactionManager")
public PlatformTransactionManager db2TransactionManager(
@Qualifier("db2EntityManager") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
DB 정상 분리 완료!
PostgreSQL 스케줄러
(파티셔닝으로 해보려고했는데 JPA로 생성한 Entity와 SQL 스크립트로 생성한 Entity간에 충돌이 있어서 스케줄러로 변경)
module-common → [DeletedMember]
: project2 DB에 회원 탈퇴시 사용될 엔티티 DeletedMember 생성
@NoArgsConstructor
@Entity
@Table(name = "deleted_member")
public class DeletedMember {
@Id
@Column(name = "deleted_member_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String email;
@Column(unique = true)
private String nickname;
@Column(unique = true)
private String phone;
@Enumerated(EnumType.STRING)
private Gender gender;
@Column
private String password;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate birthday;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Column(name = "created_at")
private LocalDate createdAt;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Column(name = "cancelled_at")
private LocalDate cancelledAt;
@Enumerated(EnumType.STRING)
private Role role;
@Enumerated(EnumType.STRING)
private Provider provider;
@OneToMany(mappedBy = "deletedMember", cascade = CascadeType.ALL, orphanRemoval = true)
private List<DeletedProfile> deletedProfiles = new ArrayList<DeletedProfile>();
}
module-common → [DeletedProfile]
: project2 DB에 회원 탈퇴시 사용될 엔티티 DeletedProfile 생성
@NoArgsConstructor
@Getter
@Setter
@Entity
@Table(name = "deleted_profile")
public class DeletedProfile {
@Id
@Column(name = "deleted_profile_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@JsonIgnore
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "deleted_member_id")
private DeletedMember deletedMember;
@Column
private String nickname;
@Enumerated(EnumType.STRING)
private Gender gender;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate birthday;
@Column
private Boolean owner; // 본인인지, 아닌지 True, False
@Enumerated(EnumType.STRING)
private Choice pregnancy;
@Enumerated(EnumType.STRING)
private Choice smoking;
@Enumerated(EnumType.STRING)
private Choice hypertension;
@Enumerated(EnumType.STRING)
private Choice diabetes;
}
module-common → [DeletedMemberRepository]
@Repository
public interface DeletedMemberRepository extends JpaRepository<DeletedMember, Long> {
List<DeletedMember> findByCancelledAtBefore(LocalDate date);
}
module-common → [DeletedServiceImpl]
: DeletedMember의 DeletedProfile 리스트에 CascadeType.ALL, OrphanRemoval = true 설정을 걸어 DeletedMember가 삭제되면 DeletedProfile이 함께 삭제되도록 설정하였다. 그래서 DeletedProfile을 삭제하는 로직은 따로 존재하지 않는다.
@Scheduled(fixedDelay = 30000) // 30초
@Transactional
public void cleanupDeletedMembers() {
// 3년 전에 탈퇴한 회원을 삭제하는 로직
LocalDate threeYearsAgo = LocalDate.now().minusYears(3);
List<DeletedMember> membersToDelete = deletedMemberRepository
.findByCancelledAtBefore(threeYearsAgo);
for (DeletedMember member : membersToDelete) {
deletedMemberRepository.delete(member);
}
System.out.println("Deleted members older than 3 years.");
}
→ 테스트를 위해 fixedDelay = 30000으로 설정
@Scheduled(cron = "0 0 0 * * ?")
→ 이후 자정마다 실행되는 로직으로 변경
테스트
탈퇴일자를 나타내는 cancelled_at '2020-09-11'으로 설정
30 초 간격으로 스케줄러 정상 작동
제대로 삭제되는거 확인
'개발' 카테고리의 다른 글
[스프링부트] JPA Specification 이용한 멤버 조회 구현 (0) | 2024.09.13 |
---|---|
[스프링부트] LazyInitializationException 지연 로딩 오류 (0) | 2024.09.12 |
[Spring Cloud Gateway] Admin 로그인 API가 따로 필요한가 ? ? (0) | 2024.09.10 |
[Spring Cloud Gateway] module-admin (0) | 2024.09.09 |
[Springboot + Redis + CoolSms] 회원가입 인증 부분 문제 해결 (0) | 2024.09.06 |