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.