IBatis (MyBatis): Trabalhando com Stored Procedures

29 Mar 2011
5 mins read

Este tutorial tem como objetivo fazer o setup de uma aplicação Java usando iBatis (MyBatis) demonstrando o uso de stored procedures.

Pré-Requisitos

Para este tutorial usei:

IDE: Eclipse (você pode usar a sua IDE favorita)
DataBase: MySQL
Libs/jars: MybatisMySQL 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.

Como vamos trabalhar com stored procedures, você também vai precisar executar o script que contém as procedures. Aqui está:

[code lang="sql" firstline="1" toolbar="true" collapse="false" wraplines="false"]
USE `blog_ibatis`;
DROP procedure IF EXISTS `getTotalCity`;
DELIMITER $$
USE `blog_ibatis`$$
CREATE PROCEDURE `blog_ibatis`.`getTotalCity` (OUT total INTEGER)
BEGIN
SELECT count(*) into total
FROM city;
END
$$
DELIMITER ;

-- --------------------------------------------------------------------------------

USE `blog_ibatis`;
DROP procedure IF EXISTS `getTotalCityStateId`;
DELIMITER $$
USE `blog_ibatis`$$
CREATE PROCEDURE `blog_ibatis`.`getTotalCityStateId` (IN stateId SMALLINT, OUT total INTEGER)
BEGIN
SELECT count(*) into total
FROM city
WHERE state_id = stateId;
END
$$
DELIMITER ;

-- --------------------------------------------------------------------------------

USE `blog_ibatis`;
DROP procedure IF EXISTS `getStates`;
DELIMITER $$
USE `blog_ibatis`$$
CREATE PROCEDURE `blog_ibatis`.`getStates` ()
BEGIN
SELECT state_id, state_code, state_name
FROM state;
END
$$
DELIMITER ;
[/code]

1 - SPMapper - XML

Não encontrei nada no manual do MyBatis sobre como chamar stored procedures, então decidi procurar na lista de discussão do framework. Acabei encontrando algumas dicas.

Na versão anterior do MyBatis, existe uma tag XML especial para stored procedures. Mas na versão atual (versão 3) não existe mais.

Para chamar uma procedures usando MyBatis/iBatis 3 você precisa seguir as seguintes dicas:

  1. É necessário setar o statement type para CALLABLE
  2. É necessário usar a sequência padrão de escape do JDBC para stored procedures: {call xxx (parm1, parm2)}
  3. É necessário setar o MODE de todos os parâmetros (IN, OUT, INOUT)
  4. TODOS os parâmetros IN, OUT, e INOUT precisam fazer parte de um parameterType ou parameterMap (não recomendado).  A única exceção é se estiver usando um Map como parâmetro. Neste caso não é necessário adicionar os parâmetros OUT no map antes de chamar a procedure. O MyBatis vai fazer isso automaticamente.
  5. resultType ou resultMap é usado apenas quando a procedure retorna um result set.
  6. IMPORTANTE: cursores do Oracle geralmente são retornados como parâmetros, e NÃO diretamente da stored procedure. Então se tiver usando cursores, não precisa usar resultMap ou resultType.

Primeiro Exemplo:

Vamos chamar a procedure getTotalCity e esta procedure tem apenas um parâmetro de saída (OUT), e nenhum parâmetro de entrada e/ou entrada e saída (IN, INOUT). Como fazer a chamada?

Vamos usar parâmetros inline neste primeiro exemplo. Para usar parâmetros inline, crie uma classe POJO para representar os parâmetros e configure o parameterType com a classe que você criou. Use a seguinte notação para cada parâmetro inline::

#{nomeDoParametro, mode=OUT, jdbcType=INTEGER}

  • mode pode ser IN, OUT, INOUT
  • e especifique o jdbcType do parâmetro

Para criar a configuração do MyBatis em XML, você pode usar as tags select ou update.Não esqueça de setar o statementType para CALLABLE.

O código vai ficar assim:

[code lang="xml" firstline="1" toolbar="true" collapse="false" wraplines="false"]
<select id="callGetTotalCity" parameterType="Param" statementType="CALLABLE">
{ CALL getTotalCity(#{total, mode=OUT, jdbcType=INTEGER})}
</select>
[/code]

E este é o POJO que representa os parâmetros da procudure getTotalCity:

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

public class Param {

private int total;

public int getTotal() {
return total;
}

public void setTotal(int total) {
this.total = total;
}
}
[/code]

Segundo Examplo:

Agora vamos tentar chamar a mesma procedure que usamos no primeiro exemplo, mas vamos usar um parameterMap, como é feito na versão 2.x.

Uma observação muito importante: não é aconselhável usar parameterMap na versão 3 do MyBatis.

Vamos declarar a classe Param como parameterMap:

[code lang="xml" firstline="1" toolbar="true" collapse="false" wraplines="false"]
<parameterMap type="Param" id="testParameterMap">
<parameter property="total" jdbcType="INTEGER" mode="OUT" />
</parameterMap>
[/code]

E o código para chamar a stored procedure:

[code lang="xml" firstline="1" toolbar="true" collapse="false" wraplines="false"]
<update id="callGetTotalCity2" parameterMap="testParameterMap" statementType="CALLABLE">
{ CALL getTotalCity(?) }
</update>
[/code]

Note que usamos "?" (interrogação) para representar cada parâmetro.

Terceiro Examplo:

Agora vamos ver um exemplo de como chamar uma procedure com parâmetros de entrada e saída (IN e OUT). Vamos seguir as mesmas regras do primeiro exemplo.

Como vamos usar parâmetros inline, vamos criar um POJO para representar os parâmetros.

Código MyBatis:

[code lang="xml" firstline="1" toolbar="true" collapse="false" wraplines="false"]
<select id="callGetTotalCityStateId" parameterType="Param2" statementType="CALLABLE">
{ CALL getTotalCityStateId(
#{stateId, mode=IN, jdbcType=INTEGER},
#{total, mode=OUT, jdbcType=INTEGER})}
</select>
[/code]

Param2 POJO:

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

public class Param2 {

private int total;
private int stateId;

public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
public int getStateId() {
return stateId;
}
public void setStateId(int stateId) {
this.stateId = stateId;
}
}
[/code]

Quarto Examplo:

Vamos tentar obter um resultSet de uma stored procedure. Para isso vamos usar um resultMap.

[code lang="xml" firstline="1" toolbar="true" collapse="false" wraplines="false"]
<resultMap type="State" id="resultState">
<result property="id" column="state_id"/>
<result property="name" column="state_name"/>
<result property="code" column="state_code"/>
</resultMap>

<select id="callGetStates" resultMap="resultState" statementType="CALLABLE">
{ CALL getStates()}
</select>
[/code]

State POJO:

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

public class State {

private int id;
private String code;
private String name;

public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
[/code]

2- SPMapper - Annotations

Vamos agora tentar fazer as mesmas coisas que fizemos com stored procedures.

Annotation para o primeiro exemplo (XML):

[code lang="java" firstline="1" toolbar="true" collapse="false" wraplines="false"]
@Select(value= "{ CALL getTotalCity( #{total, mode=OUT, jdbcType=INTEGER} )}")
@Options(statementType = StatementType.CALLABLE)
Object callGetTotalCityAnnotations(Param param);
[/code]

É muito similar com o @Select, mas temos que configurar o statement type para CALLABLE. Para isso, podemos usar a annotation @Options.

Com annotations, podemos apenas usar parâmetros inline, então não será possível fazer o segundo exemplo com annotations.

Annotation para o terceiro exemplo (XML):

A explicação é a mesma do primeiro exemplo, então vamos apenas listar o código:

[code lang="java" firstline="1" toolbar="true" collapse="false" wraplines="false"]
@Select(value= "{ CALL getTotalCityStateId( #{stateId, mode=IN, jdbcType=INTEGER}, #{total, mode=OUT, jdbcType=INTEGER})}")
@Options(statementType = StatementType.CALLABLE)
Object callGetTotalCityStateIdAnnotations(Param2 param2);
[/code]

Annotation para o quarto exemplo (XML):

Tentei fazer o quarto exemplo com annotations, mas consegui apenas isso (e não é a maneira correta, e nem funciona):

[code lang="java" firstline="1" toolbar="true" collapse="false" wraplines="false"]
//TODO: set resultMap with annotations
/*@Select(value= "{ CALL getTotalCityStateId()}")
@Options(statementType = StatementType.CALLABLE)
/*@Results(value = {
@Result(property="id", column="state_id"),
@Result(property="name", column="state_name"),
@Result(property="code", column="state_code"),
})*/
List<State> callGetStatesAnnotations();
[/code]

Procurei na lista de discussão do MyBatis, e não tive sorte. Ainda não é possível representa um resultMap com annotations quando se trata de stored procedures. É uma limitação, por isso que todo o poder do MyBatis ainda está na configuração XML. Se você tiver alguma outra idéia de como fazer, por favor, compartilhe nos comentários! :)

Download

Se desejar, pode fazer o download do código completo na minha conta do GitHub: https://github.com/loiane/ibatis-stored-procedures

Se desejar fazer o download do arquivo zip do projeto, clique em download (depois é só importar no Eclipse ou outra IDE):

Mais artigos sobre iBatis estão por vir. Fique ligado!

Bons códigos! :)