2014-09-03 98 views
2

基本上,表单提交中的表单支持对象始终为空。任何人都可以看到为什么这样吗?Thymeleaf,Spring MVC在表单上缺少表单支持对象提交:null

我有一个简单的控制器,显示设备列表。可以选择设备并将功能应用于它们。这是控制器:

@Controller 
@RequestMapping 
public class DeviceListController { 

    private static final Logger LOG = LoggerFactory.getLogger(DeviceListController.class); 

    @Autowired 
     DeviceService deviceService; 

    @RequestMapping("/devices/list") 
    public String getDevicesPage(ModelMap model) { 
     int page = 0; 
     int size = 10; 

     if(model.get("deviceCommand") != null) { 
      DeviceCommand cmd = (DeviceCommand) model.get("deviceCommand"); 
      page = cmd.getPage(); 
      size = cmd.getSize(); 
     } 
     Pageable pageRequest = new PageRequest(page, size); 
     Page<Device> devices = deviceService.findAllPaginated(pageRequest); 

     model.addAttribute("devices", DeviceMapper.map(devices)); 
     model.addAttribute("deviceCommand", new DeviceCommand(page, size)); 
     return "devices"; 
    } 

    @RequestMapping("/devices/modify") 
    public String modifyDevices(ModelMap model) { 

     LOG.debug("Trying to get the device command {}.", model.get("deviceCommand")); 

     if(model.get("deviceCommand") != null) { 
      DeviceCommand cmd = (DeviceCommand) model.get("deviceCommand"); 

      LOG.debug("Processing directives {} and {} for {}.", cmd.getNewVersion(),cmd.getNewCommand(),cmd.getDeviceModificationIds()); 

     } 
     return getDevicesPage(model); 
    } 
} 

设备模型对象是一个休眠实体:

@Entity //TODO: no connection between device and device details! 
@Table(name="device") 
public class Device { 
    @Id 
    @GeneratedValue 
    private Long id; 

    /** 
    * This is an external representation of the device, and the ID for which the device is most 
    * commonly searched for. The deviceID is a common reference point, used in T2 and in device reporting 
    * on the client side. 
    */ 
    @Column(name="device_id") 
    private String deviceId; 

    /** 
    * A reference to the store number or store identification. Stores may have more than one device. 
    */ 
    @Column(name="retailer_id") 
    private String retailerId; 

    /** 
    * The name of the store where the device is situated. 
    */ 
    @Column(name="retailer_name") 
    private String retailerName; 

    /** 
    * The current version of the client software, which launches the browser. This is only applicable to integrated solutions 
    */ 
    @Column(name="current_client_version") 
    private String currentClientVersion; 

    /** 
    * The target version of the client software, which launches the browser. This is only applicable to integrated solutions 
    */ 
    @Column(name="next_client_version") 
    private String nextClientVersion; 

    /** 
    * Commands to be performed on the client, used most often to "clear the cache". As soon as hte se commands are run, they 
    * are cleared from this column. 
    */ 
    @Column(name="commands") 
    private String commands; 


    @Column(name = "language") 
    private String language; 

    /** 
    * The signature is the client's position in the marketplace and used by other entities 
    * to determine their applicability to the device. 
    * RETAILER_GROUP:Tobaccoland|CHANNEL:Logistik|LOCALE:de-AT|INDUSTRY:5499 
    * The * notation means any, the locale is using the standard locale code (up to 10 characters) 
    * and the industry uses the merchant category codes, which in this case is 
    * Miscellaneous Food Stores - Convenience Stores and Specialty Markets 
    */ 
    @Column(name = "signature") 
    private String signature; //TODO: normalise? 

    /** 
    * This is the traceId that the device is currently up to 
    */ 
    @Column(name = "trace_id") 
    private Long traceId; 
    /** 
    * This is a tracing number that will be generated by touchpoint on each request. 
    */ 
    @Column(name = "last_trace_id") 
    private Long lastTraceId; 

    /** 
    * This is the transaction number that will come from Mercury in each response. 
    */ 
    @Column(name = "last_transaction_id") 
    private Long lastTransactionId; 

    @ManyToMany(cascade=CascadeType.ALL) 
    @JoinTable(name="device_role", 
     joinColumns = {@JoinColumn(name="device_id", referencedColumnName="id")}, 
     inverseJoinColumns = {@JoinColumn(name="role_id", referencedColumnName="id")} 
    ) 
    private List<Role> roles; 

    @ManyToMany(cascade= CascadeType.ALL, mappedBy = "devices", fetch = FetchType.LAZY) 
    private Set<User> users; 

    // getters, setters, equals, hashcode, omitted 
} 

和被映射到DeviceViewModel与DeviceMapper

的DeviceViewModel:

public class DeviceViewModel { 

    private String id; 
    private String retailerName; 
    private String currentClientVersion; 
    private String nextClientVersion; 
    private String commands; 
    private boolean shallModify; 

    public String getId() { 
     return id; 
    } 

    public void setId(String id) { 
     this.id = id; 
    } 

    public String getRetailerName() { 
     return retailerName; 
    } 

    public void setRetailerName(String retailerName) { 
     this.retailerName = retailerName; 
    } 

    public String getCurrentClientVersion() { 
     return currentClientVersion; 
    } 

    public void setCurrentClientVersion(String currentClientVersion) { 
     this.currentClientVersion = currentClientVersion; 
    } 

    public String getNextClientVersion() { 
     return nextClientVersion; 
    } 

    public void setNextClientVersion(String nextClientVersion) { 
     this.nextClientVersion = nextClientVersion; 
    } 

    public String getCommands() { 
     return commands; 
    } 

    public void setCommands(String commands) { 
     this.commands = commands; 
    } 

    public boolean isShallModify() { 
     return shallModify; 
    } 

    public void setShallModify(boolean shallModify) { 
     this.shallModify = shallModify; 
    } 
} 

和DeviceMapper:

public class DeviceMapper { 

    public static DeviceViewModel map(Device device) { 
     DeviceViewModel dto = new DeviceViewModel(); 
     dto.setId(device.getDeviceId()); 
     dto.setRetailerName(device.getRetailerName()); 
     dto.setCurrentClientVersion(device.getCurrentClientVersion()); 
     dto.setNextClientVersion(device.getNextClientVersion()); 
     dto.setCommands(device.getCommands()); 
     return dto; 
    } 

    public static List<DeviceViewModel> map(Page<Device> devices) { 
     List<DeviceViewModel> dtos = new ArrayList<>(); 

     for(Device device : devices) { 
      dtos.add(map(device)); 
     } 

     return dtos; 
    } 
} 

现在DeviceCommand,这是我的表单支持对象:

public class DeviceCommand { 

    private List<String> deviceModificationIds; 
    private String newVersion; 
    private String newCommand; 
    private int page; 
    private int size; 

    public DeviceCommand() {} 

    public DeviceCommand(int page, int size) { 
     this.page = page; 
     this.size = size; 
    } 

    public List<String> getDeviceModificationIds() { 
     return deviceModificationIds; 
    } 

    public void setDeviceModificationIds(List<String> deviceModificationIds) { 
     this.deviceModificationIds = deviceModificationIds; 
    } 

    public String getNewVersion() { 
     return newVersion; 
    } 

    public void setNewVersion(String newVersion) { 
     this.newVersion = newVersion; 
    } 

    public String getNewCommand() { 
     return newCommand; 
    } 

    public void setNewCommand(String newCommand) { 
     this.newCommand = newCommand; 
    } 

    public int getPage() { 
     return page; 
    } 

    public void setPage(int page) { 
     this.page = page; 
    } 

    public int getSize() { 
     return size; 
    } 

    public void setSize(int size) { 
     this.size = size; 
    } 
} 

终于devices.html页面的相关部分:

<form action="#" th:action="@{/devices/modify}" th:object="${deviceCommand}" method="post"> 
<table class="box-table-a"> 
    <h1 th:text="#{device.table.caption}">Site Users</h1> 
    <thead> 
     <tr> 
      <th scope="col" th:text="#{device.check.label}">Select</th> 
      <th scope="col" th:text="#{device.id.label}">(<span th:text="${device.retailer.name.label}"></span>)</th> 
      <th scope="col" th:text="#{device.current.label}">Curr Version</th> 
      <th scope="col" th:text="#{device.next.label}">Next Version</th> 
      <th scope="col" th:text="#{device.commands.label}">Commands</th> 
     </tr> 
    </thead> 
    <tbody> 
     <tr th:each="d : ${devices}"> 
      <td><input type="checkbox" th:field="*{deviceModificationIds}" th:value="${d.id}"/></td> 
      <td th:text="${d.id}">(<span th:text="${d.retailerName}"></span>)</td> 
      <td th:text="${d.currentClientVersion}">Washington</td> 
      <td th:text="${d.nextClientVersion}">gwash</td> 
      <td th:text="${d.commands}">gwash</td> 
     </tr> 
     <tr> 
     <td colspan="2"> 

      </td> 
      <td th:text="#{device.change.version.label}"></td> 
      <td th:text="#{device.add.command.label}"></td> 
      <td></td> 
     </tr> 
     <tr> 
     <td colspan="2"> 

      </td> 
      <td><input type="text" th:field="*{newVersion}"/></td> 
      <td><input type="text" th:field="*{newCommand}"/></td> 
      <td><button type="submit" th:text="#{device.modify.action.button}">Action</button></td> 
     </tr> 
    </tbody> 
</table> 
</form> 

虽然表单操作,确实到达控制器中的modifyDevices方法,但deviceCommand form-b​​acking对象为null。

这是为什么呢?

回答

2

只需添加DeviceViewModel类型的参数传递给modifyDevices方法
所以它会是这样

@RequestMapping("/devices/modify") 
public String modifyDevices(ModelMap model, DeviceViewModel deviceCommand) { 
/* access the submitted object as you want ....*/ 
} 

你会发现这个问题How to pass two objects to use in a form using thymeleaf?有用

+0

辉煌 - 这就是答案。实际上,我刚刚发现了@ModelAttribute注释的奇迹 – 2014-09-03 10:18:02