I have created simple design to ease explanation, here is the UML :
here is the Attachment class code :
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING)
#DiscriminatorValue("Attachment")
#Table(name = "ATTACHMENTS")
public class Attachment {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="ID")
private long id;
#ManyToOne
#JoinColumn(name = "USER_ID")
private User user;
...
}
and for the BookAttachment class :
#Entity
#DiscriminatorValue("BookAttachment")
public class BookAttachment extends Attachment{
#ManyToOne
#JoinColumn(name="Book_ID")
private Book book;
...
}
My question here, how can I do the bidirectional relation in the Book class ? should it be done like this? ( it's not polymorphism and not sure if it's good design )
public class Book{
#OneToMany(mappedBy = "book", cascade = CascadeType.PERSIST)
private List<BookAttachment> bookAttachments;
...
}
or the following :
I don't know yet how can I use the mappedBy as the attachment class don't have relation to Book.
public class Book{
#oneToMany
private List<Attachment> bookAttachments;
...
}
is not using polymorphism in this example good? Book class shouldn't be related to userAttachment. ( The example is just to provide a clear understanding of how the design will be. it's not a real one, so I need to understand if not using polymorphism, in this case, is good or bad design).
In my opinion there's no a correct way... you can even have both options implemented in your Book class in the same time, the proper one depends on your requirements.
If I have to choose one I would select the second one since it's more general and modular.
But, for sure, in your second solution the name of attribute is wrong! It should be attachments instead of bookAttachments.
Related
I am using Spring Boot and Jackson and Hibernate to create an API. Hibernate connects to a MySQL database.
I understand the good practices but I'm stuck on a particular point.
I have an n:m relationship that contains an extra field.
Ex: Author(id, ...) -> Written(idAuthor, idBook, date) <- Book(id, ...)
I understand how to map a traditional n:m relationship, but this technique does not apply to me this time.
For this, I found a source on the internet that showed the solution: create an intermediate class in my code that contains an Author type object and a Book type object + my additional fields.
#Entity
#Table(name = "Author")
public class Author implements Serializable {
/...
#Id
#GeneratedValue
private int id;
#OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
private Set<Written> written= new HashSet<>();
/...
}
#Entity
#Table(name = "Book")
public class Book implements Serializable{
/...
#Id
#GeneratedValue
private int id;
#OneToMany(mappedBy = "book", cascade = CascadeType.ALL)
private Set<Written> written= new HashSet<>();
/...
}
public class Written implements Serializable {
#Id
#ManyToOne
#JoinColumn(name = "idAuthor")
private Author author;
#Id
#ManyToOne
#JoinColumn(name = "idBook")
private Book book;
//Extra fields ....
}
That's a bidirectional link.
With this code, I get an infinite recursivity error:
Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: java.util.ArrayList[0]->com.exampleAPI.api.model.Book["written"])]
I tried to use #JsonIgnore, #JsonManagedReference and #JsonBackReference on the Written class, also tried to use transient keyword, but nothing worked.
I can't find any source on the internet that could help me, and neither can the documentation for this particular case.
Can someone help me?
When unhandled bidirectional relationship occurs, Jackson faces infinite recursion.
I tried to use #JsonIgnore, #JsonManagedReference and #JsonBackReference on the Written class
You need to use #JsonManagedReference and #JsonBackReference annotations separately to prevent these cycles between Book and Written. A side note, transient has nothing to do with the persistence but the serialization. JPA works with the #Transient annotation.
public class Book implements Serializable {
#OneToMany(mappedBy = "book", cascade = CascadeType.ALL)
#JsonBackReference
private Set<Written> written= new HashSet<>();
...
}
public class Written implements Serializable {
#Id
#ManyToOne
#JoinColumn(name = "idBook")
#JsonManagedReference
private Book book;
...
}
Important: Don't send database entities through REST (probably what you are up to do). Better create a DAO object without bidirectional relationship and map entities into DAOs. There are several libraries able to do that: I highly recommend MapStruct, however ModelMapper is also an option. If there is a lower number of such entities, using constructors/getters/setters would be enough.
I'm having trouble about having to use a single DTO object or a DTO object for every Entity object.
For example I have 3 classes: Book, Author and Publisher.
Book.java
#Entity
#Table(name = "tbl_book")
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "description")
private String description;
other different fields...
}
Author.java
#Entity
#Table(name = "tbl_author")
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "description")
private String description;
other different fields...
}
Publisher.java
#Entity
#Table(name = "tbl_publisher")
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "description")
private String description;
other different fields...
}
and MyDTO.java
public class MyDTO {
private Long id;
private String description;
constructor id, description...
getters and setters...
}
I want to select only specific fields (id and description) with EntityManager:
em.createQuery("SELECT NEW MyDTO(id,description) FROM Book");
But my question is should I use a single DTO object(which is MyDTO) for all projection? Something like:
em.createQuery("SELECT NEW MyDTO(id,description) FROM Author");
em.createQuery("SELECT NEW MyDTO(id,description) FROM Publisher");
Tutorials about using DTO for projection just saying use DTO for read and use Entity for write but they are not telling about having a single object DTO or not. Will you please provide an example why single DTO or why DTO for every Entity. Thank you!
I've been working with DTOs for a while now, and here's some things I can share:
Try building a DTO hierarchy based on your needs (abstract classes and/or interfaces)
Even if your BookDTO 'just' extends a superclass (AbstractIdDescriptionDTO for instance), it's always more readable (IMO), and it's less tedious to maintain, and you can't misinterpret an object for another
I never use the new DTO syntax, I implemented an automatic converter instead
In my opinion create a DTO for each field. Having a single DTO couples your objects a lot. For example if in Book class you change the description to something else all your code will not work anymore.
Even if you start with thinking of a good name for the DTO you will see the problem. "MyDTO" doesn't sounds good and doesn't show its purpose but you won't be able to think of a meaningful name.
And last but not least there is the single responsibility principle that is a good thing and having one DTO to present multiple objects kind of breaks it. It's not a good practice and breaks the SOLID principle to have one class that represents different types of objects. If your projects share some common properties it's better to have separate classes and create an Interface for the common part. That's just standard OOP design. It's not related to DTOs
That's just my opinion. I might be wrong.
Also creating the DTOs like that in the JPQL query kind of makes it hard coded and harder to maintain. You can do something like:
.setResultTransformer( Transformers.aliasToBean( PostDTO.class ) )
for your query or there are other ways to do it.
I'm using Play Framework 2.4. I want to model a set of classes with this inheritance chain:
Research <- Publication <- Article
The problem is, I would like the Publication class to refer to the other objects of this class, like this:
#MappedSuperclass
public abstract class Research extends Model {
#Id
public Long id;
}
#Entity
public class Publication extends Model {
#ManyToOne
public Publication translationOf;
}
This works when I don't inherit from Publication.
When I add inheriting from Article:
#MappedSuperclass
public abstract class Publication extends Model {
#ManyToOne
public Publication translationOf;
}
#Entity
public class Article extends Publication { }
I get:
java.lang.RuntimeException: Error with association
to [class models.research.Publication]
from [models.research.publication.Article.translationOf].
Is class models.research.Publication registered?
I thought that maybe configuring the InheritanceType explicity will help, but adding #Inheritance tag causes NullPointerException without any other informative nested exceptions in the stacktrace. This happens for example in this case:
#MappedSuperclass
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "dtype", discriminatorType = DiscriminatorType.STRING)
public abstract class Research extends Model {
#Id
public Long id;
public String dtype;
}
#Entity
#DiscriminatorValue("P")
public class Publication extends Model {
#ManyToOne
public Publication translationOf;
}
In your first example, Publication is an #Entity. After that it's suddenly a #Mappedsuperclass
I don't understand what you want here. Do you want one publication to be linked to other publications? Because that's a ManyToMany relationship.
And for ManyToMany relationships, you need to specify the "mappedBy" argument.
Same for articles and publications. One Article could appear in many Publications and many publications could have the same article.
Unless it has to be specifically different for your setup?
EDIT: Played around with it a bit, this setup works:
#MappedSuperclass
public abstract class Research extends Model {
#Id
public Long id;
}
#Entity
public class Publication extends Research {
#ManyToMany(cascade = CascadeType.ALL, mappedBy = "myReferences")
// What other publications refer to this one?
public List<Publication> referencedBy;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(
name = "CROSS_REFERENCE",
joinColumns = {
#JoinColumn(name = "MY_ID")
},
inverseJoinColumns = {
#JoinColumn(name = "REFERENCER_ID")
})
// What publications am I referring to?
public List<Publication> myReferences;
// The list of articles in this publication.
#ManyToMany(cascade = CascadeType.ALL)
public List<Article> articles;
}
#Entity
public class Article extends Research {
// The publications this article appears in.
#ManyToMany(cascade = CascadeType.ALL, mappedBy = "articles")
public List<Publication> publications;
}
ManyToMany relations always need have an "owner".
In the publication-to-publication case, it is the myReferences field. This is why the "referencedBy" field has the "mappedBy" argument in the #ManyToMany annotation.
A publication can't know ahead of time what future publications will reference them and past publications won't change to reference this new one.
So you can only ever say of a new publication which old ones it is referencing.
And which articles it contains.
You can add in both directions, but it is generally a best practice to use 1 direction and stick to it.
Hope it helps.
I have searched and found similar issues, but they don't quite seem to be the same problem as
Why am I getting this NullPointer exception?
OneToOne Mapping with hibernate/JBoss/Seam
ANN-613 - NPE when mappedBy property is wrong on a #OneToOne
ANN-558 - #OneToMany(mappedBy="") can not recognize properties in parent classes
Hibernate Users - NPE with #Id on #OneToOne
I have a few entities mapped like this:
Person
|
+--User
I want to add a new entity PersonPartDeux with a OneToOne mapping to Person. The resulting mapping should look something like this:
Person + PersonPartDeux
|
+--User
When I do so, a NullPointerException is thrown while trying to load the mapping:
java.lang.NullPointerException
at org.hibernate.cfg.OneToOneSecondPass.doSecondPass(OneToOneSecondPass.java:135)
How do I specify the mapping so I can avoid this exception?
Here's my code:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class Person implements Serializable
{
#Id
#GeneratedValue
public Long id;
#Version
public int version = 0;
public String name;
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
public PersonPartDeux personPartDeux;
}
#Entity
public class PersonPartDeux implements Serializable
{
#Id
#GeneratedValue(generator = "person-primarykey")
#GenericGenerator(
name = "person-primarykey",
strategy = "foreign",
parameters = #Parameter(name = "property", value = "person")
)
public Long id = null;
#Version
public int version = 0;
#OneToOne(optional=false, mappedBy="person")
public Person person;
public String someText;
}
#Entity
#PrimaryKeyJoinColumn(name = "person_Id")
public class User extends Person
{
public String username;
public String password;
}
As for why I'm bothering, I need both the inheritance and the OneToOne mapping to solve different known issues in my application.
Attach the Hibernate source to your project, so you can click thru or 'Open Type' (Ctrl-Shift-T in Eclipse) to view the OneToOneSecondPass source.
Seeing the source, will give you a clear indication as to what needs to be specified.
In my source (Hibernate 4.1.7), line 135 is
propertyHolder.addProperty( prop, inferredData.getDeclaringClass() );
However you're probably using an earlier version.
Looking at the mappings, I'm suspicious of the #OneToOne definition -- mappedBy="person".
#OneToOne(optional=false, mappedBy="person")
public Person person;
What does it usefully mean, to map an association property by itself? Hibernate already knows the property is a OneToOne -- you just told it that.
Pointing the underpinning mapping/ FK of the property, at itself.. probably isn't actually telling Hibernate any correct or useful information.
Here's an example from the HB dosc, perhaps showing better how to do what you want:
#Entity
class MedicalHistory implements Serializable {
#Id Integer id;
#MapsId #OneToOne
#JoinColumn(name = "patient_id")
Person patient;
}
#Entity
class Person {
#Id #GeneratedValue Integer id;
}
Source: http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html_single/
(3.5 docs off JBoss site.)
Cheers, hope this helps.
I am designing the persistence repository for an app.
I am new to Hibernate+JPA2 and I am having trouble creating more complex relationships in this case a Foreign mandatory key.
An example (just wrote on notepad, so it's not exactly this.)
I have a Top Class called Person which can hold several Posts (another class).
If I map my top class like this
#Entity
#Table(name="tb_people")
public class Person{
#Id
#GeneratedValue
public long id;
#OneToMany(mappedBy="person")
List<Post> listOfPosts;
.
. more code
.
}
#Entity
#Table(name="tb_posts")
public class Post{
#Id
#GeneratedValue
public long id;
#ManyToOne
#JoinColumn(name = "person_id")
Person person;
.
.more code
.
}
How can I using annotations make the person field in Post mandatory ?
I tryed with #Column(nullable=false) but I get an exception telling me I cannot use that annotation on a #ManyToOne Collection.
Thank you !
You have to use #JoinColumn(name=..., nullable=false) not #Column
See the complete API
Or you can just use #NotNull from javax.validations.constraints package.
It should be enough to just use #ManyToOne(optional = false)