Spring MVC Data Binding: bind parameter to an object field with different name - spring

Here is my #Controller:
#RequestMapping(value = "/add")
public void addMember(Member member) {
// ... ...
}
here is Member class:
class Member {
private Integer id;
private String username;
// ... ...
}
And here is the request url:
/add?memberId=1&username=bruce
How can I bind the memberId parameter to Member.id field?
Thanks!

If you don't want to use
#RequestMapping(value = "/add")
public void addMember(#RequestParam(value = "username") String username, ...) {
Member member = new Member(username, ...)
// ...
}
you can implement HandlerMethodArgumentResolver:
public class MemberHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return Member.class.equals(parameter.getParameterType());
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String username = webRequest.getParameter("username");
...
return new Member(username, ...);
}
}
And in config:
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean id="memberResolver" class="com.company ... MemberHandlerMethodArgumentResolver"/>
</mvc:argument-resolvers>
</mvc:annotation-driven>
or JavaConfig:
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
...
#Override
public void addArgumentResolvers(List<Handlermethodargumentresolver> argumentResolvers) {
argumentResolvers.add(new MemberHandlerMethodArgumentResolver());
}
}

IMHO the simplest way to do that is to use an additional setter in Member class :
class Member {
private Integer id;
private String username;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public void setMemberId(Integer memberId) { // this one should to the trick
this.id = memberId;
}
// ... ...
}

You can use #JsonProperty Annotation for it.Like below.
class Member {
#JsonProperty("memberId")
private Integer id;
private String username;
// ... ...
}

Related

Converter works for RequestParameter but not for RequestBody field

I have the following converter:
#Component
public class CountryEnumConverter implements Converter<String, CountryEnum> {
#Override
public CountryEnum convert(String country) {
CountryEnum countryEnum = CountryEnum.getBySign(country);
if (countryEnum == null) {
throw new IllegalArgumentException(country + " - Country is not supported!");
}
return countryEnum;
}
}
Registered it is invoked when used for RequestParam
#GetMapping(value = RestApiEndpoints.RESULTS, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ResultDto> getResults(
Principal principal,
#RequestParam CountryEnum country) {
....
}
But this converter is never invoked when used for field in the RequstBody:
#GetMapping(value = RestApiEndpoints.RESULTS, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ResultDto> getResults(
Principal principal,
#RequestBody MyBody myBody) {
....
}
public class MyBody {
#NotNull
private CountryEnum country;
public MyBody() {
}
public CountryEnum getCountry() {
return country;
}
public void setCountry(CountryEnum country) {
this.country = country;
}
}
Your existing org.springframework.core.convert.converter.Converter instance will only work with data submitted as form encoded data. With #RequestBody you are sending JSON data which will be deserialized using using the Jackson library.
You can then create an instance of com.fasterxml.jackson.databind.util.StdConverter<IN, OUT>
public class StringToCountryTypeConverter extends StdConverter<String, CountryType> {
#Override
public CountryType convert(String value) {
//convert and return
}
}
and then apply this on the target property:
public class MyBody {
#NotNull
#JsonDeserialize(converter = StringToCountryTypeConverter.class)
private CountryEnum country;
}
Given the similarity of the 2 interfaces I would expect that you could create one class to handle both scenarios:
public class StringToCountryTypeConverter extends StdConverter<String, CountryType>
implements org.springframework.core.convert.converter.Converter<String, CountryType> {
#Override
public CountryType convert(String value) {
//convert and return
}
}
I found out that if I add the following code to my CountryEnum will do the trick.
#JsonCreator
public static CountryEnum fromString(String value) {
CountryEnumConverter converter = new CountryEnumConverter();
return converter.convert(value);
}

java.lang.ClassCastException: Entity A incompatible with Entity B

I'm trying to get proficient in generics in Java. I have some 100 entities that use the same findBy method in JPA interface. Almost all of them require a call to AwrSnapDetails so instead of adding
#ManyToOne private AwrSnapDetails awrSnapDetails; to each Entity, I've created a HelperEntity class and using #Embedded annotation. Now I have gotten to the point in coding where I can't figure out what I am doing wrong and how to go about resolving this error.
Entity
#Entity
public class AwrMemStats {
String description;
double begin_;
double end_;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
#Embedded
private HelperEntity helperEntity;
public AwrMemStats() {
}
public AwrMemStats(String description, double begin_, double end_, AwrSnapDetails awrSnapDetails) {
this.description = description;
this.begin_ = begin_;
this.end_ = end_;
HelperEntity h = new HelperEntity(awrSnapDetails);
}
// getters/setters removed for clarity
}
Embedded Entity
#Embeddable
public class HelperEntity implements Serializable{
private static final long serialVersionUID = 1L;
#ManyToOne
AwrSnapDetails awrSnapDetails;
public HelperEntity() {
}
public HelperEntity(AwrSnapDetails awrSnapDetails) {
super();
this.awrSnapDetails = awrSnapDetails;
}
public AwrSnapDetails getAwrSnapDetails() {
return awrSnapDetails;
}
public AwrSnapDetails setAwrSnapDetails(AwrSnapDetails awrSnapDetails) {
return this.awrSnapDetails = awrSnapDetails;
}
}
Service Class
#Service
public class HelperService<T> {
#Autowired
private HelperRepository<T> repository;
public void add(T entity) {
repository.save(entity);
}
public void add(List<T> entities) {
repository.saveAll(entities);
}
public T get(T entity) {
T t = repository.findByHelperEntityAwrSnapDetailsStartSnapIdAndHelperEntityAwrSnapDetailsInstanceDetailDbNameAndHelperEntityAwrSnapDetailsInstanceDetailDbId(
((HelperEntity) entity).getAwrSnapDetails().getStartSnapId(),
((HelperEntity) entity).getAwrSnapDetails().getInstanceDetail().getDbName(),
((HelperEntity) entity).getAwrSnapDetails().getInstanceDetail().getDbId());
//((AwrMemStats) entity).getHelperEntity().getAwrSnapDetails().getStartSnapId(),
//((AwrMemStats) entity).getHelperEntity().getAwrSnapDetails().getInstanceDetail().getDbName(),
//((AwrMemStats) entity).getHelperEntity().getAwrSnapDetails().getInstanceDetail().getDbId());
if (t!= null) {
return t;
}
return null;
}
}
Controller
#RestController
public class HelperController<T> {
#Autowired
private HelperService<T> service;
public void add(T entity) {
service.add(entity);
}
public void add(List<T> entities) {
service.add(entities);
}
public T get(T entity) {
return service.get(entity);
}
}
Execution
getAwrSnapDetails() initilized in HelperLoader
#Component
public class LoadAwrMemStats extends HelperLoader{
#Autowired
private HelperController<AwrMemStats> controller;
public void doThis() {
AwrMemStats profile = new AwrMemStats("a",1.0,1.0,getAwrSnapDetails());
AwrMemStats s = controller.get(profile);
ANd finally the ERROR message
Exception in thread "restartedMain" java.lang.reflect.InvocationTargetException
...
...
Caused by: java.lang.ClassCastException: net.mharoon.perfmon.awr.entities.AwrMemStats incompatible with net.mharoon.perfmon.awr.entities.HelperEntity
at net.mharoon.perfmon.awr.service.HelperService.get(HelperService.java:27)
at net.mharoon.perfmon.awr.controller.HelperController.get(HelperController.java:24)
...
...
Update this code works but only for given class AwrMemStats.
public List<T> get(T entity) {
List<T> ts = repository.findByHelperEntityAwrSnapDetailsStartSnapIdAndHelperEntityAwrSnapDetailsInstanceDetailDbIdAndHelperEntityAwrSnapDetailsInstanceDetailDbName(
//((HelperEntity) entity).getAwrSnapDetails().getStartSnapId(),
//((HelperEntity) entity).getAwrSnapDetails().getInstanceDetail().getDbName(),
//((HelperEntity) entity).getAwrSnapDetails().getInstanceDetail().getDbId());
((AwrMemStats) entity).getHelperEntity().getAwrSnapDetails().getStartSnapId(),
((AwrMemStats) entity).getHelperEntity().getAwrSnapDetails().getInstanceDetail().getDbId(),
((AwrMemStats) entity).getHelperEntity().getAwrSnapDetails().getInstanceDetail().getDbName());
if (!ts.isEmpty()) {
return ts;
}
return null;
}
The reason is because you are returning an Object that is not AwrMemStats and assigning it to AwrMemStats.
A simple work around is to replace
public T get(T entity)
with
public <T extends AwrMemStats> T get(T entity)
EDIT : Another solution (which is more generic) is..
replace
public class AwrMemStats
with
public class AwrMemStats extends HelperEntity
then replace
AwrMemStats s = controller.get(profile);
with
AwrMemStats s = (AwrMemStats) controller.get(profile);

Spring Boot - Apache Derby duplicating IDs of a ListArray objects

This little project follows a basic MVC pattern, i'm using spring boot and apache derby as an embedded data base.
1) When adding a hardcoded object list inside service class, they all share the same id. Is there an explanation for this behavior ?
This shows the problem (Don't mind the 'kkk' objects, i've solved that part already)
Screen1
So this is the object account i'm working with :
#Entity
public class Account {
#Id
#Column(name="id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
private String owner;
private double budget;
private double budgetInvest;
private double budgetFonction;
public Account() {
}
public Account(String owner, double budget, double budgetInvest, double budgetFonction
) {
this.owner=owner;
this.budget = budget;
this.budgetInvest = budgetInvest;
this.budgetFonction = budgetFonction;
}
public Account (String owner, double budget) {
this.owner = owner;
this.budget=budget;
}
public Account (String owner) {
this.owner=owner;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public double getBudget() {
return budget;
}
public void setBudget(double budget) {
this.budget = budget;
}
public double getBudgetInvest() {
return budgetInvest;
}
public void setBudgetInvest(double budgetInvest) {
this.budgetInvest = budgetInvest;
}
public double getBudgetFonction() {
return budgetFonction;
}
public void setBudgetFonction(double budgetFonction) {
this.budgetFonction = budgetFonction;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
}
These are the lines responsible for displaying the objects inside the view :
<tr th:each="account : ${accounts}">
<td th:text="${account.id}">id</td>
<td><a href="#" th:text="${account.owner}">Title
...</a></td>
<td th:text="${account.budget}">Text ...</td>
</tr>
Here is the controller :
#Controller
public class AccountController {
#Autowired
private AccountService accountService;
#RequestMapping(value="/", method=RequestMethod.GET)
public String index() {
return "index";
}
#RequestMapping(value="/accountAdd", method=RequestMethod.GET)
public String addAccount(Model model) {
model.addAttribute("account", new Account());
return "accountAdd";
}
#RequestMapping(value="/accountAdd", method=RequestMethod.POST)
public String postAccount(#ModelAttribute Account account) {
accountService.addAccount(account);
return "redirect:listAccount";
}
#RequestMapping(value="/listAccount", method=RequestMethod.GET)
public String listAccount(Model model) {
System.out.println(accountService.getAllAccounts());
model.addAttribute("accounts",accountService.getAllAccounts());
return "listAccount";
}
}
And finally the service class :
#Service
public class AccountService {
#Autowired
private AccountRepository accountRepository;
public List<Account> getAllAccounts(){
List<Account>accounts = new ArrayList<>(Arrays.asList(
new Account("Maths Department",1000000,400000,600000),
new Account("Physics Department",7000000,200000,500000),
new Account("Science Department",3000000,700000,1000000)
));
accountRepository.findAll().forEach(accounts::add);
return accounts;
}
public Account getAccount(long id) {
return accountRepository.findById(id).orElse(null);
}
public void addAccount(Account account) {
accountRepository.save(account);
}
public void updateAccount(long id, Account account) {
accountRepository.save(account);
}
public void deleteAccount(long id) {
accountRepository.deleteById(id);
}
}
Ok, so while i haven't yet found the exact answer as to why it affects the same id for every object in a static list.
I found an elegant workaround to not only solve the issue but also enhance the structure of the code.
Instead of doing whatever barbaric initialization I was trying to perform, It's way better to do this inside the main class :
#SpringBootApplication
public class PayfeeApplication {
#Autowired
private AccountRepository accountRepository;
public static void main(String[] args) {
SpringApplication.run(PayfeeApplication.class, args);
}
#Bean
InitializingBean sendDatabase() {
return () -> {
accountRepository.save(new Account("Maths Department",1000000,400000,600000));
accountRepository.save(new Account("Physics Department",7000000,200000,500000));
accountRepository.save(new Account("Science Department",3000000,700000,1000000));
};
}
}

Java Spring 4 (Annotated) Rest Controller not being hit by REST Client tool in Firefox

Hi,
I have a problem that is very confusing for me because the mapping should work and it looks like it does map when the Spring Boot is started in debug mode. I don't know where else I can check for an obvious solution to this problem.
Here is the application.properties:
server.port=8082
server.contextPath = /
Here is the SpringBootInitializer class that adds a further "/api" to the >Servlet registration:
public class App extends SpringBootServletInitializer {
#Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
#Bean
public ServletRegistrationBean dispatcherServletRegistration() {
final ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet(), "/api/*");
final Map<String, String> params = new HashMap<String, String>();
params.put("contextClass", "org.springframework.web.context.support.AnnotationConfigWebApplicationContext");
params.put("contextConfigLocation", "org.spring.sec2.spring");
params.put("dispatchOptionsRequest", "true");
registration.setInitParameters(params);
registration.setLoadOnStartup(1);
return registration;
}
//
#Override
protected SpringApplicationBuilder configure(final SpringApplicationBuilder application) {
return application.initializers(new MyApplicationContextInitializer()).sources(App.class);
}
public static void main(final String... args) {
new SpringApplicationBuilder(App.class).initializers(new MyApplicationContextInitializer()).run(args);
}
}
Here is the Controler which adds a further "users" to the mapping. The method >which I have set a debug point is the findAll and requires no futher mapping to >get to it (i.e. the root of /users/:
#Controller
#RequestMapping(value = users)
public class UserController extends AbstractController<User> {
#Autowired
private IUserService userService;
public UserController() {
super(User.class);
}
// API
// find
#RequestMapping(method = RequestMethod.GET)
#ResponseBody
public void getItsWorking() {
System.out.println("It's Working!!!");
}
}
Here is the User entity:
#Entity
public class User implements IEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="user_id")
private Long user_id;
#Column(name = "username", unique = true, nullable = false)
private String name;
#Column(unique = true, nullable = false)
private String email;
#Column(nullable = false)
private String password;
#Column(nullable = false)
private Boolean locked;
public User() {
super();
}
public User(final String nameToSet, final String passwordToSet, /*final
Set<Role> rolesToSet,*/ final Boolean lockedToSet) {
super();
name = nameToSet;
password = passwordToSet;
locked = lockedToSet;
}
// API
public Long getId() {
return user_id;
}
public void setId(final Long idToSet) {
user_id = idToSet;
}
public String getName() {
return name;
}
public void setName(final String nameToSet) {
name = nameToSet;
}
public String getEmail() {
return email;
}
public void setEmail(final String emailToSet) {
email = emailToSet;
}
public String getPassword() {
return password;
}
public void setPassword(final String passwordToSet) {
password = passwordToSet;
}
public Boolean getLocked() {
return locked;
}
public void setLocked(final Boolean lockedToSet) {
locked = lockedToSet;
}
}
Here is the output on my Spring Boot debug when it starts up:
Mapped "{[/users],methods=[GET]}" onto public
java.util.List<org.um.persistence.model.User>
org.um.web.controller.UserController.findAll(javax.servlet.http.HttpServletRequest)
So, it looks like it is mapping correctly, but when I hit it using the Rest >Client tool add on in Firefox, I get the following when doing a "GET" on the >following url: http://localhost:8082/api/users using Content-Type: application/json in my header .
What is going on? Very confused.
You should put a #RequestMapping("/api") on you class, and a #RequestMapping("/users") on your method (that should preferably return something to the client).
This ways your endpoint will be exposed as /api/users and you will be able to easily add further endpoints under /api/* into this class.

How to use #ModelAttribute annotation to pass an object to the database?

I am going to use #ModelAttribute instead of #RequestParam to bind the user input and write it to the database. I am just confused when the #ModelAttribute bind the data in my request handler method (#ModelAttribute book Book) as an book object then how should I pass this object to the database? Normally using #RequestParam I bind the user inputs variable by variable according to my model class and then I send them to the db using the related DAO method. I show my classes in below. Can anybody say how my request handler method should look like if I use #ModelAttribute?
Model Class:
#Component
public class Book {
int bookId;
String title;
Author author;
Publisher publisher;
public int getBookId() {
return bookId;
}
public void setBookId(int bookId) {
this.bookId = bookId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Author getAuthor() {
return author;
}
public void setAuthor(Author author) {
this.author = author;
}
public Publisher getPublisher() {
return publisher;
}
public void setPublisher(Publisher publisher) {
this.publisher = publisher;
}
}
DAO:
public class BookDAO extends JdbcDaoSupport {
#Autowired
AuthorDAO authorDAO;
#Autowired
PublisherDAO publisherDAO;
public void addBook(String title, int authorId, int publisherId)
throws ClassNotFoundException, SQLException {
String sql = "insert into tbl_book (title, authId, pubId) values (?, ?, ?)";
this.getJdbcTemplate().update(sql, new Object[]{title, authorId, publisherId});
}
}
Service:
#Service
public class BookService {
#Autowired
BookDAO bookDAO;
public Book getBookById(int bookId) throws ClassNotFoundException,
SQLException {
return bookDAO.getBookById(bookId);
}
public List<Book> getAllBooks() throws ClassNotFoundException,
SQLException {
List<Book> bookList = bookDAO.getAllBooks();
return bookList;
}
public void addBook(String title, int authorId, int publisherId) throws ClassNotFoundException,
SQLException {
bookDAO.addBook(title, authorId, publisherId);
}
}
Controller:
#Controller
public class BookController {
#RequestMapping(value = "/addBookExecution", method = equestMethod.POST)
protected ModelAndView addBookExecution(#RequestParam String title,
#RequestParam int authorId, #RequestParam int blisherId)
throws ClassNotFoundException, SQLException {
bookService.addBook(title, authorId, publisherId);
ModelAndView model = new ModelAndView("adminFunctionsPage");
model.addObject("Msg", "Your request has been processed successfully.");
return model;
}
}
Your form should have parameters names as your book object, check below sample code
<form >
<input type="text" name="authorId"/>
<input type="text" name="authorName"/>
etc...
</form>
Book.java
class Book{
Integer authorId;
String authorName;
etc..
}
#RequestMapping(value = "/addBookExecution", method = equestMethod.POST)
protected ModelAndView addBookExecution(#ModelAttribute Book book)
throws ClassNotFoundException, SQLException {
bookService.addBook(book);
ModelAndView model = new ModelAndView("adminFunctionsPage");
model.addObject("Msg", "Your request has been processed successfully.");
return model;
}

Resources