Spring MVC转换如何 [英] Spring MVC conversion HOW TO

查看:123
本文介绍了Spring MVC转换如何的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有车辆服务,其中包括零件清单。添加新服务不是问题,查看服务不是问题,但是当我尝试实现编辑时,它不会预先选择部件列表。因此,我认为这是一个Thymeleaf问题,我在这里发布了这个问题。

I have vehicle service that, among other has list of parts. Adding new service is not a problem, viewing of service is not a problem, but when I try to implement edit, it does not preselects the list of parts. So, thinking it is a Thymeleaf issue, I post the question here.

我得到的答案是尝试实现弹簧转换服务。我做到了(我想),现在我需要帮助让我摆脱这个烂摊子。问题是视图将来自服务的零件实例与包含所有零件的零件实例的零件实例进行比较,并且从不使用转换器,因此它不起作用。我没有收到错误......只是在视图中,未选择部件。
Bellow你会发现Converters,WebMVCConfig,PartRepository,ServiceController和html w / thymeleaf,供你参考。我究竟做错了什么???

And the answer that I got was to try to implement spring conversion service. I did just that (I think), and now I need help to get me out of this mess. Problem is that view compares instances of parts from service with instances of parts form partsAttribute containing all parts, and never uses converters, so it does not work. I receive no errors... Just in view, parts are not selected. Bellow you will find Converters, WebMVCConfig, PartRepository, ServiceController and html w/ thymeleaf, for your reference. What am I doing wrong???

转换器:

PartToString:

PartToString:

    public class PartToStringConverter implements  Converter<Part, String> {   
    /** The string that represents null. */
    private static final String NULL_REPRESENTATION = "null";

    @Resource
    private PartRepository partRepository;

    @Override
    public String convert(final Part part) {
        if (part.equals(NULL_REPRESENTATION)) {
                return null;
        }
        try {
          return part.getId().toString();
        }
        catch (NumberFormatException e) {
            throw new RuntimeException("could not convert `" + part + "` to an valid id");
        }
    }
}

StringToPart:

StringToPart:

public class StringToPartConverter implements  Converter<String, Part> {   
        /** The string that represents null. */
        private static final String NULL_REPRESENTATION = "null";

        @Resource
        private PartRepository partRepository;

        @Override
        public Part convert(final String idString) {
            if (idString.equals(NULL_REPRESENTATION)) {
                    return null;
            }
            try {
              Long id = Long.parseLong(idString);
              return this.partRepository.findByID(id);
            }
            catch (NumberFormatException e) {
                throw new RuntimeException("could not convert `" + id + "` to an valid id");
            }
        }
    }

WebMvcConfig的相关部分:

Relevant parts of WebMvcConfig:

@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
...
    @Bean(name="conversionService")
    public ConversionService getConversionService(){
        ConversionServiceFactoryBean bean = new ConversionServiceFactoryBean();
        bean.setConverters(getConverters());
        bean.afterPropertiesSet();
        ConversionService object = bean.getObject();
        return object;
    }
    private Set<Converter> getConverters() {
        Set<Converter> converters = new HashSet<Converter>();

        converters.add(new PartToStringConverter());
        converters.add(new StringToPartConverter());
        System.out.println("converters added");
        return converters;
    }
}

零件存储库如下所示:

@Repository
@Transactional(readOnly = true)
public class PartRepository {

protected static Logger logger = Logger.getLogger("repo");

    @PersistenceContext
    private EntityManager entityManager;

    @Transactional
    public Part update(Part part){
        try {
            entityManager.merge(part);
            return part;
        } catch (PersistenceException e) {
            return null;
        }
    }

    @SuppressWarnings("unchecked")
    public List<Part> getAllParts(){
        try {
            return entityManager.createQuery("from Part").getResultList();
        } catch (Exception e) {
            return new ArrayList<Part>();
        }
    }

    public Part findByID(Long id){
        try {
            return entityManager.find(Part.class, id);
        } catch (Exception e) {
            return new Part();
        }
    }
}

编辑ServiceController的一部分:

Edit part of ServiceController:

    @Controller
    @RequestMapping("/")
    public class ServisController {

        protected static Logger logger = Logger.getLogger("controller");

        @Autowired
        private ServisRepository servisRepository;
        @Autowired
        private ServisTypeRepository servisTypeRepo;
        @Autowired
        private PartRepository partRepo;
        @Autowired
        private VehicleRepository2 vehicleRepository;   

        /*-- **************************************************************** -*/
    /*--  Editing servis methods                                          -*/
    /*--                                                                  -*/
    /*-- **************************************************************** -*/

        @RequestMapping(value="/admin/servisi/editServis", method = RequestMethod.GET)
        public String getEditServis(@RequestParam(value="id", required=true) Long id, Model model){
            logger.debug("Received request to show edit page");

            List<ServisType> servisTypeList = servisTypeRepo.getAllST();
            List<Part> partList = partRepo.getAllParts();
            List<Part> selectedParts = new ArrayList<Part>();
            Servis s = servisRepository.getById(id);
            for (Part part : partList) {
                for (Part parts : s.getParts()) {
                    if(part.getId()==parts.getId()){
                        selectedParts.add(part);
                        System.out.println(part);
                    }
                }
            }
            s.setParts(selectedParts);

            logger.debug("radjeni dijelovi " + s.getParts().toString());
            logger.debug("radjeni dijelovi " + s.getParts().size());
            s.setVehicle(vehicleRepository.findByVin(s.getVehicle().getVin()));
            model.addAttribute("partsAtribute", partList);
            model.addAttribute("servisTypesAtribute", servisTypeList);
            model.addAttribute("servisAttribute", s);

            return "/admin/servis/editServis";
        }

        @RequestMapping(value="/admin/servisi/editServis", method = RequestMethod.POST)
        public String saveEditServis(@ModelAttribute("servisAttribute") @Valid Servis servis, BindingResult result){
            logger.debug("Received request to save edit page");
            if (result.hasErrors()) 
            {
                String ret = "/admin/servis/editServis";
                return ret;
            }

            servisRepository.update(servis);

            return "redirect:/admin/servisi/listServis?id="+servis.getVehicle().getVin();
        }
}

view正确显示服务,只是它没有预先选择零件。

view displays the service correctly, just it does not preselect parts.

editService:

editService:

<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring3-3.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:th="http://www.thymeleaf.org">
<head th:include="fragments/common :: headFragment">
<title>Edit Vehicle Service</title>
</head>
<body>

<div th:include="fragments/common :: adminHeaderFragment"></div>

<div class="container">

<section id="object">
  <div class="page-header">
    <h1>Edit service</h1>
  </div>

<div class="row">

    <form action="#" th:object="${servisAttribute}"
        th:action="@{/admin/servisi/editServis}" method="post" class="form-horizontal well">

        <input type="hidden" th:field="*{vehicle.vin}" class="form-control input-xlarge" />
          <div class="form-group" th:class="${#fields.hasErrors('vehicle.vin')} ? 'form-group has-error' : 'form-group'">
          <label for="vehicle.licensePlate" class="col-lg-2 control-label">License Plate</label>
            <div class="col-lg-10">
                <input type="text" th:field="*{vehicle.licensePlate}" class="form-control input-xlarge" placeholder="License Plate" readonly="readonly"/>
              <p th:if="${#fields.hasErrors('vehicle.licensePlate')}" class="label label-danger" th:errors="*{vehicle.licensePlate}">Incorrect LP</p>
            </div>
          </div>    
          <div class="form-group" th:class="${#fields.hasErrors('serviceDate')} ? 'form-group has-error' : 'form-group'">
          <label for="serviceDate" class="col-lg-2 control-label">Servis Date: </label>
            <div class="col-lg-10">
              <input type="date" th:field="*{serviceDate}" class="form-control input-xlarge" placeholder="Servis Date" />
              <p th:if="${#fields.hasErrors('serviceDate')}" class="label label-danger" th:errors="*{serviceDate}">Incorrect Date</p>
            </div>
          </div>
          <div class="form-group" th:class="${#fields.hasErrors('serviceType.id')} ? 'form-group has-error' : 'form-group'">
          <label for="serviceType.id" class="col-lg-2 control-label">Vrsta Servisa</label>
            <div class="col-lg-10">
                <select th:field="*{serviceType.id}" class="form-control">
                <option th:each="servisType : ${servisTypesAtribute}" 
                        th:value="${servisType.id}" th:selected="${servisType.id==servisAttribute.serviceType.id}"
                        th:text="${servisType.name}">Vrsta Servisa</option>
                </select>
              <p th:if="${#fields.hasErrors('serviceType.id')}" class="label label-danger" th:errors="${serviceType.id}">Incorrect VIN</p>
            </div>
          </div>
          <div class="form-group" th:class="${#fields.hasErrors('parts')} ? 'form-group has-error' : 'form-group'">
          <label for="parts" class="col-lg-2 control-label">Parts</label>
            <div class="col-lg-10">
                <select class="form-control" th:field="*{parts}" multiple="multiple" >
                <option th:each="part : ${partsAtribute}" 
                        th:field="*{parts}"
                        th:value="${part.id}"
                        th:text="${part.Name}">Part name and serial No.</option>
                </select>
              <p th:if="${#fields.hasErrors('parts')}" class="label label-danger" th:errors="*{parts}">Incorrect part ID</p>
            </div>
          </div>
          <div class="form-group" th:class="${#fields.hasErrors('completed')} ? 'form-group has-error' : 'form-group'">
          <label for="completed" class="col-lg-2 control-label">Is service completed?</label>
            <div class="col-lg-10">
              <select th:field="*{completed}" class="form-control">
                <option value="true">Yes</option>
                <option value="false">No</option>
              </select>
              <p th:if="${#fields.hasErrors('completed')}" class="label label-danger" th:errors="*{completed}">Incorrect checkbox</p>
            </div>
          </div>
        <hr/>
          <div class="form-actions">
            <button type="submit" class="btn btn-primary">Edit Service</button>
            <a class="btn btn-default" th:href="@{/admin/servisi/listServis(id=${servisAttribute.vehicle.vin})}">Cancel</a>
          </div>
    </form>

</div>
</section>

<div class="row right">
  <a class="btn btn-primary btn-large" th:href="@{/admin/part/listPart}">Back to list</a>
</div> 

<div th:include="fragments/common :: footerFragment"></div>
</div>
<!-- /.container -->
<div th:include="fragments/common :: jsFragment"></div>

</body>
</html>

更新:
在Avnish的帮助下,我做了几处修改,这就是我来的回复:

UPDATE: With help from Avnish, I made several changes and this is what I came back with:

添加转换服务不起作用,所以在研究和阅读文档后,回过头来改变我的WebMvcConfig文件,而不是@Bean我添加了这个(全部我必须做的是查看WebMvcConfigurationSupport上的文档:

adding conversion service did not work, so after researching and reading docs, went back and changed my WebMvcConfig file so in stead of @Bean I added this (All I had to do is look at the documentation on WebMvcConfigurationSupport:

@Override
    protected void addFormatters(FormatterRegistry registry){
        registry.addFormatter(new PartTwoWayConverter());
    }

然后我删除了我的转换器,并制作了一个魔术的格式化程序。不要对名称感到困惑,它是形成器:

Then I removed my converters and made just one formatter that does the magic. Don't get confused by the name, it is formater:

public class PartTwoWayConverter implements Formatter<Part>{

    /** The string that represents null. */
    private static final String NULL_REPRESENTATION = "null";

    @Resource
    private PartRepository partRepository;

    public PartTwoWayConverter(){
        super();
    }

    public Part parse(final String text, final Locale locale) throws ParseException{
        if (text.equals(NULL_REPRESENTATION)) {
            return null;
        }
        try {
            Long id = Long.parseLong(text);
        // Part part = partRepository.findByID(id); // this does not work with controller
        Part part = new Part(); // this works
        part.setId(id);         // 
        return part;
        }
        catch (NumberFormatException e) {
            throw new RuntimeException("could not convert `" + text + "` to an valid id");
        }       
    }

    public String print(final Part part, final Locale locale){
        if (part.equals(NULL_REPRESENTATION)) {
            return null;
        }
        try {
            return part.getId().toString();
        }
        catch (NumberFormatException e) {
            throw new RuntimeException("could not convert `" + part + "` to an valid id");
        }
    }

}

然后我编辑了我的HTML。无法使百里香运行,所以我这样做:

Then I edited my HTML. Could not make thymeleaf work out, so I did it like this:

<div class="form-group" th:class="${#fields.hasErrors('parts')} ? 'form-group has-error' : 'form-group'">
      <label for="parts" class="col-lg-2 control-label">Parts</label>
        <div class="col-lg-10">
            <select class="form-control" id="parts" name="parts" multiple="multiple" >
            <option th:each="part : ${partsAtribute}" 
                    th:selected="${servisAttribute.parts.contains(part)}"
                    th:value="${part.id}"
                    th:text="${part.name}">Part name and serial No.</option>
            </select>
          <p th:if="${#fields.hasErrors('parts')}" class="label label-danger" th:errors="*{parts}">Incorrect part ID</p>
        </div>
      </div>

最后,经过很多麻烦和转换错误,我无法弄清楚,改变了我的控制器更新方法:

And finally, after a lot of trouble and conversion errors that I could not figure out, changed my controller update method:

@RequestMapping(value="/admin/servisi/editServis", method = RequestMethod.POST)
    public String saveEditServis(@ModelAttribute("servisAttribute") @Valid Servis servis, BindingResult result){
        logger.debug("Received request to save edit page");
        if (result.hasErrors()) 
        {
            logger.debug(result);
            String ret = "/admin/servis/editServis";
            return ret;
        }
        List<Part> list = new ArrayList<Part>();
        for (Part part : servis.getParts()) {
            list.add(partRepo.findByID(part.getId()));
        }
        Servis updating = servisRepository.getById(servis.getId());

        updating.setCompleted(servis.getCompleted());
        updating.setParts(list); // If just setting servis.getParts() it does not work
        updating.setServiceDate(servis.getServiceDate());
        updating.setServiceType(servis.getServiceType());

        servisRepository.update(updating);

        return "redirect:/admin/servisi/listServis?id="+servis.getVehicle().getVin();
    }

即使这样有效,我仍然不满意,因为这段代码看起来更多像修补而不是正确的编码。仍然困惑为什么从partRepository返回Part不起作用。为什么百里香叶不起作用......如果有人能把我送到正确的方向,我会非常感激!

Even though this works, I am still not happy, since this code looks more like patching than proper coding. Still puzzled why return Part from partRepository did not work. And why thymeleaf did not work... If anyone can send me to the right direction, I would greatly appreciate it!

推荐答案

Thymeleaf使用Spring框架 SelectedValueComparator.isSelected 来比较值(包含选项html中的selected =selected标签),它首先依赖于java相等性。如果失败,则返回两个值的String表示。以下是摘自它的文档

Thymeleaf compares values (for inclusion of selected="selected" tag in option html) using spring frameworks SelectedValueComparator.isSelected which inherently depends upon java equality first. If that fails, it falls back to String representation of both the values. Following is excerpt from it's documentation

用于测试候选值是否与数据绑定值匹配的实用程序类。急切地试图通过多种途径来证明比较,以处理实例不等式,逻辑(基于字符串表示)的相等性和基于PropertyEditor的比较等问题。

提供完全支持来比较数组,集合和地图。

平等合同

对于单值对象,首先使用标准Java相等性测试相等性。因此,用户代码应该努力实现Object.equals以加速比较过程。如果Object.equals返回false,则尝试进行详尽的比较,目的是证明相等而不是反驳它。

接下来,尝试比较候选者和约束值。这可能在许多情况下都是正确的,因为这两个值在向用户显示时将表示为字符串。

接下来,如果候选值是字符串,则尝试进行比较将相应的PropertyEditor应用于候选者的结果值。此比较可以执行两次,一次针对直接String实例,然后针对String表示,如果第一次比较结果为false。

Utility class for testing whether a candidate value matches a data bound value. Eagerly attempts to prove a comparison through a number of avenues to deal with issues such as instance inequality, logical (String-representation-based) equality and PropertyEditor-based comparison.
Full support is provided for comparing arrays, Collections and Maps.
Equality Contract
For single-valued objects equality is first tested using standard Java equality. As such, user code should endeavour to implement Object.equals to speed up the comparison process. If Object.equals returns false then an attempt is made at an exhaustive comparison with the aim being to prove equality rather than disprove it.
Next, an attempt is made to compare the String representations of both the candidate and bound values. This may result in true in a number of cases due to the fact both values will be represented as Strings when shown to the user.
Next, if the candidate value is a String, an attempt is made to compare the bound value to result of applying the corresponding PropertyEditor to the candidate. This comparison may be executed twice, once against the direct String instances, and then against the String representations if the first comparison results in false.

对于您的具体情况,我会写下转换服务,以便我的部分对象转换为字符串,如 http://www.thymeleaf.org/doc/html/Thymeleaf-Spring3.html#configuring-a-conversion-service 。发布这个我使用th:value =$ {part}并让SelectedValueComparator做比较对象和在html中添加selected =selected部分的魔力。

For your specific case, I'd write down conversion service so that my part object is converted to string as described for VarietyFormatter in http://www.thymeleaf.org/doc/html/Thymeleaf-Spring3.html#configuring-a-conversion-service . Post this I'd use th:value="${part}" and let SelectedValueComparator do it's magic of comparing the objects and add selected="selected" part in the html.

同样在我的设计中,我总是实现基于主键的equals方法(通常我在我的顶级抽象实体上实现它,所有其他实体都从该实体继承)。这进一步强化了我的系统中域对象的自然比较。你在设计中做了类似的事吗?

Also in my design, I always implement equals method based on primary key (usually I do it at my top level abstract entity from which all other entities inherit). That further strengths the natural comparison of domain objects in my system throughout. Are you doing something similar in your design?

希望有所帮助!!

这篇关于Spring MVC转换如何的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆