I have an abstract class entity.
abstract class AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "id")
#SequenceGenerator(name = "id", sequenceName = "id_sequence", allocationSize = 1000)
var id: Long? = null
#Version
private var version: Int = 0
#NotNull
var createdDate = ZonedDateTime.now()!!
}
And I have a class(javax.persistence Entity ) that inherits from AbstarctEntiy
#Entity
#Table(schema = "query")
data class Query(
var name: String?,
) : AbstractEntity()
Is there any difference between using users in class header and in class body as in the following two codes?
1
#Entity
#Table(schema = "query")
data class Query(
var name: String?,
#OneToMany(mappedBy = "id", fetch = FetchType.EAGER)
var users : List<Username> = mutableListOf()
) : AbstractEntity()
2
#Entity
#Table(schema = "query")
data class Query(
var name: String?,
) : AbstractEntity() {
#OneToMany(mappedBy = "id", fetch = FetchType.EAGER)
var users : List<Username> = mutableListOf()
}
There is a difference between passing an item through the constructor, and setting it as a property because you are using a data class to hold those.
While in example 1 and 2 Kotlin is generating a getter and a setter for both the user and name fields, main benefits of using a data class only get leveraged for items passed through the constructor.
In Example 1, because it's a data class Kotlin overrides the 'copy', 'toString', 'hashCode' and 'equals' classes for BOTH the properties you're passing into the constructor. So just as an example, the 'toString' function would look like so in the decompiled java code
#NotNull
public String toString() {
return "Query(name=" + this.name + ", users=" + this.users + ")";
}
In Example 2, you only get this benefit for the name property you are passing into the constructor, but not for the user list. In this case, the toString() and all the other functions I mentioned would only take into consideration name
public String toString() {
return "Query1(name=" + this.name + ")";
}
This is true for all the rest of copy() hashCode(), and equals()
If you care about Kotlin handling these for both user and name then pass both through in the constructor. Otherwise, it doesn't matter.
Related
I am writing unit tests in kotlin, for this purpose I need to assign value on a "val", here is the simplified version of the code:
#Entity
#Table(name = "Request")
data class Request(
#Column(name = "Name")
val name: String,
) {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
var id: Long? = null
#CreationTimestamp
#Column(name = "Created")
val created: LocalDateTime = LocalDateTime.now()
}
#Test
fun `test one`() {
val name = RandomStringUtils.randomNumeric(10)
val id = Random.nextLong(100)
val created = LocalDateTime.now().minusHours(48)
val request = Request(playerUid = playerUid).apply {
id = id
created = created
}
}
it has an compile error when assigning "created" in the test. How should I manage this unit test since I need to set my desire "created" value? (I can not touch any part of the "Request class")
If you cannot change any part of the Request class then you will not be able to change created.
You will either need to test created by using an approximate test range (created needs to be 0<now<2s sort of thing)
It is a design flaw to encode a static accessor to functions like LocalDateTime.now() - this should be set externally in a service class. If you really cannot do that, then here is another hacky approach:
add a CLOCK object somewhere (does not need to be in a companion object) but ultimately you have to change the created assignment:
#Entity
#Table(name = "Request")
data class Request(
#Column(name = "Name")
val name: String,
) {
companion object {
/** used for carrying a Clock only in the case of tests **/
val CLOCK = ThreadLocal<Clock>()
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
var id: Long? = null
#CreationTimestamp
#Column(name = "Created")
val created: LocalDateTime = LocalDateTime.now(CLOCK.get() ?: Clock.systemUTC())
}
Normally you don't touch that CLOCK but in the Unit Tests you define a
private val fixedClock = Clock.fixed(Instant.parse("2022-08-29T08:20:50Z"), ZoneOffset.UTC)
then you need
#BeforeEach
fun beforeEach() {
// this is necessary because the serialization of MemberMentorCommon.weeksOnPlan uses a clock
CLOCK.getOrSet { fixedClock }
}
#AfterEach
fun afterEach() {
CLOCK.remove()
}
Yes, ThreadLocals are nasty, but this allows you to change the behaviour of the Request class to override the now() function
I have an Entity class as below:
#Entity(name = "Person")
#Table(name = "person")
class Person(
_firstName: String? = null,
_lastName: String?,
_address: String? = null)
) {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
#GenericGenerator(name = "native", strategy = "native")
var personId: Long? = null
var firstName = _firstName
var lastName = _lastName
var address = _address
}
The repository is as :
#Repository
interface PersonRepository : JpaRepository<Person?, Long?> {
fun findByIdAndFirstName (personId: Long, firstName: String): Person?
}
In my service class I am doing a findById(personId) and getting an Optional<Person?> in return.
While writing the testcases I am running into an issue with the Optional.
#Test
fun `test my changes`() {
every { personRepository.findById(PERSON_ID) } answers { Optional.of(getPerson())}
}
private fun getPerson(): Person {
return Person(_firstName = "First Name", _lastName = "Last Name", _address = "Address")
}
Since my getPerson() directly return the Person entity via a constructor the response is of type Person which is non-nullable.
But personRepository.findById(PERSON_ID) expects a nullable type Optional<Person?>.
Getting this as compilation error in my test class:
Type mismatch.
Required:
Optional<Person?>!
Found:
Optional<Person>
How can I fix this issue ?
You can cast it to nullable:
#Test
fun `test my changes`() {
every { personRepository.findById(PERSON_ID) } answers {
Optional.of(getPerson()) as Optional<Person?>
}
}
However (as Sweeper said in the comment) the type Optional<Person?> doesn't make much sense, and it looks like your JpaRepository should be parameterized on non-nullables. If it was, then findById would return Optional<Person> and you wouldn't have this issue.
I've already deserialized some nested field in the past in Java, following instructions from https://www.baeldung.com/jackson-nested-values (section 5) :
#JsonProperty("brand")
private void unpackNested(Map<String,Object> brand) {
this.brandName = (String)brand.get("name");
Map<String,String> owner = (Map<String,String>)brand.get("owner");
this.ownerName = owner.get("name");
}
ownerName being a field in the bean.
Now, I need to do something similar in Kotlin, but I am not happy with what I have so far. Assuming I have a MyPojo class that has a createdAt field, but in the JSON that represents it, the field is nested under a metadata attribute:
data class MyPojo(var createdAt: LocalDateTime = LocalDateTime.MIN) {
#JsonProperty("metadata")
private fun unpackNested(metadata: Map<String, Any>) {
var createdAtAsString = metadata["createdAt"] as String
this.createdAt = LocalDateTime.parse(createdAtAsString,DateTimeFormatter.ISO_DATE_TIME)
}
}
One of the thing I don't like here is that I am forced to make createdAt a var, not a val.
Is there a Kotlin trick to make things overall better here?
For the sake of simplicity, I used Int as type for createdAt.
You could do it like this:
class JsonData(createdAt: Int = 0) {
private var _createdAt: Int = createdAt
val createdAt: Int
get() = _createdAt
#JsonProperty("metadata")
private fun unpackNested(metadata: Map<String, Any>) {
_createdAt = metadata["createdAt"] as Int
}
}
createdAt will be a parameter with a default value. Since a data classe's constructor can only have properties (var/val) you will loose the advantages of a data class (toString() out of the box etc.).
You will assign this parameter to a private var _createdAt when the class is instantiated.
The only thing that will be exposed to the outside is a property without a backing field createAt (just a getter in Java terms). So, _createdAt cannot be changed after instantiation.
There are two cases now:
If you instantiate the class, _createdAt will be set to the value you specify.
If Jackson instantiates the class the value of _createdAt will be overwritten by the unpackNested call.
Here is an example:
val jsonStr = """{
"metadata": {
"createdAt": 1
}
}
""".trimIndent()
fun main() {
val objectMapper = ObjectMapper()
// Jackson does instantiation
val jsonData = objectMapper.readValue(jsonStr, JsonData::class.java)
// you do it directly
JsonData(5)
}
I have different user types Author and Commentor. I wanted them to have a OneToOne relationship with my User class. The user class would contain spring security logic. I created a super class of BlogUser and my Author and Commenter would extend it. When i try and set up the hibernate mappings I get:
Unknown mappedBy in:
com.legge.blenderBlog.models.security.User.blogUser, referenced
property unknown: com.legge.blenderBlog.models.abstract.BlogUser.user
Is my thinking wrong?
#MappedSuperclass
#EntityListeners(AuditingEntityListener::class)
abstract class BlogUser(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0,
#OneToOne(fetch=FetchType.LAZY)
#JoinColumn(name="USER_ID")
val user: User
) : DateAudit()
here is my Author.kt
#Entity
#Table(name = "author")
class Author(user:User
): BlogUser(user = user)
here is part of the User class
#Entity
#Table(name = "sec_user", uniqueConstraints = [(UniqueConstraint(columnNames = arrayOf("username"))), (UniqueConstraint(columnNames = arrayOf("email")))])
open class User(
....
#OneToOne(fetch=FetchType.LAZY, mappedBy="user")
var blogUser: BlogUser?,
...
) : DateAudit()
Here is dateAudit
#MappedSuperclass
#EntityListeners(AuditingEntityListener::class)
#JsonIgnoreProperties(value = ["dateCreated", "dateUpdated"], allowGetters = true)
abstract class DateAudit : Serializable {
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "date_created", nullable = false, updatable = false)
#CreatedDate
var dateCreated: Date = Date()
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "date_updated", nullable = false)
#LastModifiedDate
var dateUpdated: Date = Date()
}
I have a problem with Room ORM working on Kotlin. My task is having ability to save and get data models RouteTemplateModel, that contains list of addresses of type AddressModel and object of class RouteModel that contains title of the specific route. Here is my code:
AddressModel.kt
#Entity(foreignKeys = arrayOf(
ForeignKey(entity = RouteModel::class,
parentColumns = arrayOf("routeId"),
childColumns = arrayOf("parentId"))))
data class AddressModel(
#PrimaryKey(autoGenerate = true)
var addressId: Long,
var parentId: Long,
var street: String,
var house: String,
var entrance: String,
var title: String){
constructor(): this(0, 0, "", "", "", "")
}
RouteModel.kt
#Entity
data class RouteModel(
#PrimaryKey(autoGenerate = true)
var routeId: Long,
var title: String) {
constructor() : this(0, "")
}
Here is my simple models, I found in documentation of Room that for creating relations between models I need to use #ForeignKey and #Relation
So with code samples in doc and tutorials I create RouteTemplateModel that contains object of RouteModel and list of AddressModels. Here is the class
RouteTemplateModel
class RouteTemplateModel{
private var id: Long = 0
#Embedded
private var routeModel: RouteModel = RouteModel()
#Relation(parentColumn = "routeId", entityColumn = "parentId")
private var addressList: List<AddressModel> = listOf()
constructor()
constructor(id: Long, routeModel: RouteModel, title: String,
addressList: List<AddressModel>){
this.id = id
this.routeModel = routeModel
this.addressList = addressList
}
fun getId(): Long{
return id
}
fun getRouteModel(): RouteModel{
return routeModel
}
fun getAddressList(): List<AddressModel>{
return addressList
}
fun setId(id: Long){
this.id = id
}
fun setRouteModel(routeModel: RouteModel){
this.routeModel = routeModel
}
fun setAddressList(addressList: List<AddressModel>){
this.addressList = addressList
}
}
So what`s a problem? I am getting such errors:
Error:The columns returned by the query does not have the fields [id]
in com.innotech.webcab3kotlin.model.RouteTemplateModel even though
they are annotated as non-null or primitive. Columns returned by the
query: [routeId,title]
And
Error:Type of the parameter must be a class annotated with #Entity or
a collection/array of it.
It is a real problem, because if my trying to fix first error and annotate in RouteTemplateModel id variable to return this column too, I need annotate class as Entity (like in second error), but when I do it I am getting an error
Error:Entities cannot have relations.
Here is AppDatabase.kt
#Database(entities = arrayOf(RouteModel::class, AddressModel::class), version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun getRouteDao(): RouteDao
}
and RouteDao.kt
#Dao
interface RouteDao {
#Query("SELECT routeId, title FROM RouteModel")
fun getAll(): List<RouteTemplateModel>
#Insert
fun insertAll(vararg models: RouteTemplateModel)
#Delete
fun delete(model: RouteTemplateModel)
}
Thats really confusing. Please, help me)
Your "parentId" column is capable of holding long value only, make its type to "Text" then create a TypeConverter from "List" to String and vice a versa for reference please have a look at link .