Multiple optional #RequestParam not fetching Data in spring boot - spring

I want to paginate response Data in Spring boot project. According to business logic, I added multiple RequestParam. All is ok, if I pass value for all request Param i.e Gender and Country. But if I don't give value either one or both, I get 500 status code, although I make gender and Country requestParam as optional. It means,
if I hit
http://localhost:8080/api/v1/users?page=1&country=Russia&gender=M,
I get response with pagination.
But if I hit
http://localhost:8080/api/v1/users?page=1&gender=M
http://localhost:8080/api/v1/users?page=1&country=Russia
http://localhost:8080/api/v1/users?page=1.
I get exception
Here is my code.
UserRepository.kt
#Repository
interface UserRepository: JpaRepository<User, Long> {
#Query(
nativeQuery = true,
value = "SELECT * FROM user_info WHERE gender =:gender AND country =:country"
)
fun getUsers(gender: String?, country: String?, pageable: Pageable): Page<User>
}
UserServiceImpl.kt
#Service
class UserServiceImpl(
#Autowired private val userRepository: UserRepository
): UserService {
override fun getUsers(gender: String?, country: String?, pageable: Pageable): Page<User> {
return userRepository.getUsers(gender, country, pageable)
}
}
UserController.kt
#RestController
#RequestMapping(
path = [
"/api/v1/"
]
)
class UserController(
#Autowired private val userService: UserService
) {
#GetMapping("users")
fun getUsers(
#RequestParam(required = true) page: Int,
#RequestParam(required = false) gender: String?,
#RequestParam(required = false) country: String?
): Page<User> {
return userService.getUsers(gender, country, PageRequest.of(page, 10))
}
}
response
{
"status": "500 INTERNAL_SERVER_ERROR",
"message": "Internal server error occurs",
"error": "could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet"
}

It is better to use jdbcTemplate here like this:
String query = "SELECT * FROM user_info WHERE gender = :gender AND country = :country";
Map<String, Object> params = new HashMap<>();
params.put("gender", gender);
params.put("country", country);
Map<String, Long> results = new HashMap<>();
// Execute Your Query Here like: users = jdbcTemplate.query(query, params, ...
for thos params you can check if for example gender not null append gender = :gender to main query.

yes the error you're getting is correct because the values for your query doesn't exist i would suggest you make the following changes ...
#Repository
interface UserRepository: JpaRepository<User, Long> {
#Query(
nativeQuery = true,
value = "SELECT * FROM user_info WHERE country =:country"
)
fun getUsersWithoutGender( country: String?, pageable: Pageable): Page<User>
#Query(
nativeQuery = true,
value = "SELECT * FROM user_info WHERE gender =:gender AND country =:country"
)
fun getUsersWithoutCountry(gender: String?, pageable: Pageable): Page<User>
#Query(
nativeQuery = true,
value = "SELECT * FROM user_info WHERE gender =:gender AND country =:country"
)
fun getUsers(gender: String?, country: String?, pageable: Pageable): Page<User>
#Query(
nativeQuery = true,
value = "SELECT * FROM user_info"
)
fun getallUsers(): Page<User>
}
and in your controller
RestController
#RequestMapping(
path = [
"/api/v1/"
]
)
class UserController(
#Autowired private val userService: UserService
) {
#GetMapping("users")
fun getUsers(
#RequestParam(required = true) page: Int,
#RequestParam(required = false) gender: String?,
#RequestParam(required = false) country: String?
): Page<User> {
if(country == null && gender =! null){
return userService.getUsersWithoutCountry(gender,PageRequest.of(page, 10))
} else if (gender== null && country =! null){
return userService.getUsersWithoutGender(country,PageRequest.of(page, 10))
}else if (gender && country == null){
return userService.getAllUsers()
}else {
return userService.getUsers(gender, country, PageRequest.of(page, 10))
}
}
}
this way all your queries will run as they don't have a null value.

Finally, I solved the problem by JpaSpecificationExecutor which helps to create dynamic query. The code is given bellow.
UserSpecification.kt
import com.example.demo.entity.User
import org.springframework.data.jpa.domain.Specification
import javax.persistence.criteria.CriteriaBuilder
import javax.persistence.criteria.CriteriaQuery
import javax.persistence.criteria.Root
import java.text.MessageFormat.format
object UserSpecification {
fun countryContains(country: String?): Specification<User>? {
return country?.let {
Specification { root: Root<User>, _: CriteriaQuery<*>, criteriaBuilder: CriteriaBuilder ->
criteriaBuilder.like(root.get("country"), format("%{0}%", country))
}
}
}
fun genderContains(gender: String?): Specification<User>? {
return gender?.let {
Specification { root: Root<User>, _: CriteriaQuery<*>, criteriaBuilder: CriteriaBuilder ->
criteriaBuilder.equal(root.get<String>("gender"), gender)
}
}
}
}
UserRepository.kt
#Repository
interface UserRepository: JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
}
UserServiceImpl.kt
#Service
class UserServiceImpl(
#Autowired private val userRepository: UserRepository
): UserService {
override fun getUsers(gender: String?, country: String?, pageable: Pageable): Page<User> {
return userRepository.findAll(
where(
countryContains(country)
).and(
genderContains(gender)
), pageable
)
}
}

Related

Repository not executing methods in test

I am trying to simply test a repository. Here is my test code:
#ContextConfiguration(classes = [
DatabaseTestConfiguration::class
])
#TestPropertySource(
properties = [
"spring.jpa.hibernate.ddl-auto=create-drop",
"spring.flyway.enabled=false"
]
)
#AutoConfigureTestDatabase
#AutoConfigureDataJpa
#AutoConfigureTestEntityManager
internal open class UserTest {
#Autowired
private lateinit var entityManager: TestEntityManager
#Autowired
private lateinit var userRepository: UserRepository
#Test
#Transactional
open fun whenSuccessfullyFoundAnyDuplicatedUserThenReturnTrue() {
val user = User()
user.name = NAME
user.surname = SURNAME
user.gender = GENDER
entityManager.persistAndFlush(user)
assertEquals(true, userRepository.anyDuplicate(
name = user.name,
surname = user.surname,
gender = user.gender,
))
}
companion object {
const val NAME = "John"
const val SURNAME = "Snow"
const val GENDER = "male"
}
Here is my DatabaseTestConfiguration:
#TestConfiguration
#EntityScan(value = [
"com.username.db.entities"
])
#EnableJpaRepositories(
"com.username.db.repositories"
)
class DatabaseTestConfiguration {
}
This is my repository:
#Repository
interface UserRepository: JpaRepository<User,Long> {
#Query(
"""
SELECT CASE
WHEN COUNT(user.id) > 0 THEN TRUE
ELSE FALSE END
FROM User user
WHERE user.name = :name AND user.surname = :surname AND user.gender = :gender
)
"""
)
fun anyDuplicate(
#Param("name") name: String?,
#Param("surname") surname: String?,
#Param("gender") gender: String?
): Boolean
}
This is my entity:
#Entity
#Table(name = "user")
class User {
#Id
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "cdr_generator"
)
#SequenceGenerator(
name = "cdr_generator",
sequenceName = "cdr_seq",
allocationSize = 1
)
#Column(name = "id", nullable = false)
var id: Long? = null
#Column(name = "name", nullable = false)
var name: String? = null
#Column(name = "surname", nullable = false)
var surname: String? = null
#Column(name = "gender", nullable = false)
var gender: String? = null
}
Now when i am testing. this will always fail my test. When i debug it, i get that in my test it never enters userRepository.anyDuplicate() function. I assume that the problem is with configuring test. Can anybody help?

Spring JPA Expecting Member Declaration for Derived Query

I'm trying to add a derived query to my Spring application to return users from my SQL database with a matching email, but I've been running into the following error (sorry for the external link, this is my 1st Stack Overflow question):
Error Image Here
Here's my code for the files that I think could conceivably matter for this:
repository.kt
package com.companyname.productname.repository
import com.companyname.productname.entity.User
import org.springframework.stereotype.Repository
import org.springframework.data.jpa.repository.JpaRepository
import java.util.List
#Repository
interface UserRepository: JpaRepository<User, Long> {
List<User> findByEmail(String email);
}
service.kt
package com.companyname.productname.service
import com.companyname.productname.entity.User
import com.companyname.productname.repository.UserRepository
import org.springframework.stereotype.Service
import java.util.Optional;
#Service
class UserService(
val userRepository: UserRepository
) {
fun findAllUsers(): List<User> {
return userRepository.findAll()
}
fun findOneUser(id: Long): Optional<User> {
return userRepository.findById(id)
}
fun findByEmail(email: String): Optional<User> {
return userRepository.findByEmail(email)
}
fun createUser(user: User) {
userRepository.save(user)
}
fun updateUser(existingID: Long, info: User) {
val userCurrent = findOneUser(existingID).get()
val userUpdate = User()
userUpdate.id = existingID
userUpdate.firstName = if(info.firstName != "") info.firstName else userCurrent.firstName
userUpdate.lastName = if(info.lastName != "") info.lastName else userCurrent.lastName
userUpdate.email = if(info.email != "") info.email else userCurrent.email
userUpdate.admin = info.admin
userUpdate.enabled = info.enabled
userRepository.save(userUpdate)
}
fun deleteUser(id: Long) {
userRepository.deleteById(id)
}
}
entity.kt
package com.companyname.productname.entity
import javax.persistence.*
#Entity
#Table(name = "users")
class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0L
#Column(name = "first_name")
var firstName: String = ""
#Column(name = "last_name")
var lastName: String = ""
#Column(name = "email")
var email: String = ""
#Column(name = "security_role")
var admin: Boolean = false
#Column(name = "enabled")
var enabled: Boolean = true
}
controller.kt
package com.companyname.productname.controller
import com.companyname.productname.entity.User
import com.companyname.productname.service.UserService
import org.springframework.web.bind.annotation.*
import java.util.Optional;
#RestController
class DemoController {
#GetMapping("/hello-world")
fun helloWorld(): String {
return "Hello World!"
}
}
#RestController
class UserController(
val userService: UserService
) {
#GetMapping("/users")
fun findAllUsers(): List<User> {
return userService.findAllUsers()
}
#GetMapping("/users/{id}")
fun findOneUser(#PathVariable id: Long): Optional<User> {
return userService.findOneUser(id)
}
#GetMapping("/users/getEmail/{email}")
fun findByEmail(#PathVariable email: String): Optional<User> {
return userService.findByEmail(email)
}
#PostMapping("/users")
fun createUser(#RequestBody newUser: User) {
userService.createUser(newUser)
}
#PutMapping("/users/{id}")
fun updateUser(#PathVariable id: Long, #RequestBody newInfo: User) {
userService.updateUser(id, newInfo)
}
#DeleteMapping("/users/{id}")
fun deleteUser(#PathVariable id: Long) {
userService.deleteUser(id)
}
}
My apologies if this is something obvious, I'm new to Spring and all the documentation I've seen has led me to believe this should be working. Other requests I've implemented so far other than findByEmail have worked so far. Thanks in advance for any help!
The syntax of the findByEmail method in the UserRepository is not valid Kotlin, it's Java. The signature should look like this:
fun findByEmail(email: String): List<User>
By the way, there is yet another problem: you are trying to return List<User> from UserService.findByEmail while the method return type is Optional<User>.

Unable to insert entity using room

I am new to android Room and trying to insert some one-to-many relationships into my database. But I am facing some issues that I have not managed to fix sofar.
Here is my data constellation:
Entity:
#Entity(tableName = "artist")
data class Artist(
#PrimaryKey(autoGenerate = true) val id: Long = 0,
val name: String,
) {
}
#Entity(
tableName = "song",
foreignKeys = [ForeignKey(
entity = Artist::class,
parentColumns = ["id"],
childColumns = ["artistId"],
onDelete = ForeignKey.CASCADE
)]
)
data class Song(
#PrimaryKey(autoGenerate = true) val id: Long = 0,
val artistId: Long,
val title: String?
) {
}
data class ArtistWithSongs(
#Embedded val artist: Artist,
#Relation(parentColumn = "id", entityColumn = "artistId", ) val songs: List<Meal>
) {
}
Repository:
#Singleton
class AppRepository #Inject constructor(
private val artistDao: ArtistDao
) {
suspend fun insert(artist: Artist) {
artistDao.insert(artist)
}
}
Dao:
#Dao
interface ArtistDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(artist: Artist)
}
ViewModel:
#HiltViewModel
class ArtistViewModel #Inject constructor(
private val repository: AppRepository,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
fun insert(artist: Artist) = viewModelScope.launch {
repository.insert(artist)
}
}
Then in my activity I call:
private val viewModel: ArtistViewModel by viewModels()
...
val artist = Artist("Bob")
viewModel.insert(artist)
But insert artist does not work. The database is still empty.
Thanks
I was using viewModel.artist.value to check if any artist was present. As the method returning artist has LiveData as return type, I was getting null.
Observing the artist instead proved that my insert method is working just right.

Spring boot "Operation is not supported for read-only collection" when updating a record in database

I am fairly new to Spring Boot and unable to find out why my CRUD repository seems to keep throwing an exception saying "Operation is not supported for read-only collection" when sending a request to a Put endpoint. All other repositories seem to work just fine. Here's my code:
User.kt
package com.karbal.tutortek.entities
import com.karbal.tutortek.dto.userDTO.UserPostDTO
import java.sql.Date
import javax.persistence.*
#Entity
#Table(name = "users")
data class User(
#Id
#Column(name = "id", nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_generator")
#SequenceGenerator(name = "user_generator", sequenceName = "user_seq", allocationSize = 1)
var id: Long? = null,
#Column(name = "firstName", nullable = false)
var firstName: String = "",
#Column(name = "lastName", nullable = false)
var lastName: String = "",
#Column(name = "birthDate", nullable = false)
var birthDate: Date = Date(System.currentTimeMillis()),
#Column(name = "rating", nullable = false)
var rating: Float = 0.0F,
#OneToMany(mappedBy = "user")
var payments: List<Payment> = listOf(),
#OneToMany(mappedBy = "user")
var topics: List<Topic> = listOf()
){
constructor(userPostDTO: UserPostDTO) : this(
null,
userPostDTO.firstName,
userPostDTO.lastName,
userPostDTO.birthDate,
userPostDTO.rating
)
fun copy(user: User){
firstName = user.firstName
lastName = user.lastName
birthDate = user.birthDate
rating = user.rating
payments = user.payments
topics = user.topics
}
}
UserController.kt
package com.karbal.tutortek.controllers
import com.karbal.tutortek.dto.userDTO.UserGetDTO
import com.karbal.tutortek.dto.userDTO.UserPostDTO
import com.karbal.tutortek.entities.User
import com.karbal.tutortek.services.UserService
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.*
import org.springframework.web.server.ResponseStatusException
import java.util.*
#RestController
class UserController(val userService: UserService) {
#PostMapping("/users/add")
fun addUser(#RequestBody userDTO: UserPostDTO): UserGetDTO {
val user = User(userDTO)
return UserGetDTO(userService.saveUser(user))
}
#DeleteMapping("/users/{id}")
fun deleteUser(#PathVariable id: Long){
val user = userService.getUser(id)
if(user.isEmpty) throw ResponseStatusException(HttpStatus.NOT_FOUND, "User not found")
userService.deleteUser(id)
}
#GetMapping("/users/all")
fun getAllUsers() = userService.getAllUsers().map { u -> UserGetDTO(u) }
#GetMapping("/users/{id}")
fun getUser(#PathVariable id: Long): UserGetDTO {
val user = userService.getUser(id)
if(user.isEmpty) throw ResponseStatusException(HttpStatus.NOT_FOUND, "User not found")
return UserGetDTO(user.get())
}
#PutMapping("/users/{id}")
fun updateUser(#PathVariable id: Long, #RequestBody userDTO: UserPostDTO){
val user = User(userDTO)
val userInDatabase = userService.getUser(id)
if(userInDatabase.isEmpty) throw ResponseStatusException(HttpStatus.NOT_FOUND, "User not found")
val extractedUser = userInDatabase.get()
extractedUser.copy(user)
userService.saveUser(extractedUser)
}
}
UserService.kt
package com.karbal.tutortek.services
import com.karbal.tutortek.entities.User
import org.springframework.stereotype.Service
import com.karbal.tutortek.repositories.UserRepository
#Service
class UserService(val database: UserRepository) {
fun getAllUsers(): List<User> = database.getAllUsers()
fun saveUser(user: User) = database.save(user)
fun deleteUser(id: Long) = database.deleteById(id)
fun getUser(id: Long) = database.findById(id)
}
UserRepository.kt
package com.karbal.tutortek.repositories
import com.karbal.tutortek.entities.User
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.CrudRepository
import org.springframework.stereotype.Repository
#Repository
interface UserRepository : CrudRepository<User, Long> {
#Query("SELECT * FROM users", nativeQuery = true)
fun getAllUsers(): List<User>
}
UserPostDTO.kt
package com.karbal.tutortek.dto.userDTO
import java.sql.Date
data class UserPostDTO(
var firstName: String,
var lastName: String,
var rating: Float,
var birthDate: Date
)
The JSON that I send:
{
"firstName": "Thomas",
"lastName": "Thompson",
"rating": 4.7,
"birthDate": "2000-02-03"
}
Post works fine. Put works fine on other entities in my code. But here it always responds with 500 and a message "Operation is not supported for read-only collection". Any ideas why this could be happening?
Just solved this after a couple of hours. Changed lists in User class to mutable lists and now it works fine.

Parameter specified as non-null is null:Can't Update individual Entity field in Spring Boot jpa. Caused Parameter specified as non-null is null

Recently, I am learning Spring boot with Postgrasql as Database. All of my POST, GET, DELETE methods work perfectly without PUT method. What I want is to update individual field of entity. But if I don't update all field, it says 'Parameter specified as non-null is null'
My code is
User.kt
import java.time.LocalDate
import javax.persistence.*
import javax.persistence.GenerationType.SEQUENCE
#Entity
#Table(name = "user_info")
data class User(
#Id
#SequenceGenerator(
name = "user_seq",
sequenceName = "user_seq",
allocationSize = 1
)
#GeneratedValue(
strategy = SEQUENCE,
generator = "user_seq"
)
#Column(
name = "id",
updatable = false
)
val id: Long = -1,
#Column(
name = "first_name",
nullable = false,
length = 50,
updatable = true
)
val firstName: String,
#Column(
name = "last_name",
nullable = false,
length = 50,
updatable = true
)
val lastName: String,
#Column(
name = "email",
nullable = true,
length = 150,
updatable = true
)
val email: String,
#Column(
name = "gender",
nullable = false,
length = 2,
updatable = true
)
val gender: String,
#Column(
name = "date_of_birth",
nullable = false,
updatable = true
)
val dateOfBirth: LocalDate,
#Column(
name = "country",
nullable = false,
length = 50,
updatable = true
)
val country: String
)
UserController.kt
package com.example.demo.user
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.format.annotation.DateTimeFormat
import org.springframework.format.annotation.DateTimeFormat.ISO.DATE
import org.springframework.web.bind.annotation.*
import java.time.LocalDate
#RestController
#RequestMapping(
path = [
"/api/v1/"
]
)
class UserController(
#Autowired private val userService: UserService
) {
#PutMapping("update/{id}")
fun updateUser(
#PathVariable("id") id: Long,
#RequestParam(required = false) firstName: String,
#RequestParam(required = false) lastName: String,
#RequestParam(required = false) email: String,
#RequestParam(required = false) gender: String,
#RequestParam(required = false) country: String,
#RequestParam(required = false) #DateTimeFormat(iso = DATE) dateOfBirth: LocalDate
) {
return userService.updateUser(id, firstName, lastName, email, gender, country, dateOfBirth)
}
}
UserService.kt
package com.example.demo.user
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDate
#Service
class UserService(
#Autowired private val userRepository: UserRepository
) {
fun registerUser(user: User) {
if (userRepository.findUserByEmail(user.email).isPresent) throw IllegalStateException("Email Already taken")
userRepository.save(user)
}
fun getUsers(offset: Int): List<User> {
return userRepository.getUsers(offset)
}
fun getUserInfo(userId: Long): User {
if (!userRepository.existsById(userId)) throw IllegalArgumentException("User not found")
return userRepository.findById(userId).get()
}
fun deleteUser(userId: Long) {
if (!userRepository.existsById(userId)) throw IllegalStateException("User with id $userId does not exist")
userRepository.deleteById(userId)
}
#Transactional
fun updateUser(id: Long, firstName: String, lastName: String, email: String, gender: String, country: String, dateOfBirth: LocalDate) {
val user = userRepository.findById(id).orElseThrow { throw IllegalStateException("User with id $id does not exist") }
// if (userRepository.findUserByEmail(user.email).isPresent) throw IllegalStateException("Email Already taken")
if (firstName.isNotEmpty()) user.firstName = firstName
if (lastName.isNotEmpty()) user.lastName = lastName
if (email.isNotEmpty()) user.email = email
if (gender.isNotEmpty() && (gender == "M" || gender == "F")) user.gender = gender
if (country.isNotEmpty()) user.country = country
if (dateOfBirth.toString().isNotEmpty()) user.dateOfBirth = dateOfBirth
}
}
Response payload is
{
"timestamp": "2021-03-02T11:45:27.025+00:00",
"status": 500,
"error": "Internal Server Error",
"message": "Parameter specified as non-null is null: method com.example.demo.user.UserController.updateUser, parameter dateOfBirth",
"path": "/api/v1/update/1000"
}
The problem comes from your REST controller. The request params are annotated with required=false but the type is String. So when an HTTP request comes with null values, Spring can't deserialize data in those fields. (That explains the NullpointerException.
You have to provide nullable types (like "String?") In your controller and this nullability should not necessariry proliferate in all layers of your code.
For example "firstname" is optional for update but mandatory for creation.
I have not worked much on kotlin but what I think the issue is Kotlin is null-safe programming language. You need to explicitly tell if variable can be null or not. For more detail please refer this blog. Updating your entity class with field accepting null should work.
for example To allow nulls, you can declare a variable as nullable string, written String?:

Resources