Falando sobre o plugin mybatis
Antes de discutir a tecnologia, gostaria de fazer uma pergunta, por que interpretar o código-fonte e a tecnologia central? Na verdade, a equipe júnior geralmente pensa que é o suficiente para usar e não há necessidade de gastar tanto tempo estudando o código-fonte e o processo. No entanto, eu pessoalmente sinto que interpretar o código-fonte tem as seguintes vantagens:
1. Conhecimento profundo de java, retorno ao conhecimento geral de si mesmo
2. Adote o modo de design, experimente as idéias de design da tecnologia de código aberto
3. Design independente e melhoria do pensamento lógico matemático
4. Aprenda a resumir e melhorar a si mesmo
Introdução
O plug-in mybatis é a parte central e tem um pequeno valor em aplicações práticas, como consulta de paginação física, operação em lote, monitoramento e filtragem de script de banco de dados, modificação de parâmetro, monitoramento de log, etc. O plug-in Mybatis permite chamadas de interceptação para a instrução de mapeamento do ponto. Por padrão, é a interceptação de método, como o seguinte método:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject , setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (preparar, parametrizar, lote, atualizar, consultar)
Como usar
Use a anotação @Intercepts para ter efeito, @Signature define atributos de interceptação, análise de atributo de assinatura
- tipo tipo interceptado, tipo de classe
- método O método de interceptação, tipo de string
- tipo de parâmetro do método args, tipo de matriz de classe, porque evita a sobrecarga
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class MyBatchExecutor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 前置处理
Object returnObject = invocation.proceed(); // 也可以直接执行自己定义的处理器
// 后置处理
return returnObject;
}
}
Duas configurações comumente usadas, uma configuração xml, uma configuração java, não importa o tipo de configuração, é o método configuration # addInterceptor para adicionar o interceptor
definido em xml
<!-- mybatis-config.xml -->
<plugins>
<plugin interceptor="**.**.MyBatchExecutor" />
</plugins>
Ou consulte o interceptor na anotação
import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisConfig {
@Bean
public ConfigurationCustomizer mybatisConfigurationCustomizer() {
return new ConfigurationCustomizer() {
@Override
public void customize(org.apache.ibatis.session.Configuration configuration) {
configuration.addInterceptor(new MyBatchExecutor());
}
};
}
}
Princípio do interceptor
carregamento do interceptor
O Mybati é, na verdade, um multifuncional, ou seja, toda a configuração de dados está enraizada na Configuração e é analisada e armazenada em xml durante a inicialização específica. Outra maneira de viver na primavera é construir a fábrica SqlSessionFactoryBean para construir a SqlSessionFactory, como a montagem automática MybatisAutoConfiguration da bota de mola.
interceptor
A resposta na figura acima é porque o interceptor intercepta apenas quatro lugares. O proxy dinâmico é usado aqui. Configure todos os interceptores com antecedência e chame os interceptores antes de chamar o método.
Duas etapas, respectivamente:
1. Gerar proxy dinâmico.
2. O objeto proxy é adquirido por reflexão, e o método é executado diretamente após o julgamento.
Casos de uso de interceptador
Escreva um caso de interceptor de operação em lote aqui
Plugin em massa
Todos nós sabemos que existem três tipos de Mybatis Executor, SimpleExecutor, ReuseExecutor e BatchExecutor.Na verdade, BatchExecutor implementou operações em lote, mas não é fácil de usar. Em seguida, definimos a operação de lote físico. Quando o id correspondente ao mapa termina com Batch, realizamos a operação de lote físico
import org.apache.ibatis.executor.BatchExecutor;
import org.apache.ibatis.executor.BatchResult;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.transaction.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class BatchAdaptor extends BatchExecutor {
private Logger log = LoggerFactory.getLogger(BatchAdaptor.class);
public BatchAdaptor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
if (parameter == null) {
return super.update(ms, parameter);
}
Object params = null;
if (Map.class.isAssignableFrom(parameter.getClass())) { // DefaultSqlSession#wrapCollection
final Map<String, Object> paramMap = (Map<String, Object>) parameter;
if (paramMap.size() == 1) { // Map中array
if (paramMap.get("array") != null) {
params = paramMap.get("array");
} else {
params = paramMap.values().iterator().next();
}
} else if (paramMap.size() == 2) {
params = paramMap.get("collection");
}
} else if (parameter instanceof Iterable || parameter.getClass().isArray()) {
params = parameter;
} else {
params = Collections.singletonList(parameter);
}
final Iterable<?> paramIterable = toIterable(params);
try {
for (Object obj : paramIterable) {
super.update(ms, obj); // addBatch
}
List<BatchResult> batchResults = doFlushStatements(false); // executeBatch
if (batchResults == null || batchResults.size() == 0) {
return 0;
}
return resolveUpdateResult(batchResults);
} catch (Exception e) {
log.error("batch execute", e);
doFlushStatements(true);
/**
* 批量插入,则报异常
*/
if ("INSERT".equalsIgnoreCase(ms.getSqlCommandType().name())) {
throw e;
}
return 0;
}
}
/**
* 返回批量结果成功数
*
* @param batchResults
* @return
*/
private int resolveUpdateResult(final List<BatchResult> batchResults) {
int result = 0;
for (BatchResult batchResult : batchResults) {
int[] updateCounts = batchResult.getUpdateCounts();
if (updateCounts == null || updateCounts.length == 0) {
continue;
}
for (int updateCount : updateCounts) {
result += updateCount;
}
}
return result;
}
/**
* 统一转换
*
* @param params array或者Collections
* @return
*/
private Iterable<?> toIterable(final Object params) {
if (params == null) {
return Collections.emptyList();
}
Iterable<?> paramIterable;
if (params instanceof Iterable) {
paramIterable = (Iterable<?>) params;
} else if (params.getClass().isArray()) {
Object[] array = (Object[]) params;
paramIterable = Arrays.asList(array);
} else {
paramIterable = Collections.singletonList(params);
}
return paramIterable;
}
}
Escrita do interceptor
import org.apache.ibatis.executor.BatchExecutor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.sql.SQLException;
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class MyBatchInterceptor implements Interceptor {
private Logger log = LoggerFactory.getLogger(MyBatchInterceptor.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
//check argument
if (invocation.getArgs()[1] == null) {
return invocation.proceed();
}
final MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
// 是否需要批处理标识
if (!mappedStatement.getId().endsWith("Batch")) {
return invocation.proceed();
}
// 若是批处理,则不做操作
if (BatchExecutor.class.isAssignableFrom(invocation.getTarget().getClass())) {
return invocation.proceed();
}
Executor executor = (Executor) invocation.getTarget();
// 创建批处理对象
final BatchExecutor batchExecutor = new BatchAdaptor(getConfiguration(executor), executor.getTransaction());
try {
return batchExecutor.update(mappedStatement, invocation.getArgs()[1]);
} catch (SQLException e) {
log.error("batch excute", e);
batchExecutor.flushStatements(true);
throw e;
}
}
/**
* 获取配置文件
*
* @param executor
* @return
*/
public Configuration getConfiguration(Executor executor) {
Field configField = ReflectionUtils.findField(executor.getClass(), "configuration");
if (configField == null) { // CachingExecutor
configField = ReflectionUtils.findField(executor.getClass(), "delegate");
if (!configField.isAccessible()) {
configField.setAccessible(true);
}
executor = (Executor) ReflectionUtils.getField(configField, executor);
configField = ReflectionUtils.findField(executor.getClass(), "configuration");
if (!configField.isAccessible()) {
configField.setAccessible(true);
}
}
// 获取配置文件
return (Configuration) ReflectionUtils.getField(configField, executor);
}
}
Uso de interceptor
@Configuration
public class MybatisConfig {
@Bean
public MyBatchInterceptor myBatchInterceptor() {
return new MyBatchInterceptor();
}
}
@Mapper
public interface UserTableMapper {
@Update("update user set name=#{name},msg=#{msg} where id=#{id}")
int updateBatch(List<UserTable> userTableList);
}
Resumo e reflexão
- Faça uso completo do proxy dinâmico JDK, como Plugin # wrap para construir proxy dinâmico e aprenda o modo proxy ao mesmo tempo
- Mecanismo de reflexão Java, obter atributos e construir a classe ReflectionUtils
- Montagem automática da bota de mola, início do projeto da AutoConfiguração
- Use o interceptor mybatis para fazer algumas operações em lote e interceptar processamento especial para os quatro pontos de execução (parameterHandler, statementHandler, resultSetHandler, execute)
- Os quatro corpos de execução do executor mybatis
- A versão mybatis-plus é mais sobre a versão aprimorada do processamento em lote e assim por diante.