[SOLVED] spring boot - błąd dodawania obiektu z formularza z kluczem obcym.

0

Cześć,
mam problem z dodaniem za pomocą formularza nowego obiektu SystemContract. Obiekt posiada klucz obcy do tabeli/obiektu system.
W formularzu wybór system realizowany jest przez tag SELECT.

Przy próbie dodania pojawia się poniższy błąd. Rozumiem, że spring nie jest wstanie utworzyć obiektu typu system. Ale nie do końca rozumiem co ma zrobić.

Treść Błędu.

2017-01-21 17:56:52.338 DEBUG 10168 --- [nio-8080-exec-2] org.springframework.beans.BeanUtils      : No property editor [org.customer.ml.Entity.SystemEditor] found for type org.customer.ml.Entity.System according to 'Editor' suffix convention
2017-01-21 17:56:52.345 TRACE 10168 --- [nio-8080-exec-2] o.s.beans.TypeConverterDelegate          : No String constructor found on type [org.customer.ml.Entity.System]

java.lang.NoSuchMethodException: org.customer.ml.Entity.System.<init>(java.lang.String)
	at java.lang.Class.getConstructor0(Class.java:2892) ~[na:1.7.0_79]
	at java.lang.Class.getConstructor(Class.java:1723) ~[na:1.7.0_79]

2017-01-21 17:56:52.417 TRACE 10168 --- [nio-8080-exec-2] .w.s.m.m.a.ServletInvocableHandlerMethod : Invoking 'org.customer.ml.Controller.SysContractController.submitContract' with arguments [org.customer.ml.Entity.SystemContract@777c73ff, org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'systemContract' on field 'system': rejected value [1]; codes [typeMismatch.systemContract.system,typeMismatch.system,typeMismatch.org.customer.ml.Entity.System,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [systemContract.system,system]; arguments []; default message [system]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'org.customer.ml.Entity.System' for property 'system'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'org.customer.ml.Entity.System' for property 'system': no matching editors or conversion strategy found]]

@Entity
@Table(name = "contracts")
public class SystemContract {

    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name = "system_id", referencedColumnName = "id")
    private System system;
// itd.

Fragment formularza dodania kontraktu w JSP i wybór systemu :

            <select class="form-control" name="system" id="system">
                <c:forEach items="${systems}" var="system">
                    <option value="${system.id}">${system.name}</option>
                </c:forEach>
            </select>

Kontroler:

    @RequestMapping(value="/", method = RequestMethod.POST)
    public String submitContract(@Valid @ModelAttribute SystemContract sysContract, BindingResult results){
        if (results.hasErrors())
            return "index";
        this.sysContractService.insert(sysContract);
        return "index";
    }
0

Ale czego nie rozumiesz? Rozumiem że pokazanie nam klasy System, czyli tej która powoduje błąd, uważasz za zbędne? No to wyciągam szlaną kulę, odsączam fusy z herbaty i lecimy...
Na oko klasa System nie trzyma się konwencji, tzn nie ma kontruktora który pozwalałby utworzyć tą klasę na podstawie Stringa, a mimo to oczekujesz w kontrolerze obiektu SystemContract zbudowanego z twojego formularza, a parametr System w tym formularzu jest podany jako string. Więc nie dość że oczekujesz od nas szklanej kuli, to oczekujesz jeszcze że Spring takową posiada i wie jak stworzyć System ze stringa...

0

Kolego rozumiem, że nie ma konstruktora przyjmującego sam String.
I założę się, że nie tu tkwi problem, bo poza polem "String name" Klasa zawiera wiele innych atrybutów. Wiec dodanie takiego konstruktora może zlikwiduje ten problem ale stworzy inny.

@Entity
@Table(name = "system")
public class System {

    private static Map<String, System> systems;

    @Id
    @NotNull
    @Column(name="id")
    private long id;

    @Size(max = 50)
    @Column(name="name", unique=true)
    private String name;

    public System(){}

    public System(long id, String name, ....){
        this.id = id;
        this.name = name;
        ...
        systems.put(name,this);
    }

    public long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

  

    public static System getSystemByName(String name){
            System sys = systems.get(name);
            //if (sys == null)

            return sys;
    }
2

No problem to jest widoczny gołym okiem: przepychasz obiekty encyjne do/z widoku a tego robić nie należy. Pomijając trywialne problemy jak ten który teraz masz, tzn fakt że z formularza idzie sama nazwa systemu/ id systemu a ty byś magicznie chciał tam mieć cały obiekt, są też dużo większe kłopoty. Bo obiekty entity, jeśli sesja bazy danych nadal wisi, mogą modyfikować wpisy w bazie! Więc jak wyciągnąłeś sobie taki obiekt i przed wysłaniem do widoku wywalisz z niego jakieś dane, to te dane znikną też z bazy ;]

Moja rada: zrób OSOBNE obiekty do komunikacji z formularzem.

0

Cieszę, się że dla ciebie te problemy są trywialne :) Jednak nie wszyscy są na twoim poziomie wiedzy i zjawiają się na forum by szukać pomocy i informacji.
Jasne, że z samego ID obiektu nie zbuduję, ale zamiast zgrywać mądralę mogłeś nakierować, że konieczne jest zmapowanie obiektu.

Za pomocą kilku linków np.
http://stackoverflow.com/questions/10138715/objects-binding-in-spring-mvc-form

Przerobiłem formularz:

        <form:form modelAttribute="sys_contract" method="post" action="/">
            <form:errors path="id" cssStyle="color: #ff0000;"/>
            <label for="orderNumber">orderNumber: </label>
            <form:input path="orderNumber" id="orderNumber" />
            <form:errors path="orderNumber" cssStyle="color: #ff0000;"/>
            .........
            <form:select multiple="false" path="system" items="${systems}" itemLabel="name" itemValue="id" id="system" />
            <form:errors path="system" cssStyle="color: #ff0000;"/>
            <input type="submit" value="Submit" /><input type="reset" value="Reset" class="btn btn-primary input-md" />
        </form:form>

Do kontrolera dodałem:

    @InitBinder
    protected void initBinder(WebDataBinder binder)     {
        binder.registerCustomEditor(System.class, new SystemEditor(systemService));
    }

i stworzyłem edytor:

public class SystemEditor extends PropertyEditorSupport {

    private final SystemService systemService;

    public SystemEditor(SystemService systemService){
        this.systemService= systemService;
    }

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        System system = systemService.getById(Integer.parseInt(text));
        setValue(system);
    }
}

1 użytkowników online, w tym zalogowanych: 0, gości: 1