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);
Related
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.
I have two objects. The company that can have multiple nested addresses.
#Entity
#Data
#Table(name = "company")
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "phone")
private String phone;
#OneToMany(mappedBy = "company", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private List<Address> addresses;
}
Address class looks like this:
#Data
#Entity
#Table(name = "address")
#ToString(exclude = "company")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "postal_code")
private String postalCode;
#Column(name = "city")
private String city;
#Column(name = "street")
private String street;
#JsonIgnore
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "company_id")
private Company company;
}
I want somehow if it's possible, make a dynamic query that searches through the nested collection property. I made a search method which uses example matcher but the result is wrong. Every time I got everything from DB, not only company with address postal code that I'm looking for.
My search method looks like this:
#PostMapping("/search")
public List<Company> search(#RequestBody final Company company){
return companyRepository.findAll(Example.of(company,
ExampleMatcher.matchingAny()
.withIgnoreNullValues()
.withIgnorePaths("id")
.withStringMatcher(ExampleMatcher.StringMatcher.STARTING)));
}
In my database, I have two objects and this is the result of the search:
As you can see I received everything from DB instead of the only first company which address postal code starts with 1.
Hi you can use Specification<T>
https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
For this you need to extend from interface JpaSpecificationExecutor:
public interface UserRepository extends JpaRepository<User> ,JpaSpecificationExecutor<User>{
}
And you also need to implement your custom Specification<T>
And then you can use repository.findAll(your impleneted Specification);
Spring docs :
https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/JpaSpecificationExecutor.html
I think this is helpful.
I need to join 2 tables into one object with some condition. I have the following:
#Entity
#Table(name = "polling")
public class Polling extends DomainIdObject {
#ManyToOne
#JoinColumn(name = "owner_id")
private Person owner;
#Column(name = "poll_name")
private String name;
#Column(name = "description")
private String description;
#ManyToMany(targetEntity = PollingSchedule.class, mappedBy = "polling", fetch = FetchType.EAGER)
private List<PollingSchedule> variants;
#Column(name = "start_time")
private LocalDateTime startTime;
#Column(name = "end_time")
private LocalDateTime endTime;
//getters and setters
#Entity
#Table(name = "polling_schedule")
public class PollingSchedule extends DomainIdObject {
#JoinColumn(name = "polling_id")
private Polling polling;
#Column(name = "poll_var")
private String pollingVariant;
//gettters and setters
but when I execute the following code:
Query query = getEntityManager().createNativeQuery("SELECT * FROM polling p WHERE p.id=1", Polling.class);
List list = query.getResultList();
List<PollingSchedule> variants = ((Polling) list.get(0)).getVariants();
the variants list is empty. Tables in DB looks following:
polling
|id|owner_id|poll_name|description|start_time|end_time|
polling_schedule
|id|polling_id|poll_var|
So, in result I want that Polling object contains only those PollingVariants, that have corresponding polling_id in polling_schedule table.
I've try use Filter, SecondaryTable annotations, but it`s not work for me (or I was incorrect use it).
I use hibernate4 and spring boot 1.5.1
Could anyone help me?
I think the relation between Polling and PollingSchedule is one-to-many (not many-to-many). And since you need a bidirectional relationship between those objects, you should change them like this:
Pooling.java
#Entity
#Table(name = "polling")
public class Polling extends DomainIdObject {
...
#OneToMany(mappedBy="polling")
private List<PollingSchedule> variants;
...
}
PoolingSchedule.java
#Entity
#Table(name = "polling_schedule")
public class PollingSchedule extends DomainIdObject {
#ManyToOne
#JoinColumn(name = "polling_id")
private Polling polling;
...
}
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 :)
I am developing a java-spring project that have domain classes(user,step,answer).Suppose the answer to the question is, step by step. Each step has its own text and image. One answer might be a several steps. A question has been answered by a specific user. Answer each of the separately stored in the database and also can be edit or remove from db. The best implementation for this example, how can it be?
public class Step{
private int step_no;
private String text;
private byte[] image;
//setter and getter
}
public class Answer{
private int id;
private question_id;
private String user;
private List<Step> steps;
//setter and getter
}
You are going to need to use an Object Relational Mapping tool such as JPA, Hibernate or, what fits better here, Spring Data. These tools will allow you to directly map your tables and columns into entities and attributes.
ORM works with both java annotations or xml configuration. For beginners I would suggest you use annotations.
The best way to map these classes would be for the User to hold a list of all the answers he has done and, at the same time, the answer will hold a list of all the steps that belong to it. Let me show you a quick example of how it could be done.
User:
#Entity
#Table(name = "<name in db>")
public class User {
#Id
private Integer userId;
#Column(name = "<column name in db>")
private String firstName;
#Column(name = "<column name in db>")
private String lastName;
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Answer> answers;
/* getters and setters */
}
Answer:
#Entity
#Table(name = "<name in db>")
public class Answer {
#Id
private Integer answerId;
#ManyToOne(fetch = FetchType.EAGER)
private User user;
#OneToMany(mappedBy = "answer", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Step> steps;
/* getters and setters */
}
Step:
#Entity
#Table(name = "<name in db>")
public class Step {
#Id
private Integer stepId;
#ManyToOne(fetch = FetchType.EAGER)
private Answer answer;
#Column(name = "<column name in db>")
private Object image;
#Column(name = "<column name in db>")
private String text;
/* getters and setters */
}
You can always use simple JDBC instead of powerful ORM tools to create the DB queries, but it is definitely recommended to take a look to the javax.persistence.* package and start from there.