2021年了,该懂Mybatis是怎么实现的了!福利福利

干货分享最近将个人学习笔记整理成册,使用PDF分享主要包含了Java基础,数据结构,jvm,多线程等等,由于篇幅有限,以下只展示小部分面试题,
需要的朋友可以点一点领取:戳这里即可领取。。。暗号:CSDN在这里插入图片描述

1.SpringBoot与Mybatis示例

1.1Controller

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/")
    public List<User> listUser() {
        return userService.listUser();
    }

    @PostMapping("/")
    public void saveUser(@RequestBody User user) {
        userService.saveUser(user);
    }
}

1.2 Service

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public List<User> listUser() {
        return userMapper.list();
    }

    @Transactional(rollbackFor = Exception.class)
    public void saveUser(User user) {
        userMapper.save(user);
    }
}

1.3 Mapper接口

@Mapper
public interface UserMapper {

    /**
     * 查询用户列表信息
     * @return 用户列表信息
     */
    List<User> list();

    /**
     * 新增用户
     * @param user 用户信息
     * @return 受影响的行数
     */
    int save(User user);
}

1.4 Mapper文件

<?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="com.boot.example.mapper.UserMapper">

    <sql id="BASE_SELECT_COLUMN">
        id,
        name,
        age
    </sql>

    <select id="list" resultType="com.boot.example.entity.User">
        SELECT
            <include refid="BASE_SELECT_COLUMN"/>
        FROM
            user
    </select>

    <insert id="save" parameterType="com.boot.example.entity.User">
        INSERT INTO user VALUES(#{id}, #{name}, #{age})
    </insert>
</mapper>

想要实现一套增删改查是一件极其简单的事情,让人困惑的是UserMapper是一个接口,为什么可以被注入到UserService中,为什么调用UserMapper接口中的方法就可以执行想要执行的SQL,从而完成我们想要的结果。

2.源码分析

2.1 初始化数据源
打开MybatisAutoConfiguration自动配置类,可以看到该自动配置类被@AutoConfigureAfter(DataSourceAutoConfiguration.class)注解标注,表示当前自动配置类在DataSourceAutoConfiguration配置类之后解析。
打开DruidDataSourceAutoConfigure自动配置类,可以看到该配置类被@AutoConfigureBefore(DataSourceAutoConfiguration.class)注解标注,表示当前自动配置类在DataSourceAutoConfiguration配置类之前解析。
DruidDataSourceAutoConfigure自动配置类定义如下:

@Bean(initMethod = "init")
@ConditionalOnMissingBean
public DataSource dataSource() {
    LOGGER.info("Init DruidDataSource");
    return new DruidDataSourceWrapper();
}

创建DruidDataSourceWrapper对象,并在Bean创建之后调用init()方法,DruidDataSourceWrapper又实现了InitializingBean接口,InitializingBean接口实现的回调优于init()方法
2.1.1 InitializingBean接口回调

@Override
public void afterPropertiesSet() throws Exception {
    //if not found prefix 'spring.datasource.druid' jdbc properties ,'spring.datasource' prefix jdbc properties will be used.
    if (super.getUsername() == null) {
        super.setUsername(basicProperties.determineUsername());
    }
    if (super.getPassword() == null) {
        super.setPassword(basicProperties.determinePassword());
    }
    if (super.getUrl() == null) {
        super.setUrl(basicProperties.determineUrl());
    }
    if(super.getDriverClassName() == null){
        super.setDriverClassName(basicProperties.getDriverClassName());
    }

}

设置数据库url地址、用户名、密码、驱动配置属性
2.1.2 init()回调

public void init() throws SQLException {
    try {
        // init connections
        for (int i = 0; i < initialSize; ++i) {
            // 创建与数据库连接
            PhysicalConnectionInfo pyConnectInfo = createPhysicalConnection();
            DruidConnectionHolder holder = new DruidConnectionHolder(this, pyConnectInfo);
            // 将数据库连接存放在数组中,从而实现数据库连接池
            connections[poolingCount] = holder;
            incrementPoolingCount();
        }

        if (poolingCount > 0) {
            poolingPeak = poolingCount;
            poolingPeakTime = System.currentTimeMillis();
        }
    } catch (SQLException ex) {
        LOG.error("init datasource error, url: " + this.getUrl(), ex);
        connectError = ex;
    }
}

init()回调用于初始化数据库连接池指定初始大小
2.2 创建SqlSessionFactory

@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    // ......省略部分代码,可自行查看源代码
    return factory.getObject();
}

使用MybatisProperties配置SqlSessionFactoryBean,通过SqlSessionFactoryBean创建DefaultSqlSessionFactory,DefaultSqlSessionFactory持有Configuration,Configuration持有Environment,Environment持有SpringManagedTransactionFactory和DataSource
2.3 创建SqlSessionTemplate
SqlSessionTemplate的创建比较简单,直接注入DefaultSqlSessionFactory

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
}

2.4 扫描含有@Mapper注解的接口

public static class AutoConfiguredMapperScannerRegistrar
      implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 定义扫描器
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

        try {
            if (this.resourceLoader != null) {
                scanner.setResourceLoader(this.resourceLoader);
            }
   // 自动配置路径
            List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
            if (logger.isDebugEnabled()) {
                for (String pkg : packages) {
                    logger.debug("Using auto-configuration base package '{}'", pkg);
                }
            }
   // 指定扫描注解
            scanner.setAnnotationClass(Mapper.class);
            scanner.registerFilters();
            scanner.doScan(StringUtils.toStringArray(packages));
        } catch (IllegalStateException ex) {
            logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex);
        }
    }
}

该部分自定义了扫描器,扫描自动配置包路径下含有@Mapper注解的接口,注解为BeanDefinition并指定类型为definition.setBeanClass(this.mapperFactoryBean.getClass());,MapperFactoryBean实现了FactoryBean接口。
因此我们可以得出一个结论:针对每个Mapper接口生成一个MapperFactoryBean这样一个Bean,在注入的时候会调用FactoryBean接口getObject()的实现。
2.5 MapperFactoryBean
MapperFactoryBean不仅实现了FactoryBean接口还实现了InitializingBean接口,来看看checkDaoConfig()实现

@Override
protected void checkDaoConfig() {
    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
        configuration.addMapper(this.mapperInterface);
    }

}
可以看到一个逻辑,就是有元素不添加,没有就添加。
2.5.1 addMapper()

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
        if (hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
            knownMappers.put(type, new MapperProxyFactory<T>(type));
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                knownMappers.remove(type);
            }
        }
    }
}

knownMappers.put(type, new MapperProxyFactory(type));每个Mapper接口对应一个MapperProxyFactory存放在map中,parser.parse()默认情况下会解析Mapper接口包路径下面的mapper文件。举个例子:Mapper接口对应的包路径为com.boot.example.mapper,那么我们只需要把mapper文件放在对应的路径下就可以了

2.6 注入Mapper

经过上面的分析我们得知,每个Mapper都对应一个MapperFactoryBean,MapperFactoryBean又实现了FactoryBean接口,在注入的时候会调用getObject()实现

@Override
public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    return mapperProxyFactory.newInstance(sqlSession);
}

在2.5.1章节讲解过knownMappers存放的是Mapper接口和MapperProxyFactory的对应关系,如上调用了MapperProxyFactory的newInstance()

2.7 生成代理

protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}

也就是我们在UserService中注入的UserMapper实际上是一个代理对象,当调用UserMapper的目标方法的时候会调用MapperProxy的invoke()

2.8 调用目标方法

当调用UserMapper目标方法的时候会调用MapperProxy的invoke()

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else if (isDefaultMethod(method)) {
            return invokeDefaultMethod(proxy, method, args);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
}

2.8.1 根据sql类型调用目标方法

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
     Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
    }
    return result;
  }

此处已查询列表为例executeForMany(sqlSession, args);,调用SqlSessionTemplate的selectList()

@Override
public <E> List<E> selectList(String statement, Object parameter) {
    return this.sqlSessionProxy.<E> selectList(statement, parameter);
}

紧接着调用SqlSessionInterceptor的invoke()

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 创建DefaultSqlSession
        SqlSession sqlSession = getSqlSession(
            SqlSessionTemplate.this.sqlSessionFactory,
            SqlSessionTemplate.this.executorType,
            SqlSessionTemplate.this.exceptionTranslator);
        // 调用DefaultSqlSession对应的方法
        Object result = method.invoke(sqlSession, args);
            if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                // force commit even on non-dirty sessions because some databases require
                // a commit/rollback before calling close()
                sqlSession.commit(true);
            }
            return result;
    }
}

先生成DefaultSqlSession,然后调用DefaultSqlSession的selectList()

@Override
public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

最终调用SimpleExecutor的doQuery()

@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
        Configuration configuration = ms.getConfiguration();
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        stmt = prepareStatement(handler, ms.getStatementLog());
        return handler.<E>query(stmt, resultHandler);
    } finally {
        closeStatement(stmt);
    }
}

到这里就可以看到最初进行数据库增删改查的代码了:
创建Connection
创建Statement
执行Statement得到结果

2.9 获取连接

初始化连接,那么在哪里获取的连接呢?
打开org.apache.ibatis.executor.SimpleExecutor#doQuery,进入prepareStatement()

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
}

可以很明显看到获取Connection的逻辑

protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
        return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
        return connection;
    }
}

在2.2介绍了Environment持有SpringManagedTransactionFactory和DataSource,SpringManagedTransactionFactory创建出来的就是SpringManagedTransaction,在SpringManagedTransaction的openConnection()就可以看到从数据源连接池中获取连接的逻辑了

private void openConnection() throws SQLException {
    this.connection = DataSourceUtils.getConnection(this.dataSource);
    this.autoCommit = this.connection.getAutoCommit();
    this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);

    if (LOGGER.isDebugEnabled()) {
        LOGGER.debug(
            "JDBC Connection ["
            + this.connection
            + "] will"
            + (this.isConnectionTransactional ? " " : " not ")
            + "be managed by Spring");
    }
}

3.总结

配置数据源,初始化数据库连接池
创建SqlSessionFactory
创建SqlSessionTemplate
扫描含有@Mapper注解的接口注入到IOC容器中
注入Mapper接口,调用getObject()获取接口对应MapperProxyFactory生成的代理
调用Mapper接口目标方法的时候调用MapperProxy的invoke()
从数据源数据库连接池中获取连接执行sql

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值