I have a simple POJO:
#Data
#NoArgsConstructor
public class User {
private Long id;
private String username;
private String password;
public User(String username, String password) {
this.username = username;
this.password = password;
}
}
I have mapper, saving this POJO and returning ID.
#Insert("insert into users(username, password) values(#{user.username}, #{user.password})")
#Options(useGeneratedKeys = true, keyProperty = "id")
Long save(#Param("user") User user);
Than, service receive entity by this ID:
#Override
public User save(User user) {
return Optional.ofNullable(
mapper.findById(
Optional.ofNullable(mapper.save(user))
.orElseThrow(() -> new EntityNotSavedException("Не удалось сохранить: " + user)))
)
.orElseThrow(() -> new EntityNotSavedException("Не удалось сохранить: " + user));
}
In this case, we have one request to insert entity & second - to select it from id. Costs too much.
How to return entity after inserting in one request?
You don't need to perform the select.
It's a common misunderstanding, but #Insert method returns the number of updated rows, not the generated key (this is how the underlying JDBC API works).
As you specified keyProperty="id", the generated key is set to id property of the passed User parameter.
Note that you should specify keyColumn as well.
It's required since version 3.5.0.
If your service method has to return a User, it may look something like this.
#Override
public User save(User user) {
if (mapper.save(user) != 1) {
// this may not happen.
// an exception will be thrown if insert failed.
}
return user;
}
Usually, the service method just performs INSERT and the caller keeps using the original User instance for later operation.
The service method:
#Override
public void save(User user) {
mapper.save(user);
}
The class calling the service method would look like...
User user = new User("John", "xyz");
service.save(user);
model.addAttribute("user", user);
...
Related
I am creating an API to simulate a vending machine and I'm using Spring, JPA and MySQL. I have an endpoint for a POST request that allows for new user creation by entering the user's details into a table called users. I have a check to see if the username already exists in the table and if it does, I want to return a message that says the creation of a new user was unsuccessful because that username is already in use. If the user name is not in the table, I want to return the User object.
How do I return this error message? From what I have found so far, suggestions include the usage of ResponseEntity or creating a custom exception handler which all seemed overly complicated for something that I feel is quite straightforward. Is there an easier way to do this?
So this is what I want the response to look like when a new user has been created successfully (which I have currently managed to get working successfully):
{
"username": "user",
"password": "password",
"role": "BUYER",
"deposit": 0.0,
"id": 12
}
And if it fails, I want it to return something that looks along the lines of:
Error: username already in use.
or:
{
"error" : "Username already in use"
}
User object:
#Entity
#Table(name = "users")
public class User
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int userId;
#NotNull
#Column(name = "username", unique = true)
private String username;
#NotNull
#Column(name = "password")
private String password;
#NotNull
#Column(name = "role")
private Role role;
#NotNull
#Column(name = "deposit")
private BigDecimal deposit;
public User() {}
public User(String username, String password, Role role, BigDecimal deposit)
{
this.username = username;
this.password = password;
this.role = role;
this.deposit = deposit;
}
public int getId()
{
return userId;
}
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
public Role getRole()
{
return role;
}
public void setRole(String role)
{
this.role = Role.valueOf(role);
}
public void setRole(Role role) {
this.role = role;
}
public BigDecimal getDeposit()
{
return deposit;
}
public void setDeposit(BigDecimal deposit)
{
this.deposit = deposit;
}
}
Method in the controller that is being called:
#PostMapping(value = "/create-user", consumes = {"application/json"})
public User createUser(#RequestBody User user)
{
return userService.createUser(user);
}
createUser method in UserService:
public User createUser(User user)
{
// if(userRepository.userExists(user.getUsername()) == 0)
return userRepository.save(user);
}
UserRepository is an interface that extends JpaRepository.
If I have missed some information or have worded the question incorrectly, please let me know and I'll update accordingly.
This can be achieved in multiple ways.
You can use JpaRepository's existsById() or similar "exists" methods. This would return a boolean. If it's true, that is if the entry already exists for a given id, you can send an error response. Otherwise, new user object. For that, you can have a model class something like UserOrError which will either hold a user object or an error string at a time. Use #JsonView annotation from Jackson library to show/ hide the fields. Read more
Based on the result of the above "exists" method, wrap it with ResponseEntity<T>. The return type of your controller method should be ResponseEntity<T>. Here's the link. The advantage of this method is that you can send different HTTP status codes
Throw a custom RuntimeException in your service layer and use Spring's exception handling like the RestControllerAdvice annotation
I'm creating an app with a local database using Room. Every user have an option to create a 'Group' model, and these Groups are displayed in the main feed.
The groups are stored in the database, but when the user logs out and then logs in with a different account he can still see the previous account groups.
How can I fix that? Is there a way to implement the database so that after calling the logout function the DB will swap to a different DB?
Thanks.
How can I fix that?
2 Ways (at least) as will be shown
Is there a way to implement the database so that after calling the logout function the DB will swap to a different DB?
Yes. However I'd suggest that it is not the best way and is a little more complicated than the "Universal way".
Universal Way - Recommended approach
Have a single Database the log include the user (userId) as a column in the log and showing just the logs for the current user.
Mulltiple or UserOnly approach
You have at least 3 databases one for the User's so they can login and a database for each use with the log table. Switching between users makes this more complex as it requires opening another database when the user is switched. Unless using arrays (why would you? (rhetoric)) then if switching from a user to another then you should ideally close the first user's database.
Working Example
The following is a working example/demo that utilises BOTH approaches.
Hopefully the comments and names used explain.
The Entities first :-
User (common to both approaches) :-
#Entity
class User {
#PrimaryKey
Long userId;
#ColumnInfo(index = true)
String userName;
String userPassword;
public User(){};
#Ignore
public User(Long userId, String userName, String userPassword) {
this.userId = userId;
this.userName = userName;
this.userPassword = userPassword;
}
#Ignore
public User(String userName, String userPassword) {
this(null,userName,userPassword);
}
public Long getUserId() {
return userId;
}
public String getUserName() {
return userName;
}
public String getUserPassword() {
return userPassword;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public void setUserName(String userName) {
this.userName = userName;
}
public void setUserPassword(String userPassword) {
this.userPassword = userPassword;
}
}
UserLog (common to both approaches)
#Entity(tableName = "log")
class UserLog {
#PrimaryKey
Long id;
Long timestamp;
Long userId;
String logData;
public UserLog(){}
#Ignore
public UserLog(User user, String logData) {
this.id = null;
this.timestamp = System.currentTimeMillis();
this.userId = user.getUserId();
this.logData = logData;
}
public Long getId() {
return id;
}
public Long getUserId() {
return userId;
}
public Long getTimestamp() {
return timestamp;
}
public String getLogData() {
return logData;
}
public void setId(Long id) {
this.id = id;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public void setTimestamp(Long timestamp) {
this.timestamp = timestamp;
}
public void setLogData(String logData) {
this.logData = logData;
}
}
That's the Entities and both are common to each approach.
Now the Dao's
UniversalDao (for the recommended approach)
#Dao
interface UniversalDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
long insertUser(User user);
#Insert
long insertLog(UserLog userLog);
#Query("SELECT * FROM user WHERE userId=:userId")
User getUserById(long userId);
#Query("SELECT * FROM log WHERE userId=:userId")
List<UserLog> getUserLogs(long userId);
#Query("SELECT userId FROM user WHERE userName=:userName AND userPassword=:password")
long verifyUserLogin(String userName, String password);
}
The getuserLogs Dao being used for getting and showing a specific user's logs.
UserOnlyDao
#Dao
interface UserOnlyDao {
#Insert
long insertUserLog(UserLog userLog);
#Query("SELECT * FROM Log")
List<UserLog> getUserLogs();
}
simpler BUT ....
The Database (#Database) classes utilsing singletons
UniversalUserDatabase (all-in-one database approach - Recommended)
#Database(entities = {User.class,UserLog.class},version = 1)
abstract class UniversalUserDatabase extends RoomDatabase {
abstract UniversalDao getAllDao();
private static UniversalUserDatabase instance;
static UniversalUserDatabase getInstance(Context context) {
if (instance == null) {
instance = Room.databaseBuilder(
context,
UniversalUserDatabase.class,"universaluser.db"
).allowMainThreadQueries()
.build();
}
return instance;
}
}
Pretty straightforward
UserOnlyDatabase (may need some time to understand this one)
#Database(entities = {UserLog.class},version = 1)
abstract class UserOnlyDatabase extends RoomDatabase {
abstract UserOnlyDao getUserOnlyDao();
private static volatile UserOnlyDatabase instance;
private static volatile User currentUser;
static UserOnlyDatabase getInstance(Context context, User user) {
if (user == null) {
throw new RuntimeException("Attempt to open Invalid - CANNOT continue");
}
if ( currentUser == null || (currentUser.getUserId() != user.getUserId())) {
if (instance != null) {
if (instance.isOpen()) {
instance.close();
}
instance = null;
}
}
if (instance == null) {
instance = Room.databaseBuilder(context,UserOnlyDatabase.class,user.userName+ "_log")
.allowMainThreadQueries()
.build();
}
return instance;
}
}
Note this works but has only been tested for a simple sceanrio
the getInstance method is where the swapping of userlog database is undertaken
Now putting it all together in an activity that demonstrates.
Note for convenience and brevity this runs on the main thread. So no consideration for not running it on the main thread has been included. However, as singletons are used there is probably little that isn't covered, just beware that there may be issues to consider.
MainActivity
public class MainActivity extends AppCompatActivity {
static int MAXLOGINATTEMPTS = 5;
static String TAG = "USERLOGINFO";
UniversalUserDatabase uniDB;
UniversalDao uniDao;
UserOnlyDatabase uoDB;
UserOnlyDao uoDao;
User currentUser = new User();
int loginAttempts = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
uniDB = UniversalUserDatabase.getInstance(this);
uniDao = uniDB.getAllDao();
// Add Some Users
uniDao.insertUser(new User("Fred","password"));
uniDao.insertUser(new User(Long.valueOf(1000),"Mary","password"));
// Login to the first user logging the login attempt
if (forceLogin("Fred","password",true)) {
Log.d(TAG,currentUser.userName + " successfully logged in.");
uniDao.insertLog(new UserLog(currentUser,"Logged In Successfully"));
uoDB = UserOnlyDatabase.getInstance(this,currentUser);
uoDao = uoDB.getUserOnlyDao();
uoDao.insertUserLog(new UserLog(currentUser,"UOLogged in was Good"));
}
// Write the logs to the log for Universal and userOnly approach
logCurrentUserLog();
logUserOnlyLog();
// SWITCH USER and database for User Only approach logging login to the logs
if (forceLogin("Mary","password",true)) {
Log.d(TAG,currentUser.userName + " successfully logged in.");
uniDao.insertLog(new UserLog(currentUser,"Logged in Successfuly"));
uoDB = UserOnlyDatabase.getInstance(this,currentUser);
uoDao = uoDB.getUserOnlyDao();
uoDao.insertUserLog(new UserLog(currentUser,"UOLogged in was Good"));
}
logCurrentUserLog();
logUserOnlyLog();
}
private boolean addUser(String userName, String password) {
return uniDao.insertUser(new User(userName,password)) > 0;
}
private boolean login(String userName, String password) {
long userId;
if (++loginAttempts >= MAXLOGINATTEMPTS) return false;
if (!((userId = uniDao.verifyUserLogin(userName,password)) > 0)) return false;
currentUser = uniDao.getUserById(userId);
loginAttempts = 0;
return true;
}
private boolean forceLogin(String userName, String password, boolean crashIfTooManyAttempts) {
while (!login(userName,password)) {
if (loginAttempts >= MAXLOGINATTEMPTS) {
if (crashIfTooManyAttempts)
throw new RuntimeException("Too Many Login Attempts - Goodbye");
return false;
}
}
return true;
}
private void logCurrentUserLog() {
for(UserLog ul: uniDao.getUserLogs(currentUser.getUserId())) {
Log.d(TAG,ul.timestamp + " User = " + currentUser.userName + "Log = " + ul.logData);
}
}
private void logUserOnlyLog() {
for(UserLog ul: uoDao.getUserLogs()) {
Log.d(TAG + "_UO",ul.timestamp + " UO ID = " +ul.userId + " " + ul.id + ul.logData);
}
}
}
So this :-
Gets an instance of the Universal Database and gets the Dao.
Adds 2 users Fred and Mary (Mary with an id forced to be 1000 just for show, makes no difference to the demo what Mary's id is)
1.Logs on to Fred writing.
A login entry is added for Fred in the Universal Database log.
An instance of Fred's UserOnly database is obtained and a login entry is made in Fred's log in Fred's database.
Fred's log entries, from the Universal database, are output to the Device's log.
Fred's log entries from his personal database are output to the Device's log.
USER is SWITCHED to Mary when Mary log's on.
A login entry is added for Mary in the Universal Database Log.
An instance of Mary's UserOnly database is obtained and a login entry is made in Mary's log in Mary's database.
Mary's log entries. from the Universal Database Log are output to the Device's log.
NOTE Fred's entries ARE NOT output even though they exist in the Log..
Mary's log entries from his personal database are output to the Device's log.
Results
The Device's Log after the first run :-
2021-04-18 13:38:18.381 D/USERLOGINFO: Fred successfully logged in.
2021-04-18 13:38:18.412 D/USERLOGINFO: 1618717098382 User = FredLog = Logged In Successfully
2021-04-18 13:38:18.415 D/USERLOGINFO_UO: 1618717098385 UO ID = 1 1UOLogged in was Good
2021-04-18 13:38:18.419 D/USERLOGINFO: Mary successfully logged in.
2021-04-18 13:38:18.453 D/USERLOGINFO: 1618717098419 User = MaryLog = Logged in Successfuly
2021-04-18 13:38:18.457 D/USERLOGINFO_UO: 1618717098429 UO ID = 1000 1UOLogged in was Good
The database's via Database Inspector
Showing the Universal Database Log (recommended apporaoch) :-
As Fred's database was closed this is shown as closed
Showing The UserOnly log (Mary's) :-
You can shoud clear your databases every time a users logs out:
YourDataBase.getInstance(context).clearTables()
I'm struggling with form update. Please consider this example:
// Entity with which I need to perform CRUD operations
public class User {
private String name;
private String email;
private String phone;
private String address;
}
I send to UI is UserDTO:
public class UserDTO {
private String name;
private ContactDataDTO contactDataDTO;
}
public class ContactDataDTO {
private String email;
private String phone;
private String address;
}
My mapper:
#Mapper
public interface UserMapper {
#Mappings({
#Mapping(source="email", target="contactDataDTO.email"),
#Mapping(source="phone", target="contactDataDTO.phone"),
#Mapping(source="address", target="contactDataDTO.address")
})
UserDTO userToUserDTO(User user);
#InheritInverseConfiguration
User updateUserFromUserDTO(UserDTO userDTO, #MappingTarget User user);
}
userToUserDTO() works as expected, but generated userDTOToUser() for me seems wierd:
#Override
public User updateUserFromUserDTO(UserDTO userDTO, User user) {
if ( userDTO == null ) {
return null;
}
String address = userDTOContactDataDTOAddress( userDTO );
if ( address != null ) {
user.setAddress( address );
}
String phone = userDTOContactDataDTOPhone( userDTO );
if ( phone != null ) {
user.setPhone( phone );
}
String email = userDTOContactDataDTOEmail( userDTO );
if ( email != null ) {
user.setEmail( email );
}
user.setName( userDTO.getName() );
return user;
}
Problematic use case:
Fill in all fields for User.
Open form again and clear phone field.
That means to backend I will send smth like this:
userDTO: {
name: 'John Doe';
contactDataDTO: {
email: 'johndoe#gmail.com',
phone: null,
address: 'Home'
}
}
So, user.phone won't be updated, as far as I have null check for it in generated code.
I thought NullValueCheckStrategy is what I need, but there no option which fits me.
For now the only option I see - write my own implementation of userDTOToUser() without null checks.
Maybe you can advise better solution, cause for me it looks like a problem that can happen in any mapper for a target update from DTO with non-primitive source.
Runnable demo: https://repl.it/#aksankin/SlateblueUnimportantStack
Thanks a lot.
If you want null to have a certain value for you then you are looking for source presence checking. You can then control in the setPhone of the DTO whether it was set or not and add hasPhone that would use the flag. Then MapStruct will use the presence check method when setting the value.
Probably Optional<> is what you're searching for. In this case you would have null for empty field, Optional if null was send from UI and Optional for actual value. But probably you need to create different DTO for request and response.
try:
#Mapper( )
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper( UserMapper.class );
#Mappings({
#Mapping(source="email", target="contactDataDTO.email"),
#Mapping(source="phone", target="contactDataDTO.phone"),
#Mapping(source="address", target="contactDataDTO.address")
})
UserDTO userToUserDTO(User user);
default void updateUserFromUserDTO(UserDTO userDTO, User user) {
intUpdateUserFromUserDTO( userDTO, userDTO.getContactDataDTO(), user );
}
void intUpdateUserFromUserDTO(UserDTO userDTO, ContactDataDTO contactDataDTO, #MappingTarget User user);
}
(note: I returned void iso a type, which is strictly not needed).
I am trying to update some user information by passing List of User-Ids as parameter
i want to update isActive field of User fo which i am passing the user ids.
Below is my controller
#PutMapping
#ResponseStatus(HttpStatus.OK)
#RequestMapping("/UserUpdate")
public ResponseEntity<?> updateUsers(List<Long> userIds) {
**userService.updateUsers(userIds);**
return ResponseEntity.ok(200);
}
updateUsers() is a method in my Service where i have to write the logic
I tried something like below but it's not working
public void updateUsers(List<Long> userIds) {
List<Users> userList= userRepository.findAll();
for (Long i : userIds) {
for ( Users user : userList)
{
if(userRepository.findById(i) != null)
{
user.setIsActive(9L);
user.setUserName("Update Test");
}
my dto
public class UserDto {
private List<Users> userList;
private String appName="Users Project";
// getters and setters removed for brevity
And my Users entity class
#Entity
#Table(name="USERS")
public class Users {
#Id
#Column(name="USER_ID")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long userId;
#Column(name="NAME")
private String userName;
#Column(name="ACTIVE")
private Long isActive;
// getters and setters removed for brevity
Alternatively you can use the following code
#Modifying
#Query("update Users u set u.isActive = ?1, u.userName = ?2 where u.userId in ?3")
void updateUsers(Long isActive, String userName, List<Long> userId);
Add this code in your userRepository and use the method.
public void updateUsers(List<Long> userIds) {
for (Long i : userIds) {
User user = userRepository.findById(i);
if(user != null){
user.setIsActive(9L);
user.setUserName("Update Test");
// call your update method here (this is not stated in your code)
}
}
}
I have a user entity that has many attributes (some fields not shown here):
#Entity
public class User {
#OneToOne(cascade = ALL, orphanRemoval = true)
private File avatar; // File is a custom class I have created
#NotEmpty
#NaturalId
private String name;
#Size(min = 6)
private String password;
#Enumerated(EnumType.STRING)
private Role role;
}
In my thymeleaf template I have a form that submits username, password and avatar (MultipartFile). Now in my controller instead of these parameters...
#PostMapping("/register")
public String register(#RequestParam String username,
#RequestParam String password,
#RequestParam MultipartFile avatar) { ...
...I want to use #ModelAttribute #Valid User user. My problem is that:
password first should be encrypted then passed to the user entity,
bytes[] from MultipartFile should be extracted then stored in user entity (as a custom File object),
some other fields such as Role should be set manually in the service class.
How can I take advantage of #ModelAttribute?
Instead of trying to shoehorn everything into your User class, write a UserDto or UserForm which you can convert from/to a User. The UserForm would be specialized for the web and converted to a User later on.
The conversions you are talking about should be done in your controller (as that is ideally only a conversion layer before actually talking to your business services).
public class UserForm {
private MultipartFile avatar;
#NotEmpty
private String username;
#Size(min = 6)
#NotEmpty
private String password;
public UserForm() {}
public UserForm(String username) {
this.username = username;
}
static UserForm of(User user) {
return new UserForm(user.getUsername());
}
// getters/setters omitted for brevity
}
Then in your controller do what you intended to do (something like this):
#PostMapping("/register")
public String register(#ModelAttribute("user") UserForm userForm, BindingResult bindingResult) {
if (!bindingResult.hasErrors()) {
User user = new User();
user.setName(userForm.getUsername());
user.setPassword(encrypt(userForm.getPassword());
user.setAvataor(createFile(userForm.getAvatar());
userService.register(user);
return "success";
} else {
return "register";
}
}
This way you have a specialized object to fix your web based use cases, whilst keeping your actual User object clean.
Maybe you can just use a setter to make all these actions. When Spring is mapping data to fields, and you have setters in the entity, it will use them to pass data. You can preprocess data in this way and set final values to fields.
#PostMapping("/register")
public String register(#ModelAttribute User user, Model model) { // remember if You have another name for parameter and backing bean You should type this one #ModelAttribute(name="userFromTemplate") User user
encryptPassword(user.getPassword); //remember that is sample code, You can do it however You want to
extractBytes(user.getAvatar); //same here
user.setRole(manuallySetRole);
model.addAttribute("user", user);
return "success"; // here u can redirect to ur another html which will contain info about ur user
} else
return "redirect:sorry";
}
encryptPassword(String password) { ... }
same for the rest methods
Here i give You sample code how to use #ModelAttribute in your example. If You have questions feel free to comment.