一只会飞的旺旺
文章93
标签72
分类8
MyBatis学习笔记二

MyBatis学习笔记二

小卖铺上新啦!ChatGPT账号大甩卖! 一键直达
JDBC问题总结
* 数据库连接创建,释放频繁,浪费系统资源
* SQL语句硬编码
* SQL参数设置存在硬编码
* 结果解析存在硬编码

框架设计思路

使⽤端:

提供核⼼配置⽂件:

  • sqlMapConfig.xml : 存放数据源信息,引⼊mapper.xml
<!--提取配置到文件中,解决硬编码问题-->
<configuration>
    <!--数据库连接信息-->
    <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
    <property name="jdbcUrl" value="jdbc:mysql:///mybatis"></property>
    <property name="user" value="root"></property>
    <property name="password" value="root"></property>
    <!--引⼊sql配置信息-->
    <mapper resource="mapper.xml"></mapper>
</configuration>
  • Mapper.xml : sql语句的配置⽂件信息

<mapper namespace="com.ww.mapper.IUserDao">

    <!--sql的唯一标识: namespace.id-->
    <select id="findAll" resultType="com.ww.pojo.User">
        select * form user
    </select>

    <select id="findByCondition" resultType="com.ww.pojo.User" paramterType="com.ww.pojo.User">
        select * from user where id = #{id} and username = #{username}
    </select>

</mapper>

框架端:

1.读取配置⽂件,封装到实体配置类中

  • Configuration: 数据库连接配置
/*定义基于sqlMapConfig.xml的配置类*/
@Data
public class Configuration {

    /*数据库连接配置*/
    private DataSource dataSource;

    /**
     * 封装mapper.xml中的SQL语句
     * key: statementId namespace.sqlId
     */
    private Map<String, MappedStatement> mapppedStatementMap = new HashMap<>();

}
  • MappedStatement: SQL语句配置
/**
 * 定义一个SQL该有的属性, 唯一标识,出参入参,以及SQL语句
 */
@Data
public class MappedStatement {

    // id标识
    private String id;

    //返回值类型
    private String resultType;

    //参数类型
    private String paramterType;

    //sql语句
    private String sql;

}

2.解析配置⽂件

  • 加载配置文件

InputStream resource = Resources.class.getClassLoader().getResourceAsStream(path);
  • 使用dom4j解析配置封装到Configuration
    public Configuration parseConfig(InputStream inputStream)throws Exception{
        Document document=new SAXReader().read(inputStream);
        // <configuration>
        Element rootElement=document.getRootElement();
        List<Element> list=rootElement.selectNodes("//property");
        // 解析配置文件
        Properties properties=new Properties();
        for(Element element:list){
        String name=element.attributeValue("name");
        String value=element.attributeValue("value");
        properties.setProperty(name,value);
        }
        // 获取连接池
        ComboPooledDataSource comboPooledDataSource=new ComboPooledDataSource();
        comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
        comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
        comboPooledDataSource.setUser(properties.getProperty("username"));
        comboPooledDataSource.setPassword(properties.getProperty("password"));

        configuration.setDataSource(comboPooledDataSource);

        // 解析mapper文件
        List<Element> list1=rootElement.selectNodes("//mapper");
        for(Element element:list1){
        String mapperPath=element.attributeValue("resource");
        InputStream resource=Resources.getResourceAsStream(mapperPath);
        XMLMapperBuilder xmlMapperBuilder=new XMLMapperBuilder(configuration);
        xmlMapperBuilder.parse(resource);
        }

        return configuration;

3.通过SqlSessionFactoryBuilder的build方法,创建SqlSessionFactory

/* 定义工厂,创建会话使用 */
public interface SqlSessionFactory {

    public SqlSession openSession();
}

4.通过SqlSessionFactory的openSession方法,创建SqlSession

5.在sqlSession中封装增删改查方法

  • 定义方法
public interface SqlSession {

    //查询所有
    public <E> List<E> selectList(String statementId, Object... params) throws Exception;

    //根据条件查询单个
    public <T> T selectOne(String statementId, Object... params) throws Exception;

    //为Dao层生成代理实现类
    public <T> T getMapper(Class<?> mapperClass);
}

  • 动态代理实现
    public<T> T getMapper(Class<?> mapperClass){
        //使用JDK动态代理,来为dao接口生成代理对象并返回
        Object proxyInstance=Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(),new Class[]{mapperClass},new InvocationHandler(){
@Override
public Object invoke(Object proxy,Method method,Object[]args)throws Throwable{
        String methodName=method.getName();
        String className=method.getDeclaringClass().getName();
        String statementId=className+"."+methodName;
        //获取方法的返回值类型
        Type genericReturnType=method.getGenericReturnType();
        if(genericReturnType instanceof ParameterizedType){
        List<Object> objects=selectList(statementId,args);
        return objects;
        }
        return selectOne(statementId,args);
        }
        });
        return(T)proxyInstance;
        }

6.定义Executor实现JDBC原生操作,以及出入参映射操作

public class SimpleExecutor implements Executor {


    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        //1.注册驱动,获取链接
        Connection connection = configuration.getDataSource().getConnection();
        //2.获取sql语句
        String sql = mappedStatement.getSql();
        //3.占位符替换
        BoundSql boundSql = getBound(sql);
        //4.获取预处理对象
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
        //5.设置参数
        //获取参数全路径
        String paramterType = mappedStatement.getParamterType();
        Class<?> paramterTypeClass = getClassType(paramterType);
        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent();
            //反射
            Field declaredField = paramterTypeClass.getDeclaredField(content);
            //暴力访问
            declaredField.setAccessible(true);
            Object o = declaredField.get(params[0]);
            preparedStatement.setObject(i + 1, o);
        }

        //6.执行sql
        ResultSet resultSet = preparedStatement.executeQuery();
        String resultType = mappedStatement.getResultType();
        Class<?> resultTypeClass = getClassType(resultType);

        List<Object> list = new ArrayList<>();
        //7.返回结果集
        while (resultSet.next()) {
            Object o = resultTypeClass.newInstance();
            //元数据
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                //字段名
                String columnName = metaData.getColumnName(i);
                //字段的值
                Object value = resultSet.getObject(columnName);
                //使用反射,根据数据库表和实体的对应关系,进行映射
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(o, value);
            }
            list.add(o);
        }
        return (List<E>) list;
    }

    private Class<?> getClassType(String paramterType) throws ClassNotFoundException {
        if (null != paramterType) {
            Class<?> aClass = Class.forName(paramterType);
            return aClass;
        }
        return null;
    }

    /**
     * 将#{}使用?替换
     * 解析出#{}里面的参数值
     *
     * @param sql
     * @return
     */
    private BoundSql getBound(String sql) {
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        //解析SQL
        String parseSql = genericTokenParser.parse(sql);
        //#{}解析出来的参数
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
        BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
        return boundSql;
    }
}

具体实现

https://github.com/wangwangit/mybatis-study.git

重点剖析

架构设计

image-20220928201325851

主要组件

组件 描述
SqlSession 表示与数据库交互的会话,完成必要数据库增删改查功能
Executor MyBatis的执行器,负责SQL语句的生成和查询缓存的维护
StatementHandler 封装了JDBC Statement操作,负责对JDBC Statement操作,如设置参数
ParameterHandler 参数处理
ResultSetHandler 结果集处理
TypeHandler java类型与jdbc类型的转换
MappedStatement 维护mapper.xml中 一条select,update,insert,delete节点的封装
SqlSource 根据用户传递的parameterObject动态的生成SQL并封装到BoundSql中
BoundSql 表示动态生成的SQL语句,以及相应的参数信息

image-20220928201336243

一.缓存使用

1.二级缓存开启三步走

  • 开启全局二级缓存配置

<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>
  • 在需要使用二级缓存的mapper文件中添加配置标签

<cache></cache>
  • 在CRUD标签上配置userCache=true

<select id="findById" resultType="com.lagou.pojo.User" useCache="true">
    select * from user where id = #{id}
</select>

2.config中的配置解析,将配置设置到configuration中

  private void parseConfiguration(XNode root){
        try{
        // issue #117 read properties first
        propertiesElement(root.evalNode("properties"));
        Properties settings=settingsAsProperties(root.evalNode("settings"));
        loadCustomVfs(settings);
        loadCustomLogImpl(settings);
        typeAliasesElement(root.evalNode("typeAliases"));
        pluginElement(root.evalNode("plugins"));
        objectFactoryElement(root.evalNode("objectFactory"));
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        reflectorFactoryElement(root.evalNode("reflectorFactory"));
        // 缓存标签解析
        settingsElement(settings);
        // read it after objectFactory and objectWrapperFactory issue #631
        environmentsElement(root.evalNode("environments"));
        databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        typeHandlerElement(root.evalNode("typeHandlers"));
        mapperElement(root.evalNode("mappers"));
        }catch(Exception e){
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: "+e,e);
        }
        }

3.mapper中的cache标签解析,以nameSpace为key存储cache到configuration中

  private void cacheElement(XNode context){
        if(context!=null){
        String type=context.getStringAttribute("type","PERPETUAL");
        Class<?extends Cache> typeClass=typeAliasRegistry.resolveAlias(type);
        String eviction=context.getStringAttribute("eviction","LRU");
        Class<?extends Cache> evictionClass=typeAliasRegistry.resolveAlias(eviction);
        Long flushInterval=context.getLongAttribute("flushInterval");
        Integer size=context.getIntAttribute("size");
        boolean readWrite=!context.getBooleanAttribute("readOnly",false);
        boolean blocking=context.getBooleanAttribute("blocking",false);
        Properties props=context.getChildrenAsProperties();
        builderAssistant.useNewCache(typeClass,evictionClass,flushInterval,size,readWrite,blocking,props);
        }
        }


public Cache useNewCache(Class<?extends Cache> typeClass,
        Class<?extends Cache> evictionClass,
        Long flushInterval,
        Integer size,
        boolean readWrite,
        boolean blocking,
        Properties props){
        Cache cache=new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass,PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass,LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
        //只会解析赋值一次  
        configuration.addCache(cache);
        //每个mapper只有一个cache  
        currentCache=cache;
        return cache;
        }

4.每个sql语句中的userCache配置,在buildStatementFromContext时设置给每个MappedStatement对象

  public MappedStatement addMappedStatement(
        String id,
        SqlSource sqlSource,
        StatementType statementType,
        SqlCommandType sqlCommandType,
        Integer fetchSize,
        Integer timeout,
        String parameterMap,
        Class<?> parameterType,
        String resultMap,
        Class<?> resultType,
        ResultSetType resultSetType,
        boolean flushCache,
        boolean useCache,
        boolean resultOrdered,
        KeyGenerator keyGenerator,
        String keyProperty,
        String keyColumn,
        String databaseId,
        LanguageDriver lang,
        String resultSets){

        if(unresolvedCacheRef){
        throw new IncompleteElementException("Cache-ref not yet resolved");
        }

        id=applyCurrentNamespace(id,false);
        boolean isSelect=sqlCommandType==SqlCommandType.SELECT;

        MappedStatement.Builder statementBuilder=new MappedStatement.Builder(configuration,id,sqlSource,sqlCommandType)
        .resource(resource)
        .fetchSize(fetchSize)
        .timeout(timeout)
        .statementType(statementType)
        .keyGenerator(keyGenerator)
        .keyProperty(keyProperty)
        .keyColumn(keyColumn)
        .databaseId(databaseId)
        .lang(lang)
        .resultOrdered(resultOrdered)
        .resultSets(resultSets)
        .resultMaps(getStatementResultMaps(resultMap,resultType,id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache,!isSelect))
        .useCache(valueOrDefault(useCache,isSelect))
        //添加前面创建的cache对象
        .cache(currentCache);

        ParameterMap statementParameterMap=getStatementParameterMap(parameterMap,parameterType,id);
        if(statementParameterMap!=null){
        statementBuilder.parameterMap(statementParameterMap);
        }

        MappedStatement statement=statementBuilder.build();
        configuration.addMappedStatement(statement);
        return statement;
        }

5.执行查询语句时,逻辑如下


public<E> List<E> query(MappedStatement ms,Object parameterObject,RowBounds rowBounds,ResultHandler resultHandler)throws SQLException{
        BoundSql boundSql=ms.getBoundSql(parameterObject);
        //创建缓存key
        CacheKey key=this.createCacheKey(ms,parameterObject,rowBounds,boundSql);
        return this.query(ms,parameterObject,rowBounds,resultHandler,key,boundSql);
        }


public<E> List<E> query(MappedStatement ms,Object parameterObject,RowBounds rowBounds,ResultHandler resultHandler,CacheKey key,BoundSql boundSql)throws SQLException{
        //判断是否配置了开启缓存
        Cache cache=ms.getCache();
        if(cache!=null){
        //如果需要刷新缓存的化,flushCache="true"
        this.flushCacheIfRequired(ms);
        if(ms.isUseCache()&&resultHandler==null){
        this.ensureNoOutParams(ms,boundSql);
        //访问二级缓存
        List<E> list=(List)this.tcm.getObject(cache,key);
        if(list==null){
        // 缓存未命中,访问一级缓存,一级缓存没有,再查询数据库
        list=this.delegate.query(ms,parameterObject,rowBounds,resultHandler,key,boundSql);
        // 缓存查询结果
        this.tcm.putObject(cache,key,list);
        }

        return list;
        }
        }

        return this.delegate.query(ms,parameterObject,rowBounds,resultHandler,key,boundSql);
        }

6.二级缓存安全问题,以及为什么需要提交才能更新缓存值

// 事务缓存管理器
public class TransactionalCacheManager {

    // Cache与TransactionCache的映射关系表
    private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();

    public void clear(Cache cache) {

        getTransactionalCache(cache).clear();
    }

    public Object getObject(Cache cache, CacheKey key) {
        return getTransactionalCache(cache).getObject(key);
    }

    public void putObject(Cache cache, CacheKey key, Object value) {
        getTransactionalCache(cache).putObject(key, value);
    }

    public void commit() {
        for (TransactionalCache txCache : transactionalCaches.values()) {
            txCache.commit();
        }
    }

    public void rollback() {
        for (TransactionalCache txCache : transactionalCaches.values()) {
            txCache.rollback();
        }
    }

    private TransactionalCache getTransactionalCache(Cache cache) {
        // 从映射表中获取 TransactionalCach,若不存在,则创建一个新的对象,并设cache值进去
        return MapUtil.computeIfAbsent(transactionalCaches, cache, TransactionalCache::new);
    }

}

// 这是Cache缓存的一个子类
public class TransactionalCache implements Cache {

    private static final Log log = LogFactory.getLog(TransactionalCache.class);

    //真正的缓存对象,和上⾯的Map<Cache, TransactionalCache>中的Cache是同⼀个
    private final Cache delegate;
    private boolean clearOnCommit;
    // 在事务被提交前,所有从数据库中查询的结果将缓存在此集合中
    private final Map<Object, Object> entriesToAddOnCommit;
    // 在事务被提交前,当缓存未命中时,CacheKey 将会被存储在此集合中
    private final Set<Object> entriesMissedInCache;

    public TransactionalCache(Cache delegate) {
        this.delegate = delegate;
        this.clearOnCommit = false;
        this.entriesToAddOnCommit = new HashMap<>();
        this.entriesMissedInCache = new HashSet<>();
    }

    @Override
    public String getId() {
        return delegate.getId();
    }

    @Override
    public int getSize() {
        return delegate.getSize();
    }

    @Override
    public Object getObject(Object key) {
        // issue #116
        Object object = delegate.getObject(key);
        if (object == null) {
            entriesMissedInCache.add(key);
        }
        // issue #146
        if (clearOnCommit) {
            return null;
        } else {
            return object;
        }
    }

    @Override
    public void putObject(Object key, Object object) {
        entriesToAddOnCommit.put(key, object);
    }

    @Override
    public Object removeObject(Object key) {
        return null;
    }

    @Override
    public void clear() {
        clearOnCommit = true;
        entriesToAddOnCommit.clear();
    }

    public void commit() {
        if (clearOnCommit) {
            delegate.clear();
        }
        //当commit时,会调用刷新方法
        flushPendingEntries();
        reset();
    }

    public void rollback() {
        unlockMissedEntries();
        reset();
    }

    private void reset() {
        clearOnCommit = false;
        entriesToAddOnCommit.clear();
        entriesMissedInCache.clear();
    }

    //将缓存中的值刷新到真实的Cache中,
    private void flushPendingEntries() {
        for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
            delegate.putObject(entry.getKey(), entry.getValue());
        }
        for (Object entry : entriesMissedInCache) {
            if (!entriesToAddOnCommit.containsKey(entry)) {
                delegate.putObject(entry, null);
            }
        }
    }

    private void unlockMissedEntries() {
        for (Object entry : entriesMissedInCache) {
            try {
                delegate.removeObject(entry);
            } catch (Exception e) {
                log.warn("Unexpected exception while notifying a rollback to the cache adapter. "
                        + "Consider upgrading your cache adapter to the latest version. Cause: " + e);
            }
        }
    }

}

二.插件开发

1.MyBatis允许拦截的方法

  • 执⾏器Executor (update、query、commit、rollback等⽅法);
  • SQL语法构建器StatementHandler (prepare、parameterize、batch、updates query等⽅ 法);
  • 参数处理器ParameterHandler (getParameterObject、setParameters⽅法);
  • 结果集处理器ResultSetHandler (handleResultSets、handleOutputParameters等⽅法);

2.这四大对象创建时,都是调用interceptorChain.pluginAll(parameterHandler)返回的代理对象

  public ParameterHandler newParameterHandler(MappedStatement mappedStatement,Object parameterObject,BoundSql boundSql){
        ParameterHandler parameterHandler=mappedStatement.getLang().createParameterHandler(mappedStatement,parameterObject,boundSql);
        parameterHandler=(ParameterHandler)interceptorChain.pluginAll(parameterHandler);
        return parameterHandler;
        }
  public Object pluginAll(Object target){
        for(Interceptor interceptor:interceptors){
        target=interceptor.plugin(target);
        }
        return target;
        }
default Object plugin(Object target){
        return Plugin.wrap(target,this);
        }
public static Object wrap(Object target,Interceptor interceptor){
        Map<Class<?>,Set<Method>>signatureMap=getSignatureMap(interceptor);
        Class<?> type=target.getClass();
        Class<?>[]interfaces=getAllInterfaces(type,signatureMap);
        if(interfaces.length>0){
        return Proxy.newProxyInstance(
        type.getClassLoader(),
        interfaces,
        new Plugin(target,interceptor,signatureMap));
        }
        return target;
        }

3.因此调用这四大对象的方法时,会执行Plugin类中的invoke()方法逻辑

  @Override
public Object invoke(Object proxy,Method method,Object[]args)throws Throwable{
        try{
        Set<Method> methods=signatureMap.get(method.getDeclaringClass());
        if(methods!=null&&methods.contains(method)){
        return interceptor.intercept(new Invocation(target,method,args));
        }
        return method.invoke(target,args);
        }catch(Exception e){
        throw ExceptionUtil.unwrapThrowable(e);
        }
        }

4.上述处理逻辑中当方法匹配时,调用了intercept方法,所以会执行拦截器中的方法进行增强


@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class ExamplePlugin implements Interceptor {

    /**
     * 只要被拦截的目标对象方法被执行时,每次都会执行
     *
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("对方法进行了增强");
        //执行原方法
        return invocation.proceed();
    }

    /**
     * 将当前拦截器存储到拦截器链中
     *
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    /**
     * 获取配置文件的参数
     *
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {
        System.out.println("获取的配置文件参数是" + properties);
        Interceptor.super.setProperties(properties);
    }
}

三.动态代理

//获取mapper
userMapper=sqlSession.getMapper(IUserMapper.class);
//DefaultSqlSession
public<T> T getMapper(Class<T> type){
        return this.configuration.getMapper(type,this);
        }
//Configuration
public<T> T getMapper(Class<T> type,SqlSession sqlSession){
        return mapperRegistry.getMapper(type,sqlSession);
        }
//MapperRegistry
public<T> T getMapper(Class<T> type,SqlSession sqlSession){
final MapperProxyFactory<T> mapperProxyFactory=(MapperProxyFactory<T>)knownMappers.get(type);
        if(mapperProxyFactory==null){
        throw new BindingException("Type "+type+" is not known to the MapperRegistry.");
        }
        try{
        //通过静态工厂生成示例
        return mapperProxyFactory.newInstance(sqlSession);
        }catch(Exception e){
        throw new BindingException("Error getting mapper instance. Cause: "+e,e);
        }
        }
//MapperProxyFactory
public T newInstance(SqlSession sqlSession){
//创建了JDK动态代理的handler类
final MapperProxy<T> mapperProxy=new MapperProxy<>(sqlSession,mapperInterface,methodCache);
        //调用重载方法
        return newInstance(mapperProxy);
        }
// 重载方法
protected T newInstance(MapperProxy<T> mapperProxy){
        return(T)Proxy.newProxyInstance(mapperInterface.getClassLoader(),new Class[]{mapperInterface},mapperProxy);
        }
//MapperProxy 类,实现了 InvocationHandler 接⼝
public class MapperProxy<T> implements InvocationHandler, Serializable {
    //省略部分源码
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache;

    //构造,传⼊了 SqlSession,说明每个session中的代理对象的不同的!
    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface,
                       Map<Method, MapperMethod> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }
    //省略部分源码
}


    //invoke方法
    public Object invoke(Object proxy, Method method, Object[] args) throws
            Throwable {
        try {
            //如果是Object定义的⽅法,直接调⽤
            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);
        }
        // 获得 MapperMethod 对象
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        //重点在这:MapperMethod最终调⽤了执⾏的⽅法
        return mapperMethod.execute(sqlSession, args);
    }

    //execute方法
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        //判断mapper中的⽅法类型,最终调⽤的还是SqlSession中的⽅法 switch
        switch (command.getType()) {
            case INSERT: {
                //转换参数
                Object param = method.convertArgsToSqlCommandParam(args);
                //执⾏INSERT操作
                // 转换 rowCount
                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:
                //⽆返回,并且有ResultHandler⽅法参数,则将查询的结果,提交给 ResultHandler 进⾏处理
                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);
                    if (method.returnsOptional()
                            && (result == null || !method.getReturnType().equals(result.getClass()))) {
                        result = Optional.ofNullable(result);
                    }
                }
                break;
            case FLUSH:
                result = sqlSession.flushStatements();
                break;
            default:
                throw new BindingException("Unknown execution method for: " + command.getName());
        }
        //返回结果为null,并且返回类型为基本类型,则抛出BindingException异常
        if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
            throw new BindingException("Mapper method '" + command.getName()
                    + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
        }
        return result;
    }
}

四.延迟加载

1.开启全局延迟加载配置


<settings>
    <!--开启全局延迟加载功能-->
    <setting name="lazyLoadingEnabled" value="true"/>
</settings>

2.配置局部延迟加载

<!-- 开启⼀对多 延迟加载 -->
<resultMap id="userMap" type="user">
    <id column="id" property="id"></id>
    <result column="username" property="username"></result>
    <result column="password" property="password"></result>
    全局延迟加载
    在Mybatis的核⼼配置⽂件中可以使⽤setting标签修改全局的加载策略。
    注意
    7.。
    <result column="birthday" property="birthday"></result>
    <!--
    fetchType="lazy" 懒加载策略
    fetchType="eager" ⽴即加载策略
    -->
    <collection property="orderList" ofType="order" column="id"
                select="com.lagou.dao.OrderMapper.findByUid" fetchType="lazy">
    </collection>
</resultMap>
<select id="findAll" resultMap="userMap">
SELECT * FROM `user`
</select>

3.Configuration中的配置

public class Configuration {
    /** aggressiveLazyLoading:
     * 当开启时,任何⽅法的调⽤都会加载该对象的所有属性。否则,每个属性会按需加载(参考
     lazyLoadTriggerMethods).
     * 默认为true
     * */
    protected boolean aggressiveLazyLoading;
    /**
     * 延迟加载触发⽅法
     */
    protected Set<String> lazyLoadTriggerMethods = new HashSet<String>
            (Arrays.asList(new String[]{"equals", "clone", "hashCode", "toString"}));
    /** 是否开启延迟加载 */
    protected boolean lazyLoadingEnabled = false;

    /**
     延迟加载代理对象创建
     Mybatis的查询结果是由ResultSetHandler接⼝的handleResultSets()⽅法处理的。ResultSetHandler
     接⼝只有⼀个实现,DefaultResultSetHandler,接下来看下延迟加载相关的⼀个核⼼的⽅法
     * 默认使⽤Javassist代理⼯⼚
     * @param proxyFactory
     */
    public void setProxyFactory(ProxyFactory proxyFactory) {
        if (proxyFactory == null) {
            proxyFactory = new JavassistProxyFactory();
        }
        this.proxyFactory = proxyFactory;
    }

    //省略...
}

4.查看ResultSetHandler如何实现延迟加载

  private Object createResultObject(ResultSetWrapper rsw,ResultMap resultMap,ResultLoaderMap lazyLoader,String columnPrefix)throws SQLException{
        this.useConstructorMappings=false; // reset previous mapping result
final List<Class<?>>constructorArgTypes=new ArrayList<>();
final List<Object> constructorArgs=new ArrayList<>();
        //创建返回的结果映射的真实对
        Object resultObject=createResultObject(rsw,resultMap,constructorArgTypes,constructorArgs,columnPrefix);
        if(resultObject!=null&&!hasTypeHandlerForResultObject(rsw,resultMap.getType())){
final List<ResultMapping> propertyMappings=resultMap.getPropertyResultMappings();
        for(ResultMapping propertyMapping:propertyMappings){
        // issue gcode #109 && issue #149
        // 判断属性有没配置嵌套查询,如果有就创建代理对象
        if(propertyMapping.getNestedQueryId()!=null&&propertyMapping.isLazy()){
        //#mark 创建延迟加载代理对象
        resultObject=configuration.getProxyFactory().createProxy(resultObject,lazyLoader,configuration,objectFactory,constructorArgTypes,constructorArgs);
        break;
        }
        }
        }
        this.useConstructorMappings=resultObject!=null&&!constructorArgTypes.isEmpty(); // set current mapping result
        return resultObject;
        }

5.代理对象执行逻辑

private static class EnhancedResultObjectProxyImpl implements MethodHandler {

    private final Class<?> type;
    private final ResultLoaderMap lazyLoader;
    private final boolean aggressive;
    private final Set<String> lazyLoadTriggerMethods;
    private final ObjectFactory objectFactory;
    private final List<Class<?>> constructorArgTypes;
    private final List<Object> constructorArgs;

    private EnhancedResultObjectProxyImpl(Class<?> type, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        this.type = type;
        this.lazyLoader = lazyLoader;
        this.aggressive = configuration.isAggressiveLazyLoading();
        this.lazyLoadTriggerMethods = configuration.getLazyLoadTriggerMethods();
        this.objectFactory = objectFactory;
        this.constructorArgTypes = constructorArgTypes;
        this.constructorArgs = constructorArgs;
    }

    public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        final Class<?> type = target.getClass();
        EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
        Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
        PropertyCopier.copyBeanProperties(type, target, enhanced);
        return enhanced;
    }

    @Override
    public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
        final String methodName = method.getName();
        try {
            synchronized (lazyLoader) {
                if (WRITE_REPLACE_METHOD.equals(methodName)) {
                    Object original;
                    if (constructorArgTypes.isEmpty()) {
                        original = objectFactory.create(type);
                    } else {
                        original = objectFactory.create(type, constructorArgTypes, constructorArgs);
                    }
                    PropertyCopier.copyBeanProperties(type, enhanced, original);
                    if (lazyLoader.size() > 0) {
                        return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs);
                    } else {
                        return original;
                    }
                } else {
                    if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
                        if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
                            lazyLoader.loadAll();
                        } else if (PropertyNamer.isSetter(methodName)) {
                            final String property = PropertyNamer.methodToProperty(methodName);
                            lazyLoader.remove(property);
                        } else if (PropertyNamer.isGetter(methodName)) {
                            final String property = PropertyNamer.methodToProperty(methodName);
                            if (lazyLoader.hasLoader(property)) {
                                lazyLoader.load(property);
                            }
                        }
                    }
                }
            }
            return methodProxy.invoke(enhanced, args);
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
    }
}
微信支付码 微信支付
支付宝支付码 支付宝支付