How to gracefully transform entity into DTO in Kotlin? - spring

I am working on Kotlin + SpringBoot web service, in which I want to transform DTOs into entities in the most convenient way.
Entities:
#Entity
data class Shop(
#Id
#GeneratedValue
val id: Long,
val name: String
#OneToOne
val owner: User,
...
)
#Entity
data class User(
#Id
#GeneratedValue
val id: Long,
val name: String,
...
)
DTO:
data class ShopDTO(
val id: Long,
val name: String,
val ownerId: Long,
val ownerName: String,
...
)
So when someone wants to create a new Shop, my service gets a ShopDTO(name, ownerId) as request body, then I need to transform it into Shop object to be able to save it to the DB. Now here is how my mapper function looks like:
fun fromDTO(source: ShopDTO) = Shop(
id = source.id,
name = source.name,
owner = ???,
...
)
To be able to store a Shop with an owner I only need an id. It would be enough to create a new User with the given ownerId.
To achive this I tried these solutions:
Add default value to the fields in the User class.
Make the fields nullable.
Add a secondary constructor. This also needs default values.
Use some reflection magic to create an empty object and then set the id.
Call a findById method on the UserRepository with the given id.
I want to keep the non-null, immutable fields of my entities and do not want to use reflection. Also do not want to run an unnecessary select DB query just to get back the user by the id.
Could you please suggest me other options? How would you handle this situation? Is there any good mapper framework in Kotlin which can solve this problem?

Firstly, your question says you want to do entity -> DTO, but actually you want to do DTO -> entity, so you should clear that up.
Secondly, you are getting the shop name and owner Id in the ShopDTO. But you are assigning the owner Id to the shop Id in the your fromDTO(source: ShopDTO) function. Changing it up would be sufficient.
fun fromDTO(source: ShopDTO) = Shop(
name = source.name,
owner = ownerRepo.findById(source.ownerId)
)
Obviously, if you're using JPA, then you have to make a DB call to get the owner first. If your business logic doesn't ensure that a User with that Id exists, then you could write a method like this to make a user.
fun getOrCreateUser(ownerId: Long) =
ownerRepo.findUserById(ownerId) ?: User(
id = ownerId,
name = "Some random DefaultName"
).run(ownerRepo::save)
This would get a User by the Id if it exists, or create a new user with some generic name.
Do let me know if this solves your issue!

Related

best way to add new properties to a model when using spring boot with mongodb

I'm looking for a better way to change a model with Spring + Mongodb, currently every time a property is added to a model, we have to create a command to be ran in mongosh to add that field to all documents, and then save it so that it can be ran on every environment that the new model is pushed to.
So for example, lets say a Event object has the properties:
#Document
data class Device(
#Id
val id: String? = null,
#Indexed(unique = true)
val name: String,
var location: String,
)
And we want to add a field "date": 2023-02-02T20:10:19.111Z to it. Currently I will have to create a mongosh command to update all events on the collection to add that field, so something like this:
db.device.updateMany({}, {$set: {'date': new Date().toISOString()}})
We then save this, and remember to run it every single time we merge to a upstream branch.
Is there a better way to define a new model with the date, so that it can create the field automatically?
I would add the new property with a default value, but everyone usecase/trade-offs is different.
#Document
data class Device(
#Id
val id: String? = null,
#Indexed(unique = true)
val name: String,
var location: String,
val newProperty:String? = null
)
This will allow you to get values don't exist in the database as null

Implementing hierarchical DB tables with InheritanceType.JOINED and a `tenant_id` column in every table

I'm trying to implement a hierarchical structure using the InheritanceType.JOINED approach to store data in hierarchical DB tables. One caveat is that in our multi-tenant solution, a tenant_id column needs to be present on every table (for security and legal export reasons), even though this is redundant in some cases. This requirement is fixed.
Issue is that when inserting data, the query Hibernate generates does not fill in the tenant_id on the parent- and childtable, causing a constraint error.
The tables in the DB would look like this:
Code for the abstract vehicle entity:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
abstract class Vehicle(
var tenantId: Int,
var title: String
)
Car entity:
#Entity
class Car(
override var tenantId: Int,
override var title: String,
) : Vehicle(tenantId, title) {
var numberOfDoors: Int
}
Plane entity:
#Entity
class Plane(
override var tenantId: Int,
override var title: String,
) : Vehicle(tenantId, title) {
var numberOfPropellers: Int
}
When inserting a new Car in the database, Hibernate generates 2 queries, where the tenant_id is only added to the abstract Vehicle table:
insert into vehicle (tenant_id, title) values (?, ?)
insert into car (id, number_of_doors) values (?, ?)
Is there a way to instruct Hibernate to fill in the column on both tables?
One hack I've found is to implement a "second" tenantId variable on the class and specify a column explicitly, as such:
#Column(name = "tenant_id")
private val _tenantId: Int = tenantId
But it's not very clean and a neater solution would be nice.
Specifically in my case where the tenant_id column is a database setting, defining a computed default value on the tenant_id db column also works as a workaround:
current_setting('app.current_tenant', true)::INT

I get inconsistent outcomes in Spring Data Neo4j when mapping both ends of a relationship and saving it

I have a User node with FRIEND_REQUEST relationships mapped to a sentFriendRequestList list and to a receivedFriendRequestList list like below:
#Node
data class User(
#Id
#GeneratedValue(UUIDStringGenerator::class)
var userId: String?,
#Relationship(type = "FRIEND_REQUEST", direction = Relationship.Direction.OUTGOING)
var sentFriendRequestList: MutableList<FriendRequest> = mutableListOf(),
#Relationship(type = "FRIEND_REQUEST", direction = Relationship.Direction.INCOMING)
var receivedFriendRequestList: MutableList<FriendRequest> = mutableListOf(),
var email: String
)
The FriendRequest class:
#RelationshipProperties
data class FriendRequest(
#Id
#GeneratedValue
var friendRequestId: Long?,
/**
* Represents the receiver in an OUTGOING relationship and the sender in an INCOMING relationship.
*/
#TargetNode
var friendRequestOtherNode: User
){
constructor(friendRequestOtherNode: User) : this(null, friendRequestOtherNode)
}
When saving multiple friend requests, on some occasions all previously created relationships disappear from the given nodes and only the newly created relationship appears.
I save like this:
fun saveFriendRequest(sender: User, receiver: User) {
val sentFriendRequest = FriendRequest(receiver)
val receivedFriendRequest = FriendRequest(sender)
sender.sentFriendRequestList.add(sentFriendRequest)
receiver.receivedFriendRequestList.add(receivedFriendRequest)
userRepository.save(sender)
userRepository.save(receiver)
}
I don't understand what the problem is, especially since sometimes it runs without failure.
I created a small test project which is available on my GitHub: link. It contains the data structure and a test class that can be run instantly. The tests show the same problem, after multiple runs it can either fail or be successful.
I am a little bit unsure about the right query, but your custom Cypher statement does not return all friends and relationships for deeper links.
Changing it to:
MATCH (user:User{email: \$email})
OPTIONAL MATCH path=(user)-[:FRIEND_REQUEST*]-(friend:User)
WITH user, friend, relationships(path) as friend_requests
RETURN user, collect(friend_requests), collect(distinct(friend))
solved this for me (or the ten test runs were just randomly green).
Another solution would be, to avoid the custom query, define findByEmail(email: String): User and let Spring Data Neo4j create the query.
The problem that occurs is that
First operation:
frodo -> sam
sam -> frodo
Second operation:
frodo -> bilbo
bilbo -> frodo
results for Sam in something like sam-frodo-bilbo.
When you load Frodo or Bilbo the relationships are not completely hydrated to Sam.
The moment you save Bilbo (or Frodo), SDN will through all relationship and eventually not find the Sam relation. Because it is empty, SDN will remove the relationship on save.
Another problem I have seen in your code:
You should definitely annotate the manual defined constructor with #PersistenceConstructor because Kotlin creates an invisible copy constructor and Spring Data in general does not know which to choose. So you might randomly run into the copy constructor.

Get String from SQL function, no entity, kotlin

Im confused..
I need to use Oracle function to retrieve just a single String, No entity.
So i try to use CrudRepository, but that dosent work without Entity?
interface UserRepository : CrudRepository<Contact, String>{
#Query(nativeQuery = true, value = "SELECT schema.get_user(:id) FROM DUAL")
fun getUser(id: String): String?
}
or
interface UserRepository : CrudRepository<Contact, String>{
#Query(nativeQuery = true, value = "SELECT schema.get_user(:id) AS name, '' AS email FROM DUAL")
fun getUser(id: String): String?
}
where i try to name returning string to fit entity i seem to be forced to use.
Entity Contact is super simple entity with two attributes.
#Entity
data class Contact(
#Id
val name: String?,
val email: String?
)
Function will only ever return one string, but i cant figure how would i insert that as a name.. With this Query i get ORA-00904: : invalid identifier most likely because i have no idea how to match identifier returned from function.. (SQLdeveloper gives function name as column name but the returned string dosent really have ANY column name, since it is calculated...)
I can't do a repository for a String either, can i?
EDIT: trying to clarify what i need:
Entity has two attributes, just one comes from the Query including function, other one must be fetch from other side of the world. How do i run this query and return either single string or entity (with email as null) only have gotten array of errors so far, thanks

Incorrect JPA entity column type causing issues for FindById

I'm currently working on a Spring backend application. We have a Partner entity that has the #GeneratedValue(strategy = GenerationType.IDENTITY annotation for autogenerating primary keys. The primary key partnerId is of type long.
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "partner_id")
private long partnerId;
In our Partner controller class, we wrote some functions for basic CRUD operations. When we make a post call to add a Partner, we omit the primary key partnerId and include information for the other fields. The post is successful and the data can be seen in the database.
However, when we try to make a put call to update the newly added Partner, it instead creates a new Partner entry in the Partner table.
At first, it seemed like a code level issue, but we tested the put call on one of the entries that we added to the table from our base data set, it works fine. We used FindById() and isPresent() to see if an entry exists for this new user with partnerId from the incoming Partner object from the put call, but got false. But with one of the 3 existing Partner entries that we put into the database manually with queries, they returned true.
#RequestMapping(method = RequestMethod.PUT, value = PATH)
public ResponseEntity<?> updatePartner(#RequestBody Partner partner) {
...
System.out.println(partnerRepository.findById(partner.getPartnerId()).isPresent());
}
It seems as though the post call is setting the new Partner object in the database, but somehow the partnerId of any newly posted entry is not of type long, which disrupts findById().isPresent(). I'm just wondering if anyone knows why this is happening since our entity sets the primary key to type long and the controller is sending to the database a Partner object.

Resources