转载

mybatis是怎样工作的

mybatis是一款支持自定义SQL、存储过程和高级映射的持久化框架。通过封装几乎消除了使用者编写JDBC、手动设置参数和检索结果的代码,其底层实现通过XML配置文件、Java注解的方式来配置,将Mapper接口和POJO类映射到数据库,而且与springboot框架集成特别方便

如何使用

介绍使用mybatis的基本配置、一些基本用法以及介绍事务在mybatis中是怎么实现的,最后说一下如何集成到springboot中。

执行环境

  • 数据库表定义
CREATE TABLE `user` (
  `id` bigint(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  `status` tinyint(1) DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
复制代码

必要的配置

mybatis支持XML配置、Java代码配置两种方式

  • XML配置 笔者比较推荐使用Java代码的配置方式,这样可以省去一个外部文件的管理。加之,这些配置几乎在定义完成后不需要多次修改,因此也没必要使用XML来定义配置。有兴趣的可以参考官方文档
  • Java代码配置
// 加载外部的properties配置文件
Properties properties = new Properties();
properties.load(Resources.getResourceAsStream("db.properties"));
String connectUrl = properties.getProperty("db.connectionURL");
String driverClass = properties.getProperty("db.driverClass");
String userName = properties.getProperty("db.userName");
String password = properties.getProperty("db.password");

// 定义数据源,这里使用HikariCP数据源
HikariDataSource dataSource = new HikariDataSource();
dataSource.setDriverClassName(driverClass);
dataSource.setJdbcUrl(connectUrl);
dataSource.setUsername(userName);
dataSource.setPassword(password);

// 事务相关配置
TransactionFactory transactionFactory = new JdbcTransactionFactory();

// 配置Environment、Configuration。
// 在mybatis中,Configuration作为一个配置对象,包含了数据源对象、Environment对象、TransactionFactory对象等等,
// 使用组合的方式,封装了以上提到的各种对象,提供统一接口来使用这些对象。
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
复制代码

定义映射表的model类和mapper接口

  • model类
@Data
public class User {
    private Long id;

    private String name;

    private String password;

    private Boolean status;
}
复制代码
  • mapper接口
@Mapper
public interface UserMapper {


    @Insert("<script>insert into user(name, password, status) values(#{name}, #{password}, #{status})</script>")
    @SelectKey(statement = "SELECT LAST_INSERT_ID()", keyProperty = "id", keyColumn = "id", before = false, resultType = Long.class)
    int insert(User user);

    @Results(id = "user", value = {
            @Result(column = "name", property = "name"),
            @Result(column = "password", property = "password"),
            @Result(column = "status", property = "status"),
    })
    @Select("select * from user")
    List<User> selectAll();

    @ResultMap("user")
    @Select("select * from user where id = #{id}")
    User selectByPrimaryKey(Long id);
    
    
    @Delete("delete from user where id = #{id}")
    int deleteByPrimaryKey(Long id);
}
复制代码

使用mapper来执行数据库操作

// 通过之前的配置,得到的Configuration配置类的实例,通过它来添加Mapper接口
configuration.addMapper(UserMapper.class);

// 在使用mapper接口来进行数据库操作前,需要构造一个SqlSession实例。mybatis通过这个接口与开发者交互来实现数据库操作
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
SqlSession sqlSession = sqlSessionFactory.openSession();

// 通过SqlSession实例,获取为我们执行数据库操作的mapper接口
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

// 执行插入记录
User user = new User();
user.setName("测试");
user.setPassword("测试");
user.setStatus(Boolean.FALSE);
userMapper.insert(user);
// 由于在mybatis事务中,默认为「非自动提交」的,此处需要手动提交,才能保证数据真正插入到数据库中,否则在代码运行结束后将被回滚
sqlSession.commit();

// 查询所有记录
List<User> users = userMapper.selectAll();

// 根据主键ID进行查询
User u = userMapper.selectByPrimaryKey(user.getId());

// 删除记录
userMapper.deleteByPrimaryKey(user.getId());
复制代码

总结

mybatis是怎样工作的

可以看出,mybatis内部通过Environment这个类组合了DataSource、TransactionFactory来封装数据库的连接、事务这两个功能。之后通过Enviroment构造出配置类对象Configuration,封装了管理配置、获取配置的接口。之后由Configuration构造出的SqlSessionFactory,由SqlSessionFactory生产出的SqlSession对象跟开发者打交道,从而实现数据库操作。我们可以做出假设:

  • 通过 **configuration.addMapper(UserMapper.class)**会解析在UserMapper接口中定义的SQL语句,并绑定到Configuration实例中
  • 通过 sqlSession.getMapper(UserMapper.class) 可以获取到UserMapper接口的一个代理类对象,之后通过这个代理类对象进行操作时都会被拦截,在拦截逻辑里进行数据库操作

基于以上两个假设,我们来分析一下mybatis中是否真的如此?

怎么实现的

执行流程

  • 解析、绑定SQL的过程 configuration.addMapper(UserMapper.class)

    mybatis是怎样工作的
    • 通过MapperAnnotationBuilder解析注解,通过解析得到了SqlSource实例,其中包含SQL语句、SQL语句占位符的类型信息(解析的逻辑委托给了XMLLanguageDriver)
    • 通过SqlSource等信息构造出MappedStatement对象,并注册到Configuration实例中,实际上是以接口的全限定名作为key、MappedStatement对象作为value放入Configuration的一个Map成员变量中
  • 执行SQL的过程

    mybatis是怎样工作的
    • 通过动态代理构造了Mapper接口的代理类,之后对代理类的操作将被MapperProxy拦截
    • 在MapperProxy中拦截的逻辑,代理给了MapperMethod。MapperMethod作为一个封装了复杂接口的门面类,对外暴露了一个execute接口来执行开发者对数据库的操作
    • 通过Transaction接口获取数据库连接,由于每个SqlSession绑定了一个独有的Executor、Executor又绑定了一个独有的Transaction,因此每个SqlSession对象都拥有独有的一个数据库连接,那么在一个SqlSession中进行数据库操作,都由该连接所控制。
    • 执行SQL时,SqlSession委托Executor对象进行SQL预编译、SQL参数绑定、结果集处理等操作。而这些操作最终都委托给了StatementHandler统一控制。其中预编译SQL、绑定SQL参数交由ParameterHandler和BoundSql(由SqlSource构造而来)完成;结果集处理交由ResultSetHandler进行处理

设计要点

我们都知道在Java中执行SQL需要经过:预编译SQL、绑定SQL参数、处理结果集这三步。在mybatis中同样体现了这一流程,而且将每一个流程委托给不同的类进行处理,之后通过一些门面类封装暴露了比较简洁的接口连接起来整个流程。这样每个子流程之间无需理会其内部如何工作,只需要关心接口如何使用。

  • 解析SQL:MapperAnnotationBuilder、XMLLanguageDriver得到了完成SQL解析的SqlSource。SqlSource内部保存了对应的SQL语句
  • SQL预编译、参数绑定、结果集处理:StatementHandler、BoundSql、ParameterHandler、ResultSetHandler完成了执行SQL的过程
  • 事务管理:由Executor中独有的Transaction提供获取数据库连接的接口,从而保证了多个SqlSession操作,共享一个连接来保证事务

springboot集成

在当前的开发来说,我们不太可能单独使用mybatis。一般都会在基于spring框架开发的项目中集成mybatis,使这一类项目可以方便的操作数据库来达成业务开发要求。

自动配置类MybatisAutoConfiguration

由springboot自动化配置机制来加载该配置类

  • 通过SqlSessionFactoryBean构造SqlSessionFactory
  • 通过AutoConfiguredMapperScannerRegistrar扫描由注解@Mapper标记的Mapper接口,并注册到BeanFactory中。之后将Mapper接口替换为MapperFactoryBean,并增加构造器参数为该Mapper接口的全限定类名
  • 当IOC容器启动时,如果有其他bean依赖了该Mapper接口时,将根据扫描到的BeanDefinition信息实例化MapperFactoryBean,并且传入该Mapper接口
  • 实例化完了MapperFactoryBean后,会调用SqlSessionDaoSupport#setSqlSessionFactory、SqlSessionDaoSupport#setSqlSessionTemplate,之后调用DaoSupport#afterPropertiesSet来注册Mapper接口到Configuration中
  • 最后调用org.mybatis.spring.mapper.MapperFactoryBean#getObject方法获取Mapper接口的代理对象。初始化MapperFactoryBean的过程其实就是Spring的IOC容器实例化bean的流程。先执行构造器实例化、之后设置各成员变量、调用InitializingBean回调方法(afterPropertiesSet),之后由于MapperFactoryBean是一个FactoryBean,那么最终返回的bean是调用方法MapperFactoryBean#getObject的返回值,即经过mybatis生成的Mapper代理对象
    mybatis是怎样工作的

由spring来托管mybatis中的事务管理

  • 构造SqlSessionFactory时传入了SpringManagedTransactionFactory
  • SqlSessionTemplate构造了一个SqlSession的代理对象,通过SqlSessionTemplate进行数据库操作将被委托给这个代理对象,该代理对象通过SqlSessionInterceptor拦截方法执行
  • SqlSessionTemplate委托SqlSessionUtils获取SqlSessionHolder,在SqlSessionUtils内部通过TransactionSynchronizationManager#getResource获取,当第一次获取时通过SqlSessionFactory构造出SqlSession,并绑定到TransactionSynchronizationManager中。当同一个线程再次获取SqlSession时,将获得同一个SqlSession实例。
  • 当执行SQL时,Executor会触发获取连接的逻辑,委托SpringManagedTransaction获取连接,在SpringManagedTransaction内部会通过 DataSourceUtils.getConnection(dataSource) 获取连接
    mybatis是怎样工作的
    可以看出,通过SqlSessionTemplate,SqlSessionInterceptor拦截对SqlSession的方法调用。SqlSessionInterceptor保证了同一个线程获取到的SqlSession对象唯一(因为绑定到线程本地变量),SpringManagedTransaction保证了同一个线程通过Executor获取的数据库连接对象也是唯一的(同样是绑定到线程本地变量),而数据库连接对象是Spring中的TransactionInterceptor拦截service方法时,绑定到线程本地的。通过以上过程,mybatis把事务管理的工作,委托给了Spring事务管理器。
原文  https://juejin.im/post/5d944038f265da5b7244ae92
正文到此结束
Loading...