Kotlin SpringMVC - JpaRepository generating invalid update query - kotlin

I'm quite new to Spring MVC, and I'm having problems getting a simple entity update to work.
My data class looks like this...
#Entity
#Table(uniqueConstraints=[UniqueConstraint(columnNames=["name_search"])])
data class ArticleType(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
val name : String = "",
val order: Int? = null,
var name_search : String = ""
)
The repository looks like so...
interface ArticleTypeRepository : JpaRepository<ArticleType, Long> {
fun findFirstById(id: Long) : ArticleType?
fun findAllByOrderByOrderAsc(): List<ArticleType>
fun findByName(name: String): ArticleType?
}
I'm trying to update the name_search column like so...
val article_type:ArticleType? = articleTypeRepository.findFirstById(1234)
if (article_type !== null) {
article_type.name_search = "abc"
articleTypeRepository.save(article_type)
}
This results in the following error...
java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'order=99 where id=1234' at line 1
I'm assuming this means the binding isn't working correctly, and it's missing the "name_search" binding, or missing the quotes or something. I've turned on logging, and I can see the following...
org.hibernate.SQL : update article_type set name=?, name_search=?, order=? where id=?
Then it lists the binding parameters "o.h.type.descriptor.sql.BasicBinder", which all appear to be correct.
I'm not sure what's going wrong, or where I need to start to try to fix it.
This is a legacy system I've inherited, and I don't fully understand it. If there is some extra information I need to provide here, please let me know.

Related

JPA using #ElementCollection with #OrderColumn but it makes exception to 'duplicate key value violates unique constraint'

First of all, I'm n.b to spring and jpa. so, Sorry for the rudimentary question.
These days I tried to make server system to location points storing using springboot + jpa + docker + postgresql /kotlin
my idea is server get client call and store locations periodically
so, I using #ElementCollection for store location item with #Embeddable
but, I got exception from springTest code
Hibernate:
insert
into
pos_info_pos_list
(pos_info_id, pos_list_order, accuracy, event_time, geo_lati, geo_long)
values
(?, ?, ?, ?, ?, ?)
2022-11-12 22:07:34.963 WARN 25880 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 0, SQLState: 23505
2022-11-12 22:07:34.963 ERROR 25880 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : ERROR: duplicate key value violates unique constraint "pos_info_pos_list_pkey"
Detail: Key (pos_info_id, pos_list_order)=(1, 0) already exists.
I'll explain the table structure below
PosInfo(one), PosData(many)
oneToMany relation
I want to use ordercolumn for performance and want posList size limitation(MAX_POS_DATA_SIZE = 200)
#Entity
data class PosInfo(
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
var id: Long? = null
) {
#ElementCollection(fetch = FetchType.EAGER, targetClass = PosData::class)
#OrderColumn
val posList: MutableList<PosData> = mutableListOf()
fun addPosData(posData: PosDataDto) {
while (posList.size >= MAX_POS_DATA_SIZE) {
posList.removeFirst()
}
val newData = PosData(posData.geoLati, posData.geoLong, posData.eventTime, posData.accuracy)
posList.add(newData)
}
}
PosData table
#Embeddable
data class PosData(
#Column
val geoLati: String,
#Column
val geoLong: String,
#Column
val eventTime: Long,
#Column
val accuracy: Int,
)
SpringTestCode is
first of all, insert maxSize posData then add more one data again
#Test
fun addPathMax() {
val dummyPosData = PosDataDto("", "", System.currentTimeMillis(), 0)
val dummyPosData2 = PosDataDto("yyyy", "eeeee", System.currentTimeMillis(), 0)
val id = "KSH"
service.tryAddUser(id, "")
val userInfo = service.getUserInfo(id)
assertThat(userInfo).isNotNull
val posIndex = userInfo!!.posIndex
val posInfo = service.getPosInfo(posIndex)
assertThat(posInfo).isNotNull
for (i in 0 until MAX_POS_DATA_SIZE) {
posInfo!!.addPosData(dummyPosData)
}
service.updatePosInfo(posInfo!!)
println("Next Input Check KSH_TEST")
val posInfo2 = service.getPosInfo(posIndex)
posInfo2!!.addPosData(dummyPosData2)
service.updatePosInfo(posInfo2!!)
}
#Transactional
service.updatePosInfo <= it just call to crudRepository save method
but I got duplicate key again and again
Q1. Shouldn't the 'pos_list_order' be 'existing last +1' since the first data of the previous data was erased and the new data was inserted? why '0'?
// Key (pos_info_id, pos_list_order)=(1, 0) already exists.
Q2. Is this structure not good for updating and storing location data periodically?(using ElementCollection, should I use OneToMany?)
=To be honest, I've tried "one To Many" before. By the way, I gave up because I was tired of fixing strange build errors. I came back with "Element Collection," which I thought was easy
Thank you in advance for all the helpful comments
===========================
= I already tried before below
OneToMany with mapped, but it made many error and when I tried insert more value, it was made all delete row and re-install all and + newer again
ElementCollection looks simple, but it was made duplicated exception again and again
I already checked using below
#CollectionTable(
name = "pos_data",
joinColumns = [JoinColumn(name = "pos_info_id")]
)
JpaRepository.save then flush doesn't work
but same result, I don't know why.. really sad
I got a solution
Now this problem was caused by my poor understanding of 'Transactional'
it's fixed with below annotation
#Transactional(propagation = Propagation.REQUIRES_NEW)
#Transactional(propagation = Propagation.REQUIRES_NEW)
#Rollback(false)
#Test
fun addPathMax() {
val dummyPosData = PosDataDto("", "", System.currentTimeMillis(), 0)
val dummyPosData2 = PosDataDto("yyyy", "eeeee", System.currentTimeMillis(), 0)
val id = "KSH"
service.tryAddUser(id, "")
val userInfo = service.getUserInfo(id)
assertThat(userInfo).isNotNull
val posIndex = userInfo!!.posIndex
val posInfo = service.getPosInfo(posIndex)
assertThat(posInfo).isNotNull
for (i in 0 until Constants.MAX_POS_DATA_SIZE) {
posInfo!!.addPosData(dummyPosData)
}
service.updatePosInfo(posInfo!!)
println("Next Input Check KSH_TEST")
val posInfo2 = service.getPosInfo(posIndex)
posInfo2!!.addPosData(dummyPosData2)
service.updatePosInfo(posInfo2!!)
}
I thought service already including 'Transactional' annotation
so it can be made query persist context to database
but it was not

kotlin exposed failed (insert is successful, but select is failure)

thanks reading this question.
I created simple kotlin project and I want to learn kotlin exposed.
I use H2 database.
I wrote code like below.
package learn.exposed.tables
import org.jetbrains.exposed.sql.Table
object AuthorTable : Table("author") {
val name = varchar("name", 30)
}
fun main() {
// this url based on http://www.h2database.com/html/features.html#execute_sql_on_connection
val url = "jdbc:h2:mem:test;INIT=runscript from 'classpath:/create.sql'\\;runscript from 'classpath:/init.sql'"
Database.connect(url, driver = "org.h2.Driver", user = "root", password = "")
transaction {
AuthorTable.insert {
it[name] = "hoge"
}
println("insert done.") // this message can show on console. I think Insert is successfull.
}
transaction {
AuthorTable.selectAll().firstOrNull()
}
}
and sql files below.
create table author (name varchar(30));
insert into author values ('author1');
When execute main(), console showing insert done.. in short, I think insert is doing well, but when execute AuthorTable.selectAll().firstOrNull(), happend Exception like below,
Exception in thread "main" org.jetbrains.exposed.exceptions.ExposedSQLException: org.h2.jdbc.JdbcSQLNonTransientException: 一般エラー: "java.lang.NullPointerException"
General error: "java.lang.NullPointerException" [50000-200]
SQL: [Failed on expanding args for SELECT: org.jetbrains.exposed.sql.Query#27406a17]
at org.jetbrains.exposed.sql.statements.Statement.executeIn$exposed_core(Statement.kt:62)
at org.jetbrains.exposed.sql.Transaction.exec(Transaction.kt:135)
at org.jetbrains.exposed.sql.Transaction.exec(Transaction.kt:121)
at org.jetbrains.exposed.sql.AbstractQuery.iterator(AbstractQuery.kt:65)
at kotlin.collections.CollectionsKt___CollectionsKt.firstOrNull(_Collections.kt:267)
at learn.exposed.MainKt$main$2.invoke(Main.kt:22)
at learn.exposed.MainKt$main$2.invoke(Main.kt)
at org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManagerKt.inTopLevelTransaction$run(ThreadLocalTransactionManager.kt:179)
at org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManagerKt.access$inTopLevelTransaction$run(ThreadLocalTransactionManager.kt:1)
at org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManagerKt$inTopLevelTransaction$1.invoke(ThreadLocalTransactionManager.kt:205)
at org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManagerKt.keepAndRestoreTransactionRefAfterRun(ThreadLocalTransactionManager.kt:213)
at org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManagerKt.inTopLevelTransaction(ThreadLocalTransactionManager.kt:204)
at org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManagerKt$transaction$1.invoke(ThreadLocalTransactionManager.kt:156)
at org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManagerKt.keepAndRestoreTransactionRefAfterRun(ThreadLocalTransactionManager.kt:213)
at org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManagerKt.transaction(ThreadLocalTransactionManager.kt:126)
at org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManagerKt.transaction(ThreadLocalTransactionManager.kt:123)
at org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManagerKt.transaction$default(ThreadLocalTransactionManager.kt:122)
at learn.exposed.MainKt.main(Main.kt:21)
at learn.exposed.MainKt.main(Main.kt)
can I solve this? do you know something how to solve this?
thanks.
Seems like you need at least one Primary Key(PK) or Constraint because of H2 bug.
https://github.com/h2database/h2database/issues/2191
https://github.com/JetBrains/Exposed/issues/801

InvalidDataAccessApiUsageException - Parameter value did not match expected type

I need to get all users with a specific role from a database. For this I need to use JPA.
All roles are stored in a special set:
UserAccount.kt
#ManyToMany(cascade = [(CascadeType.MERGE)])
#JoinTable(
name = "user_authorities",
joinColumns = [JoinColumn(name = "user_id", referencedColumnName = "id")],
inverseJoinColumns = [JoinColumn(name = "authority_id", referencedColumnName = "id")]
)
var authoritySet: MutableSet<Authority> = hashSetOf()
I want to do it like this:
UserAccountService.kt
override fun getAllUsersByAuthorityName(name: String): List<UserAccountDto> {
return userAccountRepository.findUsersByAuthoritySet(mutableSetOf(authorityService.findAuthorityByAuthorityName(name))).map { it.toDto() }
}
UserAccountRepository.kt
#Query("select u from UserAccount u where u.authoritySet = ?1")
fun findUserAccountByAuthoritySet(authoritySet: MutableSet<Authority>): List<UserAccount>
But when calling these methods in tests, it gives an error:
Parameter value [Authority(id=1, authority='ROLE_ADMIN')] did not match expected type [java.util.Set (n/a)]; nested exception is java.lang.IllegalArgumentException: Parameter value [Authority(id=1, authority='ROLE_ADMIN')] did not match expected type [java.util.Set (n/a)]
Tell me how you can properly organize the search for users?
Can you show sites with examples of such requests?
First, I started using the JPA functionality and added the "In" particle to the method name in the repository.
In the second, I removed the #Query annotation
Third, I revisited my tests, and it turned out that I added roles after the saved users, and therefore my changes were not committed to the database. I fixed it.
Problem solved

Exposed Kotlin. Problem with cyrillic encoding

I tryed to fix a problem with encodings. So, I sent from 'Postman', from web browser request to server, where I search data in database by keys in request. Request can be like this:
http://localhost:8080/books.getBooksByGenre/Документальное/0/10
(in browser).
Server receive string, like
http://localhost:8080/books.getBooksByGenre/%D0%94%D0%BE%D0%BA%D1%83%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5/0/10
then, takes params from url:
genreName: 'Документальное'
start: 0
count: 10.
Then, this data sends to dao:
override fun findGenreByName(genreName: String): DatabaseGenre {
return transaction(db) { getGenreByName(genreName) }
}
private fun getGenreByName(genreName: String): DatabaseGenre {
return try {
val foundGenre = GenreEntity.find { Genres.genre eq genreName }.single()
DatabaseGenre(foundGenre.id.value, foundGenre.genre, foundGenre.link)
} catch (e: Exception) {
throw NothingFoundInDatabaseException("no one genre found by '$genreName'")
} catch (e: NoSuchElementException) {
val m = "Duplicates of genre with name '$genreName'"
throw DuplicatedDataInDatabaseException(m)
}
}
In log I see, that sql-query for finding genres is correct, but I receive an exception:
java.util.NoSuchElementException: Collection is empty.
The sql-query, as I said, is correct:
SELECT genres.id, genres.genre, genres.link FROM genres WHERE genres.genre = 'Документальное'
Structure of genres table:
genres
id: int(10)
genre: varchar(100)
link: varchar(100
I tryied, to select all genres, and this query executed almost correctly. So, I decided, to check this query with english word, and this query correctly executed:
SELECT genres.id, genres.genre, genres.link FROM genres WHERE genres.genre = 'simpleGenre'
I have not exceptions with this query.
So, what I've done wrong and how to fix problem with collations?
UPD:
As I said at github (issue), I've tryied this query it mysql cli and I receive correct answer.
Also, I've tryed to decode url params (with java URLDecoder class). It doesn't helps too.
Thanks, #madhead.
I tryied an advance of #madhead, and it works. So, from this time my DB connection URL looks like this:
val connect = Database.connect(
url = "jdbc:mysql://localhost:3306/my_database_name?characterEncoding=utf8&useUnicode=true",
driver = "com.mysql.jdbc.Driver",
user = user_name,
password = password
)

Kotlin data classes JSON Deserialization

I am trying convert ApiEmployee to Employee and have written a test around it. I am confused about nulls in Kotlin as I am new to it.
ApiEmployee would be used for JSON conversion so it can have missing name field or or empty or can come as null. In that case, I don't want to add into list and safely ignore it.
I am getting Method threw 'kotlin.KotlinNullPointerException at exception. at apiEmployee.name!!.isNotBlank()
ApiEmployee
data class ApiEmployee(val image: String? = "image",
val name: String? = "name test",
val description: String? = "",
val id: String? = "")
Employee
data class Employee(val imagePath: String, val id: String)
EmployeeConverter(converts ApiEmployee to Employee)
fun apply(apiEmployees: List<ApiEmployee>): List<Employee> {
val employees = mutableListOf<Employee>()
for (apiEmployee in apiEmployees) {
if (apiEmployee.name!!.isNotBlank()){
employees.add(Employee(apiEmployee.image!!, apiEmployee.id!!)
}
}
}
EmployeeConverterTest
#Test
fun `should not add employee without name into employee list`() {
val invalidApiEmployee = ApiEmployee("image", null, "description", "id")
val convertedEmployees : List< Employee > = employeeConverter.apply(listOf( invalidApiEmployee))
assertThat(convertedEmployees.size).isEqualTo(0)
}
What you want to do is check if the name is null first and then if it is empty.
val employeeNameIsNotEmpty = apiEmployee.name?.isNotBlank() ?: false
if (employeeNameIsNotEmpty) {
// do stuff
}
The apiEmployee.name?.isNotBlank() will run and return a value only if name is not null. If name is null then the statment on the right side of ?: will return its value, which in this case should be false.
In this case however Kotlin has already put this particular example into an extension function
.isNullOrBlank()
So you could change it to:
if (!apiEmployee.name.isNullOrBlank()) {
// do stuff
}
As a side note you really don't whant to do this Employee(apiEmployee.image!!, apiEmployee.id!!).
Because image and id could still be null and crash your code with the same error.
Either pass the value for name.
ApiEmployee("image", "name", "description", "id")
(or)
Change the if condition as mentioned below (with ? operator):-
if (apiEmployee.name?.isNotBlank()){
?. performs a safe call (calls a method or accesses a property if the
receiver is non-null)
!! asserts that an expression is
non-null
The code asserts that name is not null and checking for not blank.
Probably, I think you are trying to do null and not blank check. You can use ? operator (safe call) for that. This means isNotBlank() gets executed only if the name is not null.