Cycle detect, XmlTransient and unmarshalling - java

I have two class:
Restaurant
public class Restaurant implements Serializable {
#Id
#GeneratedValue
private long id;
...
#OneToMany(mappedBy = "restaurant", fetch = FetchType.EAGER)
private Set<Comment> comments;
}
Comment
public class Comment implements BasicVO, Serializable {
#Id
#GeneratedValue
private long id;
...
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "restaurant_id", referencedColumnName = "id")
private Restaurant restaurant;
#XmlTransient
public Restaurant getRestaurant() {
return restaurant;
}
}
I used annotation #XmlTransient to prevent "Marshalling Error: A cycle is detected in the object graph.", but how can I retrieve back information about restaurant field in comment class? When I try to pass Comment object by WebService information about restaurant is of course lost (because of XmlTransient). How can I get this information back?

If I understand question correctly, you can just find comment by id.
For example
Restaurant restaurant = entityManager.getReference(Comment.class, id).getRestaraunt();
Of course it requires some logic on your server side.

Related

Hibernate many to many with an additional column without changing HQL

In the existing project we have a many-to-many relationship, it turned out that now we need to keep the time when that connection happed. The following approach has been used
https://vladmihalcea.com/the-best-way-to-map-a-many-to-many-association-with-extra-columns-when-using-jpa-and-hibernate/
Now we have these classes (the names are just taken from the link above):
#Embeddable
public class PostTagId
implements Serializable {
#Column(name = "post_id")
private Long postId;
#Column(name = "tag_id")
private Long tagId;
}
#Entity
#Table(name = "post_tag")
public class PostTag {
#EmbeddedId
private PostTagId id;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("postId")
private Post post;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("tagId")
private Tag tag;
#Column(name = "created_on")
private Date createdOn = new Date();
}
#Entity
public class Tag {
#Id
#GeneratedValue
private Long id;
private String name;
}
#Entity
public class Post {
#Id
#GeneratedValue
private Long id;
private String title;
#OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<PostTag> tags = new ArrayList<>();
}
Since it is a big project and there are tons of places to change, the Post class still has methods like getTags, setTags, removeTags, etc.
List<Tag> getTags() {
return tags.stream().map(PostTag::getTag).collect(collectingAndThen(Collectors.toSet(), Collections::unmodifiableSet))
}
void addTag(Tag tag) {
tags.add(new PostTag(this, tag, new Date())
}
It allows to hide from the outer world that collection is changed from <Tag> to <PostTag>. The problem happens with buzillion HQL queries that we have at the moment, since smth like this won't work:
...
JOIN post.tags t
WHERE t.name = <?>
Because "t" is now PostTag and not Tag, hence t.name is trying to access an unknown field
Are there any ways to mitigate that problem? Add some annotations above PostTag, so that instead of accessing tag.name hibernate will do t.tag.name, use other ways of mapping, etc.

JPA Entity Class using class object as parameter

I have a class User which has a parameter of another class type ShoppingList.
As this...
#Entity
public class Employee implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(nullable = false, updatable = false)
private Long id;
private String Name;
#?????
private ShoppingList[] shoppingList;
}
How can i make this ManyToOne relationship while the variable being an array?
The idea is to have a User table and another ShoppingList table, so the user can have multiple lists at the same time.
This would be the correct way:
One Employee has many ShoppingLists.
One ShoppingList has only one Employee.
#Entity
public class Employee implements Serializable {
....
#OneToMany(mappedBy = "employee", fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
private List<ShoppingList> shoppingList;
....
}
#Entity
public class ShoppingList implements Serializable {
....
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "employee_id", nullable = false)
private Employee employee;
....
}
You can fine-tune your entities as per your need.
For more info, I would refer to this tutorial, it has helped me a lot.

Persisting object does not persist all its relational objects

I am have three entities with relations look like this:
Customer and Order: one-to-many
Order and Article: one-to-many
Here are my Java Classes:
#Entity
#Table
public class Order implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "order_id")
private long id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "customer_id", nullable = false)
private Customer customer;
#OneToMany(mappedBy = "order")
private List<Article> orderedArticles;
}
#Entity
#Table
public class Customer implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "customer_id")
private long id;
#OneToMany(mappedBy = "customer")
private List<Order> orders;
}
#Entity
#Table
public class Article implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "article_id")
private long id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "order_id", nullable = false)
private Order order;
}
And then I persist the order to the database:
Customer customer = createCustomer()
List<Article> articles = createArticles();
Order order = new Order(customer, articles)
entityManager.persist(order);
The order and customer were successfully persisted but strangely that the article not. Can anyone help me here what did i do wrong? How can I make one call to persist the order and parallel the customer and article will be also persisted?
Thank you very much!
Try change
#OneToMany(mappedBy = "order")
private List<Article> orderedArticles;
to
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<Article> orderedArticles;
As it mentioned in this article:
Cascading only makes sense for Parent – Child associations (the Parent entity state transition being cascaded to its Child entities). Cascading from Child to Parent is not very useful and usually, it’s a mapping code smell.
So, for example your Order - Article association should be corrected in this way:
#Entity
#Table
public class Order implements Serializable {
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<Article> orderedArticles;
}
#Entity
#Table
public class Article implements Serializable {
#ManyToOne // CascadeType.ALL should not be used here
#JoinColumn(name = "order_id", nullable = false)
private Order order;
}
The same correction should be made for the Customer - Order association.
You use bidirectional #OneToMany. So, as documentation states:
Whenever a bidirectional association is formed, the application developer must make sure both sides are in-sync at all times.
For example your Order entity should have the following methods:
#Entity
#Table
public class Order implements Serializable {
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<Article> orderedArticles;
public void addArticle(Article article) {
orderedArticles.add(article);
article.setOrder(this);
}
public void removeArticle(Article article) {
orderedArticles.remove(article);
article.setOrder(null);
}
}
to make the bidirectional association Order - Article in-sync. The same correction should be made for the Customer - Order association.
Assuming that your Order and Customer entities have the appropriate helper methods, the valid example of persisting can look like this:
Article article1 = new Article();
// ...
Article article2 = new Article();
// ...
Order order = new Order();
order.addArticle(article1);
order.addArticle(article2);
Customer customer = new Customer();
customer.addOrder(order);
entityManager.persist(customer);
So, you should start from articles creation, then add them to the order (or orders), then add your order (or orders) to the customer entity and then persist the customer. Due to the usage of CascadeType.ALL all children entities will be persisted too.

Java JPA how relate an entity instance with all instances of another entity?

I work with an embedded H2 database in which I use the #OneToMany relationship to relate an entity instance (product) to multiple instances of the other entities (suppliers); it's useful when I have specific suppliers for a particular product.
However now, I want to associate all the suppliers with every single product; I don't want to generate in my supplier table different supplier records for each product, instead I want to have only 5 records (5 suppliers) in my supplier table which are associated to every single product, it few words I want to achieve something like "one to all", is it possible to do it using JPA annotations?
Product entity
#Entity
public class Product {
#Id
private String productCode;
#OneToMany
#JoinColumn(name = "supplier_id", referencedColumnName = "productCode")
private List<Supplier> suppliers;
}
Supplier entity
#Entity
public class Supplier {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
private String name;
}
Unidirectional #OneToMany association:
#Entity
public class Product {
#Id
// #Column(name = "id") maybe
// #GeneratedValue maybe
private String productCode;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) // according to your need
private List<Supplier> suppliers;
...
}
And,
#Entity
public class Supplier {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
...
}
#ManyToOne association:
#Entity
public class Product {
#Id
// #Column(name = "id") maybe
// #GeneratedValue maybe
private String productCode;
...
}
And,
#Entity
public class Supplier {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
#JoinColumn(name = "product_id", foreignKey = #ForeignKey(name = "PRODUCT_ID_FK"))
private Product product;
private String name;
...
}

When having a bidirectional relationship, how to avoid a field only when accessing from one side of the relationship

I have two classes :
#Entity
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Client {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "client_id")
private Long id;
private char gender;
#OneToMany(mappedBy = "client", cascade = CascadeType.ALL)
private List<SurveyData> survey = new ArrayList<SurveyData>(); }
And SurveyData:
#Entity
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class SurveyData{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "SURVEY_ID")
private Long Id;
private double score;
#ManyToOne
#JoinColumn(name = "client_id")
public Client client; }
This way when I access SurveyData, I get Client model as a result too. And when I access Client, I get SurveyData too.
But my problem is this.
I have a query that returns SurveyData Based on a condition, so say I would have this as a JSON Result:
{id:1
client:
{name: Name,
lastName: last,
survey:[{
score: 10,
///
}]
How do I avoid getting the survey List as a result when accessing from SurveyData.
I cannot use JsonIgnore, because I have other methods where when I access Client model, I will need to be able to access survey model using the List.
Is there a way to do this ?
So, basically, only when calling SurveyData, I need to ignore the List survey in the Client class..else I need to use it.
Please have a look at #JsonView. In the Message class sample, it provides a nice example using #JsonView(View.Summary.class) and #JsonView(View.SummaryWithRecipients.class), which is exactly what you're looking for.
public class View {
interface Summary {}
interface SummaryWithRecipients extends Summary {}
}
public class Message {
#JsonView(View.Summary.class)
private Long id;
#JsonView(View.Summary.class)
private LocalDate created;
#JsonView(View.Summary.class)
private String title;
#JsonView(View.Summary.class)
private User author;
#JsonView(View.SummaryWithRecipients.class)
private List<User> recipients;
private String body;
}
Now, depending on the annotation, it will either include or exclude the list of recipipients.
// excludes list of recipients
#JsonView(View.Summary.class)
#GetMapping("/")
public List<Message> getAllMessages() {
return messageService.getAll();
}
// includes list of recipients
#JsonView(View.SummaryWithRecipients.class)
#GetMapping("/with-recipients")
public List<Message> getAllMessagesWithRecipients() {
return messageService.getAll();
}

Categories

Resources