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 }
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... } }
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().
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);
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);
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);
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!
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());
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!!!");
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.