I'm really new to Hibernate and this is my first app which is enterprise level.
I'm stuck at one to many mapping. I did the mapping whole day but it doesn't give to correct table structure for me.
Here is the ER diagram I want to map
These are the Classes
Feed Class
#Entity
public class Feed {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
private String name;
private long quantity;
//With getters and setters
}
Feed Order Details Class
#Entity
public class FeedOrderDetail {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private double unitPrice;
private long quantity;
#OneToMany(cascade=CascadeType.MERGE)
#JoinColumn(name="feed_id")
private List<Feed> feed = new ArrayList<Feed>();
//Getters and Setters
}
after deploying app I get the following table structure
My problems are
why feed_id is in Feed table?
Should I add same feed every time I add a feed order detail? (it isn't a good idea)
I can achieve this by moving #OneToMany annotation and attributes to Feed table. but if I move it to Feed class how can I represent feed order details in JSP pages?
I'm using spring with this project also.
I would suggest to have a object reference on both sides of the relationship.
So have a reference to List<FeedOrderDetail> in Feed and have a single refernce to a Feed in your FeedOrderDetail.
Class Feed:
#Entity
public class Feed {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
private String name;
private long quantity;
//With getters and setters
// !new!
#OneToMany(mappedBy = "feed") // mappedBy references the fieldname in the other Java class
private List<FeedOrderDetail> details;
}
Class Detail:
#Entity
public class FeedOrderDetail {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private double unitPrice;
private long quantity;
#ManyToOne(cascade=CascadeType.MERGE)
#JoinColumn(name="feed_id")
private Feed feed; // only a single feed reference
//Getters and Setters
}
If you want to get a list of feeds using Hibernate's JPA API, then you can use this code:
TypedQuery<Feed> query = entityManager.createQuery("SELECT f FROM Feed f", Feed.class);
List<Feed> feeds = query.getResultList();
You designed it backwards. You schema says that 1 Feed instance includes M FeedOrderDetail instances.
So, in the class Feed, you should have a List<FeedOrderDetail>. But that's not what you've done. You have a List<Feed> in FeedOrderDetail.
The correct one is:
#Entity
public class Feed {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
private String name;
private long quantity;
//With getters and setters
#OneToMany(cascade=CascadeType.MERGE, mappedBy="feed")
private List<FeedOrderDetail> orders = new ArrayList();
}
And
#Entity
public class FeedOrderDetail {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private double unitPrice;
private long quantity;
#ManyToOne(cascade=CascadeType.MERGE)
#JoinColumn(name="feed_id")
private Feed feed;
//Getters and Setters
}
My problems are why feed_id is in Feed table?
You are right, it should not be in case your ER diagram is right.
Your ER diagram and table structure does not match, so I am trying to give you class mapping according to your ER-diagram.
Feed.java
#Entity
public class Feed {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
private String name;
private long quantity;
//With getters and setters
#OneToMany(fetch = FetchType.LAZY, mappedBy = "feed")
public Set<FeedOrderDetail> getFeedOrderDetail() {
return this.feedOrderDetail ;
}
}
FeedOrderDetail.java
#Entity
public class FeedOrderDetail {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private double unitPrice;
private long quantity;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "FEED_ID", nullable = false)
public Feed getFeed() {
return this.feed;
}
//Getters and Setters
}
I hope it helps :)
Related
I have a Product :
#Data
#Entity
#Table(name = "products", schema = "laboratory", catalog = "laboratory")
#JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
public class Product {
#Id
#GeneratedValue
private int id;
#ManyToOne(fetch = FetchType.LAZY, cascade= CascadeType.ALL)
#JoinColumn(name = "project_id")
#Transient
private Project project; // this one is for read only
#Column(name="project_id") // this will save the id in db for the project
private int projectId;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="id")
private Inspection inspection;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="id")
private Information information;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="id")
private Departure departure;
private String un;
private String tc;
}
There is 3 class that this product needs in order to be a Product : Information, Inpection, Departure
All 3 of these classes are similar.
I want to link them by the Product.id witch is a #GeneratedValue AI in sql.
Here is one of the 3 class :
Information
#Data
#Entity
#Table(name = "products_informations", schema = "laboratory", catalog = "laboratory")
#JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
public class Information {
#Id
private int productId;
private String description;
private String model;
private int year;
private String serialNumber;
private int odometre;
private int noCrochet;
private int nbKeys;
private String localisation;
private String cemeteryPosition;
#JsonFormat(pattern = "yyyy-MM-dd")
private Date receptionDate;
}
I want, WHEN I save() the product, that the private String productId in this class to automatically take the Id from the Product class without having to do it manually in my controller.
You have the mappings backwards in your model.
By using
public class Product {
#Id
#GeneratedValue
private int id;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="id")
private Information information;
You've told JPA to use the PRODUCT.ID primary key as a foreign key to the Information table; foreign keys are controlled by the relationship, so it means your ID value is pulled from the information.productId value. Opposite of what you are asking for and it means you have 4 mappings trying to set the PRODUCT.ID column value (set them different and see for yourself).
Try this instead:
public class Product {
#Id
#GeneratedValue
private int id;
#OneToOne(mappedby="product", cascade = CascadeType.ALL)
private Information information;
..
}
public class Information {
#Id
private int productId;
#MapsId
private Product product;
..
}
With this you will need to set the Information.product reference, but JPA will use that to set your productId value, using the one you set within the product.id property. You just need to set this relationship when you add an Information instance to a product. Do the same for the other relationships
I am currently learning hibernate and created a simple database that is simulating a library:
I got 3 classes: Book, Customer, Order:
#Entity
public class Book
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private int ISBN;
private String name;
private String autorName;
private double price;
//Getters setters etc...
}
#Entity
public class Customer
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private String lastName;
private String city;
//Getters setters etc...
}
#Entity
#Table(name = "TB_order")
public class Order
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private Date date;
private String status;
#OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
#JoinColumn
private List<Book> books;
#ManyToOne(cascade = CascadeType.ALL)
private Customer customer;
//Getters setters etc...
}
When I try to change some order by saveOrUpdate() (eg. change the book that customer ordered) I create new instance of Order and I pass it to saveOrUpdate() method:
Order o = new Order(Date.valueOf("2019-06-01"),"old",bookslist.subList(2,3), customerslist.get(0));
o.setId(1);
session.saveOrUpdate(o);
It does not only change values in table order, but it also create new entities in table book and table Customers. I guess it is like this because i put the list of books and customer instace created in the main method.
So what instance of Order object shall be passed into the saveOrUpdate() method to only change the values in table Order to already existing values from database?
I've got four entities in my project:
Topic, Article, Comment and Notification.
One topic can have multiple articles.
One article can have multiple comments.
You can add multiple notifications for comment.
Those are my entities:
#Entity
#Table(name = "topics")
public class Topic {
#Id
private Integer id;
private String name;
private String description;
#OneToMany(mappedBy = "article")
private List<Article> articles;
//getters & setters ommited
}
#Entity
#Table(name = "articles")
public class Article {
#Id
private Integer id;
private String name;
private String description;
#ManyToOne
#JoinColumn(name = "topic_id")
private Topic topic;
#OneToMany(mappedBy = "comment")
private List<Comment> comments;
//getters & setters ommited
}
#Entity
#Table(name = "comments")
public class Comment {
#Id
private Integer id;
private String name;
private String description;
private LocalDateTime createDate;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "article_id")
private Article article;
//getters & setters ommited
}
And last one:
#Entity
#Table(name = "notifications")
public class Notification {
#Id
private Integer id;
private LocalDate date;
private String description;
#ManyToOne(optional = false)
#JoinColumn(name = "comment_id", nullable = false)
private Comment comment;
//getters & setters ommited
}
Now what I try to achive is to get set of topics with notifications between dates.
I even created a solution:
public Set<Topic> getTopicsWithNotificationsBetweenDates(LocalDate begin, LocalDate end) {
return notificationRepository.findByDateBetween(begin, end)
.stream()
.map(notification-> getTopic(notification)))
.collect(Collectors.toSet());
}
private Topic getTopic(Notification notification){
return notification.getComment().getArticle().getTopic();
}
But this solution loop through all notification to get topics(and obviously there are repetitions). Getting them from customer side would've save a lot of time and effort in case there would've be e.g. 100 notifications and only e.g. 5 topics.
Now what I'm trying to do is loop through topics instead of notifications, but I do not have idea how could I query should look like.
I will be grateful for even a small help or atleast a point to the right direction.
How about adding of a bi-directional relation between a Comment and Notification entities? Then you will be able to do what you want in a single query like this:
List<Topic> findByArticlesCommentsNotificationsDateBetween(begin, end);
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();
}
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.