Banco-de-dados é algo bastante simples. Qualquer programador deveria se sentir confortável em trabalhar com SQL, índices, transações, cache, lazy loading, etc. Mas infelizmente não é isso que acontece. O tal do Hibernate virou PADRÃO de mercado. Segundo esse heavyweight framework, banco-de-dados é algo muito complicado e precisa ser abstraído. Então o que ele propõe? Sai SQL e entra HQL ou Criteria. Transações, cache, lazy loading, pode esquecer. Isso vai acontecer via mágica. E se algo não sair como esperado? Aí se vira amigo! Como vc pode ser tão burro de não configurar aquelas anotações direito? É o que eu chamo de trocar uma complexidade por outra maior/pior, com pouquíssimas vantagens.
Abaixo irei argumentar contra as vantagens oferecidas pelo Hibernate e apresentar uma alternativa muito mais simples e lightweight, para aqueles programadores que já ouviram falar em coisas "muito complexas" como um JOIN e um DAO.
Uma grande falácia! A não ser para aqueles CRUDs simples, com o Hibernate você tem que escrever isso em HQL ou Criteria. Se você nunca viu SQL talvez vc goste dessas alternativas específicas do Hibernate. Agora se você entende bem SQL é como chegar para uma pessoa que fala português e dizer: "Esquece português, agora você vai falar espanhol. São parecidos, não se preocupe. Pode começar. E nem pense em tentar falar em português comigo!".
Vamos deixar claro uma coisa: Ninguém vai defender o uso de JDBC puro no século XXI. Claro que seria um tédio e muito código desnecessário para manter. Bem-vindo ao mundo dos query helpers e SQL builders.
Faltou completar: Hibernate vai fazer o mapeamento dos seus objetos para as tabelas do banco-de-dados junto com TODOS os seus relacionamentos, das maneiras mais complexas que você for inexperiente o bastante para imaginar. É muito fácil usar o Hibernate. Basta colocar umas anotações nos seus objetos e sair usando. Quanta inocência! Se as pessoas usassem esse mapeamento da maneira mais simples possível talvez isso fosse verdade. Mas o que acontece na prática é uma tentativa de configurar tudo, todos os relacionamentos, todas as relações entre os objetos em ambas as direções, chaves estrangeiras, estratégia de chave primária, etc. via XML ou Annotation. O resultado prático é uma zona de XML ou anotações onde a coisa funciona sem que vc saiba muito bem como. Alguém aí já ouviu falar de configuração programática?
Claro, claro. É sempre importante para qualquer projeto poder trocar o banco-de-dados a qualquer momento. Em vários projetos que eu trabalhei, pelo menos uma vez por mês o gerente chegava e falava assim: "Pô, esse MySQL é legal e tal, mas vamos experimentar SQLServer um pouco para ver como ele se sai. Tenho certeza que nenhum relatório, nenhum cronjob, nenhum backend, nenhum esquema de replicação vai notar a diferença. Troca aí no Hibernate, vai." Realmente uma piada engraçada. Aí o cara lá no fundo grita desesperado. "Pelo amor de Deus!, você quer poder trocar o banco-de-dados para poder rodar os testes unitários em memória, com outro banco-de-dados." Então agora realmente temos que usar o Hibernate! Não vale a pena considerar o fato de que o ideal seria rodar os testes contra um database de testes do mesmo tipo que o database de produção. Também não vale a pena considerar o fato de que alguns databases em memória como o H2 possuem mode de compatibilidade com outros bancos. Temos que rodar os testinhos unitários num banco-de-dados em memória logo não podemos deixar de usar o Hibernate e escrever todas as nossas queries de acesso ao banco em HQL ou Criteria e confiar no seu dialect. É triste, mas esses argumentos são recorrentes.
[size=30][color=blue]O que usar então?[/color][/size]
Bom, se você não entendeu nada do que foi escrito aí em cima, se você não sabe o que é um JOIN, nunca codificou um DAO, não gosta de SQL mas gosta de HQL (!!??), prefere fazer queries usando objetos (ah! Criteria é fácinho, se vira né!) (!!??), gosta de configurar as coisas com XML e Annotations até que elas funcionam sem você saber muito como, adora a flexibilidade e clareza do pom.xml do Maven, quer saber a ferramenta padrão do mercado para aumentar a sua empregabilidade, ou porque o seu chefe te obrigou (já aconteceu comigo!) então você vai usar o Hibernate e ser feliz.
Ou então você pode usar iBatis ou o MentaBean, que irei apresentar abaixo. (Obs: Os exemplos abaixo foram extraídos dos testes automatizados (não confundir isso com teste unitário) do MentaBean, que você pode visualizar aqui)
[color=blue]Nosso bean:[/color] (Repare um efeito colateral legal da configuração programática: O seu bean fica limpinho, totalmente desacoplado de qualquer código relacionado a persistência)
public class User {
public static enum Status {
BASIC, PREMIUM, GOLD
}
private int id;
private String username;
private Date birthdate;
private Status status = Status.BASIC;
private boolean deleted;
private Date insertTime;
public User() {
}
public User(int id) {
this.id = id;
}
public User(String username, String birthdate) {
this.username = username;
this.birthdate = fromString(birthdate);
}
// um monte de getters and setters
}
[color=blue]Mapeamento Programático:[/color] (Pergunte ao Google Guice ou ao Martin Fowler porque configuração programática é legal)
private void createTables(Connection conn) throws SQLException {
execUpdate(conn, "create table Users(id integer primary key auto_increment, username varchar(25), bd datetime, status varchar(20), deleted tinyint, insert_time timestamp)");
}
private BeanConfig getUserBeanConfig() {
// programmatic configuration for the bean... (no annotation or XML)
BeanConfig config = new BeanConfig(User.class, "Users");
config.pk("id", DBTypes.AUTOINCREMENT);
config.field("username", DBTypes.STRING);
config.field("birthdate", "bd", DBTypes.DATE); // note that the database column name is different
config.field("status", new EnumValueType(User.Status.class));
config.field("deleted", DBTypes.BOOLEANINT);
config.field("insertTime", "insert_time", DBTypes.TIMESTAMP).defaultToNow("insertTime");
return config;
}
[color=blue]CRUD:[/color] (Para o básico vc não precisa escrever nenhum SQL)
BeanManager beanManager = new BeanManager();
BeanConfig userConfig = getUserBeanConfig();
beanManager.addBeanConfig(userConfig);
conn = getConnection();
BeanSession session = new H2BeanSession(beanManager, conn); // ou MySQLBeanSession, OracleBeanSession (via IoC, claro)
createTables(conn);
// INSERT:
User u = new User("saoj", "1980-03-01");
u.setStatus(User.Status.GOLD);
session.insert(u);
Assert.assertEquals(1, u.getId());
Assert.assertEquals("saoj", u.getUsername());
Assert.assertEquals("1980-03-01", BD_FORMATTER.format(u.getBirthdate()));
Assert.assertEquals(false, u.isDeleted());
Assert.assertEquals(User.Status.GOLD, u.getStatus());
// SELECT:
u = new User(1);
boolean loaded = session.load(u);
Assert.assertEquals(true, loaded);
Assert.assertEquals(1, u.getId());
Assert.assertEquals("saoj", u.getUsername());
Assert.assertEquals("1980-03-01", BD_FORMATTER.format(u.getBirthdate()));
Assert.assertEquals(false, u.isDeleted());
Assert.assertEquals(User.Status.GOLD, u.getStatus());
Assert.assertTrue((new Date()).getTime() >= u.getInsertTime().getTime());
// UPDATE:
u.setUsername("soliveira");
int modified = session.update(u);
Assert.assertEquals(1, modified);
Assert.assertEquals(1, u.getId());
Assert.assertEquals("soliveira", u.getUsername());
Assert.assertEquals("1980-03-01", BD_FORMATTER.format(u.getBirthdate()));
Assert.assertEquals(false, u.isDeleted());
Assert.assertEquals(User.Status.GOLD, u.getStatus());
Assert.assertTrue((new Date()).getTime() >= u.getInsertTime().getTime());
// make sure the new username was saved in the database
u = new User(1);
loaded = session.load(u);
Assert.assertEquals(true, loaded);
Assert.assertEquals(1, u.getId());
Assert.assertEquals("soliveira", u.getUsername());
Assert.assertEquals("1980-03-01", BD_FORMATTER.format(u.getBirthdate()));
Assert.assertEquals(false, u.isDeleted());
Assert.assertEquals(User.Status.GOLD, u.getStatus());
Assert.assertTrue((new Date()).getTime() >= u.getInsertTime().getTime());
// DELETE:
boolean deleted = session.delete(u);
Assert.assertEquals(true, deleted);
// make sure the bean is deleted from the database...
u = new User(1);
loaded = session.load(u);
Assert.assertEquals(false, loaded);
[color=blue]Outro bean:[/color] (com um relacionamento OneToOne com o User)
public class Post {
private int id;
private int userId;
private User user; // contém User (one-to-one relationship)
private String title;
private String body;
private Date insertTime;
public Post() {
}
public Post(int id) {
this.id = id;
}
public Post(int userId, String title, String text) {
this.userId = userId;
this.title = title;
this.body = text;
}
// setters and getters here...
}
[color=blue]Mapeamento Programático:[/color]
private void createTables(Connection conn) throws SQLException {
execUpdate(conn, "create table Posts(id integer primary key auto_increment, user_id integer, title varchar(200), body text, insert_time timestamp)");
}
private BeanConfig getPostBeanConfig() {
// programmatic configuration for the bean... (no annotation or XML)
BeanConfig config = new BeanConfig(Post.class, "Posts");
config.pk("id", DBTypes.AUTOINCREMENT);
config.field("userId", "user_id", DBTypes.INTEGER);
config.field("title", DBTypes.STRING);
config.field("body", DBTypes.STRING);
config.field("insertTime", "insert_time", DBTypes.TIMESTAMP).defaultToNow("insertTime");
return config;
}
[color=blue]SQL:[/color] (Não precisa fazer bind em PreparedStatement ou get de ResultSet nenhum!)
BeanManager beanManager = new BeanManager();
BeanConfig userConfig = getUserBeanConfig();
BeanConfig postConfig = getPostBeanConfig();
beanManager.addBeanConfig(userConfig);
beanManager.addBeanConfig(postConfig);
conn = getConnection();
JdbcBeanSession session = new H2BeanSession(beanManager, conn);
createTables(conn);
User u = new User("saoj", "1980-01-02");
session.insert(u);
Assert.assertEquals(1, u.getId());
// Now insert a post for this user...
Post p = new Post(1, "Test", "This is a test!");
session.insert(p);
Assert.assertEquals(1, p.getId());
// Load from the database...
p = new Post(1);
boolean loaded = session.load(p);
Assert.assertEquals("Test", p.getTitle());
Assert.assertEquals(1, p.getUserId());
Assert.assertNull(p.getUser()); // you did NOT load any user from the database here... (we like manual lazy loading, not automatic lazy loading!)
// Load user for this post... (let's do our manual lazy loading)
u = new User(p.getUserId());
loaded = session.load(u);
Assert.assertEquals(true, loaded);
p.setUser(u); // manual lazy loading (forget about automatic lazy loading, you want control!)
// Use JOIN to load all dependencies with a single query... (you know how to make a join, right?)
p = new Post(1);
StringBuilder query = new StringBuilder(256);
query.append("select ");
query.append(session.buildSelect(Post.class, "p"));
query.append(", ");
query.append(session.buildSelect(User.class, "u"));
query.append(" from Posts p join Users u on p.user_id = u.id");
query.append(" where p.id = ?");
stmt = conn.prepareStatement(query.toString());
stmt.setInt(1, p.getId());
rset = stmt.executeQuery();
if (rset.next()) {
session.populateBean(rset, p, "p");
u = new User();
session.populateBean(rset, u, "u");
p.setUser(u); // manual lazy loading (we prefer to have control!)
}
Assert.assertEquals(1, p.getId());
Assert.assertEquals("Test", p.getTitle());
Assert.assertEquals(1, u.getId());
Assert.assertEquals("saoj", p.getUser().getUsername());
Assert.assertTrue((new Date()).getTime() >= p.getInsertTime().getTime());
rset.close();
stmt.close();
// Deleting => No cascade deletes, if you want that implement in the database level...
u = new User(1);
boolean deleted = session.delete(u);
Assert.assertEquals(true, deleted);
// Post of course is still there...
p = new Post(1);
loaded = session.load(p);
Assert.assertEquals(true, loaded);
Assert.assertEquals(1, p.getUserId()); // of course this user is gone!
u = new User(1);
loaded = session.load(u);
Assert.assertEquals(false, loaded); // use was deleted above...
[color=blue]Outros:[/color]
- loadList - Carregamento de listas baseadas em propiedades de um bean
- loadUnique - Fazer query com checagem automático se ela retorna um bean apenas
- buildSelectMinus - Quando vc quer carregar apenas algumas propriedades de um bean (e não as 1000)
- Vários tipos de dados para serem configurados
- Vários tipos de bancos suportados
- Update dinâmico (só faz update dos campos que foram alterados no bean)
- Interface MentaBean para que o seu bean implemente persistencia in-place via user.load(), user.insert(), user.update(), user.delete().
[color=blue]Desvantagens:[/color] (sendo imparcial)
- Não suporta lazy loading automático como o Hibernate faz via cglib. Lazy Loading é sempre o padrão quando vc carrega um bean. Ex: se vc carrega um Post ele virá apenas com o ID do User autor. Se você precisar do User inteiro, você faz o lazy loading na mão (via um DAO, claro) e preenche a referencia no Post. Esse user pode vir de um cache por exemplo. O que eu geralmente faço é um JOIN que me retorna o ID e o USERNAME do user e eu guardo esses valores no Post. Assim posso listar posts com o username e um link para o gara visualizar o User inteiro caso ele queira, e só aí o Lazy Loading MANUAL acontece. Para um discussão extensa sobre Lazy Loading Manual x Automático veja aqui: http://www.guj.com.br/java/57590-ser-lazy-preguicoso-ou-nao-ser
[color=blue]DISCLAIMER:[/color]
- Isso é apenas minha opinião pessoal e o meu ponto de vista, condicionados pela minhas experiência, preferência e estilo. Caso você ame o Hibernate e ache que é a melhor coisa do mundo, você não deixará de ir para o céu por causa disso. Também não vejo problema em sermos amigos.
[color=blue]URL do Projeto:[/color]
http://mentabean.soliveirajr.com
[color=red][size=18]"Em tudo na vida a perfeição é finalmente atingida, não quando nada mais existe para acrescentar, mas quando não há mais nada para retirar."[/size][/color] - Antoine de Saint-Exupéry (Complexity Kills so Kiss!)