Need help regarding JPA entity mapping - spring

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.

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.

JPA Collection with user defined order

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.

Relational database foreign keys in Spring Boot JPA/Hibernate

I'm using Spring Boot JPA with Gradle. I'm struggling to find a guide that I can follow which focusses on creating a relational database with the correct Syntax for Spring Boot. I had a go but I get this error
No property idTestCase found for type TestRun!
I want TestRun and TestData entities with a OneToOne relationship with each other, and a TestCase entity that has a OneToMany relationship with TestRun. I reckon that TestRun should contain the foreign keys for TestData and TestCase.
Many times I make changes and it will not build, and when it does build the tables do not look correct, this is what I created:
#Entity
public class TestRun {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private long testRunId;
private Boolean result;
#OneToOne #JoinColumn(name="testData_id")
private TestData testData;
#ManyToOne #JoinColumn(name="testCase_id")
private TestCase testCase;
}
#Entity
public class TestCase {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private long testCaseId;
private String name;
private String description;
#OneToMany(cascade=CascadeType.ALL, mappedBy="testCase",targetEntity=TestRun.class)
private Collection<TestRun> testRun;
}
#Entity
public class TestData {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private long testDataId;
#OneToOne(cascade=CascadeType.ALL, mappedBy="testData",targetEntity=TestRun.class)
private TestRun testRun;
}
From the guides it isn't clear to me what goes in #JoinColumn(name= some say it needs to link to a field on your POJO and some say it doesn't. If I create the foreign key field in my POJO then I get two foreign key fields in the database table and if I don't it doesn't build at all.
For example from the error I can infer it wants me to add the following fields to TestRun:
private long idTestCase;
private long idTestData;
But then my database appears as:
SELECT * FROM TEST_RUN;
TEST_RUN_ID
ID_TEST_CASE
ID_TEST_DATA
RESULT
TEST_CASE_ID
TEST_DATA_ID
(no rows, 3 ms)
I tried setting #JoinColumn(name= to the name of the primary key field on the other side of the relationship but again it did not build.
Many thanks
I found the guide at JBoss to be the most helpful in describing the different mappings.

spring security datamodel

I'm currently using the spring-security libraries and I asked myself the following question: How should I combine my database model with the spring-security tables?
As you know spring-security needs two tables (users and authorities) to define an authentication manager in the database. From my pov there are now two possibilities where I store my additional user-information (like email, lastname, last-logged-on, ....)
I could have a plain user-table for authentication purposes and another one for the rest (linked by the username)
I extend the user-table of spring-security with my necessary attributes.
What is the best design from your perspective? What are your experiences?
Lomu
I created a POJO User which represents the User entity as conceived by the Spring Security library, and secondly I created a POJO ProfiledUser to represent a specialized type of user of my application. It is called ProfiledUser because I needed a user associated to a profile. Of course, a similar approach can be applyied for every type of user you need to represent. Basically, if you need more than one type of user you can make your classes to extend the User POJO.
In the following you find the class, with the JPA annotations.
#Entity
#Table(name="USERS")
#Inheritance(strategy=InheritanceType.JOINED)
public class User implements UserDetails {
private static final long serialVersionUID = 1L;
private long id;
private String username;
private String password;
private boolean enabled = true;
Set<Authority> authorities = new HashSet<Authority>();
//...getters & setters
}
#Entity
#Table(name="PROFILED_USERS")
public class ProfiledUser extends User{
private static final long serialVersionUID = 1L;
//some custom attributes
private PersonalData personalData;
private ContactData contactData;
private AddressData addressData;
//...getters & setters
}
If you need to represent only one type of user, I think it should work to add attributes to the User class. However, I prefer to separate the abstract concept of user defined by the Spring Security framework from my business logic. So I'd recommend to implement your own SomethingUser and extend the User class.
A person is a person and you should have a class/table representing a person†.
A user is a user, and is different from a person (hence the two different words), and you should have a class/table representing a user.
Can a person exist without a user? Yes
Can a user exist without a person? No, a username belongs to someone.
#Entity
abstract class Party {
#Id
Long id;
String name;
#OneToMany
List<User> usernames = new ArrayList<>();
}
#Entity
class Individual extends Party {
DateTime dateOfBirth;
}
#Entity
class User {
#ManyToOne
Party party;
String username;
String password; //you better use BCrypt/Blowfish hashing!
Boolean enabled = true;
}
You could instead use a #OneToOne relationship if you only want one username per party.
† Actually you should have a more abstract class/table representing a legal party.

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