Começando com iBatis (MyBatis): Configuração em XML

14 Feb 2011
10 mins read

Este tutorial tem como objetivo mostrar como fazer o setup do iBatis (MyBatis) em uma aplicação Java e mostrar exemplos simples de como fazer insert, update, select e detele.

Pre-Requisitos

Para este tutorial usei:

IDE: Eclipse (você pode usar a sua IDE favorita)
DataBase: MySQL
Libs/jars: Mybatis, MySQL conector e JUnit (para testes)

No Eclipse, a estrutura do seu projeto vai ficar assim:

Dados de Exemplo

Execute o script que está dentro da pasta sql antes de começar o projeto. Este arquivo contém os dados de exemplo usados neste tutorial.

[code lang="sql" firstline="1" toolbar="true" collapse="false" wraplines="false"]
DROP TABLE IF EXISTS `blog`.`contact`;
CREATE TABLE`blog`.`contact` (
`CONTACT_ID` int(11) NOT NULL AUTO_INCREMENT,
`CONTACT_EMAIL` varchar(255) NOT NULL,
`CONTACT_NAME` varchar(255) NOT NULL,
`CONTACT_PHONE` varchar(255) NOT NULL,
PRIMARY KEY (`CONTACT_ID`)
)
ENGINE=InnoDB;

insert into CONTACT (CONTACT_NAME, CONTACT_PHONE, CONTACT_EMAIL) values ('Contact0','(000) 000-0000', 'contact0@loianetest.com');
insert into CONTACT (CONTACT_NAME, CONTACT_PHONE, CONTACT_EMAIL) values ('Contact1', '(000) 000-0000', 'contact1@loianetest.com');
insert into CONTACT (CONTACT_NAME, CONTACT_PHONE, CONTACT_EMAIL) values ('Contact2', '(000) 000-0000', 'contact2@loianetest.com');
insert into CONTACT (CONTACT_NAME, CONTACT_PHONE, CONTACT_EMAIL) values ('Contact3', '(000) 000-0000', 'contact3@loianetest.com');
insert into CONTACT (CONTACT_NAME, CONTACT_PHONE, CONTACT_EMAIL) values ('Contact4', '(000) 000-0000', 'contact4@loianetest.com');
insert into CONTACT (CONTACT_NAME, CONTACT_PHONE, CONTACT_EMAIL) values ('Contact5', '(000) 000-0000', 'contact5@loianetest.com');
insert into CONTACT (CONTACT_NAME, CONTACT_PHONE, CONTACT_EMAIL) values ('Contact6', '(000) 000-0000', 'contact6@loianetest.com');
insert into CONTACT (CONTACT_NAME, CONTACT_PHONE, CONTACT_EMAIL) values ('Contact7', '(000) 000-0000', 'contact7@loianetest.com');
insert into CONTACT (CONTACT_NAME, CONTACT_PHONE, CONTACT_EMAIL) values ('Contact8', '(000) 000-0000', 'contact8@loianetest.com');
insert into CONTACT (CONTACT_NAME, CONTACT_PHONE, CONTACT_EMAIL) values ('Contact9', '(000) 000-0000', 'contact9@loianetest.com');
insert into CONTACT (CONTACT_NAME, CONTACT_PHONE, CONTACT_EMAIL) values ('Contact10', '(000) 000-0000', 'contact10@loianetest.com');
insert into CONTACT (CONTACT_NAME, CONTACT_PHONE, CONTACT_EMAIL) values ('Contact11', '(000) 000-0000', 'contact11@loianetest.com');
insert into CONTACT (CONTACT_NAME, CONTACT_PHONE, CONTACT_EMAIL) values ('Contact12', '(000) 000-0000', 'contact12@loianetest.com');
insert into CONTACT (CONTACT_NAME, CONTACT_PHONE, CONTACT_EMAIL) values ('Contact13', '(000) 000-0000', 'contact13@loianetest.com');
insert into CONTACT (CONTACT_NAME, CONTACT_PHONE, CONTACT_EMAIL) values ('Contact14', '(000) 000-0000', 'contact14@loianetest.com');
insert into CONTACT (CONTACT_NAME, CONTACT_PHONE, CONTACT_EMAIL) values ('Contact15', '(000) 000-0000', 'contact15@loianetest.com');
insert into CONTACT (CONTACT_NAME, CONTACT_PHONE, CONTACT_EMAIL) values ('Contact16', '(000) 000-0000', 'contact16@loianetest.com');
insert into CONTACT (CONTACT_NAME, CONTACT_PHONE, CONTACT_EMAIL) values ('Contact17', '(000) 000-0000', 'contact17@loianetest.com');
insert into CONTACT (CONTACT_NAME, CONTACT_PHONE, CONTACT_EMAIL) values ('Contact18', '(000) 000-0000', 'contact18@loianetest.com');
insert into CONTACT (CONTACT_NAME, CONTACT_PHONE, CONTACT_EMAIL) values ('Contact19', '(000) 000-0000', 'contact19@loianetest.com');

[/code]

1 - Contact POJO

Vamos criar um POJO para representar o contato – modelo de dados usado neste projeto de exemplo – que contém id, nome, telefone e endereço de email:

[code lang="java" firstline="1" toolbar="true" collapse="false" wraplines="false"]
package com.loiane.model;

public class Contact {

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

public Contact(int id, String name, String phone, String email) {
super();
this.id = id;
this.name = name;
this.phone = phone;
this.email = email;
}

public Contact() {}

//getters and setters
}
[/code]

2 - Contact.xml

Este é o arquivo de configuração do iBatis/myBatis para a classe Contact. Vamos escrever todas as queries, mapeamento query-objeto neste arquivo. É aqui onde toda a mágica acontece!

[code lang="xml" firstline="1" toolbar="true" collapse="false" wraplines="false"]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="Contact">

<resultMap id="result" type="Contact">
<result property="id" column="CONTACT_ID"/>
<result property="name" column="CONTACT_NAME"/>
<result property="phone" column="CONTACT_PHONE"/>
<result property="email" column="CONTACT_EMAIL"/>
</resultMap>

<select id="getAll" resultMap="result">
SELECT * FROM CONTACT
</select>

<select id="getById" parameterType="int" resultMap="result">
SELECT * FROM CONTACT WHERE CONTACT_ID = #{id}
</select>

<delete id="deleteById" parameterType="int">
DELETE from CONTACT WHERE CONTACT_ID = #{id};
</delete>

<insert id="insert" parameterType="Contact">
INSERT INTO CONTACT (CONTACT_EMAIL, CONTACT_NAME, CONTACT_PHONE)
VALUES (#{name}, #{phone}, #{email});
<selectKey keyProperty="id" resultType="int" order="AFTER">
select last_insert_id() as id
</selectKey>
</insert>

<update id="update" parameterType="Contact">
UPDATE CONTACT
SET
CONTACT_EMAIL = #{email},
CONTACT_NAME = #{name},
CONTACT_PHONE = #{phone}
WHERE CONTACT_ID = #{id};
</update>

</mapper>
[/code]

O que este arquivo contém:

Result Map

Este é o element mais importante e poderoso do MyBatis. É o que permite que você faça 90% do código que o JDBC necessita para carregar dados do banco de dados, e em algun casos também permite que você faça que o JDBC não suporta. Na verdade, para escrever um código equivalente para algo como um mapeamento join complexo, por exemplo, você poderia “economizar” milhares de linhas de código. O design do ResultMaps é tão simples que para queries simples você nem precisa especificar o mapeamento, e mapeamentos complexos só precisam do necessário para descrever os relacionamentos.

Neste exemplo, o nome das colunas do banco de dados é diferente do nome dos atributos da classe Contato. Por isso precisamos fazer o mapeamento de coluna-atributo. Caso os nomes fosse iguais, não precisaria fazer o mapeamento.

E lembre-se que TypeAliases (apelidos) são seus amigos. Use-os para não precisa especificar o nome complete da classe toda vez que precisar usar. Vamos configurar no arquivo de configuração principal do iBatis.

Select statment

A primeira query deste exemplo é um select chamado (id) "getAll", e significa que vamos usar essa id para chamar essa query na classe DAO. A outra opção que setamos é o resultMap, onde fixemos o mapeamento da query para a classe Contact – o que significa que esta query irá retornar uma lista de contatos (List<Contact>).

A segunda query select neste exemplo possui id de nome "getById". Configuramos uma opção chamada parameterType (parâmetro do tipo int – ou Integer) e retorna um objeto do tipo Contact. Note a notação do parâmetro #{id}. Isso diz ao iBatis para criar um parâmetro PreparedStatement. Se fosse usar JDBC, este parâmetro seria identificado por “?” no SQL passado para o new PreparedStatement.

Delete Statment

A query de delete é bem simples. Configuramos o parâmetro do tipo Integer (int) (o mesmo que a query getById) para poder filtrar os registros que serão deletados.

Update Statment

Na query de update, configuramos o parâmetro do tipo Contact, que significa que iremos passar um objeto do tipo Contact como parâmetro no método da classe DAO. Note a notação dos parâmetros  #{name}, #{phone}, #{email} e #{id}. Todos os parâmetros precisam ter o mesmo nome do atributo da classe Contact, caso contrário o iBatis não saberá como fazer o mapeamento.

Insert Statment

A query de insert é um pouco mais complicada, pois possui mais opções (atributos e elementos extras) que permitem gerenciar a geração de keys (ids) e várias maneiras. Se o banco de dados que está usando suporta a geração automática de Keys (por exemplo MySQL e SQL Server) então você pode usar o atributo useGeneratedKeys=”true” e configurar a keyProperty para mapear o atributo correspondente e está pronto.

O iBatis (MyBatis) também tem outra forma de manusear a geração de keys em banco de dados que não possuem suporte a este recurso. Neste exemplo, vamos setar manualmente o id gerado pelo bando de dados com a opção selectKey (apesar do MySQL ter suporte). A query selectKey sera executada após o insert (você também pode criar um função para gerar o id e executar antes do insert, passando o id como parâmetro também) e vamos usar a função last_insert_id() do MySQL para obter a última key gerada (do tipo int) e setar a propriedade id.

3 – Arquivo de Configuração - Mapper

O arquivo de configuração do iBatis (MyBatis) contém algumas configurações que possuem grande importância em como o IBatis irá se comportar. A estrutura em high level do arquivo é apresentada abaixo:

Uma dica importante: você precisa seguir a ordem acima, senão o iBatis irá disparar uma exception.

O arquivo SqlMapConfig.xml deste projeto de exemplo – apenas configurações básicas:

[code lang="xml" firstline="1" toolbar="true" collapse="false" wraplines="false"]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

<typeAliases>
<typeAlias alias="Contact" type="com.loiane.model.Contact"/>
</typeAliases>

<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/blog"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>

<mappers>
<mapper resource="com/loiane/data/Contact.xml"/>
</mappers>

</configuration>
[/code]

Vamos dar uma olhada nas configurações que usamos no projeto.

Type Aliases - Apelidos

Um type alias (apeliado) é um nome menor para um tipo Java. É apenas relevante para a configuração em XML e só existe para evitar redundância de digitar o nome completo de uma classe.

Lembra que usamos Contact como tipo no resultMap no arquivo Contact.xml? Foi porque configuramos Contact como apelido.

Environments - Ambientes

O MyBatis pode ser configurado para ser usado em vários ambientes. Isso ajuda a re-usar os mapeamentos XML e, vários bancos de dados. Por exemplo, você provavelmente tem uma configuração diferente para os ambientes de desenvolvimento, teste e produção. Ou ainda, você pode ter múltiplos bancos de produção que compartilham o mesmo schema, e você gostaria de usar os mesmos SQLs para ambos. Há vários casos!

E uma coisa é importante sempre lembrar: você pode configurar vários ambientes, mas você pode escolher apenas UM ambiente por instância do SqlSessionFactory.

A ambiente padrão/default and e o ID do ambiente acho que se auto-explica. Você pode dar o nome que você desejar, mas tenha certeza de que um deles seja o mesmo que o padrão.

Transaction Manager – Gerenciamento de Transações

Existem dois tipos de TransactionManager (por exemplo type=”[JDBC|MANAGED]”) que estão incluídos no iBatis (MyBatis):

Neste tutorial estamos usando o tipo JDBC.

Data Source

O element dataSource configure a fonte da conexão do JDBC usando a interface padrão do JDBC DataSource.

4 - MyBatisConnectionFactory

Toda aplicação iBatis (MyBatis) é centralizada numa instância do SqlSessionFactory. Uma instância do SqlSessionFactory pode ser adquirida usando o SqlSessionFactoryBuilder. O SqlSessionFactoryBuilder pode criar uma instância do SqlSessionFactory a partir de um XML ou a partir de uma instância customizada da classe Configuration.

Neste exemplo vamos usar XML:

[code lang="java" firstline="1" toolbar="true" collapse="false" wraplines="false"]
package com.loiane.dao;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Reader;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class MyBatisConnectionFactory {

private static SqlSessionFactory sqlSessionFactory;

static {
try {

String resource = "SqlMapConfig.xml";
Reader reader = Resources.getResourceAsReader(resource);

if (sqlSessionFactory == null) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
}
catch (FileNotFoundException fileNotFoundException) {
fileNotFoundException.printStackTrace();
}
catch (IOException iOException) {
iOException.printStackTrace();
}
}

public static SqlSessionFactory getSqlSessionFactory() {

return sqlSessionFactory;
}
}
[/code]

5 - ContactDAO

Agora que configuramos basicamente tudo que precisamos, vamos criar o DAO. Para chamar as queries, vamos chamar pelo namespace e o nome da query (você pode ter queries com o mesmo nome, basta que o namespace seja diferente):

[code lang="java" firstline="1" toolbar="true" collapse="false" wraplines="false"]
package com.loiane.dao;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;

import com.loiane.model.Contact;

public class ContactDAO {

private SqlSessionFactory sqlSessionFactory;

public ContactDAO(){
sqlSessionFactory = MyBatisConnectionFactory.getSqlSessionFactory();
}

/**
* Returns the list of all Contact instances from the database.
* @return the list of all Contact instances from the database.
*/
@SuppressWarnings("unchecked")
public List<Contact> selectAll(){

SqlSession session = sqlSessionFactory.openSession();

try {
List<Contact> list = session.selectList("Contact.getAll");
return list;
} finally {
session.close();
}
}

/**
* Returns a Contact instance from the database.
* @param id primary key value used for lookup.
* @return A Contact instance with a primary key value equals to pk. null if there is no matching row.
*/
public Contact selectById(int id){

SqlSession session = sqlSessionFactory.openSession();

try {
Contact contact = (Contact) session.selectOne("Contact.getById",id);
return contact;
} finally {
session.close();
}
}

/**
* Updates an instance of Contact in the database.
* @param contact the instance to be updated.
*/
public void update(Contact contact){

SqlSession session = sqlSessionFactory.openSession();

try {
session.update("Contact.update", contact);
session.commit();
} finally {
session.close();
}
}

/**
* Insert an instance of Contact into the database.
* @param contact the instance to be persisted.
*/
public void insert(Contact contact){

SqlSession session = sqlSessionFactory.openSession();

try {
session.insert("Contact.insert", contact);
session.commit();
} finally {
session.close();
}
}

/**
* Delete an instance of Contact from the database.
* @param id primary key value of the instance to be deleted.
*/
public void delete(int id){

SqlSession session = sqlSessionFactory.openSession();

try {
session.delete("Contact.deleteById", id);
session.commit();
} finally {
session.close();
}
}
}
[/code]

Download

Se quiser aprender mais sobre as opções de configuração do MyBatis, leia o User Guide. Você vai achar tudo o que precisa saber neste documento. Todas as citações que fiz neste tutorial são deste documento. Também usei como referência para fazer este projeto.

Também criei uma classe de testes usando JUnit. Se desejar fazer o download completo deste projeto, você pode fazer o download a partir da minha conta do GitHub: https://github.com/loiane/ibatis-helloworld

Se desejar fazer o download do aquivo zip, basta clicar no botão de download:

Nos próximos artigos/tutorias, vamos explorar um pouco mais sobre as opções do iBatis (Mybatis) com vários exemplos práticos!

Até a próxima! :)