この記事について
Kotlin学習のため、以下の「Kotlin サーバーサイドプログラミング実践開発」を読んでいました。書籍の動作確認環境はcorretto-11でしたが、coretto-17を使用したところ複数詰まる箇所があったため共有します。
MyBatisGeneratorのバージョンが違う(5章、6章)
書籍だと以下のバージョンが指定されていますが、これでは動作しませんでした。
id("com.arenagod.gradle.MybatisGenerator") version "2.4"
以下のバージョンを指定すると、動作しました。
id("com.thinkimi.gradle.MybatisGenerator") version "2.4"
BookWithRentalMapperExtentions.ktがコンパイルエラーになる(6章-4)
書籍では、下記のような記述でBookWithRentalMapperを使用するExtentionsが書かれています。import含めて書籍の内容と揃えてもコンパイルエラーとなってしまいました。
private val columnList = listOf(...)
fun BookWithRentalMapper.select(): List<BookWithRentalRecord> {
val selectStatement = select(columnList).from(Book, "b") {
leftJoin(Rental, "r") {
on(Book.id, equalTo(Rental.bookId)
}
}
return selectMany(selectStatement)
}
そこで、以下のサイトの記述を真似て、直接SQLを書いたところ動作しました。
@Mapper
interface BookWithRentalMapper {
@Select(
"SELECT b.id, b.title, b.author, b.release_date, r.user_id, r.rental_datetime, r.return_deadline FROM book b left join rental r on b.id = r.book_id"
)
@Results(
id = "BookWithRentalResult",
value = [
Result(column = "id", property = "id", id = true),
Result(column = "title", property = "title"),
Result(column = "author", property = "author"),
Result(column = "release_date", property = "releaseDate"),
Result(column = "user_id", property = "userId"),
Result(column = "rental_datetime", property = "rentalDatetime"),
Result(column = "return_deadline", property = "returnDeadline")
]
)
fun selectMany(): List<BookWithRentalRecord>
}
MyBatis+Kotlinで調べても公式ドキュメント含めて情報が少ない気がします。。。
あまりこの組み合わせはメジャーじゃないのでしょうか?
SecurityConfigがコンパイルエラーになる(7章-1)
バージョンの都合か書籍のSecurityConfigの内容をそのまま使用することが出来なかったです。そこで以下のように書き換えました。
'authorizeRequests()' はバージョン 6.1 以降は非推奨かつ廃止予定になっています
のように非推奨の書き方だらけですが、SpringSecurityの詳細を学ぶことが主目的では無いため良いとしました。
package com.book.manager.bookmanager.presentation.config
import com.book.manager.bookmanager.application.service.AuthenticationService
import com.book.manager.bookmanager.application.service.BookManagerUserDetailsService
import com.book.manager.bookmanager.domain.enum.RoleType
import com.book.manager.bookmanager.presentation.handler.BookManagerAccessDeniedHandler
import com.book.manager.bookmanager.presentation.handler.BookManagerAuthenticationEntryPoint
import com.book.manager.bookmanager.presentation.handler.BookManagerAuthenticationFailurehandler
import com.book.manager.bookmanager.presentation.handler.BookManagerAuthenticationSuccessHandler
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.authentication.dao.DaoAuthenticationProvider
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.web.SecurityFilterChain
import org.springframework.web.cors.CorsConfiguration
import org.springframework.web.cors.CorsConfigurationSource
import org.springframework.web.cors.UrlBasedCorsConfigurationSource
@Configuration
@EnableWebSecurity
class SecurityConfig(
private val authenticationService: AuthenticationService
) {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http.authorizeRequests()
.requestMatchers("/login").permitAll()
.requestMatchers("/admin/**").hasAuthority(RoleType.ADMIN.toString())
.anyRequest().authenticated()
.and()
.csrf().disable()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/login")
.usernameParameter("email")
.passwordParameter("pass")
.successHandler(BookManagerAuthenticationSuccessHandler())
.failureHandler(BookManagerAuthenticationFailurehandler())
.and()
.exceptionHandling()
.authenticationEntryPoint(BookManagerAuthenticationEntryPoint())
.accessDeniedHandler(BookManagerAccessDeniedHandler())
.and()
.cors()
.configurationSource(corsConfigurationSource())
return http.build()
}
@Bean
fun authenticationProvider(): DaoAuthenticationProvider {
val provider = DaoAuthenticationProvider()
provider.setUserDetailsService(BookManagerUserDetailsService(authenticationService))
provider.setPasswordEncoder(BCryptPasswordEncoder())
return provider
}
private fun corsConfigurationSource(): CorsConfigurationSource {
val corsConfiguration = CorsConfiguration()
corsConfiguration.addAllowedMethod(CorsConfiguration.ALL)
corsConfiguration.addAllowedHeader(CorsConfiguration.ALL)
corsConfiguration.addAllowedOrigin(CorsConfiguration.ALL)
corsConfiguration.allowCredentials = true
val corsConfigurationSource = UrlBasedCorsConfigurationSource()
corsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration)
return corsConfigurationSource
}
}
フロントエンドが立ち上げられなかった(7章)
おそらく私の環境起因ですが、フロントエンドサーバーを立ち上げることが出来ず、全てAPIを直接叩きました。
URLがサンプルコードから見つけられず、手打ちしていました。大変だったので主要なURLを置いておきま
curl -i -c cookie.txt -H 'Content-Type:application/x-www-form-urlencoded' -X POST -d 'email=user@example.com' -d 'pass=password' http://localhost:8080/login
curl -i -b cookie.txt -H 'Content-Type:application/json' -X POST -d '{"id": 101, "title": "New Book", "author": "yamayamaKo", "release_date": "2020-12-12"}' http://localhost:8080/admin/book/register
curl -i -b cookie.txt http://localhost:8080/book/list
curl -b cookie.txt -H 'Content-Type:application/json' -X POST -d '{"book_id": 100}' http://localhost:8080/rental/start
curl -b cookie.txt -X DELETE http://localhost:8080/rental/end/100
終わりに
サーバーサイドKotlinの書籍の数が少ない気がしますが、2025年2月時点だと何を読むのが良いのでしょうか?