I have 2 entity. One of them User, and the other one is Followers. I'm trying to make a follower system like Twitter. User can follow another user but, i want to check if user followed, don't do it again.
This is my Follower Entity :
#Entity
public class Followers {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="from_user_fk")
#JsonIgnore
private User from;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="to_user_fk")
#JsonIgnoreProperties({ "password", "balance","id","mail" })
private User to;
public Followers() {};
public Followers(User from, User to) {
this.from = from;
this.to = to;
}
public User getFrom() {
return from;
}
public void setFrom(User from) {
this.from = from;
}
public User getTo() {
return to;
}
public void setTo(User to) {
this.to = to;
}
}
And this is the Service class :
public ResponseEntity<?> followUser(String username, User user) {
User byUsername = getByUsername(username);
List<Followers> followers1 = byUsername.getFollowers();
List<Followers> collect = followers1.stream().filter(p -> p.getTo().getId().equals(user.getId())).collect(Collectors.toList());
if(followers1.size()>0){
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("e");
}
Followers followers = new Followers();
followers.setFrom(user);
followers.setTo(byUsername);
followersRepository.save(followers);
return ResponseEntity.ok(new GenericResponse("Followed"));
}
public List<Followers> getUserFollowers(String username) {
User byUsername = getByUsername(username);
List<Followers> followers = byUsername.getFollowers();
return followers;
}
As you can see, I got the followers of the user I want to follow, and try to check if its following or not. But I couldn't.
Any help would be appreciated
What you've built is incredibly inefficient:
Go to the DB and fetch all followers for a given user.
Then check through these if the person you'd like to add already exists.
If no, add it.
If yes, don't do anything or show an error.
The fail whale is in your near future with this kind of inefficiency.
There's a much, much simpler way. Just.. add it! Don't do any check at all.
Your DB should be configured to disallow having the same user/follower pair, so if you attempt to pull that stunt when that user already has that follower, the DB will refuse and throw an exception that indicates that there's a DB constraint violation. That's your cue to render whatever error you please.
Note that 'check if X is allowed, if yes, do X' is fundamentally broken when talking about multicore architecture. What you're forgetting: What if the user 'double clicks' the 'add follower' link? Then 2 requests start, simultaneously. They both check if X is already a follower (they are not), then they both add X as follower (and now X is a double-follower which you did not want).
Generally if a DB is involved, it is better at data consistency and transactions support, so use the best tool for the job: A DB constraint.
Related
Hibernate validator works well for me to validate objects fetched by hibernate, but the problem is that I would like to make sure that certain conditions are met after persisting/updating objects in database. For example:
My condition is: User can host at most 3 games
Constraint annotation:
#Target({ FIELD, TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = GamesCountValidator.class)
#Documented
public #interface ConstrainHostGamesCount {
String message() default "{com.afrixs.mygameserver.db.constraint.ConstrainHostGamesCount.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
Validator:
public class GamesCountValidator implements ConstraintValidator<ConstrainHostGamesCount, User> {
#Override
public void initialize(ConstrainHostGamesCount constraintAnnotation) {
}
#Override
public boolean isValid(User user, ConstraintValidatorContext context) {
if (user == null)
return true;
return user.getGames().size() <= 3;
}
}
User class:
#Entity
#Table(name="Users")
#ConstrainHostGamesCount
public class User {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="id", nullable=false, unique=true, length=11)
private int id;
#Column(name="name", length=30, unique=true)
private String name;
#OneToMany(mappedBy = "user", orphanRemoval = true, fetch = FetchType.LAZY)
private Set<Game> games = new HashSet<>();
//generic getters and setters
}
Game class:
#Entity
#Table(name="Games")
public class Game {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="id", nullable=false, unique=true, length=11)
private int id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="user_id")
#ConstrainHostGamesCount
private User user;
//generic getters and setters
}
Test method:
public class Test {
public static void hostGames(String userName, int count) {
try {
Session session = DatabaseManager.getSessionFactory().getCurrentSession();
session.beginTransaction();
Query userQuery = session.createQuery("from User where name = :name");
userQuery.setParameter("name", name);
User user = (User)userQuery.uniqueResult();
for (int i = 0; i < count; i++) {
Game = new Game();
game.setUser(user);
session.persist(game);
}
session.getTransaction().commit();
} catch (Exception e) {
DatabaseManager.getSessionFactory().getCurrentSession().getTransaction().rollback();
e.printStackTrace();
}
}
}
Desired behavior for Test.hostGames("afrixs", 4) would be to fail. However the validator validates the state of the user object before the update, ie. with games.size() equal to 0, so the constraint condition is met and nothing fails until Test.hostGames("afrixs", 4) is called for the second time. Of course in this situation we could manually add games to user user.getGames().add(game) but this attitude is error prone (the game needs to be added to user this way everywhere in the code) and it doesn't solve the problem if for example two Test.hostGames("afrixs", 2) are called asynchronously.
So my question is: what is the proper way of constraining the database integrity using hibernate? Is there a way to make the validator check the final state of objects after storing them into database? Or do I need to do the constraining manually (like performing another transaction after session.getTransaction().commit and check the conditions and roll back the updating transaction if they are not met)? Or should I leave out hibernate and use SQL triggers for this? Thank you for your answers, they will help a lot
And here is my current hibernate validation configuration:
<property name="javax.persistence.validation.group.pre-persist">javax.validation.groups.Default</property>
<property name="javax.persistence.validation.group.pre-update">javax.validation.groups.Default</property>
<property name="hbm2ddl.auto">validate</property>
Ok, I have made some experiments, writing down a small test class. To make things simple I changed the constraint to "User can host at most 1 game".
public class DBTest {
#Test
public void gamesCountConstraintWorking() {
DBManager.deleteHostedGames("afrixs");
boolean ok1 = DBManager.createOneGame("afrixs");
boolean ok2 = DBManager.createOneGame("afrixs");
int gamesCount = DBManager.deleteHostedGames("afrixs");
System.out.println("Sync test: count: "+gamesCount+", ok1: "+ok1+", ok2: "+ok2);
assertTrue(gamesCount <= 1);
assertTrue(!(ok1 && ok2));
}
#Test
public void gamesCountConstraintWorkingAsync() throws InterruptedException {
DBManager.deleteHostedGames("afrixs");
for (int i = 0; i < 30; i++) {
CreateOneGameRunnable r1 = new CreateOneGameRunnable(1);
CreateOneGameRunnable r2 = new CreateOneGameRunnable(2);
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
int maxCount = 0;
while (r1.running || r2.running) {
int count = DBManager.selectHostedGamesCount("afrixs");
System.out.println("count: "+count);
maxCount = Math.max(maxCount, count);
}
t1.join();
t2.join();
int gamesCount = DBManager.deleteHostedGames("afrixs");
System.out.println("Async test: count: "+gamesCount+", maxCount: "+maxCount+", ok1: "+r1.ok+", ok2: "+r2.ok);
assertTrue(maxCount <= 1 && gamesCount <= 1);
assertTrue(!(r1.ok && r2.ok));
}
}
private class CreateOneGameRunnable implements Runnable {
public boolean ok;
public boolean running = true;
private int number;
CreateOneGameRunnable(int number) {
this.number = number;
}
#Override
public void run() {
System.out.println("Starting "+number);
ok = DBManager.createOneGame("afrixs");
System.out.println("Finished "+number);
running = false;
}
}
}
First I tried out #Guillaume's suggestion to use user.getGames().add(game); along with game.setUser(user); when assigning the relation. gamesCountConstraintWorking test was successful, however, gamesCountConstraintWorkingAsync wasn't. It means that this attitude was successful in maintaining the session consistency (at the cost of fetching all user games), however, the database integrity wasn't maintained.
A solution that actually worked for both tests was (as #OrangeDog suggested) to add the constraint directly into database schema. MySQL:
DELIMITER $$
CREATE TRIGGER check_user_games_count
AFTER INSERT
ON Games FOR EACH ROW
BEGIN
DECLARE gamesCount INT;
SET gamesCount = (SELECT COUNT(id) FROM Games WHERE user_id = new.user_id);
IF gamesCount > 1 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'User may host at most 1 game';
END IF;
END $$
DELIMITER ;
So my summary is that Hibernate works great as a layer above the database to work with, but if you want to make sure the persisted data look like you want, you need to dive directly into your database schema and perform actions in there. (But that's only the result of this experiment, maybe someone knows a solution for this using Hibernate)
Note: I tried the tests with BEFORE UPDATE triggers and random delays inside the triggers and the tests were successful as well, it seems like some kind of lock is acquired for the table while inserting, so yes, this is a safe solution. (Note2: BEFORE UPDATE trigger for this needs gamesCount+1 > 1 condition and the constraint could fail (not tested) in the case of inserting multiple rows in one query)
I was just wondering if what I'm doing to update a entire table in realm is the correct safe approach. I receive a list of conversations from the server and update my db like this:
#Override
public void saveConversations(final List<Conversation> conversations) {
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
// Remove all conversations and replace with the one passed in
// Realm does not support cascade deletes. Remove messages as well
// https://github.com/realm/realm-java/issues/1104
realm.delete(Conversation.class);
realm.delete(Message.class);
realm.copyToRealmOrUpdate(conversations);
}
});
}
Conversation.java has a RealmList of messages inside:
public class Conversation extends RealmObject {
private RealmList<Message> messages = new RealmList<>();
This works, I couldn't find any bugs with it but it does not look particularly elegant. What if realm.copyToRealmOrUpdate(conversations); goes wrong? I would lose all my data.
Anyways, I know this is not very probable, but I was wondering if there was a better way of doing things.
PS: bear in mind that I delete everything from the db because I don't want conversations in my db that don't exist in the server anymore.
Don't forget that you're executing a transaction, so if copyToRealmOrUpdate() fails, then the transaction is cancelled, which means you wouldn't lose all your data.
Personally, I used to go with the "delete all" approach, and if you can clear out all tables then it won't cause issues, but if you have a third table where you're referring to Conversation and Message (for example User.class), you'd be invalidating all relations. So I personally prefer to merge like this.
merging data and removing all data that's not in the list you've saving
.
public class Contact {
#PrimaryKey
private long id;
#Index
private String name;
#Index
private String phoneNumber;
#Index
private boolean isBeingSaved; // this line is important
//getters, setters
}
Then merge:
// background thread
Realm realm = null;
try {
realm = Realm.getDefaultInstance();
final List<Contact> contacts = getContacts();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.insertOrUpdate(contacts);
realm.where(Contact.class)
.equalTo(ContactFields.IS_BEING_SAVED, false) // compile 'dk.ilios:realmfieldnameshelper:1.1.0'
.findAll()
.deleteAllFromRealm(); // delete all non-saved data
// in your case, this is where you'd handle the cascading too though manually
for(Contact realmContact : realm.where(Contact.class).findAll()) { // realm 0.89.0+
realmContact.setIsBeingSaved(false); // reset all save state
}
}
});
} finally {
if(realm != null) {
realm.close();
}
}
I'm trying to upgrade from Spring Data Neo4J 3 to 4 - I'm using Neo4J 2.2.2.
I use a GraphRepository instance to query the database, fetching back an object.
This object has several relationships, which are not fetched (deliberately, to avoid reading in the entire graph).
In the SDN3 code, simply used the Neo4JTemplate class to perform a fetch call for each relationship I needed to fetch. This worked extremely well.
However, in SDN4 this facility has been removed, and replaced by various implementations of the load() method. It's not clear from the documentation how to achieve what I did in SDN3.
To be clear: if I have a Set of objects in the first class I retrieve, governed by a relationship, I want to retrieve only the objects in that Set, not the entire collection of those objects in the database.
Have I missed something crucial in the upgrade process, or is there a simple way of doing what I'm trying to do?
Adding code:
My entity class:
#NodeEntity
public class File implements MetroNode {
private Long id;
private String fileName;
private SourceState sourceState;
private Set<State> states;
#GraphId
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
#Relationship(type = "HAS_FILE", direction = Relationship.INCOMING)
public SourceState getSourceState() {
return sourceState;
}
public void setSourceState(SourceState sourceState) {
this.sourceState = sourceState;
}
public State addState(MetroNode otherNode, StateStatus status) {
if (this.states == null) {
this.states = new HashSet<State>();
}
State state = new State(this, otherNode, status.toString());
this.states.add(state);
return state;
}
#Relationship(type = "HAS_STATE", direction = Relationship.OUTGOING)
public Set<State> getStates() {
return states;
}
public State getActiveState() {
if (this.states != null) {
for (State state : this.states) {
if (state.isActive()) {
return state;
}
}
}
return null;
}
}
My repository class:
public interface FileRepository extends GraphRepository<File> {
File findByFileName(String fileName);
}
When executing the getActiveState() method I get a null return, because the states Set is empty (hasn't been fetched).
Looking again at my code, I wonder if it's because I'm not using a "native" load method from the repository, but the overloaded version?
SDN 4 allows you to control loading of related entities with the persistence horizon.
Loading an entity with depth 0 will fetch properties of the entity and no related entities.
Depth 1 will fetch the first level of related entities, but not their relations and so on.
Controlling the depth by relationship type is not supported.
I was unable to find a complete example for designing a ManyToMany relationship with Play-2.2 in Java.
My Problem is, i can't get the Form-to-Model binding to work on my ManyToMany Relationship...
There are many users. A user can have many jobs.
There are many jobs. A job can "have" many members/participants(users).
Job.java
#Entity
public class Job extends TimmeeyModel {
#Constraints.Required
#Column(unique = true)
public String jobName;
#ManyToMany(mappedBy="jobs")
public List<User> members = new ArrayList<User>();
public static Finder<Long,Job> find = new Finder<Long, Job>(Long.class,Job.class);
#Override
public Job create(){
this.save();
return this;
}
#Override
public Job update(Long id){
Job oldJob = find.byId(id);
oldJob.setJobName(this.getJobName());
oldJob.setMembers(this.getMembers());
oldJob.save();
oldJob.refresh();
return oldJob;
}
#Override
public void delete(){
this.delete();
}
public String toString(){
String userString ="";
for (User user : members) {
userString+=user.getUsername() +", ";
}
return jobName + ", " + id +" " + userString;
}
}
User.java
#Entity
public class User extends TimmeeyModel {
#Constraints.Required
#Column(unique = true)
public String username;
#ManyToMany(cascade=CascadeType.PERSIST)
public List<Job> jobs = new ArrayList<Job>();
public static Finder<Long, User> find = new Finder<Long, User>(Long.class, User.class);
public String getEmail(){
return this.username + "#pool.math.tu-berlin.de";
}
#Override
public User create(){
//jobs.add(Job.find.all().get(0));
this.save();
return this;
}
#Override
public User update(Long id){
User user = find.ref(id);
user.setUsername(this.getUsername());
user.setUsername(this.getUsername());
user.save();
user.refresh();
return user;
}
public void delete(){
this.delete();
}
public String toString(){
String jobString = "";
for (Job job : jobs) {
jobString += job.toString() + ", ";
}
return "User Id: " + id + " "+username + "Jobs: " + jobString;
}
UserController.java
public static Result showCreate(){
return ok(views.html.user.add.render(Job.find.all(), userForm));
}
public static Result create(){
Map<String, String> newData = new HashMap<String, String>();
Form<User> filledForm = userForm.bind(newData);
if(filledForm.hasErrors()) {
return badRequest("Leider nein" + filledForm.toString());
} else {
String logString = filledForm.toString();
User user = filledForm.get();
user.create();
return ok(user.toString() + logString);
}
}
The view scala template
#form(routes.Users.create()) {
#inputText(userForm("username"))
#for((value,index) <- jobs.zipWithIndex ) {
<input type="checkbox" name="jobs[]" value="#value.getId()">#value.getJobName()</input>
}
i just cant get it to work. After submitting the Joblist of a user stays empty. But when i set the list "hardcoded" inside the user object, it stays there and works like expected. SO i don't think it is related to some db/config issue. SO i suspect there is something wrong while binding the form-data to the User-Model.
I also tried different version of the form like
#value.getJobName()
#value.getJobName()
#value.getJobName()
#value.getJobName()
Nothing works. So has anyone an idea what i doing wrong, or what i don't get about play!? Maybe it is impossible to do this without writing HUGE ammounts of boilerplate code in every Controller where i want to handle models with manyToMany relationships?
Because i know i could parse the form data in the controller "by hand" and force-put the jobs-List into the users. But this can not be the right way for such a MVC framework like play :-)
Thank you
By the way, there is not even ONE example/sample in the Play-Documentation/GitHub where the term "ManyToMany" occurs...
Really i just need some not totally fucked way to handle ManyToMany Relations ( if i can't use checkboxes.. Okay, if i should use something else, OK.... I just need some way for it to work)
I answered this question myself. In short: It is NOT possible with Play! to autobind Objects from their identifiers to a List.
Maybe you will read something about custom Databinder or some magical form helpers.
The CustomDatabinder is good when you want to get a SINGLE complex Object mapped for example
User.java
public Job mainJob;
If you now enter the id of a Job into the User form, a custom Databinder is able to bind the data (if you provided one).
But this will not work for List
In fact the only thing is to use a little boilerplate code.
I did it that way
User user = filledForm.get();
List<Job> jobs = new ArrayList<Job>();
for (Job job : user.getJobs()) {
jobs.add(Job.find.byId(job.getId()));
}
user.create();
The form binder will generate a List of Jobs for the jobs List in the User. But these jobs are just "stubs" i had too iterate through the generated Jobs, and build a list with the real ones by searching for them in the Database..
It's not pretty, but also not too ugly.
Maybe this explanation can help others.
I have the following class
public class ElementBean {
private String link;
private Set<ElementBean> connections;
}
I need to create a map table where elements are mapped to each other in a many-to-many symmetrical relationship.
#ManyToMany(targetEntity=ElementBean.class)
#JoinTable(
name="element_elements",
joinColumns=#JoinColumn(name="FROM_ELEMENT_ID", nullable=false),
inverseJoinColumns=#JoinColumn(name="TO_ELEMENT_ID", nullable=false)
)
public Set<ElementBean> getConnections() {
return connections;
}
I have the following requirements
When element A is added as a connection to Element B, then element B should become a connection of Element A. So A.getConnections() should return B and B.getConnections() should return A. I do not want to explicitly create 2 records one for mapping A to B and another for B to A.
Is this possible?
UPDATE : Thank you for all the suggestions.
When I try #Pascal's suggestion two records are created as follows when I try to connect element 1 with element 2.
FROM_ELEMENT_ID, TO_ELEMENT_ID
1, 2
2, 1
I want the records to be symmetrical where 1,2 is the same as 2,1
How is this issue dealt with?
Update
I explicitly created a map bean
class ConnectionBean {
ElementBean from;
ElementBean to;
}
#NotNull
#ManyToOne
#JoinColumn(name="FROM_ELEMENT_ID", nullable=false)
public ElementBean getFrom() {
return from;
}
#NotNull
#ManyToOne
#JoinColumn(name="TO_ELEMENT_ID", nullable=false)
public ElementBean getTo() {
return to;
}
I updated the ElementBean to
public class ElementBean {
private String link;
private Set<ConnectionBean> from;
private Set<ConnectionBean> to;
}
#OneToMany(mappedBy="from", fetch=FetchType.LAZY)
public Set<ConnectionBean> getFrom() {
return from;
}
#OneToMany(mappedBy="to", fetch=FetchType.LAZY)
public Set<ConnectionBean> getTo() {
return to;
}
Here I can control the insertion and deletion. For example before inserting a new ConnectionBean, I check to see if a connection exists between elements A & B by checking the Connection table for records where
((from == A && to == B) || (from == B && to == A))
before insertion.
When working with many to many, you don't have to worry too much about duplication as Hibernate will handle that for you. You do have to worry about making sure your object model is consistent and Hibernate won't necessarily help with that.
In your addConnection() method you should not have a getConnections() method that returns the actual connection, rather return a read only version, you'll need to make sure you handle both sides of the relationship:
addConnection(ElementBean element) {
if (element ==null) {
throw new IllegalArgumentException("cannot add null element");
}
connections.add(element);
element.getConnections0().add(this);
}
This means your requirement will hold and when it saves it will save okay. It will not save twice as it will notice that the one is a back reference for the other.
You should probably make the getConnections0() private or package.
This will be solved by hibernate's first level cache to keep track of the references as long as you map the m-2-m from both sides.
When working with bi-directional links, you need to take care of the link on both sides and, quoting the documentation:
Many developers program defensive and create a link management methods to correctly set both sides.
So in your case, I'd suggest to add the following methods:
public Set<ElementBean> getConnections() {
return connections;
}
public void setConnections(Set<ElementBean> connections) {
this.connections = connections;
}
public void addToConnection(ElementBean connection) {
this.getConnections().add(connection);
connection.getConnections().add(this);
}
public void removeFromConnection(ElementBean connection) {
this.getConnections().remove(connection);
connection.getConnections().remove(this);
}