ExtJS, Spring MVC 3 e Hibernate 3.5: Exemplo de um CRUD Grid

Este tutorial demonstra como implementar um CRUD Grid (Create, Read, Update, Delete) usando ExtJS, Spring MVC 3 e Hibernate 3.5

O que geralmente queremos fazer com os dados

  • Create (Criar) – (Insert)
  • Read (Ler/Visualizar) – (Select)
  • Update (Atualizar) – (Update)
  • Delete (Deletar) – (Delete)

Até a versão 3.0 do ExtJS, podíamos apenas LER dados utilizando o componente dataGrid. Se você quisesse fazer um update, insert ou delete, você tinha que codificar funções específicas para essas ações no lado ExtJS. Com a versão 3.0 (e versões mais recentes) do ExtJS, a biblioteca javascript introduziu o ext.data.writer, e você não tem todo aquele trabalho de criar as funções específicas, pode utilizar o Writer para ter um CRUD Grid.

Mas o que é preciso para ter todas as funcionalidades funcionando apenas com o uso desse writer?

No exemplo desse tutorial, estou usando JSON como formato de dados para troca de informações entre brwoser e servidor.

Código ExtJS

Primeiro, é preciso criar um Ext.data.JsonWriter:

 // The new DataWriter component.
    var writer = new Ext.data.JsonWriter({
        encode: true,
        writeAllFields: true
    });

Onde writeAllFields significa que queremos enviar todos os campos do registro para o banco de dados. identifies that we want to write all the fields from the record to the database. Se você tem uma estrutura de dados um pouco complicada ou o usuário irá fazer muitas iterações de update, é melhor deixar setado como false.

Por exemplo, Essa é a declaração da minha estrutura de dados no ExtJS:

    var Contact = Ext.data.Record.create([
	{name: 'id'},
    {
        name: 'name',
        type: 'string'
    }, {
        name: 'phone',
        type: 'string'
    }, {
        name: 'email',
        type: 'string'
    }]);

Se eu apenas atualizar o nome do contato, a aplicação irá apenas enviar o nome do contato e a id do mesmo para o servidor dizendo que foi atualizado (se o campo writeallfields estiver como false). Se tiver setado como true, irá enviar todos os campos, e o trabalho para descobrir o que sofreu alteração ficará para o server. Como o hibernate possui o método saveOrUpdate, precisamos enviar o objeto completo para o server, pois o hibernate descobre sozinho o que é preciso fazer update ou não.

Agora, é necessário configurar o proxy, como esse:

    var proxy = new Ext.data.HttpProxy({
        api: {
            read : 'contact/view.action',
            create : 'contact/create.action',
            update: 'contact/update.action',
            destroy: 'contact/delete.action'
        }
    });

E só para constar, é assim que meu reader se parece:

    var reader = new Ext.data.JsonReader({
        totalProperty: 'total',
        successProperty: 'success',
        idProperty: 'id',
        root: 'data',
        messageProperty: 'message'  // <-- New "messageProperty" meta-data
    },
    Contact);

O próximo passo é juntat tudo (writer, proxy e reader) no objeto store:

 // Typical Store collecting the Proxy, Reader and Writer together.
    var store = new Ext.data.Store({
        id: 'user',
        proxy: proxy,
        reader: reader,
        writer: writer,  // <-- plug a DataWriter into the store just as you would a Reader
        autoSave: false // <-- false would delay executing create, update, destroy requests until specifically told to do so with some [save] buton.
    });

O autosave significa que deseja salvar as alterações automaticamente no servidor (não precisa de um botão salvar na tela, assim que o usuário atualizar, deleter ou criar um novo dado, será enviado automaticamente para o servidor). Para este exemplo, implementei um botão salvar, assim, qualquer registro ou dado que for adicionado ou alterado terá uma marcação vermelha (no canto superior esquerdo da célula), assim quando o evento (ou botão) salvar for disparado, serão enviados para o servidor os dados que sofreram alteração (marcados com o flag vermelho). Você pode fazer múltiplos updates e enviar todos para o servidor em apenas uma vez (Observe como isso foi tratado no código da classe de serviço no código fonte desse projeto).

E para deixar a vida ainda mais fácil (afinal, pra isso que usamos bibliotecas como ExtJS :D ), vamos usar o plugin RowEditor, que permite a edição dos dados de forma muito simples. Tudo o que precisa fazer para usar esse plugin é primeiro adicionar os arquivos necessários na sua página HTML (ou JSP, ou outra extensão!):

<!-- Row Editor plugin css -->
	<link rel="stylesheet" type="text/css" href="/extjs-crud-grid/ext-3.1.1/examples/ux/css/rowEditorCustom.css" />
	<link rel="stylesheet" type="text/css" href="/extjs-crud-grid/ext-3.1.1/examples/shared/examples.css" />
	<link rel="stylesheet" type="text/css" href="/extjs-crud-grid/ext-3.1.1/examples/ux/css/RowEditor.css" />

<!-- Row Editor plugin js -->
	<script src="/extjs-crud-grid/ext-3.1.1/examples/ux/RowEditor.js"></script>

E adicionar o plugin na declaração do grid:

    var editor = new Ext.ux.grid.RowEditor({
        saveText: 'Update'
    });

    // create grid
    var grid = new Ext.grid.GridPanel({
        store: store,
        columns: [
            {header: "NAME",
             width: 170,
             sortable: true,
             dataIndex: 'name',
             editor: {
                xtype: 'textfield',
                allowBlank: false
            }},
            {header: "PHONE #",
             width: 150,
             sortable: true,
             dataIndex: 'phone',
             editor: {
                 xtype: 'textfield',
                 allowBlank: false
            }},
            {header: "EMAIL",
             width: 150,
             sortable: true,
             dataIndex: 'email',
             editor: {
                xtype: 'textfield',
                allowBlank: false
            }})}
        ],
        plugins: [editor],
        title: 'My Contacts',
        height: 300,
        width:610,
		frame:true,
		tbar: [{
            iconCls: 'icon-user-add',
            text: 'Add Contact',
            handler: function(){
                var e = new Contact({
                    name: 'New Guy',
                    phone: '(000) 000-0000',
                    email: 'new@loianetest.com'
                });
                editor.stopEditing();
                store.insert(0, e);
                grid.getView().refresh();
                grid.getSelectionModel().selectRow(0);
                editor.startEditing(0);
            }
        },{
            iconCls: 'icon-user-delete',
            text: 'Remove Contact',
            handler: function(){
                editor.stopEditing();
                var s = grid.getSelectionModel().getSelections();
                for(var i = 0, r; r = s[i]; i++){
                    store.remove(r);
                }
            }
        },{
            iconCls: 'icon-user-save',
            text: 'Save All Modifications',
            handler: function(){
                store.save();
            }
        }]
    });

Código Java

E Finalmente, precisamos de código no lado servidor.

Controller:

package com.loiane.web;

@Controller
public class ContactController  {

	private ContactService contactService;

	@RequestMapping(value="/contact/view.action")
	public @ResponseBody Map<String,? extends Object> view() throws Exception {

		try{

			List<Contact> contacts = contactService.getContactList();

			return getMap(contacts);

		} catch (Exception e) {

			return getModelMapError("Error retrieving Contacts from database.");
		}
	}

	@RequestMapping(value="/contact/create.action")
	public @ResponseBody Map<String,? extends Object> create(@RequestParam Object data) throws Exception {

		try{

			List<Contact> contacts = contactService.create(data);

			return getMap(contacts);

		} catch (Exception e) {

			return getModelMapError("Error trying to create contact.");
		}
	}

	@RequestMapping(value="/contact/update.action")
	public @ResponseBody Map<String,? extends Object> update(@RequestParam Object data) throws Exception {
		try{

			List<Contact> contacts = contactService.update(data);

			return getMap(contacts);

		} catch (Exception e) {

			return getModelMapError("Error trying to update contact.");
		}
	}

	@RequestMapping(value="/contact/delete.action")
	public @ResponseBody Map<String,? extends Object> delete(@RequestParam Object data) throws Exception {

		try{

			contactService.delete(data);

			Map<String,Object> modelMap = new HashMap<String,Object>(3);
			modelMap.put("success", true);

			return modelMap;

		} catch (Exception e) {

			return getModelMapError("Error trying to delete contact.");
		}
	}

	private Map<String,Object> getMap(List<Contact> contacts){

		Map<String,Object> modelMap = new HashMap<String,Object>(3);
		modelMap.put("total", contacts.size());
		modelMap.put("data", contacts);
		modelMap.put("success", true);

		return modelMap;
	}

	private Map<String,Object> getModelMapError(String msg){

		Map<String,Object> modelMap = new HashMap<String,Object>(2);
		modelMap.put("message", msg);
		modelMap.put("success", false);

		return modelMap;
	}

	@Autowired
	public void setContactService(ContactService contactService) {
		this.contactService = contactService;
	}

}

Algumas observações:

No Spring 3, é possível obter os objetos do request diretamente nos parâmetros do método utilizando a anotação @RequestParam. Não sei porque, mas não funcionou com o CRUD do ExtJS. Tive que deixar como Object e fazer o parser de JSON-Objeto “na mão”. Por isso que utilizei uma classe Util – para fazer o parser do Objeto do request para a minha classe POJO. Se souber de um jeito para consertar isso, por favor, deixe um comentário ou entre em contato. Realmente quero saber a solução! :)

Classe de Serviço:

package com.loiane.service;

@Service
public class ContactService {

	private ContactDAO contactDAO;
	private Util util;

	@Transactional(readOnly=true)
	public List<Contact> getContactList(){

		return contactDAO.getContacts();
	}

	@Transactional
	public List<Contact> create(Object data){

        List<Contact> newContacts = new ArrayList<Contact>();

		List<Contact> list = util.getContactsFromRequest(data);

		for (Contact contact : list){
			newContacts.add(contactDAO.saveContact(contact));
		}

		return newContacts;
	}

	@Transactional
	public List<Contact> update(Object data){

		List<Contact> returnContacts = new ArrayList<Contact>();

		List<Contact> updatedContacts = util.getContactsFromRequest(data);

		for (Contact contact : updatedContacts){
			returnContacts.add(contactDAO.saveContact(contact));
		}

		return returnContacts;
	}

	@Transactional
	public void delete(Object data){

		//it is an array - have to cast to array object
		if (data.toString().indexOf('[') > -1){

			List<Integer> deleteContacts = util.getListIdFromJSON(data);

			for (Integer id : deleteContacts){
				contactDAO.deleteContact(id);
			}

		} else { //it is only one object - cast to object/bean

			Integer id = Integer.parseInt(data.toString());

			contactDAO.deleteContact(id);
		}
	}

	@Autowired
	public void setContactDAO(ContactDAO contactDAO) {
		this.contactDAO = contactDAO;
	}

	@Autowired
	public void setUtil(Util util) {
		this.util = util;
	}
}

Classe Contato – POJO:

package com.loiane.model;

@JsonAutoDetect
@Entity
@Table(name="CONTACT")
public class Contact {

	private int id;
	private String name;
	private String phone;
	private String email;

	@Id
	@GeneratedValue
	@Column(name="CONTACT_ID")
	public int getId() {
		return id;
	}

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

	@Column(name="CONTACT_NAME", nullable=false)
	public String getName() {
		return name;
	}

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

	@Column(name="CONTACT_PHONE", nullable=false)
	public String getPhone() {
		return phone;
	}

	public void setPhone(String phone) {
		this.phone = phone;
	}

	@Column(name="CONTACT_EMAIL", nullable=false)
	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}
}

Classe DAO:

package com.loiane.dao;

@Repository
public class ContactDAO implements IContactDAO{

	private HibernateTemplate hibernateTemplate;

	@Autowired
	public void setSessionFactory(SessionFactory sessionFactory) {
		hibernateTemplate = new HibernateTemplate(sessionFactory);
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<Contact> getContacts() {
		return hibernateTemplate.find("from Contact");
	}

	@Override
	public void deleteContact(int id){
		Object record = hibernateTemplate.load(Contact.class, id);
		hibernateTemplate.delete(record);
	}

	@Override
	public Contact saveContact(Contact contact){
		hibernateTemplate.saveOrUpdate(contact);
		return contact;
	}
}

Classe Util:

package com.loiane.util;

@Component
public class Util {

	public List<Contact> getContactsFromRequest(Object data){

		List<Contact> list;

		//it is an array - have to cast to array object
		if (data.toString().indexOf('[') > -1){

			list = getListContactsFromJSON(data);

		} else { //it is only one object - cast to object/bean

			Contact contact = getContactFromJSON(data);

			list = new ArrayList<Contact>();
			list.add(contact);
		}

		return list;
	}

	private Contact getContactFromJSON(Object data){
		JSONObject jsonObject = JSONObject.fromObject(data);
		Contact newContact = (Contact) JSONObject.toBean(jsonObject, Contact.class);
		return newContact;
	}
)
	private List<Contact> getListContactsFromJSON(Object data){
		JSONArray jsonArray = JSONArray.fromObject(data);
		List<Contact> newContacts = (List<Contact>) JSONArray.toCollection(jsonArray,Contact.class);
		return newContacts;
	}

	public List<Integer> getListIdFromJSON(Object data){
		JSONArray jsonArray = JSONArray.fromObject(data);
		List<Integer> idContacts = (List<Integer>) JSONArray.toCollection(jsonArray,Integer.class);
		return idContacts;
	}
}

Se quiser visualizar o código inteiro dessa app de exemplo (ou fazer o donwload do código completo), visite o meu repositório do GitHub: http://github.com/loiane/extjs-crud-grid-spring-hibernate

Este post foi um pedido de alguns leitores do post anterior de CRUD Grid (e do blog em inglês), além de ter recebido alguns emails. Fiz alguns ajustes em relação ao post anterior, mas a idéia ainda é a mesma. Espero ter respondido a todas as perguntas em relação a esse assunto. :)

Bons códigos!

Posts Similares

Filed Under: Ext JS 3HibernateSpring

About the Author

Bacharel em Ciência da Computação, trabalha como Senior Software Engineer/Tech Leader no Citibank (maior instituição financeira do mundo), em São Paulo. Autora do livro ExtJS 4 First Look, publicado mundialmente pela editora Packt Publishing. Possui um blog em inglês (http://loianegroner.com) e também contribui com artigos em inglês para o Java Lobby do portal DZone. JUG leader do CampinasJUG/Java Campinas, coordenadora do ESJUG e uma das fundadoras do JDuchessBR.

Comments (27)

Trackback URL | Comments RSS Feed

  1. Pra que ter esse DAO? Porque não jogou a responsa toda pro POJO?

    Parabéns pelo compartilhamento da informação.

    Dalton Camargo

  2. Loiane says:

    @Dalton Estou usando MVC – model view controller.

    Obrigada!

  3. Ivan Salvadori says:

    Parabéns Loiane, ótimo material.

    Acredito eu, ser possível usar o MVC e ainda sim deixar a responsabilidade para o POJO, oque eu não costumo fazer tb.
    Acredito ter mais relação com objetos de domínio anêmico., que o uso do padrão MVC.

    Ao fazer isso, tira-se toda parte comportamental da entidade, colocando em oque seria um controle. ai a Pergunta….. A logica fica no controle(serviço, afins) ou no modelo???

    Eis uma questão que persegue muitos programadores.
    Já vi muita discussão sobre isso.
    Não acredito muito em certo ou errado, apenas são diferentes…

    Sempre acompanho seus materiais, são ótimos.

  4. Loiane says:

    @Ivan
    Bem, eu nunca deixei a parte de BD para o POJO, sempre usei DAO por ser mais fácil de dar manutenção e ser mais legível. MVC + DAO sempre foi a combinação perfeita (na minha opinião). Nas empresas que trabalhei, sempre fizeram dessa forma.

    Lógica no Controller acho um absurdo! Controller foi feito apenas para redirecionar as chamadas e retornar o resultado da operação. O Serviço é quem deve ficar encarregado da lógica. Esse é o MVC que conheço. ;)

    Isso não indica que eu esteja certa, é apenas minha opinião! :)

  5. Ivan Salvadori says:

    Partilho da mesma opiniao.
    Obrigado pelas informacoes Loiane.

  6. CARLOS says:

    Olá, estou tendo o seguinte problema….
    quando vai tentar carregar os contatos dah o seguinte erro:
    “CONTACT is not mapped [FROM CONTACT]; nested exception is org.hibernate.hql.ast.QuerySyntaxException: CONTACT is not mapped [FROM CONTACT]”

    isso acontece na classe ContactDAo no método getContacts .
    valeuuu

  7. Junior says:

    Poderia postar um exemplo usando outro Framework MVC, nada contra o Spring =)
    fica a sugestão

    obrigado

  8. HalleyR says:

    Hola loaine excelente tu tutorial gracias, tengo una consulta? en la parte de Hibernate tu usas el hibernate template con lo que las anotaciones las colocas en la capa servicio, ahora he visto tambien que se usa el hibernateDaoSuport el cual se hereda en los daos segun tu experiencia cual de las dos maneras es mas recomendable trabajar con hibernate template? o con los hibernateDaoSuport? si pudieras ayudar con esta duda te lo agradeceria muchisimo.

    Saludos

  9. HalleyR says:

    Hola loiane una consulta en el ejemplo estas usando las anotacioens @transactional , en la parte de la configuracion xml de spring no veo esta linea

    segun la documentacion de spring esta linea se usaba para las anotaciones @transactional mi pregunta es
    Se te olvido poner esta linea en el xml configuración de spring? o lo remplazaste con otro comando?

    Gracias

  10. Douglas says:

    Loiane, Otimo tutorial, diga-se de passagem é dificil conseguir algum que realmente funcione, Parabens.

  11. [...] ExtJS, Spring MVC 3 e Hibernate 3.5: Exemplo de um… Este tutorial demonstra como implementar um CRUD Grid (Create, Read, Update, Delete) usando ExtJS, Spring MVC 3 e Hibernate 3.5 O que geralmente queremos fazer com os dados Create (Criar) – (Insert) Read… [...]

  12. João Paulo says:

    Olá Loiane,

    Muito bom seu tutorial… eu estava tentando resolver o problema que você levantou durante o tutorial, eu acho que consegui algo que pode ajudar… vou terminar um exemplo e mando para vc pra vc ver o que acha

    vlws

    t+

  13. Loiane says:

    @JoaoPaulo
    Muito obrigada, se quiser pode enviar sim.
    Até publico aqui no blog com seus créditos! :)

  14. Olá Loiane, estou com uma pequena dúvida gostaria de saber se vc pode me dar uma dica (não sei se esse eh o melhor canal para isso, se tiver outro melhor me avise…) Na mesma idéia deste tutorial supondo que tenho uma entidade java
    Bairro {
    Long id;
    String descricao;
    Cidade cidade;
    }

    Como monto o objeto JSON de bairro para enviar para o Controlador Java de modo que utilizando JSONObject.toBean(jsonObject, Bairro.class); eu consiga esse Cast de forma perfeita. Tem como fazer ou tem que tratar manualmente no controlador?
    Espero ter me expressado de forma correta.
    Parabéns pelos artigos do seu blog e muito obrigado.

  15. Loiane says:

    Ei Guilherme,

    O Ext.Ajax request faz isso pra vc.

  16. Marcio says:

    Loiane tenho uma dúvida sobre o HibernateTemplate. Nunca o utilizei, sempre usei o objeto Session e dele os métodos necessários para persistência.
    Você recomenda o uso do HibernateTemplate para o desenvolvimento de aplicações? 

  17. Loiane says:

    Ei Marcio,

    Pra ser sincera nunca trabalhei com HibernateTemplate num projeto, então não sou a melhor pessoa para te responder. :)

    []‘s

  18. Marcio says:

    Ok Loiane, obrigado.

  19. Alejandro Mesias says:

    Estou pensando em trabalhar com colunas dinâmicas, à principio o usuario poderia especificar colunas da tabela dinamicamente. Me parece que talvez não seja tão complicado pois em todos os tratamentos de dados que o ExtJs recebe sempre são Mapas. Então imagino que com isso ele dê flexibilidade o suficiente para isso, só vou ter um pouco de trabalho na classe de serviço para “ajeitar” esses dados, fazendo um mix entre os dados que definem a tabela e os dados propriamente ditos.

    Não conhecia isso de por anotação nos parametros do metodo e no meio do metodo:
    … public @ResponseBody Map delete(@RequestParam ….
    Tenho escrito algumas anotações nos Pojos para alguns processamentos meus, por isso que estranhei.

  20. Loiane says:

    @Alejandro,
    Sim, trabalhar com grid dinâmico dá na mesma que grid estático, tudo é um objeto. Você só precisa deixar o backend dinâmico também, para efetuar as queries no banco, que também serão dinâmicas.
    Escrevi um tutorial sobre isso tempos atrás, mas só a parte do Exté que está dinâmica, o java deixei hadrcoded mesmo: http://www.loiane.com/2009/07/como-construir-um-grid-dinamico-utilizando-ext-js/

  21. Maycon Belfort says:

    Olá Loiane, procurei em seus blogs o exemplo seu postado no forum http://www.extjs.com.br/forum/index.php?topic=5005.0 onde esse CRUD era feito com ExtJS 4, mas não achei… então posto aqui mesmo =)

    Minhas dúvidas são as seguintes:

    - Quando o usuário clicar em Add ou editar um registro, você chama a função de edit e quando ele clicar em salvar, aciona a função update. Nesta função vc cria o registro, add no grid e depois manda sincronizar, o que eu encaro como se fosse salvar o registro. O que eu quero fazer é: Mandar para o servidor, salvar no banco e depois dá um reload no grid, mas não faço a mínima ideia de como mandar o objeto (no seu caso Contact) preenchido com as informações do form.

    - A segunda dúvida é a seguinte: nesse mesmo arquivo Controller dos contatos, você utiliza uma função (getContatcsStore e getContactList) dá onde vem essas funções? Para que elas servem?

    Bom Loiane, não sei se esse era o melhor lugar para este post, mas não achei o artigo… =[ Espero que possa me ajudar… 

    Obrigado!

  22. Loiane says:

    Olá Maycon,
    O método sync da store já faz o que vc quer. Ele salva no servidor e já fica com o valor atualizado.

    Os métodos são gerados pelo Ext JS. Tem um padrão de nomenclatura. Tente usar o firebug ou a ferramenta de desenvolvedor do chrome para visualizar o prototype da classe. O get Store vai obter a Store e o get List vai obter o grid. Repare que também dei um apelido para a grid na parte de refs do Controller.

    []‘s

  23. Raphael says:

    Loiane, parabéns pelo tutorial, show de bola!

    Segue abaixo algumas dúvidas:
    1. Nesse exemplo do CRUD eu poderia utilizar os recursos nativos do container (JEE 6 + CDI) sem o spring?
    2. Ao invés de utilizar as anotações que o spring oferece para controlar as requisições, seria possível utilizar àquelas existente no jsf 2?

    []‘s

  24. Loiane says:

    Olá Raphael,
    Você pode usar qualquer tecnologia server side no lugar do spring, basta que o framework ou linguagem suportem troca de dados com json ou xml.
    As 2 tecnologias que mencionou possuem esse suporte.
    Dê uma olhada nesse link em relação ao JSF: http://www.srikanthtechnologies.com/blog/java/jquerywithjsf.aspx
    []‘s

  25. Dyener says:

    Olá Loiane, imagino que você já deve estar cansada de tantos elogios, mesmo assim deixo aqui minha gratidão por poder ler um post de tamanha qualidade como este.

    Ainda não conhecia seu trabalho, más a partir de agora com certeza nos veremos outras vezes por aqui.

    Saudações.

  26. Sandro Mueller says:

    Muito bom, é o que eu procurava, “aconcelhado” por vc mesmo… :)
    amanha mesmo vou por em produção…

Leave a Reply




If you want a picture to show with your comment, go get a Gravatar.