Guia de Integração Spring com MyBatis

Introdução

Por que integrar Spring com MyBatis?

A integração entre Spring e MyBatis permite combinar as vantagens de ambos os frameworks. O Spring oferece um container leve com injeção de dependências, simplificando a configuração e gerenciamento da aplicação. O MyBatis é um framework robusto para mapeamento objeto-relacional que facilita operações com banco de dados. Juntos, proporcionam maior produtividade e qualidade no desenvolvimento.

Configuração da Integração

Dependências Maven necessárias

Para realizar a integração, é preciso incluir as dependências no arquivo pom.xml:

<properties>
    <spring.version>5.0.2.RELEASE</spring.version>
    <mybatis.version>3.4.5</mybatis.version>
    <mysql.version>5.1.44</mysql.version>
    <pagehelper.version>5.1.2</pagehelper.version>
    <mybatis.spring.version>1.3.1</mybatis.spring.version>
    <dbcp2.version>2.1.1</dbcp2.version>
    <log4j2.version>2.9.1</log4j2.version>
</properties>

<dependencies>
    <!-- Spring Core -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-orm</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <!-- MyBatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>${mybatis.version}</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>${mybatis.spring.version}</version>
    </dependency>

    <!-- MySQL Driver -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${mysql.version}</version>
    </dependency>

    <!-- PageHelper -->
    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper</artifactId>
        <version>${pagehelper.version}</version>
    </dependency>

    <!-- Connection Pool DBCP2 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-dbcp2</artifactId>
        <version>${dbcp2.version}</version>
    </dependency>

    <!-- Log4j2 -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>${log4j2.version}</version>
    </dependency>

    <!-- JUnit -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>

O que é um pool de conexões?
O pool de conexões mantém um conjunto de conexões com o banco de dados inicializadas previamente (geralmente 10). Quando uma requisição precisa acessar o banco, uma conexão é retirada do pool e, após o uso, retornada. Opções populares incluem DBCP2, Druid e C3P0.

Configurações de recursos Maven

<resources>
    <resource>
        <directory>src/main/java</directory>
        <includes>
            <include>**/*.xml</include>
        </includes>
    </resource>
    <resource>
        <directory>src/main/resources</directory>
        <includes>
            <include>jdbc.properties</include>
            <include>*.xml</include>
        </includes>
    </resource>
</resources>

Plugin MyBatis Generator

<plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.7.0</version>
        <configuration>
            <source>1.8</source>
            <target>1.8</target>
            <encoding>UTF-8</encoding>
        </configuration>
    </plugin>
    <plugin>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-maven-plugin</artifactId>
        <version>1.3.2</version>
        <dependencies>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
        </dependencies>
        <configuration>
            <overwrite>true</overwrite>
        </configuration>
    </plugin>
</plugins>

Arquivos de Configuração

Propriedades de conexão JDBC

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis_ssm?useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=senha_banco

Configuração do Spring (spring-mybatis.xml)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- Habilitar anotações -->
    <context:annotation-config/>
    
    <!-- Varredura de componentes -->
    <context:component-scan base-package>"com.exemplo"/>

    <!-- Carregar propriedades JDBC -->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!-- Configurar DataSource com pool DBCP2 -->
    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="initialSize" value="10"/>
        <property name="maxTotal" value="100"/>
        <property name="maxIdle" value="50"/>
        <property name="minIdle" value="10"/>
    </bean>

    <!-- Configurar SqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath*:com/exemplo/**/mapper/*.xml"/>
        <property name="typeAliasesPackage" value="com/exemplo/**/model"/>
        
        <!-- Configurar PageHelper -->
        <property name="plugins">
            <array>
                <bean class="com.github.pagehelper.PageInterceptor">
                    <property name="properties">
                        <value>helperDialect=mysql</value>
                    </property>
                </bean>
            </array>
        </property>
    </bean>

    <!-- Varredura automática de mappers -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com/exemplo/**/mapper"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

    <!-- Gerenciador de transações -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <tx:annotation-driven transaction-manager="transactionManager"/>
    
    <aop:aspectj-autoproxy/>
</beans>

Arquivo de contexto (spring-context.xml)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <import resource="classpath:spring-mybatis.xml"/>
</beans>

Configuração do MyBatis Generator

<?xml version="1.0" encoding="UTF-8" ?>

<generatorConfiguration>
    <properties resource="jdbc.properties"/>

    <context id="testContext">
        <commentGenerator>
            <property name="suppressAllComments" value="true"/>
            <property name="suppressDate" value="true"/>
        </commentGenerator>

        <jdbcConnection driverClass="${jdbc.driver}"
                        connectionURL="${jdbc.url}" 
                        userId="${jdbc.username}" 
                        password="${jdbc.password}"/>

        <javaTypeResolver>
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>

        <javaModelGenerator targetProject="src/main/java" targetPackage="com.exemplo.model">
            <property name="enableSubPackages" value="false"/>
            <property name="constructorBased" value="true"/>
            <property name="immutable" value="false"/>
        </javaModelGenerator>

        <sqlMapGenerator targetProject="src/main/java" targetPackage="com.exemplo.mapper">
            <property name="enableSubPackages" value="false"/>
        </sqlMapGenerator>

        <javaClientGenerator targetProject="src/main/java" targetPackage="com.exemplo.mapper" type="XMLMAPPER">
            <property name="enableSubPackages" value="false"/>
        </javaClientGenerator>

        <table schema="" tableName="t_livro" domainObjectName="Livro"
               enableCountByExample="false" enableDeleteByExample="false"
               enableSelectByExample="false" enableUpdateByExample="false"/>
    </context>
</generatorConfiguration>

Criando a camada de negócio

Interface e implementação

Após gerar o modelo e mapper automaticamente, crie a camada de negócio:

package com.exemplo.biz;

import com.exemplo.model.Livro;

public interface LivroBiz {
    int excluirPorId(Integer id);
    int inserir(Livro livro);
    int inserirParcial(Livro livro);
    Livro buscarPorId(Integer id);
    int atualizarParcial(Livro livro);
    int atualizar(Livro livro);
}
package com.exemplo.biz.impl;

import com.exemplo.biz.LivroBiz;
import com.exemplo.mapper.LivroMapper;
import com.exemplo.model.Livro;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class LivroBizImpl implements LivroBiz {

    @Autowired
    private LivroMapper livroMapper;

    @Override
    public int excluirPorId(Integer id) {
        return livroMapper.deleteByPrimaryKey(id);
    }

    @Override
    public int inserir(Livro livro) {
        return livroMapper.insert(livro);
    }

    @Override
    public int inserirParcial(Livro livro) {
        return livroMapper.insertSelective(livro);
    }

    @Override
    public Livro buscarPorId(Integer id) {
        return livroMapper.selectByPrimaryKey(id);
    }

    @Override
    public int atualizarParcial(Livro livro) {
        return livroMapper.updateByPrimaryKeySelective(livro);
    }

    @Override
    public int atualizar(Livro livro) {
        return livroMapper.updateByPrimaryKey(livro);
    }
}

Adicione a anotação @Repository nas classes geradas pelo MyBatis Generator.

Testando a integração

package com.exemplo.biz.impl;

import com.exemplo.biz.LivroBiz;
import com.exemplo.model.Livro;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-context.xml"})
public class LivroBizImplTest {

    @Autowired
    private LivroBiz livroBiz;

    @Test
    public void testBuscarPorId() {
        System.out.println(this.livroBiz.buscarPorId(1));
    }
}

Utilizando Lombok

O Lombok reduz a verbosidade do código eliminando a necessidade de escrever getters, setters, toString e construtores manualmente.

package com.exemplo.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Livro {
    private Integer id;
    private String titulo;
    private Float preco;
}

Integração com PageHelper usando AOP

Problmea

O código de paginação precisa ser repetido em cada método de listagem:

public List<Livro> buscarTodos(LivroFiltro filtro, PageBean pagina) {
    if (pagina != null && pagina.isPagination())
        PageHelper.startPage(pagina.getPage(), pagina.getRows());
    
    List<Livro> livros = livroMapper.buscarTodos(filtro);
    
    if (pagina != null && pagina.isPagination()) {
        PageInfo<Livro> info = new PageInfo<>(livros);
        pagina.setTotal((int) info.getTotal());
    }
    return livros;
}

Solução com Aspect

Crie um aspecto para centralizar a lógica de paginação:

package com.exemplo.aspect;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.exemplo.util.PageBean;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.util.List;

@Aspect
@Component
public class PaginaAspect {

    @Around("execution(* *..*biz.*Buscar*(..))")
    public Object processarPagina(ProceedingJoinPoint ponto) throws Throwable {
        Object[] parametros = ponto.getArgs();
        PageBean pagina = null;
        
        for (Object param : parametros) {
            if (param instanceof PageBean) {
                pagina = (PageBean) param;
                break;
            }
        }

        if (pagina != null && pagina.isPagination())
            PageHelper.startPage(pagina.getPage(), pagina.getRows());
        
        Object resultado = ponto.proceed(parametros);

        if (pagina != null && pagina.isPagination()) {
            PageInfo pageInfo = new PageInfo((List) resultado);
            pagina.setTotal((int) pageInfo.getTotal());
        }
        return resultado;
    }
}

Agora o método de negócio fica simplificado:

@Override
public List<Livro> buscarTodos(LivroFiltro filtro, PageBean pagina) {
    return livroMapper.buscarTodos(filtro);
}

Tags: Spring MyBatis java ORM integration

Publicado em 7-25 18:48