I am a beginner learning about Spring Boot, and I've been having trouble making generated values work. I would like to have the database (Postgres) generate a timestamp column when a row is inserted, instead of having Spring generate it, but the problem is Spring doesn't seem to select the timestamp from the database when it performs an update.
When I save()
a new entity, it is inserted fine in the database, a timestamp is generated and returned to Spring; everything works fine. When I try to find()
an existing entity, the timestamp is also returned, so that's fine too. However when I try to update an existing entity, Spring DOESN'T access the timestamp that's in the database, and it instead returns null for the corresponding field.
Here is the entity definition:
import jakarta.persistence.*
import org.hibernate.annotations.CurrentTimestamp
import org.hibernate.generator.EventType
import java.time.OffsetDateTime
@Entity(name = "users")
@Table(name = "users")
data class User(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Int?,
val name: String,
@Column(name = "date_created")
@CreationTimestamp(source = SourceType.DB)
val dateCreated: OffsetDateTime?,
)
Say the database already has a row like 1 | 2025-01-02 02:03:04 | user1
. A PUT request like { name: "new user" }
will update the name
field in the database without changing the timestamp, but the updated entity returned by JPA will be { id: 1, name: "new user", dateCreated: null }
. I'm not sure why that is. I know that JPA executes an additional SELECT when INSERTing a new row in order to get the timestamp that was generated by Postgres, but I don't see why it wouldn't just get the already existing timestamp when UPDATEing.
The controller and service classes for completeness:
import com.example.hello.User
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping(path = ["/users"])
class UserController(private val userService: UserService) {
@PostMapping
fun createStaff(@RequestBody user: User): ResponseEntity<User> {
val createdUser = userService.create(user)
return ResponseEntity(createdUser, HttpStatus.CREATED)
}
@PutMapping(path = ["/{id}"])
fun updateUser(@PathVariable("id") id: Int, @RequestBody user: User): ResponseEntity<User> {
val updatedUser = userService.update(id, user)
return ResponseEntity(updatedUser, HttpStatus.OK)
}
@GetMapping(path = ["/{id}"])
fun readUser(@PathVariable("id") id: Int): ResponseEntity<User> {
val user = userService.get(id)
return user?.let { ResponseEntity.ok(it) } ?: ResponseEntity(HttpStatus.NOT_FOUND)
}
}
import com.example.hello.User
import com.example.hello.UserRepository
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
@Service
class UserService(private val userRepository: UserRepository) {
fun create(user: User): User {
return userRepository.save(user)
}
fun update(id: Int, user: User): User {
val userWithId = user.copy(id = id)
return userRepository.save(userWithId)
}
fun get(id: Int): User? {
return userRepository.findByIdOrNull(id)
}
}
When you call PUT api, you only pass name
in the body, but the request User
type, so Spring will understand that your body is:
{
"id": null,
"name": "your_input_name",
"date_created": null
}
And in your update
method, you only copy object with id => the dateCreated
still null
.
That why when it save to db, the date_created
column is null
You can first query the object in db with id
, change name
field then save it back.
Or you can add updatable = false
to make sure date_created
will not be updated.
@Column(name= "date_created", updatable = false)