предисловие
Недавно, когда я общался с фанатами, фанаты спрашивали меня о базовой реализации 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, выполнив следующие основные шаги:
- Получить Environment из основного файла конфигурации mybatis-config.xml (это источник данных);
- Получить источник данных из среды;
- Получить TransactionFactory из среды;
- Получить объект подключения к базе данных Connection из DataSource;
- Создать объект транзакции Transaction на полученном соединении с базой данных;
- Создайте объект Executor (этот объект очень важен, ведь все операции sqlsession выполняются через него);
- Создайте объект 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.
наконец:
Если у вас есть что-то, что вы не понимаете, пожалуйста, оставьте сообщение ниже, чтобы обсудить, или вы также можете спросить меня в частном порядке.Обычно я отвечу после того, как увижу это после работы.Иногда я не вижу это, когда я занят Светлое будущее!