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);
}