Spring

MySQL을 사용한 Spring Boot R2DBC - 예외: 테이블을 찾을 수 없음

기록만이살길 2023. 1. 19. 12:12
반응형

MySQL을 사용한 Spring Boot R2DBC - 예외: 테이블을 찾을 수 없음 질문하다

1. 질문(문제점):

저는 String Boot 및 백엔드 개발(아마도 3일 이하)을 처음 접했고 다른 클라이언트에서 사용할 REST API 를 구축하고 싶습니다.

그래서 저는 이라는 엔드포인트가 있는 간단한 데모 앱으로 시작했습니다 /register. 존재하지 않는 경우 새 사용자를 생성하기 위해 JSON문자열을 username게시 합니다 .password

나는 함께 사용 JPA하고 HSQLDB있었고 메모리에 잘 작동했습니다. RxJava그런데 최근 안드로이드에 익숙해져서 사용하고 싶어서 R2DBCwith 로 바꿨습니다 MySQL.

MySQL서버가 포트에서 제대로 실행 중이고 3306앱이 PostMan을 사용하여 테스트되었습니다.localhost:8080

사용자 테이블을 쿼리하거나 엔터티를 삽입하려고 하면 문제가 발생하며 다음과 같습니다.

{
    "timestamp": "2020-03-22T11:54:43.466+0000",
    "status": 500,
    "error": "Internal Server Error",
    "message": "execute; bad SQL grammar [UPDATE user_entity SET username = $1, password = $2 WHERE user_entity.id = $3]; nested exception is io.r2dbc.spi.R2dbcBadGrammarException: [42102] [42S02] Table \"USER_ENTITY\" not found; SQL statement:\nUPDATE user_entity SET username = $1, password = $2 WHERE user_entity.id = $3 [42102-200]",
    "path": "/register"
}

다음 은 예외에 대한 전체 로그 파일 입니다.

나는 몇 시간 동안 해결책을 찾고 있었지만 어디에서도 찾을 수 없는 것 같아서 여기에서 찾을 수 있기를 바랍니다.

솔루션을 더 쉽게 찾을 수 있도록 프로젝트를 세분화해 보겠습니다.

1. 데이터베이스:

여기에 이미지 설명 입력

2. 애플리케이션.속성:

logging.level.org.springframework.data.r2dbc=DEBUG
spring.datasource.url=jdbc:mysql://localhost:3306/demodb
spring.datasource.username=root
spring.datasource.password=root

3. 데이터베이스 구성:

@Configuration
@EnableR2dbcRepositories
class DatabaseConfiguration : AbstractR2dbcConfiguration() {

    override fun connectionFactory(): ConnectionFactory
         = ConnectionFactories.get(
                builder().option(DRIVER, "mysql")
                    .option(HOST, "localhost")
                    .option(USER, "root")
                    .option(PASSWORD, "root") 
                    .option(DATABASE, "demodb")
                    .build()
        )


}

4. 등록 컨트롤러:

@RequestMapping("/register")
@RestController
class RegistrationController @Autowired constructor(private val userService: UserService) {

    @PostMapping
    fun login(@RequestBody registrationRequest: RegistrationRequest): Single<ResponseEntity<String>>
        = userService.userExists(registrationRequest.username)
            .flatMap { exists -> handleUserExistance(exists, registrationRequest) }
    
    private fun handleUserExistance(exists: Boolean, registrationRequest: RegistrationRequest): Single<ResponseEntity<String>> 
        = if (exists) Single.just(ResponseEntity("Username already exists. Please try an other one", HttpStatus.CONFLICT))
            else userService.insert(User(registrationRequest.username, registrationRequest.password)).map { user ->
                ResponseEntity("User was successfully created with the id: ${user.id}", HttpStatus.CREATED)
            }
    
}

5. 사용자 서비스:

@Service
class UserService @Autowired constructor(override val repository: IRxUserRepository) : RxSimpleService<User, UserEntity>(repository) {

    override val converter: EntityConverter<User, UserEntity> = UserEntity.Converter

    fun userExists(username: String): Single<Boolean>
        = repository.existsByUsername(username)

}

6. RxSimpleService:

abstract class RxSimpleService<T, E>(protected open val repository: RxJava2CrudRepository<E, Long>)  {

    protected abstract val converter: EntityConverter<T, E>

    open fun insert(model: T): Single<T>
        = repository.save(converter.fromModel(model))
            .map(converter::toModel)

    open fun get(id: Long): Maybe<T>
        = repository.findById(id)
            .map(converter::toModel)

    open fun getAll(): Single<ArrayList<T>>
        = repository.findAll()
            .toList()
            .map(converter::toModels)

    open fun delete(model: T): Completable
        = repository.delete(converter.fromModel(model))

}

7. RxUserRepository:

@Repository
interface IRxUserRepository : RxJava2CrudRepository<UserEntity, Long> {

    @Query("SELECT CASE WHEN EXISTS ( SELECT * FROM ${UserEntity.TABLE_NAME} WHERE username = :username) THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) END")
    fun existsByUsername(username: String): Single<Boolean>

}

8. 마지막으로 여기 내 UserEntity가 있습니다.

@Table(TABLE_NAME)
data class UserEntity(
        @Id
        val id: Long,
        val username: String,
        val password: String
)  {

        companion object {
            const val TABLE_NAME = "user_entity"
        }

        object Converter : EntityConverter<User, UserEntity> {

            override fun fromModel(model: User): UserEntity
                   = with(model) { UserEntity(id, username, password) }

            override fun toModel(entity: UserEntity): User
                   = with(entity) { User(id, username, password) }

        }
}

User사용자 이름 과 RegistrationRequest암호가 있는 단순한 개체입니다. 내가 무엇을 놓쳤습니까? 더 많은 코드가 필요하면 댓글을 남겨주세요.

2. 해결방안:

드디어 이 실수를 해결했습니다!

문제는 너무 간단했지만 초보자에게는 너무 교활했습니다.

  1. JDBC대신 내 URL에서 사용 하고 있었습니다.R2DBC
  2. 런타임 구현 을 사용하고 H2있었으므로 H2메모리 내 데이터베이스 가 필요했습니다.
  3. ConnectionFactory말은 그다지 정확하지 않았어

그래서 내가 한 일은 다음과 같습니다.

  1. 내 업데이트 build.gradle:
    • 추가됨 : implementation("io.r2dbc:r2dbc-pool")implementation("dev.miku:r2dbc-mysql:0.8.1.RELEASE")runtimeOnly("mysql:mysql-connector-java")
    • 제거됨:runtimeOnly("io.r2dbc:r2dbc-h2")

이제 다음과 같이 표시됩니다.

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    id("org.springframework.boot") version "2.2.5.RELEASE"
    id("io.spring.dependency-management") version "1.0.9.RELEASE"
    kotlin("jvm") version "1.3.61"
    kotlin("plugin.spring") version "1.3.61"
}

group = "com.tamimattafi.backend"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11

repositories {
    mavenCentral()
    maven(url = "https://repo.spring.io/milestone")
}

dependencies {
    //SPRING BOOT
    implementation("org.springframework.boot:spring-boot-starter-security")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot.experimental:spring-boot-starter-data-r2dbc")

    //KOTLIN
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")

    //RX JAVA
    implementation("io.reactivex.rxjava2:rxjava:2.2.0")
    implementation("io.reactivex:rxjava-reactive-streams:1.2.1")

    //MYSQL
    implementation("dev.miku:r2dbc-mysql:0.8.1.RELEASE")
    implementation("io.r2dbc:r2dbc-pool")
    runtimeOnly("mysql:mysql-connector-java")

    //TEST
    testImplementation("org.springframework.boot:spring-boot-starter-test") {
        exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
    }
    testImplementation("org.springframework.security:spring-security-test")
    testImplementation("io.projectreactor:reactor-test")
    testImplementation("org.springframework.boot.experimental:spring-boot-test-autoconfigure-r2dbc")
}

dependencyManagement {
    imports {
        mavenBom("org.springframework.boot.experimental:spring-boot-bom-r2dbc:0.1.0.M3")
    }
}

tasks.withType<Test> {
    useJUnitPlatform()
}

tasks.withType<KotlinCompile> {
    kotlinOptions {
        freeCompilerArgs = listOf("-Xjsr305=strict")
        jvmTarget = "1.8"
    }
}
  1. my application.properties를 다음과 같이 업데이트했습니다.

    spring.r2dbc.url=r2dbc:pool:mysql://127.0.0.1:3306/demodb
    spring.r2dbc.username=root
    spring.r2dbc.password=root
    
  2. my를 다음과 같이 업데이트 했습니다( 다른 곳에 있어야 하기 때문에 DatabaseConfigurationthe를 제거 했습니다).@EnableR2dbcRepositories

    @Configuration
    class DatabaseConfiguration : AbstractR2dbcConfiguration() {
    
    override fun connectionFactory(): ConnectionFactory
        = MySqlConnectionFactory.from(
            MySqlConnectionConfiguration.builder()
                    .host("127.0.0.1")
                    .username("root")
                    .port(3306)
                    .password("root")
                    .database("demodb")
                    .connectTimeout(Duration.ofSeconds(3))
                    .useServerPrepareStatement()
                    .build()
        )
    
    }
    
  3. Application클래스를 업데이트했습니다(여기에 어노테이션을 가져왔습니다).

    @SpringBootApplication
    @EnableR2dbcRepositories
    class DemoApplication
    
    fun main(args: Array<String>) {
        runApplication<DemoApplication>(*args)
    }
    

지금 작동합니다! 누군가 이 글이 도움이 되었으면 좋겠습니다. Happy Coding!

반응형