JPA Collection with user defined order - spring

consider following model:
#Entity
#Getter
#Setter
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ElementCollection
#Column(name = "phone")
private List<String> phoneNumber = new LinkedList<>();
}
In front-end user should be able define order of phone numbers in this sample with drag and drop or something like that. Can you tell me what is the most effective way to handle this use case? I found solution with jpa annotation #OrderColumn which generates additional column. But I guess if I need re-order items, solution is delete all from collection and save it again with new order right? I afraid that is not very elegant solution. Can you give me your advice? Thank you.

Related

Optimal way of checking if user already upvoted/downvoted a comment on a post - Spring JPA

Post entity:
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToMany(mappedBy = "post")
private List<PostComment> postComments;
...
}
PostComment entity:
public class PostComment {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne
#JoinColumn(name = "post_id")
private Post post;
#OneToMany(mappedBy = "postComment")
private Set<PostCommentUpvote> postCommentUpvotes;
#OneToMany(mappedBy = "postComment")
private Set<PostCommentDownvote> postCommentDownvotes;
...
}
PostCommentUpvote entity (PostCommentUpvote and PostCommentDownvote have the exact same fields - these entities act like counters)
public class PostCommentUpvote {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne
#JoinColumn(name = "post_comment_id")
private PostComment postComment;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
...
}
All relations are bi-directional as you can see from the annotations.
The goal: When a user (authenticated) upvotes/downvotes a PostComment I want to do the following:
Check if user already upvoted/downvoted the PostComment.
For this I have Post id (even though this is not needed) and PostComment id and both are indexed.
There are three possible 'states' when User up/downvotes the comment:
User hasn't yet up/downvoted that comment, so it is either new upvote or new downvote
User has already upvoted and if he upvotes again, it will remove the upvote (same with downvote)
User has already upvoted and if he downvotes, upvote is removed and new downvote is added (and vice-versa)
What would be the most optimal way of doing this? Get the PostComment by its id and then loop through the List of PostCommentUpvote/PostCommentDownvote and check the User on every iteration? Or perform a tactical SQL request, which must be faster than looping in Java? If so, what would this SQL query look like? Or any other approach to make this performant. I am open to any suggestion.
Thanks
Assuming you have the post comment id and user id, the following JPA query (or close to it) will return true if the user has upvoted on the post comment and false otherwise:
select case when count(postCommentUpvote) > 0 then 'true' else 'false'
from PostCommentUpvote postCommentUpvote
join postCommentUpvote.postComment postCommnent
where postComment.id = :postCommentId
and user.id = :userId
You would then have to perform the same query using the PostCommentDownVote entity. An alternative would be to remove the up and down vote entities, simply create a PostCommentVote entity which has a boolean attribute that indicates up or down, and helper methods isUpvote() and isDownVote() that would interpret the boolean for you. You could get everything you need with a single query that returns a PostCommentVote if the user has up or down voted and null otherwise.
You did not indicate what you want to do if the user has already commented on the post; ignore the request or update the PostComment. Either way the most optimal way of doing this would be not checking at all. Create a unique index on (user_id, post_comment_id) or drop the the id column and make a composite PK of those columns. Then just insert without checking. Use the On Conflict to either ignore or update the request. You may also want to add an Up/Down vote indicator column.

Consuming sequence inside a embeddedId in springboot

I have the next issue -> I have a table on my db with a composite id...
Supose (Id1,Id2,Id3,Id4), the Id4 is generated by a sequence on the db...
My question is, in spring boot, I generate the entity 'Table1' and the corresponding 'Table1Id',
but when i want to add the corresponding GeneratedValue from the sequence, is not generating anything.
I was looking for in the internet and i found that the GeneratedValue is not working without the #Id anotation, but maybe there are some way to fix this issue.
Thank's and sorry for my english.
SOLVED:
When a composite id is required in your project, it is impossible with an embeddedId. It is necesary to use #IdClass on my compossiteId instead #EmbeddedId, because the second does not work with #GeneratedValues for example my solution was:
#Data
#Entity(name = "table_name")
#IdClass(CompositeIdTest.class)
public class TestClass implements Serializable {
#Id
#Column(name = "column", nullable = false)
private String column;
#Id
#SequenceGenerator(name = "sequence", sequenceName = "sequence", allocationSize = 1)
#GeneratedValue(generator = "sequence")
private int idGenerated;
Anyway, thank's

Need help regarding JPA entity mapping

I'm fairly new to ORM. I'm having trouble deciding how exactly I should map the following entities.
DiscussionThread
Post
User
AnonymousUser
DiscussionThread would be something similar to the ones we see in bulletin boards online. It would contain a list of Post which would be posted by User. However, I do not want the User to reveal his/her identity while posting in the DiscussionThread.
In order to achieve that I created a list of proxy usernames denoted by the entity AnonymousUser. Thus, whenever a User decides to make a Post in a DiscussionThread, he would be posting as an AnonymousUser. Any further Post made by the same User in that DiscussionThread would be linked to the same AnonymousUser.The User will have different AnonymousUser names in different DiscussionThreads. An instance of AnonymousUser may be used by two different users on two different threads.
In simpler words, there will be one AnonymousUser for one User in each DiscussionThread.
I have created the following POJO entities, but I'm stuck in how I should map them to each other.
public class AnonymousUser {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String displayPicture;
//Not sure how to make relationships here
private Set<DiscussionThread> discussionThreads;
private Set<User> users;
}
public class DiscussionThread {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
private String description;
}
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String email;
private String username;
}
Any help will be appreciated.
Thank you!
Well, you basically described:
Don't know if it's right or not but this is one way you could diagram and think about such problems. This is Chen's database notation in Visio.

Has Spring-boot changed the way auto-increment of ids works through #GeneratedValue?

Spring-Boot 2.0.0 seems to have modified the way Hibernate is auto configured.
Let's suppose two simple and independent JPA entities:
#Entity
class Car {
#Id
#GeneratedValue
private long id;
//....
}
#Entity
class Airplane {
#Id
#GeneratedValue
private long id;
//....
}
Prior, using Spring-Boot 1.5.10, I was able to generate separate sequences of auto-increments, meaning that I can get a Car with 1 as primary key and an Airplane with 1 as primary key too.
No correlation between them, e.g no shared sequence.
Now, with 2.0.0, when I sequentially create a very first Car then a very first Airplane, the car gets 1 as id and airplane gets 2.
It seems that he has to deal with the GeneratedType.AUTO, that is the "used by default" specified within the #GeneratedValue annotation source.
However, my reasoning seems to stop here since GeneratedType.AUTO was also set as default with the 1.5.10.
A simple workaround to fulfil my expectation is to specify the IDENTITY strategy type of generation like so:
#Entity
class Car {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
//....
}
#Entity
class Airplane {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
//....
}
I can't figure out an explanation of this behavior.
What has Spring-boot 2.0.0 changed, explaining this scenario?
Spring Boot 2.0 uses Hibernate 5.2 (https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes).
Hibernate changes its GeneratedType.AUTO strategy since 5.2. Any database that does not support sequences natively (e.g. MySQL), they use the TABLE generator instead of IDENTITY. (https://hibernate.atlassian.net/browse/HHH-11014)
That's why GeneratedType.AUTO does not work as you expected.
You can use
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
to use MySQL autoincrement.
If you are in need for a quick, not future-proof solution to prevent this issue from happening:
spring.jpa.hibernate.use-new-id-generator-mappings=false, as from the Spring Boot 2 docs:
spring.jpa.hibernate.use-new-id-generator-mappings= # Whether to use Hibernate's newer IdentifierGenerator for AUTO, TABLE and SEQUENCE.
This will prevent from using the new generators and keep the old functionality included in Spring boot 1.x.x.
Please note that this is probably not the best solution, but it is very helpful on short term
As Andrew has pointed out in the comment, if you don't want the id to be incremented while values are created in other tables, you can specify your ID like this:
#Id
#GeneratedValue(
strategy= GenerationType.AUTO,
generator="native"
)
#GenericGenerator(
name = "native",
strategy = "native"
)
private Long id;
Doing this will make each table has its unique id beginning with 1,2,3 ... and so on.
By default spring-boot uses the auto and increment the value based on the order the objects are saved.
To provide unique id based on each object, use the following
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

Display attributes of many to many relationship coded with #OneToMany and #ManyToOne

I'm a newbie in Spring. I think it's better to explain my problem with a little example. Let's say I have two main classes: User and Group. A User can be part of more Groups and a Group, obviously, can have more Users. So the relationship between them is many-to-many. What I would like to show, is something like this (using JSTL):
<c:forEach items="${groups}" var="group">
<c:out value="${group.name}"/> (<c:out value="${fn:length(group.users)}" />):<br />
<c:forEach items="${groups.users}" var="user">
<c:out value="${user.name}"/><br />
</c:forEach><br />
</c:forEach>
Basically, the output should be something like:
Random (2):
Joe
Bloggs
Star wars (5):
Luke
Chewbacca
Darth Vader
Princess Leia
Yoda
Nintendo (3):
Super Mario
Metroid
Zelda
I initially coded it with the classic #ManyToMany annotation, using an additional table user_has_group (created and managed by JPA) and it was working perfectly.
I needed to modify the structure since I needed the user_has_group table to have the joined_date column. To achieve it, I read online that the best solution is to create another class (i.e. UserHasGroup) and add the one-to-many relationships to this class from User and group. Doing so, it's possible to add additional attributes to the UserHasGroup class (and therefore additional columns to the user_has_group table). Something like:
User:
#Entity
#Table(name = "user")
public class User
{
#Id
#GeneratedValue
#Column
private int id;
#Column
private String alias;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
private List<UserHasGroup> userHasGroup = new ArrayList<UserHasGroup>();
// Constructors/getters/setters
}
Group:
#Entity
#Table(name = "`group`")
public class Group
{
#Id
#GeneratedValue
#Column
private int id;
#Column
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "group")
private List<UserHasGroup> userHasGroup = new ArrayList<UserHasGroup>();
// Constructors/getters/setters
}
UserHasGroup:
#Entity
#Table(name = "user_has_group")
public class UserHasGroup
{
#Id
#GeneratedValue
#Column
private int id;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#ManyToOne
#JoinColumn(name = "group_id")
private Group group;
#Column
#Temporal(TemporalType.DATE)
private Date joinedDate;
// Constructors/getters/setters
}
So far, so good. All the tests run successfully and the functionality is maintained.
But I'm facing a problem with JSTL. In fact, with this new structure is obviously not possible to do group.users to iterate through the users.
What is the best way to reach the same functionality as before but with this new structure?
Thank you.
I don't see a reason why ${fn:length(group.userHasGroup)} should'n work.
The only problem you might come accross is some no active session exception. You can solve it either by
using "open session in view" interceptor (which somebody calls an anti-pattern)
manualy iterating through the list in your service method
eager fetching the relationship - i would be very carefull here as this can lead to many queries to database
Answer to additional question:
It should look somehow like this:
<c:forEach items="${groups}" var="group">
<c:out value="${group.name}"/> (<c:out value="${fn:length(group.userHasGroup)}" />):<br />
<c:forEach items="${groups.userHasGroup}" var="userHasGroup">
<c:out value="${userHasGroup.user.name}"/><br />
</c:forEach><br />

Resources