Углубленный анализ: основной принцип реализации mybatis, узнали ли вы его после прочтения?

Java

предисловие

Недавно, когда я общался с фанатами, фанаты спрашивали меня о базовой реализации jdbc и mybatis, и спрашивал не один друг, так что я, кажется, осознал серьезность проблемы. Я потратил два дня, разбираясь сам. написал эту статью, основываясь на своих знаниях и информации, которую я проконсультировался в Интернете.Нечего сказать, ниже полно галантерейных товаров.

Прежде чем говорить о базовой реализации mybatis, давайте взглянем на базовые знания jdbc.

jdbc — самая простая реализация подключения к базе данных, любая операция с базой данных основана на jdbc.

1. 注册驱动
 Class.forName("com.mysql.jdbc.Driver");
2.获取数据库连接
Connection conn  =DriverManager.getConnection(url,user,p);

3.创建向数据发送sql 的statement对象
Statement stmt = conn.CreateStatement();

4. 向数据库发送sql
ResultSet rs  = stmt.executeQuery(sql)//select语句
int updateaSum = stmt.executeUpdate(sql)//insert,update delete语句

5. 处理结果集
while(rs.next()){
	rs.getString(列名)
	rs.getInt(列名)
}
6. 关闭资源
rs.close();
stmt.close();
conn.close();
 

Отношения и принцип анализа Sqlsession, Connection и Transaction от Mybatis

Связь JDBC — это самый простой API, который мы используем для взаимодействия с базами данных. Соединение В качестве сеанса для конкретной базы данных в контексте соединения выполняются операторы sql и возвращаются результаты. Давайте сначала рассмотрим основной процесс использования sqlsession для операций с базой данных. SqlSession Его можно рассматривать как более продвинутую абстракцию Connection, и из его метода видно, что он имеет более очевидные операционные характеристики. Сделка Транзакция (Transaction) — это представление отношения, которое завершается успешно или неуспешно одновременно при выполнении N (N>=1) операций.

Давайте сначала посмотрим, как sqlsession выполняет операции с базой данных:

 String resource = "mybatis-config.xml";
 		//获取数据配置流
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //通过SqlSessionFactoryBuilder获取SqlSessionFactory 
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //通过sqlSessionFactory获取sqlSession 
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //对数据库执行操作
        try {
            TbUserMapper userMapper = sqlSession.getMapper(TbUserMapper.class);
            TbUser user = new TbUser("liybk", "liybk","186..","123");
            userMapper.insertUser(user);
            sqlSession.commit();// 这里一定要提交,不然数据进不去数据库中
            } finally {
                sqlSession.close();
            }

Давайте сначала посмотрим на метод SqlSessionFactoryBuilder().build(inputStream):

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            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;
    }

    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }

Окончательное выполнение проходит через серию синтаксического анализа XML-файла, возвращает DefaultSqlSessionFactory и входит в конструктор DefaultSqlSessionFactory.

public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

Конструктор просто инициализирует свой атрибут конфигурации, который содержит атрибут среды, а атрибут среды имеет ряд основных атрибутов, связанных с операциями базы данных, такими как источник данных, подключение, транзакция и т. д.

public final class Environment {
    private final String id;
    private final TransactionFactory transactionFactory;
    private final DataSource dataSource;
    ....
    }

Хорошо, вернемся к основному шагу. Метод SqlSession sqlSession = sqlSessionFactory.openSession() Его класс выполнения — DefaultSqlSessionFactory, цель — получить sqlSession

public SqlSession openSession() {
        return 
//调用该类的openSessionFromDataSource方法       
this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
    }


//openSessionFromDataSource方法
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;

        DefaultSqlSession var8;
        try {
        //获取Environment
            Environment environment = this.configuration.getEnvironment();
            //从Environment中取得TransactionFactory;
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
            //在取得的数据库连接上创建事务对象Transaction
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            // 创建Executor对象
            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;
    }

Вы можете видеть, что mybatis создает sqlsession, выполнив следующие основные шаги:

  1. Получить Environment из основного файла конфигурации mybatis-config.xml (это источник данных);
  2. Получить источник данных из среды;
  3. Получить TransactionFactory из среды;
  4. Получить объект подключения к базе данных Connection из DataSource;
  5. Создать объект транзакции Transaction на полученном соединении с базой данных;
  6. Создайте объект Executor (этот объект очень важен, ведь все операции sqlsession выполняются через него);
  7. Создайте объект sqlsession.

Давайте сосредоточимся на объекте Executor, потому что этот объект является классом реализации для выполнения sql: Введите Executor executor = метод this.configuration.newExecutor(tx, execType)

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

Видно, что если кеш включен, будет создан CachingExecutor, в противном случае будет создан общий Executor.Общие Executor имеют 3 основных типа.BatchExecutor специально используется для выполнения пакетных операций SQL.ReuseExecutor будет повторно использовать оператор для выполнения операций SQL. , а SimpleExecutor просто выполняет SQL без чего-либо особенного. CachingExecutor выполняет поиск в кеше перед запросом к базе данных. Если он не найден, он вызывает делегат (то есть объект Executor, переданный во время построения) для запроса из базы данных и сохраняет результаты запроса в кеше. Объект Executor может быть перехвачен подключаемыми модулями.Если определен подключаемый модуль для типа Executor, окончательный сгенерированный объект Executor является прокси-объектом, вставляемым каждым подключаемым модулем. Кратко рассмотрим, как простейший SimpleExecutor из трех его основных базовых типов выполняет sql

public class SimpleExecutor extends BaseExecutor {
    public SimpleExecutor(Configuration configuration, Transaction transaction) {
        super(configuration, transaction);
    }

    public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        Statement stmt = null;

        int var6;
        try {
        	//拿到Configuration 属性
            Configuration configuration = ms.getConfiguration();
            //拿到StatementHandler 
            StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null);
            //拿到prepareStatement
            stmt = this.prepareStatement(handler, ms.getStatementLog());
            //prepareStatement执行sql
            var6 = handler.update(stmt);
        } finally {
            this.closeStatement(stmt);
        }

        return var6;
    }

StatementHandler

Можно видеть, что Executor, по сути, является невмешательством продавца, а определенные вещи изначально выполняются оператором StatementHandler. Когда Executor передает эстафету StatementHandler, следующим заданием является StatementHandler. Давайте сначала посмотрим, как создается StatementHandler.

publicStatementHandler newStatementHandler(Executor executor, MappedStatementmappedStatement,  
        ObjectparameterObject, RowBounds rowBounds, ResultHandler resultHandler) {  
   StatementHandler statementHandler = newRoutingStatementHandler(executor, mappedStatement,parameterObject,rowBounds, resultHandler);  
   statementHandler= (StatementHandler) interceptorChain.pluginAll(statementHandler);  
   returnstatementHandler;  
}  

Можно видеть, что каждый раз создаваемый StatementHandler является RoutingStatementHandler, который является просто распространителем, а делегат атрибута используется для указания того, какой конкретный StatementHandler использовать. Необязательными обработчиками статемента являются SimpleStatementHandler, PreparedStatementHandler и CallableStatementHandler. Какой из них выбрать, указывается в каждом операторе конфигурационного файла картографа.По умолчанию используется PreparedStatementHandler. В то же время следует отметить, что StatementHandler может быть перехвачен перехватчиками, как и Executor, объект, перехватываемый перехватчиком, является прокси-объектом. Например, как и реализация физического разбиения на страницы базы данных, многие реализации физического разбиения на страницы реализуются с использованием перехватчиков в этом месте.

Прочитав конкретный процесс выполнения Executor, он еще не окончен, мы еще не знаем, какая связь между первыми двумя частями блока кода на предыдущем шаге выполнения, и публикуем еще раз:

		.....
		//通过sqlSessionFactory获取sqlSession 
        SqlSession sqlSession = sqlSessionFactory.openSession();
1、  TbUserMapper userMapper = sqlSession.getMapper(TbUserMapper.class);
2、            TbUser user = new TbUser("liybk", "liybk","186..","123");
3、            userMapper.insertUser(user);

Итак, какова функция этого маппера, как он создается и как он связан с sqlsession и т. д.? Вводим метод:

public <T> T getMapper(Class<T> type) {
        return this.configuration.getMapper(type, this);
    }

Последний вызов — это метод настройки mapperRegistry.

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }
//mapperRegistry
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }


//mapperProxyFactory
 public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
protected T newInstance(MapperProxy<T> mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

Как видите, mapper — это прокси-объект, а интерфейс, который он реализует, — это входящий тип, поэтому к объекту mapper можно получить доступ напрямую через интерфейс. В то же время вы также можете видеть, что объект sqlsession передается при создании прокси-объекта сопоставления, так что sqlsession также связан. Входим в метод Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy), обратите внимание на переданный этим методом параметр mapperProxy

@CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);
		//拿到mapper接口类
        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
        //进行权限检查
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
         //查找/生成代理类
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
				//获取构造器
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            //将mapperProxy参数转为InvocationHandler 传入
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

mapperProxy является подклассом InvocationHandler, а затем входит в метод cons.newInstance(new Object[]{h})

 public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

В результате mapperProxy используется в качестве параметра конструкции для возврата класса реализации прокси.Давайте посмотрим на основной метод класса mapperProxy. вызывать Мы знаем, что доступ к методам проксируемого объекта будет реализован в вызове прокси-сервера, вызов MapperProxy выглядит следующим образом:

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }

            if (this.isDefaultMethod(method)) {
                return this.invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }

        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }

Вы видите, что invoke передает право на выполнение MapperMethod, давайте посмотрим, как это работает в MapperMethod:

public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        Object param;
        switch(this.command.getType()) {
        case INSERT:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case UPDATE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case DELETE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        case SELECT:
            if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if (this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } else if (this.method.returnsCursor()) {
                result = this.executeForCursor(sqlSession, args);
            } else {
                param = this.method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(this.command.getName(), param);
                if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
                    result = Optional.ofNullable(result);
                }
            }
            break;
        case FLUSH:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + this.command.getName());
        }

        if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
            throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
        } else {
            return result;
        }
    }

Как видите, MapperMethod подобен распределителю, он выбирает различные методы sqlsession для выполнения в соответствии с параметром и типом возвращаемого значения. Таким образом, объект сопоставления действительно связан с sqlsession.

наконец:

Если у вас есть что-то, что вы не понимаете, пожалуйста, оставьте сообщение ниже, чтобы обсудить, или вы также можете спросить меня в частном порядке.Обычно я отвечу после того, как увижу это после работы.Иногда я не вижу это, когда я занят Светлое будущее!