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...
Related
Suppose that a certain booking's invoice is null in the DB.
public class Booking {
#Id
private Integer id;
...
#ManyToOne(... fetch = FetchType.LAZY)
#JoinColumn(...)
private Invoice invoice;
}
It seems that a many-to-one lazy relation always creates a proxy object for booking.getInvoice(), while an eager relation would just return a null. I think I can check the booking.getInvoice().getId(), but for that I must be sure that the relation is lazy. I don't want to do that because it forces me to always track the relation fetch type in the client code.
What if I have a lot of existing code that checks null in 'eager mode' and I want to convert a certain relation to lazy? Do I have to convert all null checks for that relation as well?
I'd think that since the DB field is null then the JPA would be smart enough to not create the proxy object at all here, leaving the private field invoice null.
Is the double check for null and id the only way to go?
Invoice invoice = booking.getInvoice();
if (invoice != null && invoice.getId() > 0) {
...
}
Is there a fetch-type-independent way to check for null many-to-one children in JPA?
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
This is more a design question than a coding question. Suppose the following schema:
// schema.prisma
// Solution 1
model Entity {
id Int #id #default(autoincrement())
attrs EntityAttr[]
}
model EntityAttr {
id Int #id #default(autoincrement())
value Json // or String, doesnt matter much here
// the point is I need to attach info on the
// join table of this relation
attr Attr #relation(fields: [attrId], references: [id])
entity Entity #relation(fields: [entityId], references: [id])
entityId Int
attrId Int
##unique([entityId, attrId])
}
model Attr {
id Int #id #default(autoincrement())
entities EntityAttr[]
}
// Solution 2
model Entity {
id Int #id #default(autoincrement())
dateAttrs DateAttr[]
recordAttrs RecordAttr[]
// ... this pattern could continue for more Attr-like models
}
model DateAttr {
id Int #id #default(autoincrement())
name String
entity Entity #relation(fields: [entityId], references: [id])
value DateTime // Stronger typing in generated code
}
model RecordAttr {
// ... define another Entity #relation(...)
name String
value String
// ...
}
// ... and so on
Please note that the schema might not be 100% complete or accurate. It is mainly to get the point across.
Solution 1 has its merits where redundancy and the number of tables in the database is reduced significantly (depending on the number of Attrs). Its downfall comes as confusing queries*, possible case-specific type casting and no code-completion for the value field for each Attr-like model.
* by confusing, I mean that the option for simplified m-n queries in prisma is functionally disabled when using a custom join table (e.g. EntityAttr)
Solution 2 has its merits where the generated code results in more strongly typed code generation for the value field, however it falls in the number of generated tables (I don't actually know if more tables is a good thing or a bad thing, all I think is that if you have similar values, they ought to be in the same table).
What would you do in my shoes?
I was looking pretty long for an appropriate answer and found it here.
I'm not sure if it could be applied to your question, but this is question about prisma and polymorphism, so I think this code snippet might be useful for developers:
model Photo {
id Int #id #default(autoincrement())
likes Like[] #relation("PhotoLike")
}
model Video {
id Int #id #default(autoincrement())
likes Like[] #relation("VideoLike")
}
enum LikableType {
Photo
Video
}
model Like {
id Int #id #default(autoincrement())
Photo Photo? #relation("PhotoLike", fields: [likableId], references: [id], map: "photo_likableId")
Video Video? #relation("VideoLike", fields: [likableId], references: [id], map: "video_likableId")
likableId Int
likableType LikableType
}
Resuling relations in dbdocs:
Sometimes the use case can't be generalized to abstract and have a typing's.
if you control them and has a limited attribute sure you can create each attribute as a separate table each has it is own schema.
Some Times more freedom is needed or the blocks are dynamic.
Use Case: Build A Block Document Editor Like 'notion.so' and you want to let the user create custom blocks or configure them.
you can do it like :
model Document {
id String #id
blocks Block[]
}
model Block {
id String #id
value Json
index Int
customConfig Json?
document Document? #relation(fields: [documentID], references: [id])
documentID String?
blockType BlockType #relation(fields: [blockTypeID], references: [id])
blockTypeID String
}
model BlockType {
id String #id
name String
config Json
blocks Block[]
}
where config and custom config can contains html,custom css classes, link attribute color or anything.
using type script you can create block.types.ts and add different let say templates for the config's .
I hope that I was useful to you, To sum it, it depends on the requirements :>)
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"
}
Given this two entities:
post post_category
- id - post_id
- title - name
- text
I'd like to make this query using jpa criteria query:
select * from post
where post.id in (
select post_id from post_category
where name = '<category1>' and name = '<category2>' ... and name = '<categoryN>')
Looking at your query sketch I think it will not return any results. I have changed it to mean name in ('<category1>','<category2>', ...) instead. Which may not match your case.
This assumes that you have your mapping set up properly on your classes for JPA to work. For example
class PostCategory {
private String name;
private Post post;
...
#ManyToOne
public Post getPost() {
return post;
}
...
In which case your DAO code would resemble this (given a List nameValues object)
// Set up the subquery
DetachedCriteria dc = DetachedCriteria.forClass(PostCategory.class)
.setProjection(Projections.property("post.id"))
.add(Restrictions.in("name", nameValues));
Criteria crit = session.createCriteria(Post.class)
.add(Subqueries.eqProperty("id", dc));
But if you want a list of Post objects which have ALL of the categories then you will need to make a separate DetachedCriteria instance for each category and add each as an AND'ed Subquery.