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} }
Related
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.
I want to edit a user, should I replace it with another user? Or should I just replace the attributes?
I have this controller
#PostMapping("/edit")
public ResponseEntity<User> editUser(#RequestBody User user) {
log.info("EDIT");
return new ResponseEntity<User>(userServiceImpl.editUser(user), HttpStatus.OK);
}
this my service:
public User editUser(User user) {
User owner = userRepository.findById(user.getId());
owner=user;
userRepository.save(user);
return owner;
}
the class User is
#Entity
#Table(name = "User")
public class User implements Serializable {
private static final long serialVersionUID = -3009157732242241606L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "name")
private String name;
#Column(name = "password")
private String password;
#Column(name = "email")
private String email;
I need to do something like that, I mean do an insert query to the bd?
#Modifying
#Query("update User u set u.firstname = ?1, u.lastname = ?2 where u.id = ?3")
void setUserInfoById(String firstname, String lastname, Integer userId);
To make it clear.
Let think this simple.
You have userRepository.save(user);
What will it do?
If this object don't have an Id ~> Save it as new object
If this user HAD an ID, it will check OVERWRITE if the id is EXIT
So if you wanna Edit just SOME FIELDS, let do like convention
domain:port/user/{userId}/ (PUSH) and give some field need to change here.
~> Get the real obj in DB ~> update the object and do with SAVE.
/ ~> Or you can do the query update directly if you want
Just save updated user
public User editUser(User user) {
return userRepository.save(user);
}
It will be enough.
Here, this will ensure that no data is lost and a new record is not generated.
public User editUser(User user) {
User owner = userRepository.findById(user.getId());
if(user.getName != null){
owner.setName(user.getName);
}
if(user.getPassword != null){
owner.setPassword(user.getPassword);
}
if(user.getEmail != null){
owner.setEmail(user.getEmail);
}
return userRepository.save(owner);
}
I need to implement unique constrain validation using spring data jpa, and i know it can be done with jpa annotations but i need to do it manually.
I have a class (entity) User like this:
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String email;
private Date createdAt;
}
When i create a new User i have to validate that no other user with identical username or password exists.
I know i need to do something like this (more or less):
public interface UserRepository extends JpaRepository<User, Long> {
public List<User> findByUsernameOrEmail(String username, String email);
}
public class UserService {
#Autowired
private UserRepository userRepository;
#Transactional
public User create(User user) {
List<User> users = this.userRepository.findByUsernameOrEmail(user.getUsername(), user.getEmail());
if (users.size() > 0)
throw new RuntimeException("No repeated username or email allowed");
User newUser = this.userRepository.save(user);
return newUser;
}
}
My question is: what type of transaction isolation level should i use ?
Is it REPETEABLE_READ enough ? I need to avoid duplicated users
I am doing a project for my studies using Spring, Maven, Tomcat, mySQL. I would like to create a website where users can login and update their settings and based on these settings they generate stuff.
At the moment the login is working fine and new users can be registered and saved to the database. Now I created a new entity and made a one to one reletaionship between the two tables - one is the table of the login details like password and username and the other one contains the settings of this user. The issue I am facing:
I have some textfield and combobox in the UI with vaadin - I populate the fields and click save
A binder passes these settings to a service that saves the object
It gets the currently logged in user and loads it from the database
When SQL tries to save the objects it throws error:
Caused by: java.sql.SQLException: Field 'user_login_id' doesn't have a
default value
Here are the entities:
#Entity
#Table(name = "USERLOGIN")
public class UserLogin implements UserDetails {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#OneToOne(mappedBy = "userlogin")
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
**Other getters and setters**
}
#Entity
#Table(name = "USER")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private Integer id;
#Column(name = "meal")
private Integer meal;
#OneToOne
#JoinColumn(name = "userlogin_id")
private UserLogin userlogin;
public UserLogin getUserLogin() {
return userlogin;
}
public void setUserLogin(UserLogin userLogin) {
this.userlogin = userLogin;
userLogin.setUser(this);
**Other getters and setters**
}
The service that saves the settings:
#Service
public class AddUserServiceImpl implements AddUserService{
#Autowired
private UserRepository userRepository;
#Autowired
private CurrentUserService currentUserService;
public void saveUser(User userDAO) {
User user = new User();
user.setMeal(userDAO.getMeal());
user.setUserLogin(currentUserService.getCurrentUser());
userRepository.save(user);
}
}
The repository extends the JPArepository:
#Repository
public interface UserRepository extends JpaRepository<User, Integer>{
}
And finally the service and the repository that loads the currently logged in user:
#Service
public class CurrentUserServiceImpl implements CurrentUserService {
#Autowired
UserLoginRepository userLoginRepository;
public String getCurrentUsername() {
return SecurityContextHolder.getContext().getAuthentication().getName();
}
public UserLogin getCurrentUser() {
return userLoginRepository.findByUserName(getCurrentUsername());
}
}
#Repository
public interface UserLoginRepository extends JpaRepository<UserLogin,
Integer> {
#Query("select u from UserLogin u where u.username=:username")
UserLogin findByUserName( #Param("username") String username);
}
Any help would be appreciated I am really new to this topic. So the main goal is that I want to have a table that stores properties for the currently logged in user - these settings should be able to be updated any time. Is there any best practice for this?
Thank you!
Requirement is to add username column for every audit entry.
After googling a lot and going through the enverse docs I figured out how to implement it. I have implemented it as follows:
Implemented a revision listener:
#Configurable
public class UserRevisionListener implements RevisionListener
{
public void newRevision(Object revisionEntity)
{
UserRevEntity revision = (UserRevEntity) revisionEntity;
String username = "";
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Object principal = auth.getPrincipal();
if (principal != null && principal instanceof DepotUser)
username = ((DepotUser) principal).getUsername();
revision.setUsername(username);
}
}
Create an entity class:
#Entity
#Table(name = "USER_REV_ENTITY")
#RevisionEntity(UserRevisionListener.class)
public class UserRevEntity extends DefaultRevisionEntity
{
private static final long serialVersionUID = 1L;
private String username;
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
}
I have referred a link that has an example of the same "Thinking in Software ".
Default auditing is working fine.
Firstly, the table for the audit revision with the new username column should get auto generated (I think!). Its not getting generated.
So, I tried by creating the table and adding the table mapping in UserRevEntity. But no luck.
Can anyone help my identifying the issue?
Thanks, in advance.
#MappedSuperclass
public class DefaultRevisionEntity implements Serializable {
#Id
#GeneratedValue
#RevisionNumber
private int id;
#RevisionTimestamp
private long timestamp;
// ... rest of the class body here....
This is DefaultRevisionEntity, which has already declared with id & timestamp.
For your new column username, it has to be annotated as #Column in UserRevEntity as given below.
#Column(name="username")
private String username;