I try to make handler and router classes of spring boot webflux. The model class is user class. Codes are
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Document(collection="Users")
public class User {
#Id
private String _id;
#Indexed(unique = true)
private Long id;
#Indexed(unique=true)
private String username;
private String password;
private String email;
private String fullname;
private String role;
}
And below is the handler class of webflux project. In register method, I make the id duplication test codes. But It is totally WRONG.
#Component
public class UserHandler {
#Autowired
private UserReactiveMongoRepository userRepository;
public Mono<ServerResponse> register(ServerRequest request) {
Mono<User> monoUser = request.bodyToMono(User.class);
String id = monoUser.map(u -> u.get_id()).toString();
if(userRepository.existsById(id) == null)
return ServerResponse.ok().build(userRepository.insert(monoUser));
return ServerResponse.ok().build();
}
}
I want to extract the username or id string from Mono of spring webflux.
Any comments will be needed. I am stuck with this part.
One of the things which is wrong here is this like String id = monoUser.map(u -> u.get_id()).toString();. The toString will return you a String like "Mono#13254216541", because you are calling Mono.toString.
One more thing, you shouldn't use the request's data in the body of your function, but in a map or flatMap function.
You could replace it by something like (I'm doing it by head so it might not be 100% syntax corect):
Mono<User> userMono = request.bodyToMono(User.class);//Create a Mono<User>
userMono.map((user) -> { //In the map method, we access to the User object directly
if(user != null && user.getId() != null){
return userRepository.insert(user); // Insert User instead of Mono<User> in your repository
}
return null;
}) //This is still a Mono<User>
.map(insertedUser -> ServerResponse.ok(insertedUser)) //This is a Mono<ServerResponse>
.switchIfEmpty(ServerResponse.ok());
Hope this helps!
a more clean approach would be (i don't like the return of null in the map that others have done) is use the doOnSuccess:
request.bodyToMono(User.class)
.doOnSuccess(user -> return userRepository.insert(user))
.map(user -> ServerResponse.ok(user))
i have left out any error checks but ofc they should be done properly.
Related
public class UserList {
private String id;
private String email;
private String userType;
private String rolls;
private String partner;
private Integer customersLinked;
private String position;
private String status;
#Autowired
ICustomerRepository customerRepository;
public UserList (Users user){
this.id = user.getId();
this.email = user.getEmail();
this.userType = user.getUserType();
this.rolls = user.getRolls();
this.partner = user.getPartner();
List<Customer> customersLinked = customerRepository.findAllByLinkedUsersIn(user.getId());
this.customersLinked = 0;
this.position = user.getPosition();
this.status =user.getStatus();
}
//Getter and Setter
}
This class is used as a list in the frontEnd ,get specific data ,not send all the data
#RequestMapping(value = "usersLinked/{id}/{type}", method = RequestMethod.GET)
public Object getUsersLinkedById(#PathVariable("id") String id,#PathVariable("type") Integer type) {
List<String> users = null;
switch (type) {
case 0:
users = usersRepository.findAll().stream().map(m -> m.getId()).collect(Collectors.toList());
break;
}
//Add userList
List<UserList> userList = new ArrayList<>();
if(users != null)
{
users.forEach(userId ->
{
Optional<Users> user = this.usersRepository.findById(userId);
userList.add(new UserList(user.get()));
});
}
return userList;
}
}
As you can see from above I am calling al the data from the user repository and sending it the list
My customer repository
public interface ICustomerRepository extends MongoRepository<Customer, String> {
Customer findByBusinessInformation_businessName(String businessName);
List<Customer> findByBusinessInformation_partnerAssigned(String partnerAssigned);
#Query("{ 'LinkedUsers' : ?0 }")
Customer findByLinkedUsers(String id);
List<Customer> findAllByLinkedUsersIn(String id);
}
In the userList I get the error when I add the logic wityh the customerRepository ,without the repository there everything is working(Want to use the repository to get an array of customer and then get the size() of the array and add it to linkedCustomers). Am I missing sommething
You are trying to inject the field customerRepository using Autowired annotation, but your class is not injectable.
You can add an annotation #Repository on your class UserList
Or use constructor injection (better way to inject your beans)
You're probably missing the #repository annotation on top of your repository class.
Another unrelated word of advice:
In your controller you use findAll and filter in java to keep only the ids.
Then you go to the same repository and perform another query per user-id from above.
This is a causing you to create multiple database calls which are one of the most expensive operations you can do, when you already have all your data from the first single query...
Also if you're only looking at the bottom part of the function you don't event need a query per each user-id (when you have a list of user ids as input), you can create a query that uses the 'in' convention and pass a list of user-ids to create a single db call.
First of all I would get rid of #Autowired ICustomerRepository customerRepository; in UserList class. It doesn't belong there. The counting of linked customers should be executed in ICustomerRepository and the result to be passed into UserList via the constructor.
e.g.
public class UserList {
private String id;
private String email;
private String userType;
private String rolls;
private String partner;
private Long customersLinked; //better use Long instead of Integer
private String position;
private String status;
// constructor takes the number of linked customers as parameter
public UserList (Users user, Long customersLinked ) {
this.id = user.getId();
this.email = user.getEmail();
this.userType = user.getUserType();
this.rolls = user.getRolls();
this.partner = user.getPartner();
this.customersLinked = customersLinked;
this.position = user.getPosition();
this.status =user.getStatus();
}
//Getter and Setter
}
and then create the count query in ICustomerRepository
e.g.
public interface ICustomerRepository extends MongoRepository<Customer, String> {
//other methods
Long countByLinkedUsersIn(String id); //not so sure if this query works in mongo
}
and finally in your controller
Optional<Users> user = this.usersRepository.findById(userId);
Long count = this.usersRepository.countByLinkedUsersIn(userId);
userList.add(new UserList(user.get(), count));
P.S. I have a doubt for the query method: Long countByLinkedUsersIn(String id);. Usually when repository methods have "In" in their names, countByLinkedUsersIn, then it is expected as parameter a List and not a single user id. However if your previous method List<Customer> findAllByLinkedUsersIn(String id); worked for you, then this one should work too.
I'm new in spring & try to enhance my skill by creating little projects. In my new app I'd like to put uniqueness check on accountNumber, unfortunately I did not get success. I'd like to apply isPresent() method but it doesn't exist when I call it. Grateful if I get help.
Following is my Service class where I apply logics, I also mention Rep. & Ent. classes for your reference. I apply unique condition in 3rd line where find ***if ..... ***.
I've found similar question but the solution in this stack doesn't match with the way I like to do, I'd like to use JPA not JPQL.
AccountService
#Autowired
private AccountRepository accountRepository;
#Transactional
public ResponseEntity<Object> addAccount(CurrentAccount currentAccount){
**if(currentAccount.getAccountNumber().is)**
accountRepository.save(currentAccount);
return ResponseEntity.ok("Account Accepted");
}
CurrentAccount
public class CurrentAccount {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "account_number")
private String accountNumber;
#Column(name = "account_balance")
private Integer accountBalance;
AccountRepository
public interface AccountRepository extends JpaRepository<CurrentAccount, Long> {
Optional<CurrentAccount> findByAccountNumber(String accountNumber);
}
Simply use existsBy method of JPA, Something like this.
It returns true, if the CurrentAccount exists with the given account number.
boolean existsCurrentAccountByAccountNumber(String accountNo);
If you want to check with more than one parameter then do this.
boolean existsCurrentAccountByAccountNumberAndName(String accountNo, String name);
Service
#Transactional
public ResponseEntity<Object> addAccount(CurrentAccount currentAccount){
if(accountRepository.existsCurrentAccountByAccountNumberAndName(currentAccount.getAccountNumber(), currentAccount.getName())){
return new ResponseEntity<>(HttpStatus.BAD_REQUEST); // or anything you want to do if record is already exists.
}else{
accountRepository.save(currentAccount);
return ResponseEntity.ok("Account Accepted");
}
}
This is my method (Spring):
public boolean checkAccount (String email) {
try {
String SQL = "select * from Account where email=?";
Account account = jdbcTemplateObject.queryForObject(SQL, new Object[]{email}, new AccountMapper());
if (account != null) {
return true;
}
} catch (EmptyResultDataAccessException e) {
return false;
}
return false;
}
Good evening.
I am studying reactive programming and I have encountered the following problem.
I am running two parallel queries to the database and want to combine the results and give them back
#GetMapping
public Mono<User> get(#RequestParam("id") String id, #RequestParam("cId") String cId) {
Mono<User> userMono = Mono.fromCallable(() -> userServ.get(id))
.flatMap(userMono1 -> userMono1)
.subscribeOn(Schedulers.boundedElastic());
Mono<Comment> ger = Mono.fromCallable(() -> commentServ.ger(cId))
.flatMap(commentMono -> commentMono)
.subscribeOn(Schedulers.boundedElastic());
return Mono.zip(userMono, ger)
.map(pair -> {
User t1 = pair.getT1();
t1.setComment(pair.getT2());
return t1;
});
But the point is that the comment may be empty, and then I expect to return the json of such a structure
{
"id": "5e6cbf395214a42f51b57121",
"name": "Bob",
"surname": null,
"comment": null
}
Instead I get an empty response. Apparently this is due to mono zip, but how else can I combine the results, while maintaining query parallelism
My entities:
#Document
#AllArgsConstructor
#NoArgsConstructor
#Data
public class Comment {
#Id
String id;
String userId;
String comment;
}
#Document
#AllArgsConstructor
#NoArgsConstructor
#Data
public class User {
#Id
private String id;
private String name;
private String surname;
private Comment comment;
}
How can I resolve this situation?
Zip/ZipWith need elements to produce their output. If it could be empty , you could use below methods to set some default value.
defaultIfEmpty(new Comment()) or
switchIfEmpty(Mono.fromSupplier(() -> new Comment())
If you do not want to use new Comment() and set null to comment object, we can try this way.
userMono
.zipWith(commentMono.defaultIfEmpty(new Comment()))
.map(pair -> {
User user = pair.getT1();
Comment comment = pair.getT2();
if(Objects.nonNull(comment.getUserId()))
user.setComment(comment);
return user;
});
User model:
#Entity
#Table(name="user")
public class User {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
#NotBlank
#Column(name="username")
private String username;
#NotEmpty
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name="user_role", joinColumns = {#JoinColumn(name="user_id")},
inverseJoinColumns = {#JoinColumn(name="role_id")})
private Set<Role> roles;
}
Controller:
#RequestMapping(value = {"/users/edit/{id}"}, method = RequestMethod.POST)
public String editUser(ModelMap model, #Valid #ModelAttribute("user") User user, BindingResult result) {
if(result.hasErrors()) {
return "AddUserView";
}
return "redirect:/users";
}
Test with MockMVC:
#Test
public void performUpdateUserTest() throws Throwable {
mockMvc.perform(post("/users/edit/{id}", user.getId())
.param("username", "User"));
}
Well, fine, I can pass a param username as always using param(). But what should I do with ROLES? This field is a separate object. I can't pass it using param(). Then how is it possible to pass it in the test?
The only way out I found is to create an entity and pass it using .flashAttr():
#Test
public void performUpdateUserTest() throws Throwable {
User user = new User("User", new HashSet<Role>(Arrays.asList(new Role("USER"))));
mockMvc.perform(post("/users/edit/{id}", user.getId())
.flashAttr("user", user));
}
But then, what if I need to test that user can't be updated because of binding error in the ROLES field(ROLES can't be null, and suppose, it was set as null)? Thus, I'm not able to create user(and use it with .flashAttr) already with a binding error as the exception will be thrown. And I still have to pass it separately.
Well, after a long time of searching, I found out that I should add a converter to the MockMVC. What converter is you can read HERE, for instance.
I had it already in my project but didn't realize that it didn't work with MockMVC.
So, you can add the converter to MockMVC like that:
#Autowired
private StringToRoleConverter stringToRoleConverter;
#Before
public void init() {
FormattingConversionService cs = new FormattingConversionService();
cs.addConverter(stringToRoleConverter);
mockMvc = MockMvcBuilders.standaloneSetup(userController)
.setConversionService(cs)
.build();
}
Converter itself:
#Component
public class StringToRoleConverter implements Converter<String, Role> {
#Autowired
private RoleService roleService;
#Override
public Role convert(String id) {
Role role = roleService.findById(Integer.valueOf(id));
return role;
}
}
And then I can add param like that:
mockMvc.perform(post("/users/edit/{id}", user.getId())
.param("roles", "2"))
though I'm passing a string there, it will be converter to Role with the help of Spring converter.
I'm having a hard times figuring out how to do update scenario in Play 2 Java
I have
User.java model
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long id;
#Constraints.Required
public String email;
#Constraints.Required
public String fullname;
}
And I want to update it, so in my controller I do
public Result update(Long id) {
ObjectNode result = Json.newObject();
User employee = userService.get(id);
Form<User> userForm = formFactory.form(User.class).fill(employee);
// This won't trigger validation because it uses fill() not bind()
if (userForm.hasError()) {
result.set("message", userForm.errorsAsJson());
return badRequest(result);
}
// do update here
}
Then I try to some different approach like this
public Result update(Long id) {
ObjectNode result = Json.newObject();
User employee = userService.get(id);
Form<User> userForm = formFactory.form(User.class).fill(employee);
userForm = userForm.bindFromRequest();
// This will trigger validation but bindFromRequest will override my fill(employee) before.
if (userForm.hasError()) {
result.set("message", userForm.errorsAsJson());
return badRequest(result);
}
// do update here
}
The bindFromRequest() above will override my fill(employee). I don't want to do that because when in my request I just want to fill fullname and not my email, my email property will trigger its required validation.
So my question is, how can I only update my fullname attribute with the form i fill using existing value and still triggering validation constraints from my model?
Change userForm = userForm.bindFromRequest(); to userForm.bindFromRequest();
I have working code very similar to yours and this is the only difference I've observed.