使用场景:在控制器方法获取用户表单传来的POJO,并且根据POJO修改数据库时,会出现有些字段数据没有出现在实参的POJO中,但是如果直接修改会导致那些没有赋值的字段变为null,所以需要引入@ModelAttribute。
@ModelAttribute思想是把用户表单数据封装成POJO对象前,从数据库取出对应记录并封装成POJO对象,然后根据表单数据修改这个POJO对象,那么那些没有数据的字段就仍然会是原先数据库中的数据。
所以,需要以下步骤
- 在得到用户表单数据后,但是在封装成POJO对象前,从数据库中取出数据并封装。
- 把封装好的对象交给SpringMVC
- SpringMVC根据表单数据修改上一步传入的POJO对象
- SpringMVC把修改后的对象作为控制器中具体用来修改数据库的方法的实参传入。
- 执行数据库修改操作
顺便整理没有使用@ModelAttribute之前的步骤
- SpringMVC创建一个新的POJO对象,然后根据表单传入的数据修改对象数据
- SpringMVC把修改后的对象作为控制器中具体用来修改数据库的方法的实参传入。
- 执行数据库修改操作
所以,使用@ModelAttribute后,增加了1、2两步,并且把第三步中原来应新创建的对象改为从数据库中取出并封装好的对象。
具体实现其实添加1、2两步工作就好,第三步的差别会由SpringMVC自动识别并进行改变。
1、2两步也可以合并为一步,具体为编写一个方法,在其中取出数据库数据并封装,然后把它添加到ModelMap中。而这个方法只需要添加@ModelAttribute注解,SpringMVC将会自动在得到用户表单数据后,但是在封装成POJO对象前调用。以下为一个例子:
//在执行所有方法之前自动执行该方法@ModelAttributepublic void getUser(@RequestParam(value = "id",required = false)Integer id,ModelMap map){ if (id!=null){ //此处通过硬编码赋值的方式创建一个新的POJO对象,但是实际过程中应该是从数据库中取出数据并且封装而成的。 User user = new User(); user.setId(1); user.setUsername("tom"); user.setPassword("123456"); user.setAge(22); //把创建好的对象放入ModelMap中 map.put("user",user); }}@RequestMapping("/modelAttribute")public String modelAttribute(User user){ System.out.println(user); return "success";}
当然这里还有一些问题要处理:
- 添加了@ModelAttribute注解的方法将在所有控制器方法执行之前执行,所以必须要判断具体情况并执行我们想要的代码。上述例子中是通过判断是否存在id,这个id是通过表单传入的,而其他方法不会传入该属性。
- 在map中放入放入的键值对的键默认为POJO类名并把首字母改为小写。如果需要自定义该键名,则需要在控制器方法的POJO类型形参前加入@ModelAttribute并且把value属性改为需要自定义的键名。如:@ModelAttribute(value = “myuser”)User user
- 如果在ModelMap中不存在对应的键值对,那么SpringMVC会判断当前的控制器类是否添加了SessionAttribute注解,如果添加了且value属性中有对应的键名(在上述例子中就是user),但是实际的session中找不到该键值对,那么SpringMVC将会抛出异常,例如:org.springframework.web.HttpSessionRequiredException: Session attribute ‘user’ required - not found in session
- 如果在ModelMap中不存在对应的键值对,或当前控制器类没有添加SessionAttribute注解,或添加了SessionAttribute注解但是value属性没有对应的键名,SpringMVC将会通过反射机制创建一个新的POJO对象传入对应的方法中。
- 如果在同一控制器中定义了多个有@ModelAttribute注解的方法,则按在类中定义的顺序反序执行。