Entrevistador: Você analisou o código-fonte do Mybatis? Foi assim que respondi ~

O princípio de funcionamento do Mybatis também é um importante ponto de teste da entrevista, e deve estar muito claro para que você possa voltar. Este artigo é baseado no projeto integrado Spring + SpringMVC + Mybatis.

Eu divido seu princípio de funcionamento em seis partes:

Se o entrevistador perguntar a você, responda assim.

  1. Leia o arquivo de configuração principal mybatis-config.xmle retorne o InputStreamobjeto de fluxo.
  2. InputStreamAnalise o Configurationobjeto de acordo com o objeto de fluxo e, em seguida, crie o SqlSessionFactoryobjeto de fábrica
  3. SqlSessionFactoryCriado de fábrica com base em uma série de atributosSqlSession
  4. Da SqlSessionchamada Executorpara realizar operações de banco de dados && comandos SQL específicos gerados por análise
  5. Ao TypeHandler(数据库与java类型转换)re-encapsular os resultados da execução
  6. Commit e processamento de transação

Claro, a memória definitivamente não é forte por meio da memorização mecânica e a memória pode ser entendida em combinação com o código-fonte abaixo.

Deixe-me mostrar a você minha classe de entidade primeiro:

/**
 * 图书实体
 */
public class Book {

    private long bookId;// 图书ID

    private String name;// 图书名称

    private int number;// 馆藏数量

        getter and setter ...
}

1. Leia o arquivo de configuração principal

1.1 Arquivo de configuração mybatis-config.xml

<?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>
    <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://xxx.xxx:3306/ssm" />
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
        <mappers>
        <mapper resource="BookMapper.xml"/>
    </mappers>
</configuration>

Claro, existem muitos mais que podem ser configurados em um arquivo XML, e o exemplo acima aponta a parte mais crítica. Preste atenção à declaração no cabeçalho XML para verificar a exatidão do documento XML. O corpo do elemento de ambiente contém a configuração de gerenciamento de transações e conjunto de conexões. O elemento mappers contém um conjunto de mapeadores mapeadores (os arquivos XML desses mapeadores contêm código SQL e informações de definição de mapeamento).

1.2 BookMapper.xml

<?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="Book">
    <!-- 目的:为dao接口方法提供sql语句配置 -->
    <insert id="insert" >
        insert into book (name,number) values (#{name},#{number})
    </insert>
</mapper>

É um arquivo mapper.xml comum.

1.3 Método principal

É muito simples construir uma instância de SqlSessionFactory a partir de um arquivo XML. Recomenda-se usar o arquivo de recursos no classpath para configuração. Mas você também pode usar qualquer instância de InputStream, incluindo um caminho de arquivo na forma de uma string ou um caminho de arquivo na forma de um arquivo: // URL. MyBatis inclui uma classe de ferramenta chamada Resources, que contém alguns métodos práticos para tornar mais fácil carregar arquivos de recursos do classpath ou outros locais.

public class Main {
    public static void main(String[] args) throws IOException {
        // 创建一个book对象
        Book book = new Book();
        book.setBookId(1006);
        book.setName("Easy Coding");
        book.setNumber(110);
        // 加载配置文件 并构建SqlSessionFactory对象
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
        // 从SqlSessionFactory对象中获取 SqlSession对象
        SqlSession sqlSession = factory.openSession();
        // 执行操作
        sqlSession.insert("insert", book);
        // 提交操作
        sqlSession.commit();
        // 关闭SqlSession
        sqlSession.close();
    }
}

Este código é adaptado de um Demo que não usa XML para construir SqlSessionFactory oficialmente fornecido por Mybatis .

Nota: É um 不使用 XML 构建 SqlSessionFactoryexemplo dado pelo oficial , então encontraremos a entrada deste exemplo para analisar.

2. Gere o objeto de fábrica SqlSessionFactory de acordo com o arquivo de configuração

2.1 Resources.getResourceAsStream (recurso); análise do código-fonte

ResourcesÉ uma classe de ferramenta fornecida por mybatis para carregar arquivos de recursos.

Olhamos apenas para o método getResourceAsStream:

public static InputStream getResourceAsStream(String resource) throws IOException {
    return getResourceAsStream((ClassLoader)null, resource);
}

getResourceAsStream chama os seguintes métodos:

public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
    InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
    if (in == null) {
        throw new IOException("Could not find resource " + resource);
    } else {
        return in;
    }
}

Obtenha seu próprio objeto ClassLoader e, em seguida, entregue-o ao ClassLoader (no pacote lang) para carregar:

InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
    ClassLoader[] arr$ = classLoader;
    int len$ = classLoader.length;

    for(int i$ = 0; i$ < len$; ++i$) {
        ClassLoader cl = arr$[i$];
        if (null != cl) {
            InputStream returnValue = cl.getResourceAsStream(resource);
            if (null == returnValue) {
                returnValue = cl.getResourceAsStream("/" + resource);
            }

            if (null != returnValue) {
                return returnValue;
            }
        }
    }

O valor da observação é que ele retorna um InputStreamobjeto.

2.2 new SqlSessionFactoryBuilder (). Build (inputStream); análise do código-fonte

public SqlSessionFactoryBuilder() {
}

Portanto, new SqlSessionFactoryBuilder()basta criar uma instância do objeto, mas sem retorno do objeto (modo builder), o retorno do objeto é entregue ao build()método.

public SqlSessionFactory build(InputStream inputStream) {
    return this.build((InputStream)inputStream, (String)null, (Properties)null);
}

Passar um objeto inputStream aqui é passar o objeto InputStream que obtivemos na etapa anterior.

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    SqlSessionFactory var5;
    try {
        // 进行XML配置文件的解析
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        var5 = this.build(parser.parse());
    } catch (Exception var14) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
    } finally {
        ErrorContext.instance().reset();

        try {
            inputStream.close();
        } catch (IOException var13) {
            ;
        }

    }

    return var5;
}

Como analisar é grosso modo, Documentanalisar o objeto e, em seguida, retornar o InputStreamobjeto e, em seguida, entregá-lo para XMLConfigBuilderconstruir o org.apache.ibatis.session.Configurationobjeto e, em seguida, passá-lo para o processo de construção do método build () SqlSessionFactory:

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

public DefaultSqlSessionFactory(Configuration configuration) {
    this.configuration = configuration;
}

3. Crie SqlSession

SqlSession contém completamente todos os métodos necessários para executar comandos SQL para o banco de dados. Você pode executar diretamente a instrução SQL mapeada por meio da instância SqlSession.

public SqlSession openSession() {
    return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}

openSessionFromDataSourceMétodo de auto-denominação :

  1. getDefaultExecutorType () padroniza para SIMPLE.
  2. Observe que o nível TX é Nulo e autoCommit é falso.
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;

    DefaultSqlSession var8;
    try {
        Environment environment = this.configuration.getEnvironment();
        // 根据Configuration的Environment属性来创建事务工厂
        TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
        // 从事务工厂中创建事务,默认等级为null,autoCommit=false
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        // 创建执行器
        Executor executor = this.configuration.newExecutor(tx, execType);
        // 根据执行器创建返回对象 SqlSession
        var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
    } catch (Exception var12) {
        this.closeTransaction(tx);
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
    } finally {
        ErrorContext.instance().reset();
    }
    return var8;
}

Etapas de construção:
Environment>> TransactionFactory+ autoCommit+ tx-level>> Transaction+ ExecType>> Executor+ Configuration+ autoCommit>>SqlSession

Entre eles, Environmentestá Configurationo atributo em.

4. Chame o Executor para realizar operações de banco de dados e gerar comandos SQL específicos

Depois de obter o objeto SqlSession, chamamos seu método de inserção.

public int insert(String statement, Object parameter) {
    return this.update(statement, parameter);
}

Ele chama seu próprio método de atualização (instrução, parâmetro):

public int update(String statement, Object parameter) {
    int var4;
    try {
        this.dirty = true;
        MappedStatement ms = this.configuration.getMappedStatement(statement);
        // wrapCollection(parameter)判断 param对象是否是集合
        var4 = this.executor.update(ms, this.wrapCollection(parameter));
    } catch (Exception var8) {
        throw ExceptionFactory.wrapException("Error updating database.  Cause: " + var8, var8);
    } finally {
        ErrorContext.instance().reset();
    }

    return var4;
}

mappedStatementsÉ o objeto de mapeamento SQL que geralmente chamamos.

O código-fonte é o seguinte:
protected final Map<String, MappedStatement> mappedStatements;

Pode-se ver que se trata de uma coleção de Mapas. Quando carregamos a configuração xml, mapping.xmlas informações de namespacesoma idserão armazenadas como mappedStatementsa keyinstrução sql correspondente value.

Em seguida, chame o método de atualização no BaseExecutor:

public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (this.closed) {
        throw new ExecutorException("Executor was closed.");
    } else {
        this.clearLocalCache();
        // 真正做执行操作的方法
        return this.doUpdate(ms, parameter);
    }
}

doUpdate é a maneira real de realizar operações:

public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;

    int var6;
    try {
        Configuration configuration = ms.getConfiguration();
        // 创建StatementHandler对象,从而创建Statement对象
        StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null);
        // 将sql语句和参数绑定并生成SQL指令
        stmt = this.prepareStatement(handler, ms.getStatementLog());
        var6 = handler.update(stmt);
    } finally {
        this.closeStatement(stmt);
    }

    return var6;
}

Vamos dar uma olhada no prepareStatementmétodo primeiro e ver como mybatis une SQL:

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Connection connection = this.getConnection(statementLog);
    // 准备Statement
    Statement stmt = handler.prepare(connection);
    // 设置SQL查询中的参数值
    handler.parameterize(stmt);
    return stmt;
}

Dê uma olhada no parameterizemétodo:

public void parameterize(Statement statement) throws SQLException {
    this.parameterHandler.setParameters((PreparedStatement)statement);
}

Aqui, a instrução é convertida no objeto PreparedStatement, que é mais rápido e seguro do que a instrução.
Todos esses são objetos com os quais estamos familiarizados em JDBC, portanto, não os apresentarei, portanto, também pode ser visto que Mybatis é um encapsulamento de JDBC.

Leia o valor e o tipo do parâmetro em ParameterMapping e, em seguida, defina-o para a instrução SQL:

public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(this.mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = this.boundSql.getParameterMappings();
    if (parameterMappings != null) {
        for(int i = 0; i < parameterMappings.size(); ++i) {
            ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);
            if (parameterMapping.getMode() != ParameterMode.OUT) {
                String propertyName = parameterMapping.getProperty();
                Object value;
                if (this.boundSql.hasAdditionalParameter(propertyName)) {
                    value = this.boundSql.getAdditionalParameter(propertyName);
                } else if (this.parameterObject == null) {
                    value = null;
                } else if (this.typeHandlerRegistry.hasTypeHandler(this.parameterObject.getClass())) {
                    value = this.parameterObject;
                } else {
                    MetaObject metaObject = this.configuration.newMetaObject(this.parameterObject);
                    value = metaObject.getValue(propertyName);
                }

                TypeHandler typeHandler = parameterMapping.getTypeHandler();
                JdbcType jdbcType = parameterMapping.getJdbcType();
                if (value == null && jdbcType == null) {
                    jdbcType = this.configuration.getJdbcTypeForNull();
                }

                try {
                    typeHandler.setParameter(ps, i + 1, value, jdbcType);
                } catch (TypeException var10) {
                    throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var10, var10);
                } catch (SQLException var11) {
                    throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var11, var11);
                }
            }
        }
    }

}

5. Pacote secundário dos resultados da consulta

No método doUpdate, depois de analisar e gerar o novo SQL, execute var6 = handler.update (stmt); vamos dar uma olhada em seu código-fonte.

public int update(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement)statement;
     // 执行sql
    ps.execute();
    // 获取返回值
    int rows = ps.getUpdateCount();
    Object parameterObject = this.boundSql.getParameterObject();
    KeyGenerator keyGenerator = this.mappedStatement.getKeyGenerator();
    keyGenerator.processAfter(this.executor, this.mappedStatement, ps, parameterObject);
    return rows;
}

Como somos uma operação de inserção e retornamos um valor do tipo int, aqui mybatis retorna int diretamente para nós.

Se for uma operação de consulta, o que é retornado é um ResultSet. Mybatis quebra o resultado da consulta no ResultSetWrappertipo de processo , e então atribui valores ao tipo java passo a passo ... Se você estiver interessado, você pode ir e ver por si mesmo.

6. Compromisso e transação

Finalmente, vamos dar uma olhada no código-fonte do método commit ().

public void commit() {
    this.commit(false);
}

Chame o método commit () do próprio objeto:

public void commit(boolean force) {
    try {
        // 是否提交(判断是提交还是回滚)
        this.executor.commit(this.isCommitOrRollbackRequired(force));
        this.dirty = false;
    } catch (Exception var6) {
        throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + var6, var6);
    } finally {
        ErrorContext.instance().reset();
    }
}

Se sujo for falso, reverta; se for verdadeiro, confirme normalmente.

private boolean isCommitOrRollbackRequired(boolean force) {
    return !this.autoCommit && this.dirty || force;
}

Chame o método commit de CachingExecutor:

public void commit(boolean required) throws SQLException {
    this.delegate.commit(required);
    this.tcm.commit();
}

Chame o método commit de BaseExecutor:

public void commit(boolean required) throws SQLException {
    if (this.closed) {
        throw new ExecutorException("Cannot commit, transaction is already closed");
    } else {
        this.clearLocalCache();
        this.flushStatements();
        if (required) {
            this.transaction.commit();
        }

    }
}

Finalmente, chame o método commit de JDBCTransaction:

public void commit() throws SQLException {
    if (this.connection != null && !this.connection.getAutoCommit()) {
        if (log.isDebugEnabled()) {
            log.debug("Committing JDBC Connection [" + this.connection + "]");
        }
        // 提交连接
        this.connection.commit();
    }
}

Acho que você gosta

Origin blog.csdn.net/doubututou/article/details/109298918
Recomendado
Clasificación