I am not sure if I am doing something wrong, but when I have these two entities:
#Setter #Getter #NoArgsConstructor #FieldDefaults(level = AccessLevel.PRIVATE)
#Entity
#Table(name = "GROUP_OF_USERS")
public class Group {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
Long id;
String name;
#ManyToOne
User administrator;
#ManyToMany(mappedBy = "groups", cascade = CascadeType.ALL)
Set<User> users = new HashSet<>();
public void setAdministrator(User user){
this.administrator = user;
addUser(user);
}
public void addUser(User user){
users.add(user);
}
}
#Entity
#Setter #Getter #NoArgsConstructor #FieldDefaults(level = AccessLevel.PRIVATE)
#Table(name = "APP_USER")
public class User {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
Long id;
String name;
#Column(unique = true)
String email;
String password;
String bankAccount;
boolean isActive = false;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(
name = "GROUP_USER_RELATIONSHIP",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "group_id"))
Set<Group> groups = new HashSet<>();
public void addGroup(Group group){
groups.add(group);
}
}
and service method like this:
#Override
public Group crateGroup(String userName, String groupName) {
var userInDb = userRepository.findUserByEmailIgnoreCase(userName).orElseThrow(() -> new UserNotFoundException(userName));
userInDb.getGroups().stream()
.filter(group -> group.getName().equals(groupName))
.findAny()
.map(Group::getName)
.ifPresent(name -> {throw new GroupUniqueConstrainException(name);});
var newGroup = new Group();
newGroup.setName(groupName);
newGroup.setAdministrator(userInDb);
return groupRepository.save(newGroup);
}
than I am surprised that administrator is set in DB, but join table doesn't contain any record. Could someone help me here to understand where could be a problem? I found out that I could update the addUser method so it would also add group to the user. But I would need to do this also for addGroup method in User class so when calling that it would create infinite loop.
Related
I have two tables. A Users table and an Artists table. Users can be associate with many artists, and vice versa. I have an API call that adds an Artist to the User's list. The API seems to work correctly, but when I check the User afterward, my postman return shows an endless list of the artist I added.
The user entity:
#Entity
#Table(name = "users")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Slf4j
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", nullable = false)
Integer id;
#Column(name = "username")
String username;
#Column(name = "picture_link")
String pictureLink;
#ManyToMany
#JoinTable(
name = "user_artist",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "artist_id"))
Set<Artist> artists = new HashSet<>();
The artist entity:
#Entity
#Table(name = "artists")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Slf4j
public class Artist {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", nullable = false)
Integer id;
#Column(name = "name")
String name;
#Column(name = "description")
String description;
#Column(name = "picture_link")
String pictureLink;
#Column(name = "genres")
String genres;
#ManyToMany(mappedBy = "artists")
Set<User> users = new HashSet<>();
#Transient
List<Album> albums = new ArrayList<>();
}
The api call that causes the infinite loop:
#Override
public String addArtistToFaveList(int user_id, int artist_id) {
try{
User foundUser = new User();
Artist foundArtist = new Artist();
Optional<User> resultUser = userRepo.findById(user_id);
Optional<Artist> resultArtist = artistRepo.findById(artist_id);
if(resultUser.isPresent() && resultArtist.isPresent()){
foundUser = resultUser.get();
foundArtist = resultArtist.get();
}
Set<Artist> userFaveSet = foundUser.getArtists();
userFaveSet.add(foundArtist);
userRepo.save(foundUser);
return "THIS WORKED!";
}catch(Exception e){
return "Failed completely.";
}
}
I want to make association to some many-to-many relation entity
#Entity
#Table(name = "users")
#Data
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private String userId;
private String name;
#OneToMany(mappedBy = "user")
private List<UserGroups> userGroups;
#Table(name = "groups")
#Data
public class Group {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "group_id")
private String groupId;
private String category;
private String name;
private String description;
#OneToMany(mappedBy = "group")
private List<UserGroups> userGroups;
public void addUser(User user){
UserGroup newUserGroup = new UserGroup();
newUserGroup.setName(user.getName())
userGroups.add(newUserGroup);
user.getUserGroups().add(newUserGroup)
}
#Entity
#Data
#Table(name = "user_groups")
public class UserGroups {
#EmbeddedId
UserGroupsCompositeKey id;
#ManyToOne
#MapsId("userId")
#JoinColumn(name = "user_id")
private Users user;
#ManyToOne
#MapsId("featureId")
#JoinColumn(name = "group_id")
private Group group;
private Date created;
I trying to add POST method to service where I should get group_id from endpoint url and assosiate user_id in request body. My Service method looks like this.
#Override
public ResponseEntity<String> createUsersGroup(String groupId,
String userId) {
Optional<Group> group = groupRepository.findById(groupId).get();
Optional<User> user = userRepository.findById(userId).get();
group.addUser(user);
return ResponseEntity.ok(userId);
};
}
Is there some more proper way to do this or when I will add more users in request body I will have to pull out every user from the database and add it like that ?
I have a quite simple problem, yet a very weird behaviour on which a Set does not delete elements on true predicates for some reason.
The entities (for reference only, as this does not have anything to do with the set item - or shouldn't):
#Data
#MappedSuperclass
#EqualsAndHashCode(of = "uuid")
public abstract class Model<ID> {
#Id
#GeneratedValue
protected ID id;
protected UUID uuid; // Hybrid model
#PrePersist
private void onPersisting() {
uuid = UUID.randomUUID();
}
}
#Data
#Entity
#NoArgsConstructor
#EqualsAndHashCode(callSuper = true, of = "name")
#Table(uniqueConstraints = #UniqueConstraint(name = "scope_name", columnNames = "name"))
public class Scope extends Model<Long> {
private String name;
public Scope(UUID uuid) {
this.uuid = uuid;
}
public Scope(String name) {
this.name = name;
}
}
#Data
#Entity
#NoArgsConstructor
#EqualsAndHashCode(callSuper = true, of = "name")
#Table(uniqueConstraints = #UniqueConstraint(name = "role_name", columnNames = "name"))
public class Role extends Model<Long> {
private String name;
#ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinTable(name = "role_scopes", joinColumns = #JoinColumn(name = "role_id"), inverseJoinColumns = #JoinColumn(name = "scope_id"))
private Set<Scope> scopes = new HashSet<>();
public Role(UUID uuid, Scope... scopes) {
this.scopes = Stream.of(scopes).collect(Collectors.toSet());
this.uuid = uuid;
}
public Role(String name, Scope... scopes) {
this.scopes = Stream.of(scopes).collect(Collectors.toSet());
this.name = name;
}
}
The following snippet, called within a JUnit test case, does not delete the set elements (simplified for readability):
#Transactional
public Role create(Role role) {
role.getScopes().removeIf(unused -> true); // <----
return role;
}
For some reason, that snippet does work:
Set<String> strings = new HashSet<>();
strings.add("FOO");
strings.add("BAR");
strings.removeIf(unused -> true);
What's going on here?
It is because of overridden equals and hashCode that is done by #Data annotation on your Scope class.
If you remove it or replace with #Getter #Setter it will work
I got a lot of answers and advice, tried changing my code according to these tips.
But, unfortunately, these tips helped only partially.
Now, when creating a new project and creating a new user, I can add the desired user to the set of projects, and the required project will be added to the set of users.
But the relationship between the desired project and the desired user will not appear in the project_user table.
Please help find the answer.
Entity Project
#Data
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "project")
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
#Column
private String description;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "project_users",
joinColumns = #JoinColumn(name = "project_id"),
inverseJoinColumns = #JoinColumn(name = "users_id"))
private Set<User> projectUserSet = new HashSet<>();
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "project_task",
joinColumns = #JoinColumn(name = "project_id"),
inverseJoinColumns = #JoinColumn(name = "task_id"))
private Set<Task> projectTaskSet = new HashSet<>();
public void addUserToProject(User user){
this.projectUserSet.add(user);
user.getUserProjectsSet().add(this);
}
public void addTasksToProject(Task task){
this.projectTaskSet.add(task);
task.getTasksProjectSet().add(this);
}
//constructors, hashCode, equals, toString
}
Entity User
#Data
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String firstName;
#Column
private String lastName;
#ManyToMany(mappedBy = "projectUserSet", cascade = CascadeType.ALL)
private Set<Project> userProjectsSet = new HashSet<>();
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "users_task",
joinColumns = {#JoinColumn(name = "users_id")},
inverseJoinColumns = {#JoinColumn(name = "task_id")})
private Set<Task> userTasksSet = new HashSet<>();
public void addTaskToUser(Task task) {
this.userTasksSet.add(task);
task.getTasksUserSet().add(this);
}
//constructors, hashCode, equals, toString
}
project and user initialization
Project project1 = new Project("Project1", "Project1");
User user1 = new User("User1", "User1");
project1.addUserToProject(user1);
With code shown below, table project_user is populated, verified using H2 console. In order to avoid stack overflow, I had to modify method Project#addTaskToUser as shown below.
Please note that only code relevant to the question, is included.
Normally, issue should be described by some tests. In this case, I added a CommandLineRunner that runs at startup.
CascadeType.ALL is not recommended for many-to-many relations, hence I changed this in code shown below.
Tested using H2 in-memory db.
Project class
#Data
#Entity
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "project_users",
joinColumns = #JoinColumn(name = "project_id"),
inverseJoinColumns = #JoinColumn(name = "users_id"))
private Set<MyUser> projectUserSet = new HashSet<>();
public void addUserToProject(User user) {
this.projectUserSet.add(user);
}
}
Project repo
public interface ProjectRepo extends JpaRepository<Project, Long> { }
User class
// cannot use #Data here because it will cause cyclic ref and stack overflow when accessing userProjectsSet
#Getter
#Setter
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
#ManyToMany(mappedBy="projectUserSet")
private Set<Project> userProjectsSet = new HashSet<>();
}
User repo
public interface UserRepo extends JpaRepository<User, Long> {}
In app class
#SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
#Bean
CommandLineRunner run(ProjectRepo projectRepo, EntityManagerFactory entityManagerFactory) {
return args -> {
var testUser = new User();
testUser.setFirstName("first-name");
testUser.setLastName("last-name");
var project = new Project();
project.setName("project-name");
project.setDescription("project-description");
project.addUserToProject(testUser);
projectRepo.save(project);
// get saved user and print some properties
var userInDb = entityManagerFactory.createEntityManager().find(User.class, testUser.getId());
System.out.println(userInDb.getFirstName()); // prints "first-name"
System.out.println(userInDb.getUserProjectsSet().size()); // prints "1"
};
}
}
I have three tables: users(id, name, login, password), roles(id, name), user_roles(id, user_id, role_id)
This is my code
#Entity
#Table(name = "users")
public class User extends Model {
#Id
public Long id;
public String name;
public String login;
public String password;
#ManyToMany
#JoinTable(
name = "user_roles",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id")
)
public Set<Role> roles;
public static Finder<Integer, User> find = new Finder<>(User.class);
}
#Entity
#Table(name = "roles")
public class Role extends Model {
#Id
public Long id;
public String name;
#ManyToMany(mappedBy = "roles")
public List<User> users;
public static Finder<Integer, Role> find = new Finder<>(Role.class);
}
I want to display all users with roles, example: {"id":1, "name":"My Name", "login":"My Login", "password":"My Password", roles: [{"name":"ADMIN"}, {"name":"USER"}]}
How can I do this? I'm new in Ebean and ORM. Thanks for any help.
Update
public Result all() {
List<User> users = User.find.all();
return ok(toJson(users));
}
But now I getting stackoverflow error infinite recursion.
Make users.role = null and then return Json