Best way to associate an entity JPA - spring-boot

I have a manytoone relationship between two entities and i want to know if there's a better option to save using an existing id, for example, in the example below, should i send a company id inside json or first create a Role and the using PUT update the Role with company Id. Or maybe inside the Controller find the company entity and then set in the new Role entity and after that, save it. How to proceed in this case?
#Entity
data class Role(
val name: String = "",
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "company_id")
val company: Company,
val type: RoleType,
val description: String = ""
)

If companyfield of Role is mandatory (Not Null) then you need to create first Company so that you can link with ID or Name or whatever as foreign key. Then you first create a Company and later a Role related to it.
{
"name" : "Role Name - 1",
"company" : 1, // or "Company-"
"type" : "Type - 1",
"description" : "This a test description"
}

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

How to gracefully transform entity into DTO in Kotlin?

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!

How can I delete items with relations?

I'm using prisma2, and I don't know how to delete items having relations with other models.
This is my models.
model User {
id String #default(cuid()) #id
email String #unique
password String
name String
teams Team[]
memberships Membership[]
}
model Team {
id String #default(cuid()) #id
name String
founder User?
memberships Membership[]
}
model Membership {
id String #default(cuid()) #id
class String
owner User
team Team
}
User-Team is 1:n relationship.
Team-Membership is 1:n relationship.
And I wanna delete a Team.
I've tried this.
t.list.field("deleteTeam", {
type: "Team",
args: {
teamid: idArg()
},
resolve: (_, { teamid }, ctx) => {
return ctx.photon.teams.deleteMany({
where: { id: teamid }
});
}
});
But it doensn't work because it violates relation.
How can I delete team with disconnecting all the relations at the same time?
Deletes that have dependent relationships usually require that a cascading delete be specified.
Based on your model, I believe you need to update your graphql schemas to handle either CASCADE on SET_NULL for relations onDelete.
I know that in other systems like 8base there is a force: Boolean flag that can be specified solve this. However, here is the Prisma docs section for your problem: https://prisma-docs.netlify.com/docs/1.4/reference/prisma-api/concepts-utee3eiquo/#cascading-deletes

How to update a #ManyToOne relationship with Spring Data REST?

I use Spring Data REST with JPA. I have a User entity that has a many to one relationship with another called AccountStatus modeled in a separate RDBMS table. The JSON representation looks like this:
{
"id": "123"
"username": "user1",
"accountStatus": {
"id": "1",
"status": "Active"
}
}
The relationship in the User entity is:
#ManyToOne(optional = false)
#JoinColumn(name = "account_state")
#Getter #Setter private AccountState accountState;
Now I try to change the account status using a PATCH request on /users/123 and the payload:
{"accountState":{"id":0}}
But I get an error:
"identifier of an instance of com.domain.account.AccountState was
altered from 1 to 0; nested exception is org.hibernate.HibernateException:
identifier of an instance of com.domain.account.AccountState was
altered from 1 to 0"
I also tried to use #HandleBeforeSave/#HandleBeforeLinkSave to fetch the new AccountState from the repository and replace user.accountStatus with no success.
What am I doing wrong?
It really depends if you have an exported repository for AccountState. If you do you can update your account state with a PATCH against /users/{id}:
{
"accountState": "http://localhost:8080/accountStates/2"
}
So you are using the URI of your account state to reference the resource to assign

many to many relationship with sorting

i am trying to achieve a MANY_TO_MANY relationship between two entities with an additional attribute in the join table.
i found the following answer:
how-to-do-a-many-to-many-relationship-in-spring-roo-with-attributes-within-de-relationship
but i failed trying to adapt the offered solution to my scenario:
currently i have a straight forward MANY_TO_MANY relationship:
# book:
entity jpa --class ~.domain.Book --testAutomatically
field string --fieldName title --notNull
field string --fieldName subtitle --notNull
field string --fieldName description
# n:m relationship to author
field list --fieldName authors --type ~.domain.Author --cardinality MANY_TO_MANY
# author:
entity jpa --class ~.domain.Author --testAutomatically
field string --fieldName firstName --notNull
field string --fieldName lastName --notNull
that works as expected, but i need to have the authors ordered. i would like to achieve this by defining the relationship table and adding a integer field like 'sequence' to it, but i got stuck when i try to define the many-to-many relationship in Book:
entity jpa --class ~.domain.BookAuthorOrdered --table book_author_ordered
# the additional field to store sequence of authors:
field number --fieldName authorSequence --type java.lang.Integer --notNull
# reference to book:
field reference --fieldName bookId --type ~.domain.Book --cardinality MANY_TO_ONE
# reference to author:
field reference --fieldName authorId --type ~.domain.Author --cardinality MANY_TO_ONE
can anyone give me a hint how to define the attribute in Book so that i get a list of sorted authors using the above defined join table? here is something i tried:
# complete the relationship
focus --class ~.domain.Book
field list --type ~.domain.BookAuthorOrdered --fieldName orderedAuthors --cardinality ONE_TO_MANY --mappedBy bookId
Try to add the #OrderBy annotation to entity field (in .java file). By example:
#ManyToMany
#OrderBy("lastName ASC, firstName ASC")
private List<Author> authors;
i found a more or less working solution for my problem, but it still has some drawbacks. it is mostly derived from the above mentioned answer:
How to do a many-to-many relationship in spring Roo, with attributes within de relationship?
create join table manually with ids of the tables book and author as composed primary key:
roo> entity jpa --class ~.domain.BookAuthorOrdered --table book_author_ordered --identifierType ~.domain.BookAuthorOrderedId
field number --fieldName authorSequence --type java.lang.Integer --notNull
then edit the generated id class BookAuthorOrderedId and add the composed primary key:
#RooToString
#RooEquals
#RooIdentifier
public final class BookAuthorOrderedId {
#ManyToOne
#JoinColumn(name="book_id", insertable = false, updatable = false)
private Book book;
#ManyToOne
#JoinColumn(name="author_id", insertable = false, updatable = false)
private Author author;
}
now comes the part where i have a bad feeling, because it looks like a workaround to me. in the book entity i pushed in the getter method for authors and replaced it with the query of the join table:
public List<Author> getAuthors()
{
// return Author.findAllAuthorsOfBook(this);
System.out.println("getAuthors()");
EntityManager em = new Book().entityManager;
TypedQuery<Author> q = em.createQuery("SELECT o FROM Author AS o WHERE o.id IN ( SELECT OA.id.author FROM eu.books2ebooks.roomanova.domain.BookAuthorOrdered OA where OA.id.book = :book ) ", Author.class);
q.setParameter("book", this);
List<Author> authorList = q.getResultList();
//this.setAuthors(authorList);
return authorList;
}
and a method to get the bean value directly for fresh added authors:
public List<Author> getAddedAuthors() {
return this.authors;
}
then i had to manipulate the book controller to call a self written method to insert/update the join table (at create/update/..):
public static void setOrderedBookAuthors(Book book, List<Author> authors) {
// delete all associated authors of book:
List<BookAuthorOrdered> bookAuthorOrdereds = BookAuthorOrdered.findAllBookAuthorOrdersOfBook(book);
for (BookAuthorOrdered bookAuthorOrdered : bookAuthorOrdereds) {
log.info("removing book author: " + printAuthor(bookAuthorOrdered.getId().getAuthor()));
bookAuthorOrdered.remove();
}
if ( authors == null )
{
log.info("ordered authors: null. nothing to insert.");
return;
}
log.info("inserting sorted authors: ");
Integer authorSequence = 1;
for (Author author : authors) {
log.info("inserting book author sorted: " + printAuthor(author) + " as no " + authorSequence);
BookAuthorOrdered bookAuthorOrdered = new BookAuthorOrdered();
bookAuthorOrdered.setAuthorSequence(authorSequence);
BookAuthorOrderedId id = new BookAuthorOrderedId(book, author);
bookAuthorOrdered.setId(id);
log.info("book author ordered: " + bookAuthorOrdered);
bookAuthorOrdered.persist();
authorSequence++;
}
}
so far that works but it have the feeling that there must be a much more elegant way...

Resources