天天看点

MyBatis核心组件介绍二-组件一

作者:小雨在进步

前面介绍了如何使用MyBatis操作数据库,我们接触到MyBatis比较核心的一个组件——SqlSession。SqlSession是MyBatis提供的面向用户的操作数据库API。那么MyBatis底层是如何工作的呢?为了解开MyBatis的神秘面纱,我们需要了解一下MyBatis的其他几个比较核心的组件及这些组件的作用。

MyBatis核心组件介绍二-组件一

这些组件的作用如下:

Configuration:用于描述MyBatis的主配置信息,其他组件需要获取配置信息时,直接通过Configuration对象获取。除此之外,MyBatis在应用启动时,将Mapper配置信息、类型别名、TypeHandler等注册到Configuration组件中,其他组件需要这些信息时,也可以从Configuration对象中获取。

MappedStatement:MappedStatement用于描述Mapper中的SQL配置信息,是对Mapper XML配置文件中<select|update|delete|insert>等标签或者@Select/@Update等注解配置信息的封装。

SqlSession:SqlSession是MyBatis提供的面向用户的API,表示和数据库交互时的会话对象,用于完成数据库的增删改查功能。SqlSession是Executor组件的外观,目的是对外提供易于理解和使用的数据库操作接口。

Executor:Executor是MyBatis的SQL执行器,MyBatis中对数据库所有的增删改查操作都是由Executor组件完成的。

StatementHandler:StatementHandler封装了对JDBC Statement对象的操作,比如为Statement对象设置参数,调用Statement接口提供的方法与数据库交互,等等。

ParameterHandler:当MyBatis框架使用的Statement类型为CallableStatement和PreparedStatement时,ParameterHandler用于为Statement对象参数占位符设置值。

ResultSetHandler:ResultSetHandler封装了对JDBC中的ResultSet对象操作,当执行SQL类型为SELECT语句时,ResultSetHandler用于将查询结果转换成Java对象。

TypeHandler:TypeHandler是MyBatis中的类型处理器,用于处理Java类型与JDBC类型之间的映射。它的作用主要体现在能够根据Java类型调用PreparedStatement或CallableStatement对象对应的setXXX()方法为Statement对象设置值,而且能够根据Java类型调用ResultSet对象对应的getXXX()获取SQL执行结果。

了解了MyBatis的核心组件后,我们再来了解一下使用MyBatis操作数据库的过程。前面介绍MyBatis框架的基本使用时,我们使用到了SqlSession组件,它是用户层面的API。实际上SqlSession是Executor组件的外观,目的是为用户提供更友好的数据库操作接口,这是设计模式中外观模式的典型应用。真正执行SQL操作的是Executor组件,Executor可以理解为SQL执行器,它会使用StatementHandler组件对JDBC的Statement对象进行操作。

当Statement类型为CallableStatement和PreparedStatement时,会通过ParameterHandler组件为参数占位符赋值。ParameterHandler组件中会根据Java类型找到对应的TypeHandler对象,TypeHandler中会通过Statement对象提供的setXXX()方法(例如setString()方法)为Statement对象中的参数占位符设置值。StatementHandler组件使用JDBC中的Statement对象与数据库完成交互后,当SQL语句类型为SELECT时,MyBatis通过ResultSetHandler组件从Statement对象中获取ResultSet对象,然后将ResultSet对象转换为Java对象。

1.Configuration

MyBatis框架的配置信息有两种,一种是配置MyBatis框架属性的主配置文件;另一种是配置执行SQL语句的Mapper配置文件。Configuration的作用是描述MyBatis主配置文件的信息。Configuration类中定义了一系列的属性用来控制MyBatis运行时的行为,这些属性代码如下:

public class Configuration {
//环境变量
  protected Environment environment;

  //允许在嵌套语句中使用分页(RowBounds),false表示允许
  protected boolean safeRowBoundsEnabled;
  //允许在嵌套语句中使用分页,false表示允许(ResultHandler)
  protected boolean safeResultHandlerEnabled = true;
  // 是否开启自动驼峰命名规则的映射
  protected boolean mapUnderscoreToCamelCase;
  // 当开启时,任何方法的调用都会加载该对象的所有属性,否则,每个属性会按需加载
  // 参考lazyLoadTriggerMethods
  protected boolean aggressiveLazyLoading;
  // 是否允许单一语句返回多结果集(需要JDBC驱动支持)
  protected boolean multipleResultSetsEnabled = true;
  //允许JDBC支持自动生成主键,需要驱动兼容。如果为true, 强制使用自动生成的主键,
  //尽管理患局不能兼容,但仍可正常工作。
  protected boolean useGeneratedKeys;
  // 使用列标签代替列名,不同的驱动在这方面会有不同的表现,具体可参考JDBC驱动相关文档或通过测
  //试这两种不同的模式来观察JDBC聚动的结果
  protected boolean useColumnLabel = true;
  //是否开片Napper缓存,即二级缓存 ,true表示开启
  protected boolean cacheEnabled = true;
  //指定当结果集中的值为null的时候是否调用映射对象的Setter方法
  //对于Map.keySet()依赖或null的值初始化的时候是没有用处的,int,boolean之类不能设置成null
  protected boolean callSettersOnNulls;
  /*
  * 允许使用方法签名中的名称作为语句参数名称。
  * 为了使用该特性, 你的工程必须采用Java8编译,并 且加上-parameters选项
  * */
  protected boolean useActualParamName = true;
  //当返回行所有列为空的时候,mybatis会默认返回null
  //设置这个值的时候,mybatis会返回一个空实列。也适用于嵌套的结果集(collection,association)
  protected boolean returnInstanceForEmptyRow;

  // 指定mybatis增加到日志名称的前缀
  protected String logPrefix;
  // 指定mybaits所用日志的具体实现,未指定时候自动查找
  // 如:SLF4J LOG4J LOG4J2 FOK LOGGING COMMONS LOCGING STOOUT LOGGING NO LOGGING
  protected Class<? extends Log> logImpl;
  //指定VFS的实现,VFS的实现类的全限定名,以逗号分割
  protected Class<? extends VFS> vfsImpl;
  // Mythatis利用本地缓存机制防止循环引用和加速复查询。
  // 默认值为SESSION,这种情况下会缓存一个会话中的所有查询。
  // 设置值为STATEMENT,本地会话仅用在语句执行上,对相同 SqlSemssion的不同调用将不会共享数据
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  //当没有为参数指定JDBC类型时候,指定JDBC类型的值为null
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
  //指定哪个对象的方法会触发一次延迟加载
  protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[]{"equals", "clone", "hashCode", "toString"}));
  //设置超时的时间,它决定驱动等待数据库相应的秒数
  protected Integer defaultStatementTimeout;
  //默认的FetchSize,用于设置Statement对象的FetchSize大小,用于限制从数据库获取的最大行数
  // 为null由具体的JDBC决定
  protected Integer defaultFetchSize;
  /*
  * 配置状认的 Executor,
  * SIMPLE:普通的Executor
  * REUSE:执行器会复用Statement对象
  * BATCH:批量执行所有的更新语句
  *
  * */
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  /*
  * 指定mybatis如何自动识别列到java实体属性
  * NONE:取消自动映射,
  * PARTIAL:只会自动映射没有定义嵌套结果集映射的结果集
  * FULL:自动映射任意复杂的结果集(无论是否嵌套)
  *  */
  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
  /*
  * 指定发现自动映射目标未知列《或未知的属性》的行为
  * NONE:不做任何反应
  * WARNING:输出提醒日志
  * FAILING :映射失败抛出SqlSessionException
  *
  * */
  protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;

  protected Properties variables = new Properties();
  protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
  protected ObjectFactory objectFactory = new DefaultObjectFactory();
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();

  // 延迟加载的全局开关:当开启时, 所有关联对象都会延迟加载,特定关联关系中可通过设置fetchType
  //属性来覆盖该项的开关状态
  protected boolean lazyLoadingEnabled = false;
  // 指定mybatis创建具有延迟加载能力对象所用的代理工具,CGLIB JAVASSIST
  protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL

  protected String databaseId;

  /**
   * Configuration factory class.
   * Used to create Configuration for loading deserialized unread properties.
   *
   * @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300 (google code)</a>
   */
  protected Class<?> configurationFactory;
}           

这些属性的值可以在MyBatis主配置文件中通过<setting>标签指定,例如:

<settings>
		<setting name="useGeneratedKeys" value="true"/>
		<setting name="mapUnderscoreToCamelCase" value="true"/>
		<setting name="logImpl" value="LOG4J"/>
	</settings>           

Configuration除了控制MyBatis的行为外,还作为容器存放TypeHandler(类型处理器)、TypeAlias(类型别名)、Mapper接口及Mapper SQL配置信息。这些信息在MyBatis框架启动时注册到Configuration组件中。Configuration类中通过下面的属性保存TypeHandler、TypeAlias等信息:

// 用于注册Mapper接口信息,建立Mapper接口的Class对象和MapperProxyFactory对象之间的关系,
  // 其中MapperProxyFactory对象用于创建Mapper动态代理对象
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  // 用于注册MyBatis插件信息,MyBatis插件实际上就是一个拦截器。
  protected final InterceptorChain interceptorChain = new InterceptorChain();
//  用于注册所有的TypeHandler,并建立Jdbc类型、JDBC类型与TypeHandler之间的对应关系。
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
  // 用于注册所有的类型别名
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  // 用于注册LanguageDriver,LanguageDriver用于解析SQL配置,将配置信息转换为SqlSource对象。
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
  /*
  * MappedStatement对象描述<insert|select|update|delete>等标签或者通过
  * @Select、@Delete、@Update、@Insert等注解配置的SQL信息。
  * MyBatis将所有的MappedStatement对象注册到该属性中,其中Key为Mapper的Id,Value为MappedStatement对象。
  *
  *
  * */
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
  // 用于注册Mapper中配置的所有缓存信息,其中Key为Cache的Id,也就是Mapper的命名空间,Value为Cache对象
  protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
  // 用于注册Mapper配置文件中通过<resultMap>标签配置的ResultMap信息,ResultMap用于建立Java实体属性与数据库字段之间的映射关系,
  // 其中Key为ResultMap的Id,该Id是由Mapper命名空间和<resultMap>标签的id属性组成的,Value为解析<resultMap>标签后得到的ResultMap对象。
  protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
  // 用于注册Mapper中通过<parameterMap>标签注册的参数映射信息。Key为ParameterMap的Id,由Mapper命名空间和<parameterMap>标签的id属性构成,
  // Value为解析<parameterMap>标签后得到的ParameterMap对象。
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");

 // 用于注册KeyGenerator,KeyGenerator是MyBatis的主键生成器,MyBatis中提供了3种KeyGenerator,即Jdbc3KeyGenerator(数据库自增主键)、NoKeyGenerator(无自增主键)、
  // SelectKeyGenerator(通过select语句查询自增主键,例如oracle的sequence)。
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");

  // 用于注册所有Mapper XML配置文件路径。
  // 内容是namespace:+type
  protected final Set<String> loadedResources = new HashSet<String>();
  // 用于注册Mapper中通过<sql>标签配置的SQL片段,Key为SQL片段的Id,Value为MyBatis封装的表示XML节点的XNode对象。
  protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");

  // 存放解析异常的XMLStatementBuilder对象
  protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
  // 存放解析异常的CacheRefResolver对象
  protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
  // 存放解析异常的ResultMapResolver对象
  protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
  // 存放解析异常的MethodResolver对象
  protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();
...

           

MyBatis框架启动时,会对所有的配置信息进行解析,然后将解析后的内容注册到Configuration对象的这些属性中。

除此之外,Configuration组件还作为Executor、StatementHandler、ResultSetHandler、ParameterHandler组件的工厂类,用于创建这些组件的实例。Configuration类中提供了这些组件的工厂方法,这些工厂方法签名如下:

//ParameterHandler组件工厂方法
public ParameterHandler newParameterHandler(MappedStatement mappedStatementObject parameterObject,  BoundSql boundSql);
// ResultSetHandler 组件工厂方法
public ResultSetHandler newResultSetHandler(Executor executor,MappedStatement mappedStatement,RowBounds rowBounds,ParameterHandlerparameterHandler,ResultHandler resultHandler,BoundSql boundSql);
// StatementHandler 组件工厂方法
public StatementHandler newStatementHandler(Executor executor,MappedStatement mappedStatement,Object parameterObject,RowBounds rowBoundsResultHandler resultHandler, BoundSql boundSql);
// Executor 组件工厂方法
public Executor newExecutor(Transtransaction,ExecutorTypeexecutorType);           

这些工厂方法会根据MyBatis不同的配置创建对应的实现类。例如,Executor组件有4种不同的实现,分别为BatchExecutor、ReuseExecutor、SimpleExecutor、CachingExecutor,当defaultExecutorType的参数值为REUSE时,newExecutor()方法返回的是ReuseExecutor实例,当参数值为SIMPLE时,返回的是SimpleExecutor实例,这是典型的工厂方法模式的应用。

MyBatis采用工厂模式创建Executor、StatementHandler、ResultSetHandler、ParameterHandler的另一个目的是实现插件拦截逻辑,这一点在后面会详细介绍。

2.Executor

SqlSession是MyBatis提供的操作数据库的API,但是真正执行SQL的是Executor组件。Executor接口中定义了对数据库的增删改查方法,其中query()和queryCursor()方法用于执行查询操作,update()方法用于执行插入、删除、修改操作。Executor接口有几种不同的实现类,如图所示

MyBatis核心组件介绍二-组件一

MyBatis提供了3种不同的Executor,分别为SimpleExecutor、ResueExecutor、BatchExecutor,这些Executor都继承至BaseExecutor,BaseExecutor中定义的方法的执行流程及通用的处理逻辑,具体的方法由子类来实现,是典型的模板方法模式的应用。SimpleExecutor是基础的Executor,能够完成基本的增删改查操作,ResueExecutor对JDBC中的Statement对象做了缓存,当执行相同的SQL语句时,直接从缓存中取出Statement对象进行复用,避免了频繁创建和销毁Statement对象,从而提升系统性能,这是享元思想的应用。

BatchExecutor则会对调用同一个Mapper执行的update、insert和delete操作,调用Statement对象的批量操作功能。另外,我们知道MyBatis支持一级缓存和二级缓存,当MyBatis开启了二级缓存功能时,会使用CachingExecutor对SimpleExecutor、ResueExecutor、BatchExecutor进行装饰,为查询操作增加二级缓存功能,这是装饰器模式的应用。

接下来以一个案例介绍如何直接使用Executor组件与数据库交互,代码如下:

public void testExecutor() throws IOException, SQLException {
        // 获取配置文件输入流
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        // 通过SqlSessionFactoryBuilder的build()方法创建SqlSessionFactory实例
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 调用openSession()方法创建SqlSession实例
        SqlSession sqlSession = sqlSessionFactory.openSession();
        Configuration configuration = sqlSession.getConfiguration();
        // 从Configuration对象中获取描述SQL配置的MappedStatement对象
        MappedStatement listAllUserStmt = configuration.getMappedStatement(
                "com.blog4java.mybatis.example.mapper.UserMapper.listAllUser");
        //创建ReuseExecutor实例
        Executor reuseExecutor = configuration.newExecutor(
                new JdbcTransaction(sqlSession.getConnection()),
                ExecutorType.REUSE
        );
        // 调用query()方法执行查询操作
        List<UserEntity> userList =  reuseExecutor.query(listAllUserStmt,
                null,
                RowBounds.DEFAULT,
                Executor.NO_RESULT_HANDLER);
        System.out.println(JSON.toJSON(userList));
    }           

如上面的代码所示,Executor与数据库交互需要Mapper配置信息,MyBatis通过MappedStatement对象描述Mapper的配置信息,因此Executor需要一个MappedStatement对象作为参数。MyBatis在应用启动时,会解析所有的Mapper配置信息,将Mapper配置解析成MappedStatement对象注册到Configuration组件中,我们可以调用Configuration对象的getMappedStatement()方法获取对应的MappedStatement对象,获取MappedStatement对象后,根据SQL类型调用Executor对象的query()或者update()方法即可。

3.MappedStatement

MyBatis通过MappedStatement描述<select|update|insert|delete>或者@Select、@Update等注解配置的SQL信息。在介绍MappedStatement组件之前,我们先来了解一下MyBatis中SQL Mapper的配置。不同类型的SQL语句需要使用对应的XML标签进行配置。这些标签提供了很多属性,用来控制每条SQL语句的执行行为。下面是<select>标签中的所有属性:

<select
id="getUserById"
parameterType="intu
parameterMap="deprecated"
resultType="hashmap"
resultMap="userResultMap
flushCache="falser
useCache="true"
timeout="10000
fetchSize="256"
statementType="PREPARED
resultSetType="FORWARD ONLY">           

这些属性的含义如下。

id:在命名空间中唯一的标识符,可以被用来引用这条配置信息。

parameterType:用于指定这条语句的参数类的完全限定名或别名。这个属性是可选的,MyBatis能够根据Mapper接口方法中的参数类型推断出传入语句的类型。

parameterMap:引用通过<parameterMap>标签定义的参数映射,该属性已经废弃。

resultType:从这条语句中返回的期望类型的类的完全限定名或别名。注意,如果返回结果是集合类型,则resultType属性应该指定集合中可以包含的类型,而不是集合本身。

resultMap:用于引用通过<resultMap>标签配置的实体属性与数据库字段之间建立的结果集的映射(注意:resultMap和resultType属性不能同时使用)。

flushCache:用于控制是否刷新缓存。如果将其设置为 true,则任何时候只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值为false。

useCache:是否使用二级缓存。如果将其设置为 true,则会导致本条语句的结果被缓存在MyBatis的二级缓存中,对应<select>标签,该属性的默认值为true。

timeout:驱动程序等待数据库返回请求结果的秒数,超时将会抛出异常。

fetchSize:用于设置JDBC中 Statement对象的fetchSize属性,该属性用于指定SQL执行后返回的最大行数。

statementType:参数可选值为STATEMENT、PREPARED或CALLABLE,这会让MyBatis分别使用Statement、PreparedStatement或CallableStatement与数据库交互,默认值为PREPARED。

databaseId:如果配置了 databaseIdProvider,MyBatis会加载所有不带databaseId或匹配当前 databaseId 的语句。

resultOrdered:这个设置仅针对嵌套结果 select语句适用,如果为 true,就是假定嵌套结果包含在一起或分组在一起,这样的话,当返回一个主结果行的时候,就不会发生对前面结果集引用的情况。这就使得在获取嵌套结果集的时候不至于导致内存不够用,默认值为false。

resultSets:这个设置仅对多结果集的情况适用,它将列出语句执行后返回的结果集并每个结果集给一个名称,名称使用逗号分隔。

lang:该属性用于指定LanguageDriver实现,MyBatis中的LanguageDriver用于解析<select|update|insert|delete>标签中的SQL语句,生成SqlSource对象。

上面的属性中,resultMap、resultType、resultSetType、fetchSize、useCache、resultSets、resultOrdered是<select>标签特有的,其他属性是<update|insert|delete>标签共有的。另外,<insert>和<update>标签有几个特有的属性,下面是<insert>标签的所有属性:

<insert
id="insertUser"
parameterType="com.blog4java.User"
flushCache="true"
statementType="PREPARED"
keyProperty="id'
keyColumn="id"
useGeneratedKeys="true"
timeout="20">           

这几个属性的作用如下:

useGeneratedKeys:该属性仅对<update>和<insert>标签有用,属性值为true时,MyBatis使用JDBC Statement对象的getGeneratedKeys()方法来取出由数据库内部生成的键值,例如MySQL自增主键。

keyProperty:该属性仅对<update>和<insert>标签有用,用于将数据库自增主键或者<insert>标签中<selectKey>标签返回的值填充到实体的属性中,如果有多个属性,则使用逗号分隔。

keyColumn:该属性仅对<update>和<insert>标签有用,通过生成的键值设置表中的列名,这个设置仅在某些数据库(例如PostgreSQL)中是必需的,当主键列不是表中的第一列时需要设置,如果有多个字段,则使用逗号分隔。

MappedStatement类通过下面这些属性保存<select|update|insert|delete>标签的属性信息:

public final class MappedStatement{
    ...
	private String id;
	private Integer fetchSize;
	private Integer timeout;
	private StatementType statementType;
    private ResultSetType resultSetType;
    private ParameterMap parameterMap;
    private List<ResultMap> resultMaps;
    private boolean flushCacheRequired;
	private boolean useCache;
	private boolean resultOrdered;
    private SqlCommandType sqlCommandType;
    private LanguageDriver lang;
    private String[] keyProperties;
    private String[] keyColumns;
	private String databaseId;
	private String[] resultSets;
    ...
}               

除此之外,MappedStatement类还有一些其他的属性,这些属性及其含义如下:

private Cache cache; // 二级缓存实例
private SqlSource sqlSource; // 解析 SQL 语生成的 SqlSource 实例
private String resource; // Mapper 资源路径
private Configuration configuration; // Confiquration 对象的引用
private KeyGenerator ;//主键生成策略
private boolean hasNestedResultMaps; // 是否有套的 ResultMap
privateprivate Log statementLog; // 输出日志           

cache:二级缓存实例,根据Mapper中的<cache>标签配置信息创建对应的Cache实现。

sqlSource:解析<select|update|insert|delete>,将SQL语句配置信息解析为SqlSource对象。

resource:Mapper配置文件路径。

configuration:Configuration对象的引用,方便获取MyBatis配置信息及TypeHandler、TypeAlias等信息。

keyGenerator:主键生成策略,默认为Jdbc3KeyGenerator,即数据库自增主键。当配置了<selectKey>时,使用SelectKeyGenerator生成主键。

hasNestedResultMaps:<select>标签中通过resultMap属性指定ResultMap是不是嵌套的ResultMap。

statementLog:用于输出日志。

继续阅读