250106 MongoDB (SpringBoot, 대용량 데이터 저장/읽기)

250106 에이콘 아카데미 수업에 기반하여 작성되었음을 알립니다.


MongoDB - Spring Boot (CRUD)

프로젝트 생성

Customer

@Document(collection = "customer") // JPA에서 @Entity와 같은 역할, collection은 table
@Getter
@Setter
public class Customer {
	@Id
	private String id;
	private String name;
	private int age;
	private String gender;
	
	@Override
	public String toString() {
		return "Customer{" + "id=" + id + ", name=" + name + ", age=" + age + ", gender=" + gender +"}";		
	}
}

CustomerRepository

public interface CustomerRepository extends MongoRepository<Customer, String> {
	Customer findByName(String name);
}

CustomerService

@Service
public class CustomerService {
	
	@Autowired
	private CustomerRepository customerRepository;
	
	public void printAllCustomers() { // 전체 자료 읽기
		customerRepository.findAll().forEach(customer -> {
			System.out.println(customer);
		});
	}
	
	public void insCustomer(String name, int age, String gender) { // 추가
		Customer exCustomer = customerRepository.findByName(name);
		
		if(exCustomer == null) { // 동일한 고객명이 없는 경우만 추가
			Customer newCustomer = new Customer();
			newCustomer.setName(name);
			newCustomer.setAge(age);
			newCustomer.setGender(gender);
			customerRepository.save(newCustomer);
			
			System.out.println("고객 추가 성공" + newCustomer);
		} else {
			System.out.println("동일 고객명 존재");
		}
	}
	
	public void upCustomer(String name) { // 수정
		Customer exCustomer = customerRepository.findByName(name);
		
		if(exCustomer != null) { // 나이와 성별 수정
			exCustomer.setAge(33);
			exCustomer.setGender("여");
			customerRepository.save(exCustomer);
			
			System.out.println("고객 수정 성공" + exCustomer);
		} else {
			System.out.println("고객 찾기 실패");
		}
	}
	
	public void delCustomer(String name) { // 삭제
		Customer exCustomer = customerRepository.findByName(name);
		
		if(exCustomer != null) {
			customerRepository.delete(exCustomer);
			
			System.out.println("고객 삭제 성공" + exCustomer);
		} else {
			System.out.println("고객 찾기 실패");
		}
	}
}

MainController

@Controller
public class MainController {
	
	@GetMapping("/")
	public String start() {
		return "index";
	}
}

CustomerController

@RestController
public class CustomerController {
	
	@Autowired
	private CustomerService customerService;
	
	@GetMapping("/alldata")
	public String alldata() {
		customerService.printAllCustomers();
		
		return "전체 자료 출력";
	}
	
	@PostMapping("/insdata")
	public String insdata(@RequestParam(name="name") String name,
			@RequestParam(name="age") int age,
			@RequestParam(name="gender") String gender) {
		customerService.insCustomer(name, age, gender);
		
		return "고객 추가 성공";
	}
	
	@PutMapping("/updata")
	public String updata(@RequestParam(name="name") String name) {
		customerService.upCustomer(name);
		
		return "고객 수정 성공";
	}
	
	@DeleteMapping("/deldata")
	public String deldata(@RequestParam(name="name") String name) {
		customerService.delCustomer(name);
		
		return "고객 삭제 성공";
	}
}

index.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<a href="/alldata">전체 자료 읽기</a>
</body>
</html>


MongoDB - Spring Boot (Security 적용)

build.gradle

... 생략
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
	implementation 'org.mongodb:mongodb-driver-sync:5.2.0' // 따로 입력해서 의존성 설정해줘야함
	
	implementation 'org.springframework.boot:spring-boot-starter-security'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.springframework.security:spring-security-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
... 생략

application.properties

spring.application.name=aaaaa

# MongoDB 설정
spring.data.mongodb.uri=mongodb://localhost:27017/movieapp

# 활성화된 프로파일
spring.profiles.active=default

logging.level.org.springframework.data.mongodb.core=DEBUG

Movie

@Document(collection = "movies")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Movie {
	@Id
    private String id;

    private String title;
    private String genre;
    private double rating;
}

MovieRepository

@Repository
public interface MovieRepository extends MongoRepository<Movie, String> {
    List<Movie> findByGenre(String genre);
}

SecurityConfig

@Configuration
@EnableWebSecurity
public class SecurityConfig {
	@Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(); 
    }

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
	    http.csrf(csrf -> csrf.disable())
	        .authorizeHttpRequests(auth -> auth
	            .requestMatchers("/", "/css/**", "/js/**").permitAll() // 로그인 페이지 허용
	            .anyRequest().authenticated())
	            //.requestMatchers("/api/movies/**").authenticated()) // 인증 필요
	        .formLogin(form -> form
	            .loginPage("/login").permitAll() // 커스텀 로그인 페이지
	            .defaultSuccessUrl("/api/movies", true)) // 로그인 성공 후 이동
	        .logout(logout -> logout.permitAll()) // 로그아웃 허용
	        .exceptionHandling(ex -> ex
                .authenticationEntryPoint((request, response, authException) -> {
                    response.sendRedirect("/login");
                })); // 인증되지 않은 요청에 대해 /login으로 리다이렉트
	    return http.build();
	}
}

UserConfig

@Configuration
public class UserConfig {
    @Bean
    public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
        UserDetails user = User.builder()
                .username("kor")
                .password(passwordEncoder.encode("123"))
                .roles("USER")
                .build();
        return new InMemoryUserDetailsManager(user);
    }
}

LoginController

@Controller
public class LoginController {
	@GetMapping({"/", "/login"})
	public String login() {
		return "login";
	}
}

MovieController

@RestController
@RequestMapping("/api/movies")
public class MovieController {
	private final MovieRepository movieRepository;

    public MovieController(MovieRepository movieRepository) {
        this.movieRepository = movieRepository;
    }

    @GetMapping
    public List<Movie> getAllMovies() {
        return movieRepository.findAll();
    }

    @PostMapping 
    public Movie addMovie(@RequestBody Movie movie) {
        return movieRepository.save(movie);
    }

    @GetMapping("/recommend/{genre}")
    public List<Movie> recommendMovies(@PathVariable(name="genre") String genre) {
        return movieRepository.findByGenre(genre);
    }
}

MyErrorController

@Controller
public class MyErrorController implements ErrorController {
	@GetMapping("/error")
    public String handleError(HttpServletRequest request) {
        // 모든 에러를 /login으로 리다이렉트
        return "redirect:/login";
    }
}

login.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1>로그인</h1>
    <form method="post" action="/login">
        Username : <input type="text" name="username" required><br>
        Password : <input type="password" name="password" required><br><br>
        <button type="submit">확인</button>
    </form>
</body>
</html>

추가

 

읽기


MongoDB - 대용량 데이터

🙏 GridFS를 사용해 MongoDB에 대용량 파일을 저장

웹상의 구조가 다른 데이터를 가져오고 저장하는 방법 중 NoSQL인 MongoDB를 활용해보자!

프로젝트 생성

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>aa</groupId>
	<artifactId>mongo04_bigdata</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<dependencies>
		<!-- https://mvnrepository.com/artifact/org.mongodb/mongodb-driver-sync -->
		<dependency>
			<groupId>org.mongodb</groupId>
			<artifactId>mongodb-driver-sync</artifactId>
			<version>5.2.0</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/com.opencsv/opencsv -->
		<dependency>
			<groupId>com.opencsv</groupId>
			<artifactId>opencsv</artifactId>
			<version>5.8</version>
		</dependency>
		<dependency>
			<groupId>org.json</groupId>
			<artifactId>json</artifactId>
			<version>20240303</version>
		</dependency>
	</dependencies>
</project>

',' 로 request, response를 구분하고 있다.

MongoDbUpload (저장)

public class MongoDbUpload {

	public static void main(String[] args) {
		String connString = "mongodb://localhost:27017";
		
		try (MongoClient mongoClient = MongoClients.create(connString)) {
			MongoDatabase database = mongoClient.getDatabase("katalkdb");
			
			// GridFSBucket 생성 (분산 저장용)
			GridFSBucket gridFSBucket = GridFSBuckets.create(database, "katalkfiles");
			
			// resource 폴더에서 CSV 파일 읽기
			ClassLoader classLoader = MongoDbUpload.class.getClassLoader();
			InputStream inputStream = classLoader.getResourceAsStream("katalkdata.csv");
			
			if(inputStream != null) {
				uploadCSVtoMongoDB(inputStream, gridFSBucket);
			} else {
				System.out.println("CSV 찾기 실패");
			}
			
		} catch (Exception e) {
			System.out.println("err : " + e);
		}

	}
	
	private static void uploadCSVtoMongoDB(InputStream inputStream, GridFSBucket gridFSBucket) {
		try(BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
				CSVReader csvReader = new CSVReader(reader)) {
			List<String[]> records = csvReader.readAll();
			
			for(String[] record : records) {
				Document doc = new Document("req", record[0]).append("res", record[1]);
				
				// 대용량 자료를 1MB 크기의 청크(묶음)로 나누어 저장
				GridFSUploadOptions options = new GridFSUploadOptions().chunkSizeBytes(1024 * 1024);
				
				ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(doc.toJson().getBytes());
				
				ObjectId fileId = gridFSBucket.uploadFromStream("katalkdata", byteArrayInputStream, options);
				
				System.out.println("saved id : " + fileId.toHexString());
			}
		} catch (Exception e) {
			System.out.println("uploadCSVtoMongoDB err" + e);
		}
	}
}

실행 후 저장 확인

 

MongoDbDownload (읽기)

public class MongoDbDownload {
	
	public static void main(String[] args) {
		// GridFS로 저장된 MongoDB 자료 읽기
		String connString = "mongodb://localhost:27017";
		
		try (MongoClient mongoClient = MongoClients.create(connString)) {
			MongoDatabase database = mongoClient.getDatabase("katalkdb");
			
			GridFSBucket gridFSBucket = GridFSBuckets.create(database, "katalkfiles");
			
			MongoCursor<GridFSFile> cursor = gridFSBucket.find().iterator();
			
			while(cursor.hasNext()) {
				GridFSFile gridFSFile = cursor.next();
				
				ObjectId fileId = gridFSFile.getObjectId();
				
				ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
				
				try {
					gridFSBucket.downloadToStream(fileId, byteArrayOutputStream);
					
					String fileContent = new String(byteArrayOutputStream.toByteArray());
					
					//System.out.println("fileContent : " + fileId.toHexString() + ": " + fileContent);
					//fileContent : 677b43c868a3cc40025c971d: {"req": "너 좋아하는 차 종류 있어?", "res": "무슨 차? 자동차? 마시는 차?"}
					
					// 배열 형태인 경우
					if(fileContent.trim().startsWith("[")) {
						JSONArray jsonArray = new JSONArray(fileContent);
						
						for(int i=0; i < jsonArray.length(); i++) {
							JSONObject jsonObject = jsonArray.getJSONObject(i);
							
							String req = jsonObject.getString("req");
							String res = jsonObject.getString("res");
							System.out.println("req : " + req + ", res : " + res);
						}
					} else {
						// 단일 JSON 객체인 경우
						JSONObject jsonObject = new JSONObject(fileContent);
						
						String req = jsonObject.getString("req");
						String res = jsonObject.getString("res");
						System.out.println("req : " + req + ", res : " + res);
						
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			
		} catch (Exception e) {
			System.out.println("err : " + e);
		}
	}
}

실행 후 읽기 확인