I'm trying to persist a User-class, which contains 2 lists: one with users to see which are following that specific user, and one with users to see which users that specific user follows.
User.java:
#Entity
#XmlRootElement
#Table(name="Users")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String web;
private String bio;
#OneToMany(cascade=CascadeType.ALL)
private Collection<User> followers = new ArrayList();
#OneToMany(cascade=CascadeType.ALL)
private Collection<User> following = new ArrayList();
I have a class named UserDAOJPA.java which creates and modifies the users:
#Alternative
#Stateless
public class UserDAOJPA implements Serializable, IUserDAO {
#PersistenceContext(unitName = "KwetterSOAPPU")
private EntityManager em;
#Override
public void create(User user) {
em.persist(user);
}
#Override
public List<User> getFollowers(User user) {
List<User> followers;
followers = (List<User>) user.getFollowers();
return followers;
}
#Override
public void addFollower(User user, User follower)
{
user.addFollower(follower);
em.merge(user);
}
#Override
public List<User> getFollowing(User user) {
List<User> following;
following = (List<User>) user.getFollowing();
return following;
}
#Override
public void addFollowing(User user, User following) {
user.addFollower(following);
em.merge(user);
}
#PostConstruct
private void initUsers() {
User u1 = new User("Hans", "http", "geboren 1");
User u2 = new User("Frank", "httpF", "geboren 2");
User u3 = new User("Tom", "httpT", "geboren 3");
User u4 = new User("Sjaak", "httpS", "geboren 4");
this.create(u1);
this.create(u2);
this.create(u3);
this.create(u4);
this.addFollowing(u1, u2);
this.addFollower(u2, u1);
this.addFollowing(u1, u3);
this.addFollower(u3, u1);
this.addFollowing(u1, u4);
this.addFollower(u4, u1);
My own guess is that I'm missing a correct annotation in the User.java class, when looking at the Collection of User's.
The error message:
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.3.2.v20111125-r10461): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLIntegrityConstraintViolationException: Column 'FOLLOWING_ID' cannot accept a NULL value.
Error Code: -1
Call: INSERT INTO Users_Users (followers_ID, User_ID) VALUES (?, ?)
bind => [2 parameters bound]
Query: DataModifyQuery(name="followers" sql="INSERT INTO Users_Users (followers_ID, User_ID) VALUES (?, ?)")
Relationship between users and their followers is a bidirectional many-to-many relationship, not two one-to-many relationships, therefore you need to map it as such:
#ManyToMany(mappedBy = "following")
private Collection<User> followers = new ArrayList<>();
#ManyToMany
#JoinTable(name = "followers",
joinColumn = #Column(name = "follower_id"),
inverseJoinColumn = #Column(name = "following_id"))
private Collection<User> following = new ArrayList<>();
Also note that cascading usually should be used when referring to logically "owned" entities. In your case it makes no sense because Users don't "own" each other.
Just guessing, as I don't have an environment to test the code, but I think you need to change the create method to return the return value from the EntityManager.merge class and use the result, like this:
...
public User create(User user) {
return em.merge(user);
}
...
User user = this.create(new User(...));
User follower = this.create(new User(...));
this.addFollower(user, follower);
The reason for the error is that the reference to the Follower's id is undefined, as you don't return the merged object state one you've persisted it.
Related
I have developed two tables in Spring Boot, User and UserMeta. User is the parent and UserMeta is the child table. The foreign-key is user_id. I may be looking at it the wrong way, but I want to be able to first create an entity of User. Then, I want to create an entity of UserMeta. Simply UserMeta should contain additional data to User.
However, when first creating a User and then a UserMeta entity, I get e new User entity (ending up with two User entities and one UserMeta entity.)
The problem I think is that I create a UserMeta object with a User, since I want to have a relationship between User and UserMeta. But if I want to be able to first create a User and then a UserMeta, should I simply ignore a foreign-key? Or, does it exists another way of creating a UserMeta entity without creating a new User?
User
public class User {
#Id
#SequenceGenerator(name = "user_sequence", sequenceName = "user_sequence", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_sequence")
//#OneToOne(optional=false)
private Long userId;
private String username;
private String password;
private String email;
#OneToOne(mappedBy = "user")
private UserMeta userMeta;
public User(String username, String email, String password) {
this.username = username;
this.email = email;
this.password = password;
}
}
UserMeta
public class UserMeta {
#Id
#SequenceGenerator(name = "user_meta_sequence", sequenceName = "user_meta_sequence", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_meta_sequence")
private Long userMeta_Id;
private String lastname;
private int age;
#OneToOne(
cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
optional = false
)
#JoinColumn(
name = "user_Id",
referencedColumnName="userId"
)
private User user;
public UserMeta(String lastName, int age, User user){
this.lastname = lastName;
this.age = age;
this.user = user;
}
}
UserRepository
public interface UserRepository extends CrudRepository<User, Long> {
}
UserService
public interface UserService {
User saveUser(User user);
}
UserServiceImpl
#Service
public class UserServiceImpl implements UserService {
private UserRepository userRepository;
public UserServiceImpl(UserRepository userRepository) {
super();
this.userRepository = userRepository;
}
#Override
public User saveUser(User user) {
// TODO Auto-generated method stub
return this.userRepository.save(user);
}
UserController
#RestController
public class UserController {
private UserService userService;
public UserController(UserService userService) {
super();
this.userService = userService;
}
#PostMapping("/user")
public ResponseEntity<User> saveUser(#RequestBody User user) {
return new ResponseEntity<User>(userService.saveUser(user), HttpStatus.CREATED);
}
}
UserMetaRepository
public interface UserMetaRepository extends CrudRepository<UserMeta, Long> {
}
UserMetaService
public interface UserMetaService {
UserMeta saveUserMeta(UserMeta userMeta);
}
UserMetaServiceImpl
#Service
public class UserMetaServiceImpl implements UserMetaService{
private UserMetaRepository userMetaRepo;
public UserMetaServiceImpl(UserMetaRepository userMetaRepo) {
super();
this.userMetaRepo = userMetaRepo;
}
#Override
public UserMeta saveUserMeta(UserMeta userMeta) {
return this.userMetaRepo.save(userMeta);
}
}
UserMetaController
#RestController
public class UserMetaController {
public UserMetaService userMetaService;
public UserMetaController(UserMetaService service) {
super();
this.userMetaService = service;
}
#PostMapping("/userMeta")
public ResponseEntity<UserMeta> saveUserMeta(#RequestBody UserMeta userMeta) {
return new ResponseEntity<UserMeta>(this.userMetaService.saveUserMeta(userMeta), HttpStatus.CREATED);
}
}
you should use this constructor in the User class,
public User(String username, String email, String password, UserMeta userMeta) {
this.username = username;
this.email = email;
this.password = password;
this.userMeta = userMeta;
}
now when you save your user the user Meta will be added to your UserMeta table,
If you want to add a user Meta to an existing user you will only need to set the userMeta and save it with a simple userRepository.save(theUpdatedUser)
you can also create userMeta seperately with your code above, and if you want to assign it to a user already in data base or not you can allows use the power of spring data and use simple userRepository.save(userWithTheAssignedMeta)
the same logic applies the other way for metaUser.
The problem here is that your UserMetadata creation logic is using incomplete JSON:
{ "lastName":"foo", "age":1, "user":{ "username":"foo", "password":"bar", "email":"foo-bar" } }
Within this, the problem is the 'user' has all the data, duplicating what was already created the the database, but does not identify it. Since the mapping has cascade.ALL set on it, Spring/JPA will persist the UserMetadata and find this User instance that doesn't have identity, so persist it - giving it identity from the sequence.
There are a few ways you might correct this. First and easiest is to send the User ID in the json from the previously created instance:
{ "lastName":"foo", "age":1, "user":{ "userId":1, "username":"foo", "password":"bar", "email":"foo-bar" } }
This will allow Spring/JPA to recognize the user's identity and merge it and the data provided into the database. It means though that you must send complete data for the User - it will push incomplete data into the DB.
If that is a concern, you can change the cascade options. You may not want cascading persist/merge at all on this relationship, and I suspect when you delete userMetadata you don't really want to delete the User instance, so I think this might have been done incorrectly (maybe put it on the user->UserMetadata relationship instead?). If you remove the cascade settings, spring/JPA will let you just pass in JSON with the USER id specified, as this gives it enough to set the fk:
{ "lastName":"foo", "age":1, "user":{ "userId":1} }
I have two entities. User:
#Entity
#Table(name = "user")
#Data
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "username", unique = true)
private String username;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "user_role",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
public void addRole(Role role) {
this.roles.add(role);
}
public void addUser(User user) {
this.users.add(user);
}
Role:
#Entity
#Table(name = "role")
#Data
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name", unique = true)
private String name;
#ManyToMany(mappedBy = "roles", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Set<User> users = new HashSet<>();
I'm using Spring Data JPA and create repositories for User and Role:
public interface UserRepository extends JpaRepository<User, Long> {
public Optional<User> findByUsername(String username);
}
public interface RoleRepository extends JpaRepository<Role, Long> {
public Optional<Role> findByName(String name);
public void deleteByName(String name);
}
I write a test which creates a new User and adds a Role to the User but Role doesn't get added to the User` and the test failes:
#ExtendWith(SpringExtension.class)
#DataJpaTest
class UserRepositoryTest {
#Autowired
UserRepository userRepository;
#Autowired
RoleRepository roleRepository;
#BeforeEach
void setUp() {
Role role1 = new Role();
role1.setName("Admin");
Role role2 = new Role();
role2.setName("Teacher");
Role role3 = new Role();
role3.setName("User");
roleRepository.saveAll(Arrays.asList(role1, role2, role3));
}
#Test
#Transactional
public void createNewUserTest() {
String username = "User1";
String password = "password";
String firstName = "name";
String lastName = "last name";
User user = new User();
user.setUsername(username);
user.setPassword(password);
user.setFirstName(firstName);
user.setLastName(lastName);
Role findedUserRole = roleRepository.findByName("User").get();
Role findedAdminRole = roleRepository.findByName("Admin").get();
user.addRole(findedUserRole);
user.addRole(findedAdminRole);
userRepository.save(user);
User findedUser = userRepository.findByUsername(username).get();
Role findedRole = roleRepository.findByName("User").get();
assertEquals(firstName,findedUser.getFirstName());
assertEquals(2, findedUser.getRoles().size());
assertTrue(findedRole.getUsers().contains(findedUser));
}
}
assertTrue(findedRole.getUsers().contains(findedUser)) fails.
If using same method in application (without adding user to role) everything is ok, but in in the test it does not work.
My application:
#Component
public class InitializeData implements CommandLineRunner {
#Autowired
private UserRepository userRepository;
#Autowired
private RoleRepository roleRepository;
#Transactional
#Override
public void run(String... args) throws Exception {
Role role1 = new Role();
role1.setName("Admin");
Role role2 = new Role();
role2.setName("Teacher");
Role role3 = new Role();
role3.setName("User");
roleRepository.saveAll(Arrays.asList(role1, role2, role3));
String username = "User1";
String password = "password";
String firstName = "name";
String lastName = "last name";
User user = new User();
user.setUsername(username);
user.setPassword(password);
user.setFirstName(firstName);
user.setLastName(lastName);
Role findedUserRole = roleRepository.findByName("User").get();
Role findedAdminRole = roleRepository.findByName("Admin").get();
user.addRole(findedUserRole);
user.addRole(findedAdminRole);
userRepository.save(user);
}
}
Everything works fine!
What is going on?
You fell in the trap of JPAs first level cache.
Your complete test happens in a single transaction and therefore session.
One of the core principles of JPA is that within a session for a given class and id the EntityManager will always return the same instance.
So when you do a Role findedRole = roleRepository.findByName("User").get(); in your test, you are not actually reloading the role. You are getting the exact instance that you created in the setup part of the test, which still has no users.
The way to fix this depends on what you actually want to achieve.
For bidirectional relationships you should always keep them in sync, i.e. the call to user.addRole(..) should update the Role passed as an argument. See https://vladmihalcea.com/jpa-hibernate-synchronize-bidirectional-entity-associations/ for details on that.
While this would make your test green, it doesn't actually test if the data was written to the database and can be loaded again as expected. For this I like to use one of the following approaches.
flush & clear. Get the EntityManager injected in the test. After saving the entities call flush on it, in order to write the changes to the database and then clear to clear the 1st level cache. This will ensure that subsequent load operations actually hit the database and load new instances.
explicite transactions. Remove the #Transactional annotations from your tests. Get a TransactionTemplate injected into your test and run setup, the saving part and the loading and assertion part in separate transactions using TransactionManager.execute. I think this makes the intent of the test more obvious. But now your transactions actually commit, which might cause problems if you are reusing the same database for several tests.
So here is my problem:
I have two tables: User and Book, they are in ManyToOne relation. The Book table has attribute called user_id that connects both tables.
Using Eclipse I generated entity classes and work on them without problem until now.
The problem is, when I want to get "books" that have speciffic user_id I get an error:
java.lang.IllegalArgumentException: Parameter value [1] did not match expected type [model.User (n/a)]
The "value" is an id that I'm getting from session, I tried it in both int and String.
part of BookDao:
public List<Book> getFullListWithId(Integer id) {
List<Book> list = null;
Query query = em.createQuery("select b from Book b WHERE b.user = :id");
if (id!= null) {
query.setParameter("id", id);
}
try {
list = query.getResultList();
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
part of BookBB:
public List<Book> getFullListWithId(){
HttpSession session = (HttpSession) FacesContext.getCurrentInstance().getExternalContext().getSession(true);
Integer str =(Integer)session.getAttribute("userId");
return bookDAO.getFullListWithId(str);
}
part of Book.java
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id_offer")
private int idOffer;
private String adress;
private String contact;
#Column(name="is_ready")
private String isReady;
private String name;
private String others;
private int price;
private int rooms;
private String size;
private String surname;
//bi-directional many-to-one association to User
#ManyToOne
#JoinColumn(name="user_id")
private User user;
part of User.java
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id_user")
private int idUser;
private String login;
private String password;
//bi-directional many-to-one association to Book
#OneToMany(mappedBy="user")
private List<Book> books;
Thank you so much for any help possible.
You have two options.
1) you pass an User object with the id as a parameter to the query (I prefer that solution):
Query query = em.createQuery("select b from Book b WHERE b.user = :user");
User u = new User();
u.setId(id)
query.setParameter("user", u);
2) you pass the id as a parameter:
Query query = em.createQuery("select b from Book b WHERE b.user.id = :id");
if (id!= null) {
query.setParameter("id", id);
}
Please note the b.user.id in the query
I believe query should be select b from Book b WHERE b.user.idUser = :id
You might want to take a look at hibernate docs
https://docs.jboss.org/hibernate/orm/3.6/reference/en-US/html/queryhql.html {Chapter 16}
I'm using play framework with ebean.
I have 2 classes that share a many-to-many relationship
#Entity
public class profiles extends Model {
#Id
#Column(name = "profile_ID")
public Integer _id;
public String profile;
public String description;
#ManyToMany(cascade = CascadeType.ALL)
public List<role> roles;
......
#Entity
public class role extends Model {
#Id
#Column(name = "role_ID")
public Integer _id;
public String role;
public Integer parent;
public String description;
public Integer sequence;
#ManyToMany(cascade = CascadeType.ALL)
public ArrayList<profiles> prof_ids = new ArrayList<profiles>();
.....
I'm having trouble trying to generate a list containing all the roles that a particular profile has.
could anyone show me how this is done?
You need to provide more information on what specifically you're trying to do.
See below for an action that creates a profile with 2 roles, persists the profile (and roles), finds all profiles, logs the found profiles and their associated roles and then renders them as JSON:
public class Profiles extends Controller {
public static Result create() {
profiles profile = new profiles();
profile.description = "Testing";
profile.profile = "Test Profile";
role role = new models.role();
role.description = "Test Role 1";
role.role = "Role 1";
profile.roles.add(role);
role = new models.role();
role.description = "Test Role 2";
role.role = "Role 2";
profile.roles.add(role);
profile.save();
List<profiles> profiles = Ebean.find(profiles.class).findList();
for (profiles p : profiles) {
Logger.info("Profile: {}", p.profile);
for (role r : p.roles) {
Logger.info("\t-> has role: {}", r.role);
}
}
return ok(Json.toJson(profiles)).as("application/json");
}
}
To get the list of roles, note the reference to p.roles above. Is that what you want?
See here for more advanced queries etc.
Some things to keep in mind:
Always start your class names with a capital letter in Java. The code above is hard to read using your lowercase model/entity names...
Name your entity identifier id not _id, it causes issues when Ebean generates queries.
I am having some problems with JPA. I am new at this topic so my question maybe is really stupid, but i hope some of you could point me to the right direction.
I have Project and User entity. Every user can have as many projects assign to him as it can.
I created the relationship bidirectional
User OneToMany -> Project,
Project ManyToOne -> User
My problem is that if i want to delete a user i want all the projects to be deleted as well, but i receive an error at that point:
Internal Exception: java.sql.SQLIntegrityConstraintViolationException:
DELETE on table 'USER_DATA' caused a violation of
foreign key constraint 'PROJECT_USERNAME' for key (Test Use1312r1).
The statement has been rolled back.
Error Code: -1
Call: DELETE FROM USER_DATA WHERE (USERNAME = ?)
bind => [1 parameter bound]
My User entity looks like this:
#Entity
#Table(name="USER_DATA", uniqueConstraints = #UniqueConstraint(columnNames = {"USERNAME", "link"}))
public class User implements Serializable {
#Column(name="USERNAME")
#Id
#NotNull
private String name;
#Column(name="USERROLE")
#Enumerated(EnumType.STRING)
private UserRole role;
private String password;
private String link;
// Should be unique
private String session;
#OneToMany(mappedBy="user", cascade=CascadeType.ALL)
private Collection<Project> projects;
My Project Entity like this:
#Entity
#Table(name="PROJECT")
#XmlRootElement
public class Project implements Serializable {
#Id
private int id;
private String name;
private String description;
#Temporal(TemporalType.TIMESTAMP)
#Column(name="START_DATE")
private Date beginDate;
#Temporal(TemporalType.TIMESTAMP)
#Column(name="END_DATE")
private Date endDate;
#ManyToOne
#JoinColumn(name="USERNAME", nullable=false,updatable= true)
private User user;
And my BL:
public User getUser(String userName) throws NoDataFoundException {
EntityManager em = DbConnection.getInstance().getNewEntity();
try {
User user = em.find(User.class, userName);
if (user == null) {
throw new NoDataFoundException("User is not found in the DB");
}
return user;
} finally {
em.close();
}
}
public void deleteUser(String userName) throws ModelManipulationException {
EntityManager em = DbConnection.getInstance().getNewEntity();
try {
User userToBeDeleted = getUser(userName);
em.getTransaction().begin();
userToBeDeleted = em.merge(userToBeDeleted);
em.remove(userToBeDeleted);
em.getTransaction().commit();
} catch (Exception e) {
throw new ModelManipulationException(
"Error in deleting user data for username" + userName
+ "with exception " +e.getMessage(),e);
}
finally{
em.close();
}
}
Thanks in advance guys.
after the merge call, are there any Projects in userToBeDeleted.projects? I suspect there are none, which prevents any from being deleted. Cascade remove can only work if you populate both sides of bidirectional relationships, so check that when you associate a user to a project, you also add the project to the user's project collection.