MyBatis-底层源码解析-(详细,Java面试基础知识

                 * 添加一个Mapper接口的代理工厂对象到configuration.mapperRegistry.knownMappers集合中
                 * 参考 {@link Configuration#mapperRegistry},
                 *     {@link MapperRegistry#knownMappers}
                 */
                configuration.addMapper(boundType);
            }
        }
    }
}

}


## 1.4.8:configuration.addMapper(boundType);

public class Configuration {
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public void addMapper(Class type) {
/**
* mapperRegistry = {@link MapperRegistry} mapper接口注册器,存放所有的mapper接口信息
*/
mapperRegistry.addMapper(type);
}
}


**mapperRegistry.addMapper(type);
为Mapper接口创建一个代理工厂,方便后期使用Mapper接口时创建代理类**
**解析mapper接口的注解信息**

public class MapperRegistry {
public void addMapper(Class type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException(“Type " + type + " is already known to the MapperRegistry.”);
}
boolean loadCompleted = false;
try {
/**
* {@link MapperProxyFactory} 代理接口工厂
/
knownMappers.put(type, new MapperProxyFactory<>(type));
// It’s important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won’t try.
/
*
* {@link MapperAnnotationBuilder} mapper接口注解解析器
/
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
/
开始解析 */
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
}


## 1.4.9 parser.parse(); MapperAnnotationBuilder.parse()

**解析mapper接口的注解信息,parseStatement(method)主要在这个方法中完成**

public class MapperAnnotationBuilder {
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
/**
* 解析 @CacheNamespace 注解
/
parseCache();
/
*
* 解析 CacheNamespaceRef 注解
/
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
/
*
* 解析Sql相关注解 列如 @Select|@Update 之类的注解
*
* 重点关注
/
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
/
*
* 解析待定方法
*/
parsePendingMethods();
}
}


## 1.5.0:parseStatement(method);

**创建一个MappedStatement实例添加到 Configuration.mappedStatements的Map集合中**

public class MapperAnnotationBuilder {
private final MapperBuilderAssistant assistant;
void parseStatement(Method method) {
/**
* 此处省略一大推代码…
*/
assistant.addMappedStatement(
mappedStatementId,
sqlSource,
statementType,
sqlCommandType,
fetchSize,
timeout,
// ParameterMapID
null,
parameterTypeClass,
resultMapId,
getReturnType(method),
resultSetType,
flushCache,
useCache,
// TODO gcode issue #577
false,
keyGenerator,
keyProperty,
keyColumn,
// DatabaseID
null,
languageDriver,
// ResultSets
options != null ? nullOrEmpty(options.resultSets()) : null);
}
}


## 1.5.1:assistant.addMappedStatement();

**<mark style="box-sizing: border-box; outline: 0px; background-color: rgb(248, 248, 64); color: rgb(0, 0, 0); overflow-wrap: break-word;">这里可以参考1.4.6:builderAssistant.addMappedStatement();一模一样的操作</mark>**
<mark style="box-sizing: border-box; outline: 0px; background-color: rgb(248, 248, 64); color: rgb(0, 0, 0); overflow-wrap: break-word;">**都调用了configuration.addMappedStatement(statement);**</mark>
![在这里插入图片描述](https://upload-images.jianshu.io/upload_images/25222111-7a172e142e5239a8?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

**这里重点分析Configuration.addMappedStatement(statement);在做什么操作,并且解决 <mark style="box-sizing: border-box; outline: 0px; background-color: rgb(248, 248, 64); color: rgb(0, 0, 0); overflow-wrap: break-word;">1.4.2留下的疑点;UserMapper.xml和UserMapper接口都有findById的Sql语句定义</mark>**

public class Configuration {
public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}
}


**mappedStatements.put(ms.getId(), ms); 实际调用 Configuration.StrictMap.put()方法**
**<mark style="box-sizing: border-box; outline: 0px; background-color: rgb(248, 248, 64); color: rgb(0, 0, 0); overflow-wrap: break-word;">Configuration.StrictMap是一个重写的HashMap,put方法会先校验key是否存在</mark>**

public class Configuration {
/**
* mappedStatements Sql语句的对象
*
* Configuration.StrictMap 实现了HashMap
*/
protected final Map<String, MappedStatement> mappedStatements = new Configuration.StrictMap(“Mapped Statements collection”)
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());

protected static class StrictMap<V> extends HashMap<String, V> {
    @Override
    @SuppressWarnings("unchecked")
    public V put(String key, V value) {
        /**
         * key 是否存在 存在就抛出异常
         *
         * 由此得知,方法映射Sql语句只能定义一个,要么在 mapper.xml中定义,要么就注解定义
         */
        if (containsKey(key)) {
            throw new IllegalArgumentException(name + " already contains value for " + key
                    + (conflictMessageProducer == null ? "" : conflictMessageProducer.apply(super.get(key), value)));
        }
        if (key.contains(".")) {
            final String shortKey = getShortName(key);
            if (super.get(shortKey) == null) {
                super.put(shortKey, value);
            } else {
                super.put(shortKey, (V) new org.apache.ibatis.session.Configuration.StrictMap.Ambiguity(shortKey));
            }
        }
        return super.put(key, value);
    }
}

}


**debug调试,key已经存在,就会报错。由此得知,方法映射Sql语句只能定义一个,要么在 mapper.xml中定义,要么就注解定义**
![在这里插入图片描述](https://upload-images.jianshu.io/upload_images/25222111-a866e4089e7aa556?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

## 到此,MyBatis的启动流程就走完了。

## <mark style="box-sizing: border-box; outline: 0px; background-color: rgb(248, 248, 64); color: rgb(0, 0, 0); overflow-wrap: break-word;">2:接下来就来看下是如何执行Sql查询的。</mark>

public class Main {
public static void main(String[] args) throws IOException {
String configName = “mybatis_config.xml”;
Reader reader = Resources.getResourceAsReader(configName);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

    /**
     * 获取一个会话连接
     */
    SqlSession sqlSession = sqlSessionFactory.openSession();

    /**
     * 拿到 UserMapper 的代理类
     */
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    /**
     * 执行Sql查询
     */
    User user = userMapper.findById(1);
    System.out.println(user);
}

}


> **输出结果:User{userId=1, username=‘张三’, sex=‘男’, age=12}**

**<mark style="box-sizing: border-box; outline: 0px; background-color: rgb(248, 248, 64); color: rgb(0, 0, 0); overflow-wrap: break-word;">一行代码的查询,底层既然走了那么多流程;</mark>**

## 流程图:

![在这里插入图片描述](https://upload-images.jianshu.io/upload_images/25222111-459d4ed3e065a0d5?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

## 2.1:sqlSessionFactory.openSession();打开会话连接

**调用DefaultSqlSessionFactory.openSessionFromDataSource();**

public class DefaultSqlSessionFactory implements SqlSessionFactory {
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
/**
* mybatis_config.xml配置的
* 数据源
*/
final Environment environment = configuration.getEnvironment();

        /**
         * transactionManager 配置的事务管理器工厂   type="JDBC"  {@link JdbcTransactionFactory}
         *<environments default="developmentss">
         *    <environment id="developmentss">
         *        <transactionManager type="JDBC"></transactionManager>
         *    </environment>
         *  </environments>
         */
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);

        /**
         * 创建事务管理器,由于上面指定的事务管理器工厂是 {@link JdbcTransactionFactory}
         * 所以创建的事务管理器是  {@link JdbcTransaction}
         *
         * @param level 事务隔离级别
         * @param autoCommit 是否自动提交事务
         */
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);

        /**
         * 创建Sql执行器
         *
         * @param execType 创建执行器类型 defaultExecutorType如果不指定 默认就是 SIMPLE
         *                 <settings>
         *                     <setting name="defaultExecutorType" value="SIMPLE"/>
         *                 </settings>
         */
        final Executor executor = configuration.newExecutor(tx, execType);

        /**
         * 创建一个默认的 SqlSession实例
         */
        return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
        closeTransaction(tx); // may have fetched a connection so lets call close()
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

}


## 2.2:configuration.newExecutor(tx, execType);创建执行器

public class Configuration {
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);
/
*
* 执行器会重用预处理语句(PreparedStatement)
/
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
/
*
* 普通的执行器 也是默认的执行器
/
executor = new SimpleExecutor(this, transaction);
}
/
*
* 如果开启了二级缓存 cacheEnabled,创建一个CachingExecutor缓存执行器
* cacheEnabled 默认为true
*
*
*
*/
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
}


## 2.3:sqlSession.getMapper(UserMapper.class);

**获取Mapper接口代理类实例**

public class DefaultSqlSession implements SqlSession {
@Override
public T getMapper(Class type) {
/**
* 用 Configuration 类的 getMapper方法
*/
return configuration.getMapper(type, this);
}
}


public class Configuration {
public T getMapper(Class type, SqlSession sqlSession) {
/**
* 调用 MapperRegistry Mapper接口注册器
*/
return mapperRegistry.getMapper(type, sqlSession);
}
}


public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    /**
     * knownMappers 从缓存中获取 MapperProxyFactory Mapper接口代理工厂
     * 如果没有找到就会抛出异常,
     * 说明获取Mapper接口代理实例时,需要事先定义好  -->  相当于Spring的扫包
     */
    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);
    }
}

}


## <mark style="box-sizing: border-box; outline: 0px; background-color: rgb(248, 248, 64); color: rgb(0, 0, 0); overflow-wrap: break-word;">2.3.1:使用JDK代理创建Mapper接口代理</mark>

**InvocationHandler是MapperProxy**

public class MapperProxyFactory {

private final Class<T> mapperInterface;
/**
 * MapperMethod 缓存
 */
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();

public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
}

@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
    /**
     * JDK 生产代理类
     */
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

public T newInstance(SqlSession sqlSession) {
    /**
     * 代理类回调接口
     */
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}

}


## 2.4:userMapper.findById(1); 调用Mapper接口方法Sql查询

**会走代理类 MapperProxy.invoke**

public class MapperProxy implements InvocationHandler, Serializable {

private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;

public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else if (method.isDefault()) {
            return invokeDefaultMethod(proxy, method, args);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
    /**
     * 根据方法全限名 获取一个MapperMethod实例,并且缓存
     *
     * 注意:这里的 methodCache 只是一个引用,缓存的所有对象都在 {@link MapperProxyFactory#methodCache}中
     */
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    /**
     * 开始执行
     */
    return mapperMethod.execute(sqlSession, args);
}

/**
 * 添加到缓存 methodCache {@link MapperProxyFactory#methodCache}中
 * computeIfAbsent   HashMap  存在就获取,不存在就新增
 * @param method
 * @return
 */
private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}

}


## 2.5:mapperMethod.execute(sqlSession, args);

**执行Sql语句查询,由于我的返回结果是一个 User对象,所以会走到
result = sqlSession.selectOne(command.getName(), param);这一行,<mark style="box-sizing: border-box; outline: 0px; background-color: rgb(248, 248, 64); color: rgb(0, 0, 0); overflow-wrap: break-word;">查询一条记录</mark>**
**<mark style="box-sizing: border-box; outline: 0px; background-color: rgb(248, 248, 64); color: rgb(0, 0, 0); overflow-wrap: break-word;">实际走到 DefaultSqlSession.selectOne()</mark>**

public class MapperMethod {

private final org.apache.ibatis.binding.MapperMethod.SqlCommand command;
private final org.apache.ibatis.binding.MapperMethod.MethodSignature method;

public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new org.apache.ibatis.binding.MapperMethod.SqlCommand(config, mapperInterface, method);
    this.method = new org.apache.ibatis.binding.MapperMethod.MethodSignature(config, mapperInterface, method);
}
/**
* 开始执行Sql查询
*/
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
        case INSERT: {
            // 省略代码...   执行 insert语句 <insert>/@Insert
            break;
        }
        case UPDATE: {
            // 省略代码...   执行 insert语句 <update>/@Update
            break;
        }
        case DELETE: {
            // 省略代码...   执行 delete语句 <delete>/@Delete
            break;
        }
        case SELECT:      // 执行 select语句 <select>/@Select
            if (method.returnsVoid() && method.hasResultHandler()) {
                // 返回类型是否为空 一般情况做Sql操作都要有返回结果的
                executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (method.returnsMany()) {
                /**
                 * 是否返回多个结果集  {@link Collection}集合/数组
                 */
                result = executeForMany(sqlSession, args);
            } else if (method.returnsMap()) {
                /**
                 * 返回类型是否为 Map 集合
                 */
                result = executeForMap(sqlSession, args);
            } else if (method.returnsCursor()) {
                /**
                 * 返回类型是否是 游标  {@link org.apache.ibatis.cursor.Cursor}
                 */
                result = executeForCursor(sqlSession, args);
            } else {
                /**
                 * 将参数转换为Sql命令参数
                 */
                Object param = method.convertArgsToSqlCommandParam(args);
                /**
                 * 发起查询  调用的是 sqlSession中的方法
                 */
                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());
    }
    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;
}

}


**DefaultSqlSession.selectOne()可以看出实际是调用<mark style="box-sizing: border-box; outline: 0px; background-color: rgb(248, 248, 64); color: rgb(0, 0, 0); overflow-wrap: break-word;">selectList()</mark>,而且如果返回了多个结果集就会报错。错误信息如下**
**Expected one result (or null) to be returned by selectOne(), but found: 2**
![在这里插入图片描述](https://upload-images.jianshu.io/upload_images/25222111-51fde0f4a08cf0a4?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

## 2.6:DefaultSqlSession.selectList()

**查询多结果集**

public class DefaultSqlSession implements SqlSession {
/**
*
* @param statement 方法全限名 比如:com.mapper.UserMapper.findById
* @param parameter 参数
* @param rowBounds 分页
* @param
* @return
/
@Override
public List selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
/
*
* 根据方法全限名在 configuration.mappedStatements缓存集合中拿到方法对应的Sql Statement对象实例
/
MappedStatement ms = configuration.getMappedStatement(statement);
/
*
* 使用执行器执行 由当前设置的执行器执行
*
*
*
* 注意:cacheEnabled由于开启二级缓存默认为true,会先使用 {@link CachingExecutor} 缓存执行器查询
*/
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
}


## 2.7:executor.query执行器查询

**执行器的创建查看 <mark style="box-sizing: border-box; outline: 0px; background-color: rgb(248, 248, 64); color: rgb(0, 0, 0); overflow-wrap: break-word;">2.2:configuration.newExecutor(tx, execType);创建执行器</mark>**

/**

  • 二级缓存执行器
    /
    public class CachingExecutor implements Executor {
    @Override
    public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
    /
    *
    * 获取指定的二级缓存
    * mapper.xml 指定的
    * Mapper接口的 @CacheNamespace
    /
    Cache cache = ms.getCache();
    if (cache != null) {
    /
    是否需要刷新二级缓存 /
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
    ensureNoOutParams(ms, boundSql);
    @SuppressWarnings(“unchecked”)
    /
    *
    * 获取二级缓存
    /
    List list = (List) tcm.getObject(cache, key);
    if (list == null) {
    /
    *
    * 如果没有数据查询Sql
    /
    list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    /
    *
    * 设置缓存
    /
    tcm.putObject(cache, key, list); // issue #578 and #116
    }
    return list;
    }
    }
    /
    *
    * delegate = SimpleExecutor
    */
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
    }

## 2.8:delegate.query(); delegate = SimpleExecutor

**因为我没有指定二级缓存,所以直接走向delegate.query, <mark style="box-sizing: border-box; outline: 0px; background-color: rgb(248, 248, 64); color: rgb(0, 0, 0); overflow-wrap: break-word;">因为SimpleExecutor继承了BaseExecutor但是没有重写query方法,所以走的是BaseExecutor.query()</mark>**

public abstract class BaseExecutor implements Executor {
@Override
public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity(“executing a query”).object(ms.getId());
if (closed) {
throw new ExecutorException(“Executor was closed.”);
}
/**
* 是否需要清空一级缓存 flushCache设置为true生效
*
/
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List list;
try {
// 防止多线程重复调用处理
queryStack++;
/
*
* localCache.getObject(key) 获取一级缓存
* {@link BaseExecutor.localCache} 类型 org.apache.ibatis.cache.impl.PerpetualCache
/
list = resultHandler == null ? (List) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
/
*
* 发起数据库查询
*/
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack–;
}
if (queryStack == 0) {
for (org.apache.ibatis.executor.BaseExecutor.DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
}


## 2.9:queryFromDatabase(),


# 写在最后

作为一名即将求职的程序员,面对一个可能跟近些年非常不同的 2019 年,你的就业机会和风口会出现在哪里?在这种新环境下,工作应该选择大厂还是小公司?已有几年工作经验的老兵,又应该如何保持和提升自身竞争力,转被动为主动?

**就目前大环境来看,跳槽成功的难度比往年高很多。一个明显的感受:今年的面试,无论一面还是二面,都很考验Java程序员的技术功底。**

最近我整理了一份复习用的面试题及面试高频的考点题及技术点梳理成一份“**Java经典面试问题(含答案解析).pdf**和一份网上搜集的“**Java程序员面试笔试真题库.pdf**”(实际上比预期多花了不少精力),**包含分布式架构、高可扩展、高性能、高并发、Jvm性能调优、Spring,MyBatis,Nginx源码分析,Redis,ActiveMQ、Mycat、Netty、Kafka、Mysql、Zookeeper、Tomcat、Docker、Dubbo、Nginx等多个知识点高级进阶干货!**

**[CodeChina开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频】](

)**

**由于篇幅有限,为了方便大家观看,这里以图片的形式给大家展示部分的目录和答案截图!**
![](https://img-blog.csdnimg.cn/img_convert/1022fe3cc07585e7a561ff1ac5232f55.png)

### Java经典面试问题(含答案解析)

![](https://img-blog.csdnimg.cn/img_convert/ed302d5e93bf86015f79e85c358e9dd4.png)

### 阿里巴巴技术笔试心得

![](https://img-blog.csdnimg.cn/img_convert/937fe549eda9d86355c7494a4788152f.png)

# 2.9:queryFromDatabase(),


# 写在最后

作为一名即将求职的程序员,面对一个可能跟近些年非常不同的 2019 年,你的就业机会和风口会出现在哪里?在这种新环境下,工作应该选择大厂还是小公司?已有几年工作经验的老兵,又应该如何保持和提升自身竞争力,转被动为主动?

**就目前大环境来看,跳槽成功的难度比往年高很多。一个明显的感受:今年的面试,无论一面还是二面,都很考验Java程序员的技术功底。**

最近我整理了一份复习用的面试题及面试高频的考点题及技术点梳理成一份“**Java经典面试问题(含答案解析).pdf**和一份网上搜集的“**Java程序员面试笔试真题库.pdf**”(实际上比预期多花了不少精力),**包含分布式架构、高可扩展、高性能、高并发、Jvm性能调优、Spring,MyBatis,Nginx源码分析,Redis,ActiveMQ、Mycat、Netty、Kafka、Mysql、Zookeeper、Tomcat、Docker、Dubbo、Nginx等多个知识点高级进阶干货!**

**[CodeChina开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频】](

)**

**由于篇幅有限,为了方便大家观看,这里以图片的形式给大家展示部分的目录和答案截图!**
[外链图片转存中...(img-0BvdbrkT-1631176930935)]

### Java经典面试问题(含答案解析)

[外链图片转存中...(img-mh3rRYTR-1631176930936)]

### 阿里巴巴技术笔试心得

[外链图片转存中...(img-qm896Mu8-1631176930937)]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值