728x90
H2 데이터베이스를 활용하여, 스키마 기반으로 멀티테넌시를 구현하는 방법을 알아보도록 하겠습니다.
(스프링 부트 버전은 2.6.10 입니다.)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
우선 pom.xml을 위와 같이 설정해줍니다. (Flyway라는 마이그레이션 도구도 사용할 예정입니다.)
@Entity
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String username;
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;
// getters, setters and overriden methods from UserDetails
}
@Entity
public class Note {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String text;
// getters and setters
}
엔티티 클래스를 생성합니다.
두 클래스 모두 롬복 플러그인을 이용하여 @Getter @Setter 메서드를 생성해 주시고,
User 클래스의 경우 UserDetails의 메서드를 오버라이딩 해주세요.
getPassword() 와 같은 메서드가 클래스의 비밀번호를 반환하도록 설정해 주시고,
isAccountNonExpired()와 같은 메서드는 모두 true로 설정해주세요.
@Repository
public interface UserRepository extends CrudRepository<User, Long> {
Optional<User> findByUsername(String username);
}
@Repository
public interface NoteRepository extends CrudRepository<Note, Long> {
}
엔티티 CRUD를 위한 레파지토리를 생성합니다.
@Service
public class UserService implements UserDetailsService {
private UserRepository repository;
private PasswordEncoder encoder;
private TenantService tenantService;
public UserService(UserRepository repository, TenantService tenantService) {
this.repository = repository;
this.tenantService = tenantService;
this.encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Transactional
public User createUser(User user) {
String encodedPassword = encoder.encode(user.getPassword());
user.setPassword(encodedPassword);
User saved = repository.save(user);
tenantService.initDatabase(user.getUsername());
return saved;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return repository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User with the specified username is not found"));
}
}
@Service
public class NoteService {
private NoteRepository repository;
public NoteService(NoteRepository repository) {
this.repository = repository;
}
public Note createNote(Note note) {
return repository.save(note);
}
public Note findNote(Long id) {
return repository.findById(id).orElseThrow();
}
public Iterable<Note> findAllNotes() {
return repository.findAll();
}
}
엔티티 별 서비스도 구현해줍니다.
@RestController
@RequestMapping("/users")
public class UserController {
private UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping
public ResponseEntity<User> register(@RequestBody User user) {
User created = userService.createUser(user);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
}
@RestController
@RequestMapping("/notes")
public class NoteController {
private NoteService noteService;
public NoteController(NoteService noteService) {
this.noteService = noteService;
}
@PostMapping
public ResponseEntity<Note> createNote(@RequestBody Note note) {
Note created = noteService.createNote(note);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
@GetMapping("/{id}")
public ResponseEntity<Note> getNote(@PathVariable Long id) {
Note note = noteService.findNote(id);
return ResponseEntity.ok(note);
}
@GetMapping
public ResponseEntity<Iterable<Note>> getAllNotes() {
Iterable<Note> notes = noteService.findAllNotes();
return ResponseEntity.ok(notes);
}
}
API 호출을 위한 컨트롤러입니다.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private UserDetailsService userService;
public SecurityConfig(UserDetailsService userService) {
this.userService = userService;
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/h2-console/**").permitAll()
.antMatchers(HttpMethod.POST, "/users").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.headers().frameOptions().disable();
}
}
WebSecurityConfigurerAdapter가 Deprecated된걸로 알고있는데 이 부분은 다른 포스팅을 통해 다루도록 하겠습니다.
우선 프로젝트 기본 세팅을 마쳤구요, 다음 포스팅에 멀티테넌시 기능 구현 및 테스트 업로드하도록 하겠습니다.
ref : https://sultanov.dev/blog/schema-based-multi-tenancy-with-spring-data/
728x90
'Multi Tenancy' 카테고리의 다른 글
[Spring Boot] 스키마 기반 멀티테넌시 구현 (2/2) (5) | 2022.08.09 |
---|