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.
- Leia o arquivo de configuração principal
mybatis-config.xml
e retorne oInputStream
objeto de fluxo.InputStream
Analise oConfiguration
objeto de acordo com o objeto de fluxo e, em seguida, crie oSqlSessionFactory
objeto de fábricaSqlSessionFactory
Criado de fábrica com base em uma série de atributosSqlSession
- Da
SqlSession
chamadaExecutor
para realizar operações de banco de dados && comandos SQL específicos gerados por análise- Ao
TypeHandler(数据库与java类型转换)
re-encapsular os resultados da execução- 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 构建 SqlSessionFactory
exemplo 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 InputStream
objeto.
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, Document
analisar o objeto e, em seguida, retornar o InputStream
objeto e, em seguida, entregá-lo para XMLConfigBuilder
construir o org.apache.ibatis.session.Configuration
objeto 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);
}
openSessionFromDataSource
Método de auto-denominação :
- getDefaultExecutorType () padroniza para SIMPLE.
- 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, Environment
está Configuration
o 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.xml
as informações de namespace
soma id
serão armazenadas como mappedStatements
a key
instruçã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 prepareStatement
mé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 parameterize
mé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 ResultSetWrapper
tipo 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();
}
}