I just created my simple web application using Spring frameworks and hibernate. But I face the crucial problem when there is a transaction with my database.
When I tried to update my object, undeclared variables become null when saved to the database.
I will explain it with an example.
Model.java:
public class Model implements Serializable {
private int id;
private String firstName;
private String lastName;
private String description;
//setter getter
}
ModelDaoImpl.java:
#Repository("modelDao")
public class ModelDaoImpl implements ModelDao {
#Autowired
SessionFactory sessionFactory;
public Model create (Model model) throws Exception {
this.sessionFactory.getCurrentSession().save(model);
return model;
}
public Model update (Model model) throws Exception {
this.sessionFactory.getCurrentSession().update(model);
return model;
}
public Model get (Serializable id) throws Exception {
return (Model) this.sessionFactory.getCurrentSession().get(Model.class, id);
}
}
ModelServiceImpl.java:
#Service("modelService")
public class ModelServiceImpl implements ModelService {
#Autowired
modelDao modelDao;
#Transactional
public Model create (Model model) throws Exception {
return modelDao.create(model);
}
#Transactional
public Model update (Model model) throws Exception {
return modelDao.udpdate(model);
}
#Transactional
public Model get (Serializable id) throws Exception {
return modelDao.get(id);
}
}
ModelController.java:
#Controller
public class ModelController {
#Autowired
ModelService modelService
#RequestMapping(value="/editModel", method.RequestMethod.GET)
public String formCreator(ModelMap model, HttpServletRequest request) {
Integer id = new Integer(request.getParameter("id"));
Model modelData = new Model();
if (id != null) {
modelData = modelService.get(id);
}
model.addAttribute("modelData", modelData);
return "editModel"
}
#RequestMapping(value="/saveModel", method.RequestMethod.POST)
public String saveData(#ModelAttribute("modelData") Model modelData) {
if (modelData.getId() == null) {
modelService.create(modelData);
} else {
modelService.update(modelData);
}
return "redirect:/modelList";
}
//SKIP FOR GET MODEL LIST AND GET MODEL DETAIL
}
editModel.jsp:
<form:form action="${pageContext.request.contextPath}/saveModel" method="POST" modelAttribute="modelData">
<table>
<form:hidden path="id"/>
<tr>
<td>First Name:</td>
<td><form:input path="firstName"/></td>
</tr>
<tr>
<td>Last Name:</td>
<td><form:input path="lastName"/></td>
</tr>
<tr>
<td colspan="2" align="center"><input type="submit" value="Save"></td>
</tr>
</table>
</form:form>
From jsp page we see that I didn't declare variable 'description', but when update my existing data the variable 'description' become null. I have tried using #DynamicUpdate or related to dynamic update process, but the result was still the same, the variable 'description' still become null.
Any suggestion?
The problem is that the way your controller is written: a new Model instance is created
and populated on execution of saveData.
You can update your code to be like the below. Now on POST the Model instance
will be that returned from getModel() rather than a new instance and only the
values specified in the incoming request wil be updated. We can also update the GET to use the same code to populate the model.
Essentially then:
on GET - the model will be populated for the UI by a call to #ModelAttribute
on POST - the model will be populated and the Model instance returned will be passed to saveData();
See:
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-modelattrib-method-args
#Controller
public class ModelController {
#Autowired
ModelService modelService
#RequestMapping(value="/editModel", method.RequestMethod.GET)
public String formCreator(HttpServletRequest request) {
return "editModel"
}
#RequestMapping(value="/saveModel", method.RequestMethod.POST)
public String saveData(#ModelAttribute("modelData") Model model) {
//you can have 1 method in your service and DAO
//DAO can use saveOrUpdate() method of session;
modelService.persist(model);
return "redirect:/modelList";
}
#ModelAttribute("modelData")
public Model getModel(#RequestParam(name = "id", required = false)Integer id){
return id != null ? modelService.get(id) : new Model();
}
}
Related
Code
Order
public class Order {
private String id;
private BigDecimal amount;
//get set constructor constructors
}
IndexController
#Controller
public class IndexController {
#GetMapping
public String index() {
return "index";
}
}
OrderController
#Controller
#RequestMapping("/orders")
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
#GetMapping
public Mono<String> list(Model model) {
var orders = orderService.orders();
model.addAttribute("orders", orders);
return Mono.just("orders/list");
}
}
OrderGenerator
public class OrderGenerator {
public Order generate() {
var amount = ThreadLocalRandom.current().nextDouble(1000.00);
return new Order(UUID.randomUUID().toString(), BigDecimal.valueOf(amount));
}
}
OrderService
#Service
public class OrderService {
private final Map<String, Order> orders = new ConcurrentHashMap<>(10);
#PostConstruct
public void init() {
var generator = new OrderGenerator();
for (int i = 0; i < 25; i++) {
var order = generator.generate();
orders.put(order.getId(), order);
}
}
public Mono<Order> findById(String id) {
return Mono.justOrEmpty(orders.get(id));
}
public Mono<Order> save(Mono<Order> order) {
return order.map(this::save);
}
private Order save(Order order) {
orders.put(order.getId(), order);
return order;
}
public Flux<Order> orders() {
return Flux.fromIterable(orders.values()).delayElements(Duration.ofMillis(128));
}
}
html
<body>
<h1>Orders</h1>
<table>
<thead>
<tr>
<th></th>
<th>Id</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
<tr th:each="order : ${orders}">
<td th:text="${order.id}"></td>
<td th:text="${#numbers.formatCurrency(order.amount)}"
style="text-align: right"></td>
</tr>
</tbody>
</table>
</body>
error
An error happened during template parsing (template: "class path resource [templates/orders/list.html]")
Exception evaluating SpringEL expression: "order.id" (template: "orders/list" - line 19, col 9)
EL1008E: Property or field 'id' cannot be found on object of type 'reactor.core.publisher.FluxOnAssembly' - maybe not public or not valid?
I do not how to solve the problem, i hope you can help me.thanks for you reply.
You need to add reactive primitives like Flux and Mono as IReactiveDataDriverContextVariable.
IReactiveDataDriverContextVariable reactiveDataDrivenMode =
new ReactiveDataDriverContextVariable(orders);
model.addAttribute("orders", reactiveDataDrivenMode);
check that you are using spring-boot-starter-webflux instead of spring-boot-starter-web
I have a weird error that is driving me crazy.
I have a Controller with two different Get Methods - for two different url mappings.
URL1 works perfectly fine, with URL2, I get
[THYMELEAF][http-nio-8080-exec-3] Exception processing template
"questionnaireForm": Error during execution of processor
'org.thymeleaf.standard.processor.attr.StandardEachAttrProcessor'
(questionnaireForm:10)
caused by
java.lang.NullPointerException: null at org.thymeleaf.context.WebSessionVariablesMap.hashCode(WebSessionVariablesMap.java:276) ~[thymeleaf-2.1.4.RELEASE.jar:2.1.4.RELEASE]
After hours of checking each line, it's clear that my Controller Get Method is adding a non-null object to the Model.addAttribute before calling the view.
I've also removed all of my code from the template and just added the following to troubleshoot:
<tr th:each="var : ${#vars}">
<td th:text="${var.key}"></td>
<td th:text="${var.value}"></td>
This is returning the same null error as well!
The only thing i could find on the web was a bug with Thymeleaf 2.1.4 that was caused by not having a HttpSession Object
https://github.com/thymeleaf/thymeleaf/issues/349
but then again, my URL1 works fine!
I haven't posted any code here because I'm not sure what else to post, please ask!
EDIT: Added Controller Code. /admin/questions is URL1 that works fine, '/admin/questionnaires' is URL2 that doesn't work.
#Controller
public class AdminController {
private QuestionRepository questionRepository;
private EQuestionnaireRepository eQuestionnaireRepository;
private EQuestionnaire eQuestionnaire;
#Autowired
public AdminController(QuestionRepository questionRepository,
EQuestionnaireRepository
eQuestionnaireRepository){
this.questionRepository = questionRepository;
this.eQuestionnaireRepository = eQuestionnaireRepository;
eQuestionnaire = eQuestionnaireRepository.findAll().get(0);
}
#RequestMapping(value = "/admin/questions", method=RequestMethod.GET)
public String questionForm(Model model) {
model.addAttribute("question", new Question());
return "questionForm";
}//-->This Works fine
#RequestMapping(value = "/admin/questions", method=RequestMethod.POST)
public String saveQuestion(#ModelAttribute Question newQuestion, BindingResult bindingResult, Model model) {
if( bindingResult.hasErrors())
{
System.out.println(bindingResult);
System.out.println("BINDING RESULTS ERROR");
return "questionForm";
}
questionRepository.save(newQuestion);
return "questionForm";
}
#RequestMapping(value = "/admin/Questionnaires", method=RequestMethod.GET)
public String QuestionnaireForm(Model model){
model.addAttribute("ListofQ",eQuestionnaire.getQuestionList());
return "questionnaireForm";
UPDATE: I changed over to thymeleaf 2.1.5-SNAPSHOT, and now I'm not getting the previously mentioned error - but a different error:
There was an unexpected error (type=Internal Server Error, status=500).
Exception evaluating SpringEL expression: "ask.questionText"
(questionnaireForm:13)
So now, the object reference is null in the template. It's not null when i put it into the Model (i've got System.out of the getQuestionText method inside the GET method, so thats confirmed)
This is the simple code I'm trying in the template:
<table>
<tr th:each= "ask : ${ListofQ}"></tr>
<tr th:text= "${ask.questionText}"></tr>
</table>
Here is the Question Object:
#Entity
#Table(name = "Questions")
public class Question {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long ID;
private int sequence;
private String questionText;
private int weightage;
private String option1 = "option1";
private String option2 = "option2";
private String option3 = "option3";
public Question() {
}
public Long getID() {
return ID;
}
public void setID(Long iD) {
ID = iD;
}
public int getSequence() {
return sequence;
}
public void setSequence(int sequence) {
if (sequence > 0) this.sequence = sequence;
}
public String getQuestionText() {
return questionText;
}
public void setQuestionText(String questionText) {
this.questionText = questionText;
}
public int getWeightage() {
return weightage;
}
public void setWeightage(int weightage) {
this.weightage = weightage;
}
public void setOption1(String option1) {
this.option1 = option1;
}
public void setOption2(String option2) {
this.option2 = option2;
}
public void setOption3(String option3) {
this.option3 = option3;
}
public String getOption1() {
return option1;
}
public String getOption2() {
return option2;
}
public String getOption3() {
return option3;
}
output in "each" need to be in the same statement , i think this will work for you
<table>
<tr th:each= "ask : ${ListofQ}" th:text= "${ask.questionText}"></tr>
</table>
You are not returning the model object to the Thymeleaf.
Try something like this:
#RequestMapping(value = "/admin/Questionnaires", method=RequestMethod.GET)
public ModelAndView QuestionnaireForm(Model model){
return return new ModelAndView("questionnaireForm", "ListofQ",eQuestionnaire.getQuestionList());
}
Good evening!
public class Order {
private int idOrder;
private Basket basket;
// getter and setter
}
public class AnonymousOrder {
private String name;
private String telephone;
// getter and setter
}
public class UserOrder {
private User user;
// getter and setter
}
public class OrdersForm {
private List< ? extends Order> orders;
// getter and setter
}
#RequestMapping(value="/showOrders")
public String showOrders(Model model){
List<? extends Order> orders= adminManager.searchAllOrders();
OrdersShowForm ordersForm = new OrdersShowForm();
ordersForm.setOrders(orders);
model.addAttribute("ordersForm", ordersForm);
return "showOrders";
}
#RequestMapping(value="/showOrders", method = RequestMethod.POST)
public String showOrdersPOST(#ModelAttribute("ordersForm") OrdersShowForm ordersForm){
System.out.print(ordersForm);
return "showOrders";
}
<form:form modelAttribute="ordersForm">
<table class="features-table" border="1">
<c:forEach items="${ordersForm.orders}" var="order" varStatus="status">
<tr>
<c:if test="${order['class'].simpleName != 'UserOrder'}">
<td>
<input name="orders[${status.index}].name" value="${order.name}"/>
</td>
</c:if>
</c:forEach>
</table>
Problem: I am passing on page two types of data: UserOrder and AnonymousOrder, but when I try to get them on the server then come data type Order.
Question: How to transfer data to the server without changing their actual type?
P.S. sorry for my English)
I have an application written in Spring 3.0 hooked up using Hibernate to a database. I have a controller to an update form. Whenever the form is submitted, I expect the object that is shown to be updated however a new object is created with a new ID value. I've looked over the "petclinic" sample and i can't see how it is different.
POJO
public class Person
{
private int id;
#NotNull
private String name;
//getter/setter for id
//getter/setter for name
}
Controller
public class PersonUpdateController
{
//injected
private PersonService personService;
#RequestMapping(value="/person/{personId}/form", method=RequestMethod.POST)
public String updateForm(ModelMap modelMap, #PathVariable personId)
{
Person person = personService.getById(personId);
modelMap.addAttribute(person);
return "person/update";
}
#RequestMapping(value="/person/{personId}", method=RequestMethod.POST)
public String update(ModelMap modelMap, #Valid Person person, BindingResult bindingResult)
{
if(bindingResult.hasErrors())
{
modelMap.addAttribute(person);
return "person/update";
}
personService.save(person);
return "redirect:person/" + person.getId() + "/success";
}
}
JSP
<spring:url value="/person/${person.id}" var="action_url" />
<spring:form action="${action_url}" modelAttribute="person" method="POST">
<spring:input name="name" path="name" />
<input type="submit" value="Save" />
</spring:form>
PersonService Implementation
public class HibernatePersonService
implements PersonService
{
//injected
private SessionFactory sessionFactory;
//other methods
public void save(Person person)
{
Session session = sessionFactory.getCurrentSession();
session.saveOrUpdate(person);
}
}
Spring MVC doesn't do any magic with HTML forms. Since your form contains only one field, you get only one field populated in update method. So, you have two options:
Pass id as a hidden field in the form: <spring:hidden path = "id" />. Note that in this case you need to check possible consequences for security (what happens if malicious person changes the id).
Store Person in the session so that data from the form is used to update the stored object (note that it may cause interference if several instances of the form is opened in one session). That's how it's done in Petclinic:
-
#SessionAttributes("person")
public class PersonUpdateController {
...
#RequestMapping(value="/person/{personId}", method=RequestMethod.POST)
public String update(ModelMap modelMap, #Valid Person person,
BindingResult bindingResult, SessionStatus status)
{
...
personService.save(person);
status.setComplete(); // Removes person from the session after successful submit
...
}
#InitBinder
public void setAllowedFields(WebDataBinder dataBinder) {
dataBinder.setDisallowedFields("id"); // For security
}
}
I can pre-populate my Stripes JSP form with an object, client in my case, but when I submit this form, my object is returning as null.
I have created a second "temp" object that is a parallel duplicate of client and this retains its values, so I can't see an issue passing an object in the request
My form is as follows :
<s:form beanclass="com.jameselsey.salestracker.action.ViewClientAction">
<s:hidden name="clientA" value="${actionBean.clientA}"/>
<s:hidden name="clientId" value="${actionBean.clientId}"/>
<table>
<tr>
<td>Name : </td>
<td><s:text name="client.name"/></td>
</tr>
<tr>
<td>Sector : </td>
<td><s:text name="client.sector"/></td>
</tr>
<!-- omitted some attirbutes, not needed here -->
</table>
</s:form>
My action looks like
public class ViewClientAction extends BaseAction
{
#SpringBean
ClientService clientService;// = new ClientService();
private Integer clientId;
private Client client;
private Client clientA;
public void setClient(Client client)
{
this.client = client;
}
public Integer getClientId()
{
return clientId;
}
public void setClientId(Integer clientId)
{
this.clientId = clientId;
}
public Client getClientA()
{
return clientA;
}
public void setClientA(Client clientA)
{
this.clientA = clientA;
}
public Client getClient()
{
return client;
}
#DefaultHandler
public Resolution quickView()
{
clientA = clientService.getClientById(clientId);
client = clientService.getClientById(clientId);
return new ForwardResolution("/jsp/viewClientQuickView.jsp");
}
public Resolution save()
{
clientService.persistClient(client);
return new ForwardResolution("/jsp/reports.jsp");
}
public Resolution viewClientInfo()
{
client = clientService.getClientById(clientId);
return new ForwardResolution("/jsp/viewClientClientInfo.jsp");
}
...
If I set a breakpoint at clientService.persistClient(client); I can see that ClientA has all of the original values of the object, yet client is nulled.
Have I missed something that binds the form bean to the client object in my action?
Thanks
Add this line in your JSP:
<s:hidden name="client" value="${actionBean.client}"/>
I got this scenario working by adding a #Before method to re-hydrate the nested object. After this, save works properly
#Before(stages = LifecycleStage.BindingAndValidation)
public void rehydrate() {
if (context.getRequest().getParameter("save")!=null){
this.domainObject = getHibernateSession().load(DomainObject.class, context.getRequest().getParameter("id"));
}
}
public void save(){
Session session=getHibernateSession();
session.update(domainObject);
session.commit();
//...
}