Mentawai Web Framework

MentaBean - Persistencia de Beans

Lançamento oficial na versão 1.10. Por enquanto diponível no jar beta: http://www.mentaframework.org/beta/mentawai.jar

O Mentawai oferece um framework de ORM (Object-relational mapping) bastante simples para persistencia dos seus objetos (beans) em um banco-de-dados relacional. Através do MentaBean você pode realizar operaçoes CRUD (Create, Read, Update, Delete), executar JOINs para carregar os relacionamentos do seu bean e também carregar listas de beans usando opcionalmente sort (ordenação) e/ou limit (limite máximo de registros).

Para demonstrarmos o simples funcionamento do MentaBean, partiremos de um exemplo clássico e bastante recorrente de relacionamentos em banco-de-dados: Um CARRO possui um MOTOR (OneToOne) que por sua vez pode possuir uma ou mais PECAS (OneToMany).

|==================|   |==================|  |==================| |==================|
|     Carros       |   |     Motores      |  |   MotoresPecas   | |      Pecas       |
|==================|   |==================|  |==================| |==================|
| id: int PK       |   | id: int PK       |  | motor_id: int PK | | id: int PK       |
|------------------|   |------------------|  | peca_id: int PK  | |------------------|
| name: varchar(32)|   | name: varchar(32)|  |==================| | name: varchar(32)|
| year: date       |   | industryType: int|                       |==================|  
| motor_id: int FK |   |==================|
|==================|    

Reparem que tivemos que criar uma tabela intermediária "MotoresPecas" para o nosso relacionamento OneToMany (1 Motor - N Pecas). Reparem também que para o relacionamento OneToOne, não é necessário criar uma tabela intermediária, visto que podemos salvar o id do motor do carro na própria tabela de Carros (1 Carro - 1 Motor). (Obs: Nada lhe impede também de optar por criar tabelas intermediárias para relacionamentos OneToOne, mas geralmente é preferível salvar o id da chave estrangeira (FK) na mesma tabela.)

Abaixo listaremos o código para os nosso beans: Carro, Motor e Peca: (repare que não há qualquer tipo de annotation ou acoplamento das suas entidades com o framework)


public class Carro {
    
    private int id;
    private String name;
    private Date year;
    private int numberOfRevisions;
    
    private int motorId; // mentabean can inject directly in here...
    private Motor motor = null;
    
    // required by ORM
    public Carro() {
        
    }
    
    public Carro(int id) {
        this.id = id;
    }
    
    public void setMotor(Motor m) {
        this.motor = motor;
    }
    
    public Motor getMotor() {
        return motor;
    }
    
    // getters and setter here
}

public class Motor {
    
    private int id;
    private String name;
    private int type;
    
    private List<Peca> pecas = null;
    
    // required by ORM
    public Motor() {
        
    }
    
    public Motor(int id) {
        this.id = id;
    }
    
    public void setPecas(List<Peca> pecas) {
        this.pecas = pecas;
    }
    
    public List<Peca> getPecas() {
        return pecas;
    }
    
    // getters and setters here
}

public class Peca {
    
    private int id;
    private String name;
    
    // required by ORM
    public Peca() {
        
    }
    
    public Peca(int id) {
        this.id = id;
    }
    
    // getter and setters here
}

Configuração Programática

Ao invés de utilizarmos XML ou Annotations para a configuração dos nossos beans, utilizaremos a boa e velha configuração programática do Mentawai. Dessa maneira, além de possuirmos toda a flexibilidade de uma configuração programática, deixaremos nossos beans totalmente desacoplados de qualquer coisa, até mesmo de annotations.

public class ApplicationManager extends org.mentawai.core.ApplicationManager {
    
    public void init(Context application) {
        
    }
    
    public void loadActions() {
     
        // actions here...
     
    }
    
    public void loadBeans() {
        
        // configure bean fields...
        
        BeanConfig carro = bean(Carro.class, "Carros")
                .pk("id", DBTypes.AUTOINCREMENT)
                .field("name", DBTypes.STRING)
                .field("year", DBTypes.DATE)
                .field("motorId", "motor_id" /* = NAME IN THE DB */, DBTypes.INTEGER);
                
        BeanConfig motor = bean(Motor.class, "Motores")
                .pk("id", DBTypes.AUTOINCREMENT, "motor_id" /* = FK */)
                .field("name", DBTypes.STRING)
                .field("type", "industryType" /* = NAME IN THE DB */, DBTypes.INTEGER);
                
        BeanConfig peca = bean(Peca.class, "Pecas")
                .pk("id", DBTypes.AUTOINCREMENT, "peca_id" /* = FK */)
                .field("name", DBTypes.STRING);
                
        // configure bean relationships
        
        motor.join(Peca.class, "MotoresPecas"); // OneToMany relationship!
        
    }
}

Por último, para podermos utilizar uma session (org.mentawai.bean.BeanSession) do MentaBean dentro de nossas actions, utilizamos o bom e velho IoC + DI.

public class ApplicationManager extends org.mentawai.core.ApplicationManager {
    
    public void init(Context application) {
        
        filter(new IoCFilter());
        filter(new ConnectionFilter(connHandler));
        filter(new DIFilter("conn", Connection.class)); // JdbcBeanSession needs a Connection !!!!
        
        ioc("session", MySQLBeanSession.class); // or JdbcBeanSession for only ANSI SQL...
    }
    
    public void loadActions() {
     
    }
    
    public void loadBeans() {
        
        // omitted for clarity...
    }
}

Carregando Beans

Feita a configuração dos seus beans e dos relacionamentos entre eles, e estando uma BeanSession disponível para suas actions através de IoC + DI, tudo que você tem que fazer agora é carregar os beans do banco-de-dados.

public class BeanActionExample extends BaseAction {
    
    public String execute() throws Exception {
        
        BeanSession session = (BeanSession) input.getValue("session"); // ioc working here...
        
        Carro carro = new Carro(1); // carro id = 1 (PK)
        
        boolean loaded = session.load(carro);
        
        if (loaded) {
            
            System.out.println("Carro just loaded: " + carro.getName());
            
        } else {
            
            throw new ActionException("Not possible to load carro with id = " + carro.getId());
            
        }
        
        // let's load the Motor from the Carro (OneToOne relationship)
        
        Motor motor = new Motor(carro.getMotorId());
        
        loaded = session.load(motor);
        
        if (loaded) {
            
            carro.setMotor(motor); // add the motor to its carro...
            
            System.out.println("Motor just loaded: " + motor.getName());
            
        } else {
            
            throw new ActionException("Not possible to load motor with the id = " + motor.getId());
            
        }
        
        // now let's load the list of Pecas for the Motor (OneToMany relationship)
        
        List<Pecas> pecas = (List<Pecas>) session.loadJoin(motor, Peca.class);
        
        motor.setPecas(pecas); // add the list of pecas to its motor..
        
        System.out.println("Number of pecas loaded: " + pecas.size());
        
        // loading only the ids...
        
        List<Integer> ids = session.loadJoinIds(motor, Peca.class);
        
        // counting the ids...
        
        int total = session.countJoin(motor, Peca.class);
        
        return SUCCESS;
    }
}

No primeiro session.load() executado acima, o que aconteceu é que um bean carro foi carregado do banco-de-dados através da sua chave primária (id = 1). Por trás dos panos, o MentaBean gerou uma query (SQL) para fazer o select de todos os campos desse objeto. De posse do campo motorId, que foi carregado dentro do carro, carregamos também o motor correspondente, da mesma maneira que carregamos o carro. Note que adicionamos o motor carregado dentro do seu carro (OneToOne relationship). Feito isso, partimos para o relacionamento OneToMany onde carregamos todas as peças do motor através do método session.loadJoin(), que por trás dos panos vai criar uma query SQL para fazer o inner-join. Para esse método precisamos passar o relacionamento previamente configurado, assim como o motor para o qual desejamos carregar as peças. A lista retornada com as peças é do tipo List<Object> e precisa ser casteado para List<Peca>. Podemos também carregar apenas uma lista de IDs de peças através do método loadJoinIds(), assim como podemos apenas contar o número de pécas, através do método countJoin().


Inserindo novos beans

Para inserir um novo bean no seu banco-de-dados, tudo que você tem que fazer é populá-lo e passá-lo para o método session.insert(). Se você configurou a chave primária do seu bean como sendo do tipo AUTOINCREMENT (MySQL) ou SEQUENCE (Oracle), vc não precisará se preocupar com a geração de um ID único, pois ele será gerado automaticamente pelo seu banco-de-dados e inserido automaticamente no seu bean pelo MentaBean. Abaixo listamos alguns exemplos:

       		
        // You are using MYSQL and the id (PK) of Carro is set as AUTOINCREMENT
        
        Carro carro = new Carro(); // no PK (id) specified...
        u.setName("Parati");
        u.setYear(DateFormat.parse("21/01/2005"));
        
        session.insert(carro);
        
        System.out.println("Just inserted a carro with id: " + carro.getId());
        
        // You are using Oracle and the id (PK) of Carro is set as SEQUENCE
        // in this case you must have a sequence created in your DB with the name seq_id_Carros (seq_COL_TABLE)
        
        Carro carro = new Carro(); // no PK (id) specified...
        u.setName("Parati");
        u.setYear(DateFormat.parse("21/01/2005"));
        
        session.insert(carro);
        
        System.out.println("Just inserted a carro with id: " + carro.getId());
        
        // You want a database-independent approach, so you will provide an unique id somehow...
        // In this case you should configure the field as a regular INTEGER, not AUTOINCREMENT or SEQUENCE...
        
        int id = getUniqueId();
        
        Carro carro = new Carro(id); // pass the id to your Carro...
        u.setName("Parati");
        u.setYear(DateFormat.parse("21/01/2005"));
        
        session.insert(carro);
        
        System.out.println("Just inserted a carro with id: " + id);
       		


Inserindo novos beans em um relacionamento

No caso de relacionamentos OneToOne, onde não se faz necessário o uso de uma tabela intermediária como vimos a cima, tudo que você precisa fazer é dar um update no campo motor_id, que no nosso exemplo é o campo que faz a ligação entre o Carro e o seu Motor:

        Carro carro = new Carro(3);
        
        boolea ok = session.load(carro);
        
        if (!ok) {
            
            System.out.println("Cannot find carro with the id: " + carro.getId());
            
            return;
        }
        
        carro.setMotorId(23);
        
        session.update(carro); // only the field motor_id will be updated, not everything (smart update)
        
        

No caso de relacionamentos OneToMany ou ManyToMany, onde somos obrigados a fazer uso de uma tabela intermediária, tudo que temos que fazer é utilizar o método session.add() passando para o método o nome do relacionamento (join) configurado no BeanConfig e os beans que fazem parte do relacionamento. Veja no exemplo abaixo, como é fácil adicionar mais uma peça a um motor:

        
        Motor m = new Motor(2);
        
        boolean ok = session.load(m);
        
        if (!ok) {
            
            System.out.println("Cannot find motor with the id: " + m.getId());
            
            return;
        }
        
        Peca p = new Peca(4);
        
        ok = session.load(p);
        
        if (!ok) {
            
            System.out.println("Cannot find peca with the id: " + m.getId());
            
            return;
        }
        
        session.add(m, p);
        

Removendo beans de um relacionamento

Para remover um bean de um relacionamento OneToMany ou ManyToMany não há mistérios. O exemplo abaixo mostra como remover uma peça de um motor:

        
        Motor m = new Motor(2);
        
        boolean ok = session.load(m);
        
        if (!ok) {
            
            System.out.println("Cannot find motor with the id: " + m.getId());
            
            return;
        }
        
        Peca p = new Peca(4);
        
        ok = session.load(p);
        
        if (!ok) {
            
            System.out.println("Cannot find peca with the id: " + m.getId());
            
            return;
        }
        
        session.remove(m, p);
        

Fazendo update de beans

Como já vimos a cima, para fazer um update no bean não há qualquer mistério. O MentaBean implementa um smart update onde apenas as colunas que foram efetivamente modificadas sofrerão o update. Também não é necessário carregar um bean do banco-de-dados para só então alterá-lo. Abaixo listamos esses exemplos:

        
        Carro c = new Carro(2);
        
        boolean ok = session.load(c);
        
        if (!ok) {
            
            System.out.println("Cannot find carro with the id: " + c.getId());
            
            return;
        }
        
		c.setName("Honda");
		
		session.update(c); // only name will be updated !!!
		
		Carro c2 = new Carro(2);
		
		c2.setName("Toyota");
		
		session.update(c2); // update before any load... all non-null and non-zero values will be updated!
		
        

Deletando beans

Para deletar um bean, apenas faça como no código abaixo:

        
        Carro c = new Carro(2); // no need to load bean to delete it...
        
		boolean ok = session.delete(c);
		
		if (ok) System.out.println("Carro was deleted! id = " + c.getId());
		
        

Carregando lista de beans

Veja como o código abaixo é bem simples, não havendo necessidade para explicações:

        
        User u = new User();
        
        u.setAge(29);
        
        u.setCity("Rio de Janeiro");
        
        // the call below will return all users with age = 29 AND city = "Rio de Janeiro"
        // will sort by name and the limit will be 30 users...
        
        List<User> list = session.loadList(u, "name", 30);
        
        Iterator<User> iter = list.iterator();
        
        while(iter.hasNext()) {
        
        	User user = iter.next();
        	
        	System.out.println("User: " + user.getName());
        	
       	}
        

Você pode por exemplo checar se o usuário digitou um login e senha válido com o seguinte código abaixo:

        
        User u = new User();
        
        u.setUsername("sergio");
        u.setPassword("222222");
        
        List<User> list = session.loadList(u);
        
        if (list.size() == 0) System.out.println("Cannot find username/password!");
        
        if (list.size() == 1) System.out.println("Ok, you can log in! Welcome!");
        
        if (list.size() > 1) System.out.println("Something is wrong with your database!!!");
        

FAQ

1) Qual a diferença do MentaBean para o Hibernate?

R: O MentaBean, assim como o Hibernate e o iBatis, é um framework de ORM, ou seja, de mapeamento de objetos Java para tabelas de um banco-de-dados relacional. A diferença é que o MentaBean foca apenas nas questões simples de qualquer framework ORM, ou seja, CRUD (Create, Read, Update, Delete) de beans, load, add e remove de relacionamentos e carregamento de listas de beans com suporte a sorting e limit. O MentaBean não trata por exemplo de situações mais avançadas que o Hibernate trata como remoção em cascata, lazy-loading automático, locks, herança, etc. Na verdade o MentaBean está mais para um gerador automatizado de queries SQL (como o iBatis) do que para um framework completo de ORM (como o Hibernate).

2) Qual a vantagem do MentaBean sobre o Hibernate então?

R: Foco em simplicidade, integração com o framework Mentawai e configuração programática facilitada a la Mentawai. Para projetos onde o poder do Hiberante não é necessário, o MentaBean pode ser uma opção mais simples e prática, principalmente para os usuários do Mentawai que não querem ou podem perder tempo aprendendo Hibernate.

3) Quem deve usar o MentaBean?

R: Aqueles que trabalham apenas com JDBC puro e/ou aqueles que não querem ou não sabem trabalhar com o Hibernate/iBatis; Aqueles que estão iniciando agora com Java e/ou programação pra Web e não querem ter que aprender Hibernate/iBatis para começar a desenvolver os seus primeiros projetos web. Também para aqueles que gostam de configuração programática a la Mentawai.

4) Posso usar o MentaBean com o Hibernate?

R: Claro, nada te impede de fazer isso. Em alguns métodos do seu DAO vc pode usar MentaBeans e em outros Hibernate. Outra opção mais recomendável ainda é ter duas implementações dos seus DAOs. Uma com MentaBeans e outra com o Hibernate. Assim você terá liberdade para usar uma ou outra e também poderá concluir por si mesmo qual framework vc deseja utilizar no seu projeto.

5) O MentaBean é uma implementação ou uma especificação?

R: O MentaBean na verdade é uma especificação (interfaces), ou seja, nada impede que no futuro surjam novas implementações para o MentaBean baseadas no Hibernate ou no iBatis.

6) O MentaBean suporta transações?

R: Sim. Basta utilizar o método session.createTransaction().

7) O MentaBean não suporta lazy-loading automático? Isso não é ruim?

R: Não e isso não é ruim. Todos os beans do MentaBean são carregados de forma lazy, ou seja, quando vc faz um load de um Carro apenas o Carro é carregado do banco-de-dados sem que suas dependências sejam carregadas naquele momento. A diferença é que é vc quem deve fazer o carregamento manual dessas dependências quando elas forem necessárias. Lazy-loading automático é quando o framework é que fica responsável pelo carregamento por trás dos panos das dependências, o que ocorre automaticamente quando o método getter daquela dependência é chamado. Apesar dessa abordagem ser mais cômoda para o programador, ela é menos indicada visto que ninguém melhor do que você deveria saber quando e onde as dependências de um objeto serão necessárias. Para uma discussão boa sobre isso clique aqui: http://www.guj.com.br/posts/list/15/57590.java.