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.
Related
I want to get data from multiple table.
public class Student{
private int id;
private String name;
private List<Course> course;
}
public class Course{
private int id;
private String name;
private int studentId;
}
I want to fetch data from student and course table using spring data jpa and map to student object.
How can I do that in efficient way?
#Data
#NoArgsConstructor
public class Student{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
#OneToMany(mappedBy="studentId",cascade=CascadeType.ALL,fetch=FetchType.Eager)
private Set<Course> course;
}
You May Use Set Instead of List.
Always Use Mapped By in OneToMany Side, If you use it manyToOne side it will create an
extra table.
You can use Fetch Type eager or lazy. By default, it is lazy with You have
to use #transactional of Lazy.
#Data
#NoArgsConstructor
public class Course{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
#ManyToOne
#JoinColumn(name="studentId")
private int studentId;
}
Hope this Answer Solve your query Happy Coding!.
Note that the starting point might be wrong. I assume that a student can choose multiple courses and a course can be chosen by multiple students. So it is actually a #ManyToMany relationship but not #ManyToOne or #OneToMany.
You will definitely need a joint table to map their primary keys from two tables into the joint table.
#Entity
#Data
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#EqualsAndHashCode.Exclude
#ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
#JoinTable(
name = "courses",
joinColumns = #JoinColumn(name = "student_id"),
inverseJoinColumns = #JoinColumn(name = "course_id"))
private Set<Course> courses;
}
#Entity
#Data
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#JsonIgnore
#EqualsAndHashCode.Exclude
#ToString.Exclude
#ManyToMany(mappedBy = "courses",fetch = FetchType.EAGER,cascade = CascadeType.ALL)
private Set<Student> students;
}
Note all the modifications I have here.
For the data persisted into database, Long is a better choice than int. Similarly, e.g., use Boolean instead of boolean.
Think the Student as the side managing the many-to-many relationship, and Course as the target side. On the target side, use #JsonIgnore and #ToString.Exclude annotations to avoid an infinite recursion, StackOverflow or OOM.
#JsonIgnore
#EqualsAndHashCode.Exclude
#ToString.Exclude
#ManyToMany(mappedBy = "courses",fetch = FetchType.EAGER,cascade = CascadeType.ALL)
Use Set instead of List if a student is not supposed to select the exact same course. It ensures that one can still select 2017 fall Maths and 2018 fall Maths, while one cannot select 2017 fall Maths twice.
I am writing an online store using Spring Boot (MVC) and hiberbate. I have an order class where I need a Сart link. But the problem is that in the database I do not have a specific Сart table, but there is a cart _products table, where the peimary key consists of two columns (as shown in the picture below!). I really need a connection in the Order class, so I decided to make a Composite Primary Key at the hibernate level (and I seem to have done it), but I can't figure out what to do next! I am stuck. Please tell me where to go? How can I solve my problem?
OrderClass:
#Entity
#Table(name = "pg_order")
public class Order {
// Fields
//
private #Id
#GeneratedValue
Long id;
private String address;
#Column(name = "phone_number")
private String phoneNumber;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "date_order")
private Date dateOrder;
#Enumerated(EnumType.STRING)
#Column(name = "order_status")
private OrderStatus orderStatus;
#Column(name = "total_cost")
private BigDecimal totalCost;
// Relationships
//
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
// #OneToMany
// #JoinColumn(name = "cart_products_pkey")
// private Cart cart;
}
Cart:
#Entity
#Table(name = "cart_products")
public class Cart {
#Embeddable
#NoArgsConstructor
#AllArgsConstructor
static class CartId implements Serializable {
private Long orderId;
private Long drinkId;
}
// Fields
//
#EmbeddedId
private CartId cartId;
#ManyToOne(optional = false)
#JoinColumn(name = "order_id")
private Order order;
#ManyToOne(optional = false)
#JoinColumn(name = "drink_id")
private Drink drink;
private int count;
}
If you need access to the 'DRINKS' for that order. You need to change the relation to Cart from the Order class.
You have commented a relationship where ORDER just have access to one CART, since you need to access N CARTS (One to Many) you need to add a SET. Something like this:
#OneToMany
#JoinColumn(name = "cart_products_pkey")
private Set<Cart> cartProducts;
Now the ORDER has a SET of CART.
You can easily access to the CARTS of that ORDER with order.getCartProducts()
And since CART has a key to DRINK, you can easily access it.
Hope this can help you.
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'm trying to have 2 fields of the same domain class in my entity and I'm getting this error:
org.hibernate.MappingException: Could not determine type for: com.packt.webapp.domain.User, at table: opinions, for columns: [org.hibernate.mapping.Column(author)]
My entities:
#Entity
#Table(name="opinions")
public class Opinion {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#NotNull
private String text;
#NotNull
private String date;
#ManyToOne
#JoinColumn(name="commented_user")
private User writtenTo;
private User author;
#Entity
#Table(name="user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String username;
private String password;
#OneToMany(mappedBy="writtenTo")
private List<Opinion> opinions;
I just want to map opinions to commented users and storage author of comment in author field. When I remove author field, everything works. Whats wrong with this example?
It's complaining that it doesn't know how to map the author field. You can provide a mapping similar to how you mapped writtenTo. An opinion has one author and an author can have authored many opinions.
If you would like to ignore a field for mapping, annotate it with #Transient. The transient annotation prevents that field from being persisted to the database otherwise you have to map it like so:
Opinion entity:
#Entity
#Table(name="opinions")
public class Opinion {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#NotNull
private String text;
#NotNull
private String date;
#ManyToOne
#JoinColumn(name="commented_user")
private User writtenTo;
// map author to User entity
#ManyToOne
#JoinColumn(name="authored_user")
private User author;
// getters and setters
}
User entity:
#Entity
#Table(name="user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String username;
private String password;
#OneToMany(mappedBy="writtenTo")
private List<Opinion> opinions;
// map opinions to the author
#OneToMany(mappedBy="author")
private List<Opinion> authoredOpinions;
// getters and setters
}
Try annotating also author?
#ManyToOne
#JoinColumn(name="author")
private User author;
Just apply the #ManyToOne annotation at both the User fields.
#ManyToOne
#JoinColumn(name="commented_user")
private User writtenTo;
#ManyToOne
#JoinColumn(name="author")
private User author;
But there is a more flexible solution to problems like this. Replace the #OneToMany and #ManyToOne relations by #ManyToMany ones. Create a User and a Role entity (with descendats for the ones with specific fields). A User could have many roles (writer, author, etc) and a Role could be played by many users. In this case you can change your mind and create/remove/attach/detach roles dynamically without any data structure changing on the existing tables.
#Entity
public class User
{
#Id
private Long id;
#Column
private String name;
#ManyToMany
#JoinTable(
name="User_Role",
joinColumns=#JoinColumn(name="UserID", referencedColumnName="ID"),
inverseJoinColumns=#JoinColumn(name="RoleID", referencedColumnName="ID"))
private List<Role> roles;
}
#Entity
public class Role
{
#Id
private Long id;
#Column
private String name;
#ManyToMany( mappedBy="roles" )
private List<User> users;
}
And you can get/check the user roles by role ids/names using a utility class:
puclic class RoleUtility
{
public Role getUserRoleByName( User user_, String roleName_ )
{
User retRole = null;
Iterator<Role> i = roles_.iterator();
while ( ( retRole == null ) && i.hasNext() )
{
Role role = (Role) i.next();
if ( roleName_.isEqual( role.getName ) )
retRole = role;
}
return retRole;
}
}
The client code to checking a role:
User user = ...
Role role = RoleUtility.getRoleByName( user.getRoles(), roleName );
Stay at you example with this solution you can add a censor/moderator to the opinion or something like that without any data structure changing.
#Entity
#Table(name="User")
public #Data class User
{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="ID")
private long userId;
#Column(name="FIRSTNAME")
private String firstName;
#Column(name="MIDDLENAME")
private String middleName;
#Column(name="LASTNAME")
private String lastName;
//#JsonIgnore
#OneToMany(mappedBy="user",cascade = CascadeType.ALL)
private List<Dependent> dependents;
//#JsonIgnore
#OneToMany(mappedBy="user",cascade = CascadeType.ALL)
private List<Claim> claims;
}
This is my User class. I want to fetch only dependents when querying for User at some point. I tried to use FetchType.EAGER. It worked fine. But at the same time it is fetching claims also. If I use lazy loading, it is giving "Could not write content: failed to lazily initialize a collection of role" exception.
I also tried #JsonIgnore, but its not solution for my problem. Please help to solve this.