PaperUserModule.java
-> 모듈의 기본설정은 Config 패키지에서 작업
@Configuration
@ComponentScan("com.sp.fc.user")
@EnableJpaRepositories(basePackages = {
"com.sp.fc.user.repository"
})
@EntityScan(basePackages = {
"com.sp.fc.user.domain"
})
public class PaperUserModule {
}
Application에서 이 모듈에 대한 Bean과 특징들을 스캔할때, 스캔해가는 정보들을 취합
School.java
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Entity
@Table(name = "sp_school")
public class School {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long schoolId;
private String name;
private String city;
@Column(updatable = false)
private LocalDateTime created;
private LocalDateTime updated;
}
SchoolRepository.java
public interface SchoolRepository extends JpaRepository<School, Long> {
@Query("select distinct(city) from School")
List<String> getCities();
List<School> findAllByCity(String city);
}
SchoolService.java
@Service
@Transactional // DB 작업이기 때문
@RequiredArgsConstructor
public class SchoolService {
private final SchoolRepository schoolRepository;
public School save(School school){ // 학교 저장
if(school.getSchoolId() == null){
school.setCreated(LocalDateTime.now());
}
school.setUpdated(LocalDateTime.now());
return schoolRepository.save(school);
}
// 어느 지역의 학교인지 리스트상에 노출하기 위해
public List<String> cities(){
return schoolRepository.getCities();
}
public Optional<School> updateName(Long schoolId, String name){
return schoolRepository.findById(schoolId).map(school -> {
school.setName(name);
schoolRepository.save(school);
return school;
});
}
public List<School> findAllByCity(String city) {
return schoolRepository.findAllByCity(city);
}
}
SchoolTestHelper.java
@RequiredArgsConstructor
public class SchoolTestHelper {
private final SchoolService schoolService;
public static School makeSchool(String name, String city){
return School.builder()
.name(name)
.city(city)
.build();
}
public School createSchool(String name, String city){
return schoolService.save(makeSchool(name, city));
}
public static void assertSchool(School school, String name, String city){
assertNotNull(school.getSchoolId());
assertNotNull(school.getCreated());
assertNotNull(school.getUpdated());
assertEquals(school.getCity(), city);
assertEquals(school.getName(), name);
}
}
PaperUserTestApp.java
@SpringBootApplication
public class PaperUserTestApp {
public static void main(String[] args){
SpringApplication.run(PaperUserTestApp.class, args);
}
@Configuration
@ComponentScan("com.sp.fc.user")
@EnableJpaRepositories(basePackages = {
"com.sp.fc.user.repository"
})
@EntityScan(basePackages = {
"com.sp.fc.user.domain"
})
class Config{
// server쪽 Application에서는 스캔이 잘되지만 테스트 시에는 잘 안되는 문제가 있어 Config 빈을
// 만들어 설정하였음
}
}
SchoolTest.java
@DataJpaTest // DB에 데이터를 넣고 빼는 작업을 진행(Integration Test)
// 위 어노테이션 선언 시 DB 데이터 소스를 H2 DB에 인메모리 방식으로 만들고
// Repository는 스프링 컨테이너에서 기본적으로 만들어 줌 -> 서비스는 안만들어줌
public class SchoolTest {
@Autowired
private SchoolRepository schoolRepository;
private SchoolService schoolService;
private SchoolTestHelper schoolTestHelper;
School school;
@BeforeEach
void before() {
this.schoolRepository.deleteAll(); // Repository는 테스트 실행 시 초기화 하는 것이 좋음
this.schoolService = new SchoolService(schoolRepository);
this.schoolTestHelper = new SchoolTestHelper(this.schoolService);
school = this.schoolTestHelper.createSchool("테스트 학교", "서울");
}
@Test
void test1(){
// 학교 생성 테스트
List<School> list = schoolRepository.findAll();
assertEquals(1, list.size());
SchoolTestHelper.assertSchool(list.get(0), "테스트 학교", "서울");
}
@Test
void test2(){
//학교 이름 수정
schoolService.updateName(school.getSchoolId(), "테스트2 학교");
assertEquals(schoolRepository.findAll().get(0).getName(), "테스트2 학교");
}
@Test
void test3(){
// 지역 목록 가져오기
List<String> list = schoolService.cities();
assertEquals(1, list.size());
assertEquals("서울", list.get(0));
school = this.schoolTestHelper.createSchool("부산 학교", "부산");
list = schoolService.cities();
assertEquals(2, list.size());
}
@Test
void test_4(){
List<School> list = schoolService.findAllByCity("서울");
assertEquals(1, list.size());
}
}
유저
선생님
학생
Authority.java
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Entity
@Table(name = "sp_authority")
@IdClass(Authority.class)
public class Authority implements GrantedAuthority {
public static final String ROLE_TEACHER = "ROLE_TEACHER";
public static final String ROLE_STUDENT = "ROLE_STUDENT";
@Id
private Long userId;
@Id
private String authority;
}
User.java(UserDetails)
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Entity
@Table(name="sp_user")
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;
private String name;
@Column(unique = true)
private String email;
private String password;
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(foreignKey = @ForeignKey(name = "userId"))
private Set<Authority> authorities;
private String grade;
@ManyToOne(fetch = FetchType.EAGER)
private User teacher;
@ManyToOne(fetch = FetchType.EAGER)
private School school;
private boolean enabled;
@Column(updatable = false)
private LocalDateTime created;
private LocalDateTime updated;
@Override
public String getUsername() {
return email;
}
@Override
public boolean isAccountNonExpired() {
return enabled;
}
@Override
public boolean isAccountNonLocked() {
return enabled;
}
@Override
public boolean isCredentialsNonExpired() {
return enabled;
}
}
UserRepository.java
public interface UserRepository extends JpaRepository<User, Long> {
@Modifying(clearAutomatically = true)
@Query("update User set name = ?2 , updated = ?3 where userId = ?1")
void updateUserName(Long userId, String userName, LocalDateTime update);
Optional<User> findByEmail(String username);
@Query("select a from User a, Authority b where a.userId = b.userId and b.authority=?1")
List<User> findAllByAuthoritiesIn(String authority);
// UI에서 구현할려면 Paging 된 리스트를 받을 수 있어야 함
@Query("select a from User a, Authority b where a.userId = b.userId and b.authority=?1")
Page<User> findAllByAuthoritiesIn(String authority, Pageable pageable);
@Query("select a from User a, Authority b where a.school.schoolId = ?1 and a.userId = b.userId and b.authority = ?2")
List<User> findAllBySchool(Long schoolId, String authority);
@Query("select a from User a, User b where a.teacher.userId = b.userId and b.userId = ?1")
List<User> findAllByTeacherUserId(Long userId);
@Query("select count(a) from User a, User b where a.teacher.userId = b.userId and b.userId = ?1")
long countByAllTeacherUserId(Long userId);
@Query("select count(a) from User a, Authority b where a.userId = b.userId and b.authority = ?1")
long countAllByAuthoritiesIn(String authority);
@Query("select count(a) from User a, Authority b where a.school.schoolId = ?1 and a.userId = b.userId and b.authority = ?2")
long countAllByAuthoritiesIn(long schoolId, String authority);
}
UserService.java
@Service
@RequiredArgsConstructor
@Transactional
public class UserService {
private final SchoolRepository schoolRepository;
private final UserRepository userRepository;
public User save(User user) throws DataIntegrityViolationException{
if(user.getUserId() == null){
user.setCreated(LocalDateTime.now());
}
user.setUpdated(LocalDateTime.now());
return userRepository.save(user);
}
public Optional<User> findUser(Long userId){
return userRepository.findById(userId);
}
public Page<User> listUser(int pageNum, int size){
return userRepository.findAll(PageRequest.of(pageNum-1, size));
}
public Map<Long,User> getUsers(List<Long> userIds){
return StreamSupport.stream(userRepository.findAllById(userIds).spliterator(), false)
.collect(Collectors.toMap(User::getUserId, Function.identity()));
}
public void addAuthority(Long userId, String authority){
userRepository.findById(userId).ifPresent(user -> {
Authority newRole = new Authority(user.getUserId(), authority);
// @EqualsAnd~~ 어노테이션과 함께 참고
if(user.getAuthorities() == null){
HashSet<Authority> authorities = new HashSet<>();
authorities.add(newRole);
user.setAuthorities(authorities);
save(user);
} else if(!user.getAuthorities().contains(newRole)) {
HashSet<Authority> authorities = new HashSet<>();
authorities.addAll(user.getAuthorities());
authorities.add(newRole);
user.setAuthorities(authorities);
save(user);
}
});
}
public void removeAuthority(Long userId, String authority){
userRepository.findById(userId).ifPresent(user ->{
if(user.getAuthorities() == null) return;
Authority targetRole = new Authority(user.getUserId(), authority);
if(user.getAuthorities().contains(targetRole)){
user.setAuthorities(
user.getAuthorities().stream().filter(auth -> !auth.equals(targetRole))
.collect(Collectors.toSet())
);
save(user);
}
});
}
public void updateUsername(Long userId, String userName){
userRepository.updateUserName(userId, userName, LocalDateTime.now());
}
public Optional<User> findByEmail(String email) {
return userRepository.findByEmail(email);
}
public List<User> findTeacherList() {
return userRepository.findAllByAuthoritiesIn(Authority.ROLE_TEACHER);
}
public List<User> findStudentList() {
return userRepository.findAllByAuthoritiesIn(Authority.ROLE_STUDENT);
}
public List<User> findTeacherStudentList(Long userId) {
return userRepository.findAllByTeacherUserId(userId);
}
public Long findTeacherStudentCount(Long userId) {
return userRepository.countByAllTeacherUserId(userId);
}
public List<User> findBySchoolStudentList(Long schoolId) {
return userRepository.findAllBySchool(schoolId, Authority.ROLE_STUDENT);
}
public List<User> findBySchoolTeacherList(Long schoolId) {
return userRepository.findAllBySchool(schoolId, Authority.ROLE_TEACHER);
}
public void updateUserSchoolTeacher(Long userId, Long schoolId, Long teacherId) {
// 선생님 소속 바꾸기
userRepository.findById(userId).ifPresent(user->{
if(!user.getSchool().getSchoolId().equals(schoolId)) {
schoolRepository.findById(schoolId).ifPresent(school -> user.setSchool(school));
}
if(!user.getTeacher().getUserId().equals(teacherId)){
findUser(teacherId).ifPresent(teacher->user.setTeacher(teacher));
}
if(user.getSchool().getSchoolId() != user.getTeacher().getSchool().getSchoolId()){
throw new IllegalArgumentException("해당 학교의 선생님이 아닙니다.");
}
save(user);
});
}
public long countTeacher() {
return userRepository.countAllByAuthoritiesIn(Authority.ROLE_TEACHER);
}
public long countTeacher(long schoolId) {
return userRepository.countAllByAuthoritiesIn(schoolId, Authority.ROLE_TEACHER);
}
public long countStudent() {
return userRepository.countAllByAuthoritiesIn(Authority.ROLE_STUDENT);
}
public long countStudent(long schoolId) {
return userRepository.countAllByAuthoritiesIn(schoolId, Authority.ROLE_STUDENT);
}
public Page<User> listStudents(Integer pageNum, Integer size) {
return userRepository.findAllByAuthoritiesIn(Authority.ROLE_STUDENT, PageRequest.of(pageNum-1, size));
}
public Page<User> listTeachers(Integer pageNum, Integer size) {
return userRepository.findAllByAuthoritiesIn(Authority.ROLE_TEACHER, PageRequest.of(pageNum-1, size));
}
}
UserSecurityService.java
@Service
@RequiredArgsConstructor
@Transactional
public class UserSecurityService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findByEmail(username).orElseThrow(()->new IllegalArgumentException(username+" 사용자가 존재하지 않습니다"));
}
}
UserTestHelper.java
@RequiredArgsConstructor
public class UserTestHelper {
private final UserService userService;
private final PasswordEncoder passwordEncoder; // = NoOpPasswordEncoder.getInstance();
public static User makeUser(School school, String name){
return User.builder()
.school(school)
.name(name)
.email(name+"@test.com")
.enabled(true)
.build();
}
public User createUser(School school, String name){
User user = makeUser(school, name);
user.setPassword(passwordEncoder.encode(name+"123"));
return userService.save(user);
}
public User createUser(School school, String name, String ... authorities){
User user = createUser(school, name);
Stream.of(authorities).forEach(auth->userService.addAuthority(user.getUserId(), auth));
return user;
}
public User createTeacher(School school, String name){
User teacher = createUser(school, name);
userService.addAuthority(teacher.getUserId(), Authority.ROLE_TEACHER);
return teacher;
}
public static void assertTeacher(School school, User teacher, String name){
assertUser(school, teacher, name, Authority.ROLE_TEACHER);
}
public User createStudent(School school, User teacher, String name, String grade){
User student = User.builder()
.school(school)
.name(name)
.password(passwordEncoder.encode(name+"123"))
.email(name+"@test.com")
.teacher(teacher)
.grade(grade)
.enabled(true)
.build();
student = userService.save(student);
userService.addAuthority(student.getUserId(), Authority.ROLE_STUDENT);
return student;
}
public static void assertStudent(School school, User teacher, User student, String name, String grade){
assertUser(school, student, name, Authority.ROLE_STUDENT);
assertEquals(teacher.getUserId(), student.getTeacher().getUserId());
assertEquals(grade, student.getGrade());
}
public static void assertUser(School school, User user, String name){
assertNotNull(user.getUserId());
assertNotNull(user.getCreated());
assertNotNull(user.getUpdated());
assertTrue(user.isEnabled());
assertEquals(school.getSchoolId(), user.getSchool().getSchoolId());
assertEquals(name, user.getName());
assertEquals(name+"@test.com", user.getEmail());
}
public static void assertUser(School school, User user, String name, String ... authorities){
assertUser(school, user, name);
assertTrue(user.getAuthorities().containsAll(
Stream.of(authorities).map(auth->new Authority(user.getUserId(), auth)).collect(Collectors.toSet())
));
}
}
WithUserTest.java
public class WithUserTest {
@Autowired
protected SchoolRepository schoolRepository;
@Autowired
protected UserRepository userRepository;
protected SchoolService schoolService;
protected UserService userService;
protected UserSecurityService userSecurityService;
protected SchoolTestHelper schoolTestHelper;
protected UserTestHelper userTestHelper;
protected School school;
private boolean prepared;
protected void prepareUserServices (){
if(prepared) return;
prepared = true;
this.schoolRepository.deleteAll();
this.userRepository.deleteAll();
this.schoolService = new SchoolService(schoolRepository);
this.userService = new UserService(schoolRepository, userRepository);
this.userSecurityService = new UserSecurityService(userRepository);
this.userTestHelper = new UserTestHelper(userService, NoOpPasswordEncoder.getInstance());
this.schoolTestHelper = new SchoolTestHelper(schoolService);
this.school = this.schoolTestHelper.createSchool("테스트 학교", "서울");
}
}
UserTest.java
@DataJpaTest
public class UserTest extends WithUserTest {
@BeforeEach
protected void before (){
prepareUserServices();
}
@DisplayName("1. 사용자 생성")
@Test
void 사용자_생성 () {
userTestHelper.createUser(school, "user1");
List<User> list = StreamSupport.stream(userRepository.findAll().spliterator(), false)
.collect(Collectors.toList());
assertEquals(1,list.size());
UserTestHelper.assertUser(school, list.get(0), "user1");
}
@DisplayName("2. 이름 수정")
@Test
void 이름_수정 () {
User user = userTestHelper.createUser(school, "user1");
userService.updateUsername(user.getUserId(), "user2");
List<User> list = StreamSupport.stream(userRepository.findAll().spliterator(), false).collect(Collectors.toList());
assertEquals("user2", list.get(0).getName());
}
@DisplayName("3. 권한 부여")
@Test
void 권한_부여하기 () {
User user = userTestHelper.createUser(school, "user1", Authority.ROLE_STUDENT);
userService.addAuthority(user.getUserId(), Authority.ROLE_TEACHER);
User savedUser = userService.findUser(user.getUserId()).get();
userTestHelper.assertUser(school, savedUser, "user1", Authority.ROLE_STUDENT, Authority.ROLE_TEACHER);
}
@DisplayName("4. 권한 취소")
@Test
void 권한_취소하기 () {
User user1 = userTestHelper.createUser(school, "admin", Authority.ROLE_STUDENT, Authority.ROLE_TEACHER);
userService.removeAuthority(user1.getUserId(), Authority.ROLE_STUDENT);
User savedUser = userService.findUser(user1.getUserId()).get();
userTestHelper.assertUser(school, savedUser, "admin", Authority.ROLE_TEACHER);
}
@DisplayName("5. email검색")
@Test
void 이메일_검색기능 () {
User user1 = userTestHelper.createUser(school, "user1");
User saved = (User) userSecurityService.loadUserByUsername("user1@test.com");
userTestHelper.assertUser(school, saved, "user1");
}
@DisplayName("6. role 중복해서 추가되지 않는다.")
@Test
void Role_중복_문제 () {
User user1 = userTestHelper.createUser(school, "user1", Authority.ROLE_STUDENT);
userService.addAuthority(user1.getUserId(), Authority.ROLE_STUDENT);
userService.addAuthority(user1.getUserId(), Authority.ROLE_STUDENT);
User savedUser = userService.findUser(user1.getUserId()).get();
assertEquals(1, savedUser.getAuthorities().size());
userTestHelper.assertUser(school, savedUser, "user1", Authority.ROLE_STUDENT);
}
@DisplayName("7. email이 중복되어서 들어가는가?")
@Test
void 이메일_중복문제 () {
// jpa 기능이라 굳이..테스트 할필요는 없을듯 , 만약 unique가 없는 경우 문제 생길것 테스트
userTestHelper.createUser(school, "user1");
assertThrows(DataIntegrityViolationException.class, ()->{
userTestHelper.createUser(school, "user1");
});
}
}
TeacherTest.java
@DataJpaTest
public class TeacherTest extends WithUserTest {
User teacher;
@BeforeEach
void before(){
prepareUserServices();
this.teacher = this.userTestHelper.createTeacher(school, "teacher1");
}
@DisplayName("1. 선생님을 등록한다. ")
@Test
void 선생님_등록_테스트 () {
List<User> teacherList = userService.findTeacherList();
assertEquals(1, teacherList.size());
UserTestHelper.assertTeacher(school, teacherList.get(0), "teacher1");
}
@DisplayName("2. 선생님으로 등록한 학생 리스트를 조회한다.")
@Test
void 선생님으로_등록한_학생_리스트_조회 () {
this.userTestHelper.createStudent(school, teacher, "study1", "1");
this.userTestHelper.createStudent(school, teacher, "study2", "1");
this.userTestHelper.createStudent(school, teacher, "study3", "1");
assertEquals(3, userService.findTeacherStudentList(teacher.getUserId()).size());
}
@DisplayName("3. 선생님 리스트를 조회 한다.")
@Test
void 선생님_리스트_조회 () {
this.userTestHelper.createUser(school, "teacher2", Authority.ROLE_TEACHER);
this.userTestHelper.createUser(school, "teacher3", Authority.ROLE_TEACHER);
this.userTestHelper.createUser(school, "teacher4", Authority.ROLE_TEACHER);
assertEquals(4, userService.findTeacherList().size());
}
@DisplayName("4. 학교로 선생님이 조회된다.")
@Test
void 학교로_선생님_리스트_조회 () {
List<User> teacherList = userService.findBySchoolTeacherList(school.getSchoolId());
assertEquals(1, teacherList.size());
UserTestHelper.assertTeacher(school, teacher, "teacher1");
this.userTestHelper.createUser(school, "teacher2", Authority.ROLE_TEACHER);
this.userTestHelper.createUser(school, "teacher3", Authority.ROLE_TEACHER);
assertEquals(3, userService.findTeacherList().size());
}
}
StudentTest.java
@DataJpaTest
public class StudentTest extends WithUserTest {
User teacher;
User student;
@BeforeEach
void before(){
prepareUserServices();
this.teacher = this.userTestHelper.createTeacher(school, "teacher1");
this.student = this.userTestHelper.createStudent(school, teacher, "student1", "1");
}
@DisplayName("1. 학습자 등록 한다. ")
@Test
void 햑생_등록_테스트 () {
List<User> studentList = userService.findStudentList();
assertEquals(1, studentList.size());
UserTestHelper.assertStudent(school, teacher, studentList.get(0), "student1", "1");
}
@DisplayName("2. 선생님으로 등록하면 선생님의 학습자가 조회된다 ")
@Test
void 선생님의_학생으로_조회된다 () {
List<User> studentList = userService.findTeacherStudentList(teacher.getUserId());
assertEquals(1, studentList.size());
UserTestHelper.assertStudent(school, teacher, studentList.get(0), "student1", "1");
}
@DisplayName("3. 학교로 학습자가 조회된다.")
@Test
void 학교의_학생으로_조회된다 () {
List<User> studentList = userService.findBySchoolStudentList(school.getSchoolId());
assertEquals(1, studentList.size());
UserTestHelper.assertStudent(school, teacher, studentList.get(0), "student1", "1");
}
}