Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error when a form with a select field is submitted

In my current spring project, my forms are implement with a structure like this:

<form class="form" id="Pagina" role="form" method="POST" action="/loja/Pagina/cadastra" enctype="multipart/form-data">
...
</form>

and it's processed in server by this methos:

controller

@RequestMapping(value="cadastra", method=RequestMethod.POST)
@ResponseBody
@ResponseStatus(HttpStatus.CREATED)
public E cadastra(@ModelAttribute("object") E object, BindingResult result, @RequestParam(value="file", required=false) MultipartFile file, @RequestParam(value="icone", required=false) MultipartFile icone, @RequestParam(value="screenshot", required=false) MultipartFile screenshot[]) throws Exception {
    E ret = serv.cadastra(object, file, icone, screenshot);
    if (ret != null)
        return ret;
    else
        throw new Exception();
}

service

@PreAuthorize("hasPermission(#user, 'cadastra_'+#this.this.name)")
@Transactional
public E cadastra(E e, MultipartFile file, MultipartFile icone, MultipartFile[] screenshot) {
    return dao.persist(e);
}

My problem it's when the form have a field like this:

<label>pagina</label>
<select name="pagina.id" class="form-control select" data-lista="/loja/Pagina/listagem.json">
...
</select>

<label>produto</label>
<select name="produto.id" class="form-control select" data-lista="/loja/Produto/listagem.json">
...
</select>

which maps a atribute like this in the entiy class:

@OneToOne(fetch = FetchType.EAGER)
@JoinColumn(name="pagina_mae", nullable = true)
@Order(value=5)
@Select(name="pagina", ordem = 5)
@Sidebar
private Pagina pagina;

@OneToOne(fetch = FetchType.EAGER)
@JoinColumn(name="produto_mae", nullable = true)
@Order(value=6)
@Select(name="produto", ordem = 6)
@Sidebar
private Produto produto;

Where the options inside looks like this:

<option value="">.</option>
<option value="...">...</option>

If I submit the form with the blank option selected, I get this error:

object references an unsaved transient instance - save the transient instance before flushing: com.spring.loja.model.pagina.persistence.model.Pagina; nested exception is org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.spring.loja.model.pagina.persistence.model.Pagina

but if, for instance, insert a record manually in the database (in my case, using pgAdmin3), and select this item in the select, the form is submitted without errors.

Anyone can tell me how I fix that, to allow me submit the form with or without selected data from the <select>.

UPDATE

code for the class Pagina:

@Entity
@Table(name="pagina")
@MainForm(grupo = 2, icone = "file")
public class Pagina extends ModelEntity {

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;

    @Column(name = "nome", unique = true)
    @Order(value=1)
    @Input(type="hidden", name="nome", ordem = 1)
    private String nome;

    @Column(name = "titulo", nullable = false)
    @Order(value=2)
    @Input(name="titulo", ordem = 2)
    private String titulo;

    @Column(name = "descricao", length=65535)
    @Order(value=4)
    @Textarea(name="descricao", ordem = 4)
    private String descricao;

    @OneToOne(fetch = FetchType.EAGER)
    @JoinColumn(name="pagina_mae", nullable = true)
    @Order(value=5)
    @Select(name="pagina", ordem = 5)
    @Sidebar
    private Pagina pagina;

    @OneToOne(fetch = FetchType.EAGER)
    @JoinColumn(name="produto_mae", nullable = true)
    @Order(value=6)
    @Select(name="produto", ordem = 6)
    @Sidebar
    private Produto produto;
}

UPDATE 2

PaginaEditor.java

@Component
public class PaginaEditor extends PropertyEditorSupport {

    @Inject
    private PaginaService paginaService;

    @Override
    public void setAsText(String text) {
        if (!text.isEmpty()) {
            Pagina pagina = paginaService.getObject(text);
            setValue(pagina);
        }
    }

}

method added to my controller:

@InitBinder
public void initBinder(WebDataBinder binder) {
    binder.registerCustomEditor(Pagina.class, new PaginaEditor());
}
like image 542
Kleber Mota Avatar asked Sep 05 '14 00:09

Kleber Mota


1 Answers

Selects are tricky ones in Spring MVC.

I think your problem is that when your main entity is getting to the data layer to be persisted, the relationship is not there.

Try to debug and check if affirmation above is true.

There are two ways to get this sorted.

Let's assume a Company / Contact relationship in a contacts system. A company has many contacts and a contact has one company.

Company snippet.

// package declaration imports and all
@Entity
@Table(name = "company")
public class Company {

    private String name;

    @OneToMany(mappedBy = "company")
    private List<Contact> contacts = new ArrayList<Contact>();

    // getter and setters and any extra stuff you fancy putting here

}

Contact snippet

// package declaration imports and all
@Entity
@Table(name = "contact")
public class Contact {

    private String name;

    @ManyToOne
    private Company company;

    // getter and setters and any extra stuff you fancy putting here

}

And a jsp snippet with the select. We assume there is a "contact" object and a list of customers in the model.

<form:form modelAttribute="contact">
    <form:input path="name" />
    <form:select path="customer" items="${customers}" itemValue="id" itemLabel="name" />
</form:form>

With this code in place, you can use a PropertyEditor like this.

@Component
public class CustomerEditor extends PropertyEditorSupport {

    @Inject
    private CustomerService customerService;

    @Override
    public void setAsText(String text) {
        if (StringUtils.isNotBlank(text)) {
            Customer customer = this.customerService.findById(Integer
                    .valueOf(text));
            this.setValue(customer);
        }
    }

}

and register in your Spring Context like this.

@InitBinder
public void initBinder(WebDataBinder binder) {
    binder.registerCustomEditor(Customer.class, this.customerEditor);
}

What happens here is that, whenever Spring finds an Object of type Customer, it will use the PropertyEditor to convert the (in this case) Id to the object and by the type the contact (in this case) gets to the data layer (Hibernate), the proper Customer entity will be there waiting as happy as Larry.

That is automagic way to do it.

Another way to do it is to create a form/DTO or whatever you would like to call and add the fields including a customerId field (in this case) and convert your self before you save your entity.

I hope I understood your problem right because it took me quite a few minutes to write this... :)

like image 99
Desorder Avatar answered Oct 03 '22 13:10

Desorder